Compare commits

...
Author SHA1 Message Date
J. Nick Koston 5cd8781a94 Merge branch 'dev' into ota-inflate-table-decode 2026-10-26 06:37:45 -10:00
J. Nick Koston cfe5809654 [wifi] Hand the DPP bootstrapping key to the supplicant DER encoded (#20882) 2026-10-26 16:37:25 +00:00
J. Nick Koston 9c54f96520 [libretiny] Send only the UF2 images the device writes over OTA (#21642) 2026-10-26 06:28:54 -10:00
J. Nick Kostonandpre-commit-ci-lite[bot] 0cc5b372fb [core] Defer integration imports in ir_rf_base and media_player (#21601)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-10-26 06:26:00 -10:00
J. Nick Koston f7c8cba8b7 [components] Use pulse_reset for blocking reset pin pulses (#21472) 2026-10-26 06:22:11 -10:00
J. Nick Koston 79a22e9a9a [core] Add pulse_reset for active-low reset pins (#21470) 2026-10-26 06:22:10 -10:00
J. Nick Koston 103023737d [openthread] Resolve IPv6 literal addresses when the DNS64 client is enabled (#19269) 2026-10-26 16:18:12 +00:00
J. Nick Koston 6b6b70a8b6 [dfrobot_sen0395] Collect lines with LineReader (#20979) 2026-10-26 12:16:51 -04:00
J. Nick Koston d312bf1b73 [core] Let the yaml editor insert lines, write with a rollback and restore (#20602) 2026-10-26 06:16:26 -10:00
J. Nick Koston ff26d32406 Merge branch 'dev' into ota-inflate-table-decode 2026-10-26 06:02:30 -10:00
J. Nick Koston 2df80fbf71 Merge branch 'dev' into ota-inflate-table-decode 2026-10-25 23:49:16 -10:00
J. Nick Koston 470f7eb51d [esphome] Read a stored byte before advancing the OTA output cursor
C leaves the order of *d->dest++ and the read on the right of the
assignment open, so dest could move first; a read that needs input
flushes the output up to dest where the ack follows the write, and
would write the byte before it was stored. The test harness can now
take the output inside the read callback the same way, and every
stream runs through it.
2026-10-25 23:05:30 -10:00
J. Nick Koston e7b2dfed57 Merge branch 'dev' into ota-inflate-table-decode 2026-10-25 22:51:40 -10:00
J. Nick Koston 83080c4560 [esphome] Wrap OTA back references as an index and test stored blocks after Huffman ones
Forming a pointer before the ring is undefined behaviour even when it is
wrapped before use; wrap the ring position instead. A new test stream
puts stored blocks after dynamic ones, so they start with read ahead
bits in the bit buffer.
2026-10-25 21:52:28 -10:00
J. Nick Koston 104cb09475 [esphome] Update the inflate session size in its comment 2026-10-25 21:44:28 -10:00
J. Nick Koston a7824ed4e7 [esphome] Trim the OTA decoder's flash and heap
The careful path, which only decodes the last few bytes of each read,
uses the canonical decoder alone instead of the lookup table too. The
canonical decoder accumulates each length's first code and symbol index
as it walks, so the trees no longer store them, and the trans bound
check, which the symbol count already guarantees, goes with the size
field. 94 bytes less flash and 132 bytes less heap on a BK72xx, same
speed on a BK7238.
2026-10-25 21:43:44 -10:00
J. Nick Koston 6420db90b1 [esphome] Let the OTA decoder own its ring and finish each window in the fast loop
The decoder now starts dest at the ring and starts it over once a call
has filled it, so callers no longer set the output bounds and cannot
point them outside the ring. The fast loop runs to the end of the ring,
leaving a match that does not fit to the resume path, instead of handing
the last 258 bytes of every window to the careful path; match copies
run in at most two pieces instead of testing for the wrap on every byte.

The careful path decodes long codes through the same canonical decoder
as the fast loop, the distance lookup table is 6 bits for a 30 symbol
alphabet (1.3 KB more heap than before this change instead of 2.2 KB),
and the tests share one stream table.
2026-10-25 21:33:18 -10:00
J. Nick Koston d3c2a7c734 [esphome] Decode OTA deflate with lookup tables and a fast loop
Inflating dominated compressed uploads on slow chips: the uzlib decoder
read every Huffman code one bit at a time. Decode from a 32 bit bit
buffer through 9 bit lookup tables, run a fast loop with the bit buffer
and cursors in locals while a whole symbol fits in the input and the
output, and write each output byte once, into the ring window.

About 5 times faster on a BK7231N, a BK7238 and an ESP32, for about
830 bytes of flash and 2.2 KB more heap while a compressed upload runs.
2026-10-25 21:05:35 -10:00
67 changed files with 2423 additions and 549 deletions

No files matched your search

+17 -10
View File
@@ -1305,10 +1305,7 @@ def upload_program(
f"{option_string} is only supported with the esphome OTA platform; "
"the web_server OTA path can only update the firmware image."
)
binary = CORE.firmware_bin
if getattr(args, "file", None) is not None:
binary = Path(args.file)
return _upload_via_web_server(config, network_devices, binary)
return _upload_via_web_server(config, network_devices, _app_ota_image(args))
return _upload_via_native_api(config, network_devices, args)
@@ -1364,6 +1361,16 @@ def _esphome_ota_conf(config: ConfigType) -> ConfigType | None:
return next(iter(esphome_ota_items(config)), None)
def _app_ota_image(args: ArgsProtocol) -> Path:
"""The file an app update sends: ``--file`` or the build output, as the
target platform's ota_image hook picks it."""
binary = CORE.firmware_bin
if (file := getattr(args, "file", None)) is not None:
binary = Path(file)
ota_image = platform_hooks.get_platform_hook(CORE.target_platform, "ota_image")
return ota_image(binary) if ota_image is not None else binary
def _upload_via_native_api(
config: ConfigType, network_devices: list[str], args: ArgsProtocol
) -> tuple[int, str | None]:
@@ -1413,27 +1420,27 @@ def _upload_via_native_api(
f"flash a build with the option enabled, and then retry {option_string}."
)
binary = CORE.firmware_bin
ota_type = espota2.OTA_TYPE_UPDATE_APP
file = getattr(args, "file", None)
if getattr(args, "partition_table", False):
# Fail fast if the resolved ESPHome OTA config does not enable allow_partition_access.
# The device-side handshake also rejects this with "Device only supports app updates",
# but checking here surfaces the misconfiguration before opening a network connection.
check_partition_access("--partition-table")
binary = CORE.partition_table_bin
binary = Path(file) if file is not None else CORE.partition_table_bin
ota_type = espota2.OTA_TYPE_UPDATE_PARTITION_TABLE
elif getattr(args, "bootloader", False):
check_partition_access("--bootloader")
if (
getattr(args, "file", None) is None
file is None
and (native := native_backend())
and (hint := getattr(native, "missing_image_hint", lambda: None)())
):
raise EsphomeError(hint)
binary = CORE.bootloader_bin
binary = Path(file) if file is not None else CORE.bootloader_bin
ota_type = espota2.OTA_TYPE_UPDATE_BOOTLOADER
if getattr(args, "file", None) is not None:
binary = Path(args.file)
else:
binary = _app_ota_image(args)
if ota_type == espota2.OTA_TYPE_UPDATE_PARTITION_TABLE:
_validate_partition_table_binary(binary)
+27 -1
View File
@@ -33,9 +33,35 @@ _CACHE_VERSION = 1
_LAMBDA_KEY = "__esphome_lambda__"
_CACHE_SUFFIX = ".validated.json"
def compiled_config_path(config_filename: str) -> Path:
"""Path to the cached validated config alongside the storage sidecar."""
return CORE.data_dir / "storage" / f"{config_filename}.validated.json"
return CORE.data_dir / "storage" / f"{config_filename}{_CACHE_SUFFIX}"
def invalidate_compiled_config() -> None:
"""Drop every cache in the storage dir after a change to a file the
mtime check cannot see, such as secrets.yaml or an include; other
configurations may share that file."""
storage = compiled_config_path(CORE.config_filename).parent
try:
caches = [p for p in storage.iterdir() if p.name.endswith(_CACHE_SUFFIX)]
except FileNotFoundError:
return
except OSError as err:
raise EsphomeError(
f"Could not list the validated config caches in {storage}: {err}"
) from err
for path in caches:
try:
path.unlink(missing_ok=True)
except OSError as err:
# A stale cache would present the old key on the next upload
raise EsphomeError(
f"Could not remove the validated config cache {path}: {err}"
) from err
def save_compiled_config(config: ConfigType) -> None:
+1 -3
View File
@@ -297,9 +297,7 @@ void ADE7880::init_device_() {
void ADE7880::reset_device_() {
if (this->reset_pin_ != nullptr) {
ESP_LOGD(TAG, "Reset device using RESET pin");
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = 1});
} else {
ESP_LOGD(TAG, "Reset device using SWRST command");
this->write_u16_register16_(CONFIG, CONFIG_SWRST);
@@ -16,13 +16,7 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
}
void AXS15231Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
delay(10);
}
pulse_reset(this->reset_pin_, {.low_ms = 5, .high_after_ms = 10});
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup();
+4
View File
@@ -78,3 +78,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
# Called by writer.py; delegates to the shared libretiny implementation.
def copy_files() -> None:
libretiny.copy_files()
# Called by the OTA upload; the shared libretiny implementation.
ota_image = libretiny.device_ota_image
@@ -6,12 +6,7 @@ ESPHOME_LOG_TAG(TAG, "cst226.touchscreen");
void CST226Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
@@ -43,9 +43,7 @@ void CST328Touchscreen::setup() {
}
void CST328Touchscreen::reset_device_() {
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = 5});
this->set_timeout(CST328_TRANSITION_TIMEOUT, [this] { this->continue_setup_(); });
}
@@ -52,12 +52,7 @@ void CST816Touchscreen::continue_setup_() {
void CST816Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
@@ -10,14 +10,7 @@ static constexpr uint32_t REREAD_TIMEOUT_ID = 1;
static constexpr uint32_t REREAD_DELAY_MS = 20;
void CST9220Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 10});
// Wait for the controller to leave its bootloader before talking to it.
this->set_timeout(30, [this] { this->continue_setup_(); });
}
@@ -42,6 +42,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_LINE_READER")
await uart.new_uart_component(config)
@@ -11,10 +11,10 @@
namespace esphome::dfrobot_sen0395 {
ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395");
static constexpr char ASCII_CR = 0x0D;
static constexpr char ASCII_LF = 0x0A;
static constexpr float DET_RANGE_STEP_M = 0.15f;
static constexpr float LATENCY_STEP_S = 0.025f;
// Sent after a reset with no line end
static constexpr char PROMPT[] PROGMEM = "leapMMW:/>";
// What each CommandType cannot do while the sensor runs; empty for commands that do not need it stopped
PROGMEM_STRING_TABLE(BusyActions, "", "configure range config", "configure output latency",
@@ -121,9 +121,9 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
return false; // Command not done yet
}
if (this->read_message_()) {
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
if (!this->handle_reply_(cmd))
if (const char *message = this->read_message_()) {
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("is not recognized as a CLI command")) == nullptr) {
if (!this->handle_reply_(cmd, message))
return false; // Command not done yet
this->read_message_(); // Skip the prompt
return true;
@@ -146,12 +146,13 @@ bool DfrobotSen0395Component::run_command_(const Command &cmd, uint32_t now) {
}
void DfrobotSen0395Component::read_state_() {
if (!this->read_message_())
const char *message = this->read_message_();
if (message == nullptr)
return;
if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,0, , , *")) != nullptr) {
this->set_detected_(false);
this->set_active(true);
} else if (ESPHOME_strstr_P(this->read_buffer_, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
} else if (ESPHOME_strstr_P(message, ESPHOME_PSTR("$JYBSS,1, , , *")) != nullptr) {
this->set_detected_(true);
this->set_active(true);
}
@@ -206,8 +207,7 @@ void DfrobotSen0395Component::format_command(const Command &cmd, char *buf, size
}
}
bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
const char *message = this->read_buffer_;
bool DfrobotSen0395Component::handle_reply_(const Command &cmd, const char *message) {
auto is = [message](ESPHOME_PGM_P text) { return ESPHOME_strcmp_P(message, text) == 0; };
// Commands with their own replies
@@ -230,7 +230,7 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
}
return true;
case CommandType::COMMAND_TYPE_RESET_SYSTEM:
if (!is(ESPHOME_PSTR("leapMMW:/>")))
if (!is(PROMPT))
return false;
ESP_LOGI(TAG, "Restarted sensor.");
return true;
@@ -300,34 +300,24 @@ bool DfrobotSen0395Component::handle_reply_(const Command &cmd) {
return true;
}
bool DfrobotSen0395Component::read_message_() {
const char *DfrobotSen0395Component::read_message_() {
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == MMWAVE_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte);
if (byte == ASCII_CR)
continue;
if (byte >= 0x7F)
byte = '?'; // needs to be valid utf8 string for log functions.
this->read_buffer_[this->read_pos_] = byte;
if (this->read_pos_ == 9 && byte == '>')
this->read_buffer_[++this->read_pos_] = ASCII_LF;
if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
this->read_buffer_[this->read_pos_] = 0;
this->read_pos_ = 0;
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
return true; // Full message in buffer
const char *line = this->line_reader_.feed(byte);
if (line == nullptr && byte == '>' && this->line_reader_.length() == sizeof(PROMPT) - 1) {
line = this->line_reader_.partial();
this->line_reader_.reset();
}
this->read_pos_++;
if (line != nullptr) {
ESP_LOGV(TAG, "Message: %s", line);
return line;
}
this->line_reader_.log_if_overflowed(TAG);
}
return false; // No full message yet
return nullptr;
}
void DfrobotSen0395Component::set_detected_(bool detected) {
@@ -4,6 +4,7 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/line_reader.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
@@ -14,7 +15,7 @@
namespace esphome::dfrobot_sen0395 {
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
static constexpr uint8_t MMWAVE_READ_BUFFER_LENGTH = 253; // largest LineReader size with a one byte position
static constexpr uint8_t COMMAND_QUEUE_SIZE = 20;
// Keep in sync with the BusyActions table in dfrobot_sen0395.cpp
@@ -121,8 +122,7 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
bool led_active_{false};
bool uart_presence_active_{false};
bool start_after_boot_{false};
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
size_t read_pos_{0};
LineReader<MMWAVE_READ_BUFFER_LENGTH> line_reader_;
bool cmd_sent_{false};
uint8_t retries_left_{2};
StaticRingBuffer<Command, COMMAND_QUEUE_SIZE> cmd_queue_;
@@ -131,12 +131,13 @@ class DfrobotSen0395Component final : public uart::UARTDevice, public Component
int16_t det_range_steps_[8]{};
uint16_t latency_steps_[2]{};
bool read_message_();
/// The next complete line, valid until the next call, or nullptr
const char *read_message_();
void read_state_();
/// Run the command at the front of the queue; true when it is done
bool run_command_(const Command &cmd, uint32_t now);
/// Handle a reply to a sent command; true when the command is done
bool handle_reply_(const Command &cmd);
bool handle_reply_(const Command &cmd, const char *message);
void set_detected_(bool detected);
};
@@ -20,9 +20,7 @@ void EKTF2232Touchscreen::setup() {
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
this->reset_pin_->setup();
this->hard_reset_();
pulse_reset(this->reset_pin_, {.low_ms = 15, .high_after_ms = 15});
if (!this->soft_reset_()) {
ESP_LOGE(TAG, "Failed to soft reset EKT2232!");
this->interrupt_pin_->detach_interrupt();
@@ -96,13 +94,6 @@ bool EKTF2232Touchscreen::get_power_state() {
return (received[1] >> 3) & 1;
}
void EKTF2232Touchscreen::hard_reset_() {
this->reset_pin_->digital_write(false);
delay(15);
this->reset_pin_->digital_write(true);
delay(15);
}
bool EKTF2232Touchscreen::soft_reset_() {
auto err = this->write(SOFT_RESET_CMD, 4);
if (err != i2c::ERROR_OK)
@@ -22,7 +22,6 @@ class EKTF2232Touchscreen final : public Touchscreen, public i2c::I2CDevice {
bool get_power_state();
protected:
void hard_reset_();
bool soft_reset_();
void update_touches() override;
@@ -72,9 +72,7 @@ bool EPaperJD79660::reset() {
// As commented on SLEEP_MS_RESET1: Reset pulse must happen within time window.
// So do not use FSM loop, and avoid other calls/logs during pulse below.
this->reset_pin_->digital_write(false);
delay(SLEEP_MS_RESET1);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.low_ms = SLEEP_MS_RESET1});
this->reset_duration_ = SLEEP_MS_RESET2;
this->step_ = FSMState::RESET_STEP2_IDLECHECK;
@@ -373,7 +373,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
// Value initialized: a corrupt stream that back references the
// window before it is filled then copies zeros, never stale memory.
// Default placement, PSRAM first where present: the session lives for one
// upload and keeps 4.9 KB of internal heap free while it runs
// upload and keeps 6.0 KB of internal heap free while it runs
this->inflate_ = RAMAllocator<InflateSession>().make_unique();
if (this->inflate_ != nullptr) {
this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
@@ -36,7 +36,10 @@
* Altered for ESPHome: this is the raw deflate decoder from uzlib's
* tinflate.c (v2.9.5) with the gzip/zlib header parsers, checksums,
* runtime table builder and in-memory (non ring window) output path
* removed, and the public names prefixed with ota_inflate.
* removed, and the public names prefixed with ota_inflate. Symbols are
* decoded from a 32 bit bit buffer through 9 bit lookup tables, with a
* fast loop for when a whole symbol fits in both the input and the output,
* and the output is written straight into the ring window.
*/
#include "ota_esphome_inflate.h"
@@ -49,16 +52,6 @@
#define TINF_DICT_ERROR OTA_INFLATE_DICT_ERROR
#define TINF_DATA struct OtaInflateState
#define TINF_TREE struct OtaInflateTree
#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
/* every output byte also goes into the ring window */
#define TINF_PUT(d, c) \
{ \
*d->dest++ = c; \
d->dict_ring[d->dict_idx++] = c; \
if (d->dict_idx == d->dict_size) \
d->dict_idx = 0; \
}
/* --------------------------------------------------- *
* -- constant tables (upstream builds them at runtime) -- *
@@ -82,34 +75,52 @@ static const unsigned char CLCIDX[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4,
* -- utility functions -- *
* ----------------------- */
/* given an array of code lengths, build a tree */
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) {
unsigned short offs[16];
unsigned int i, sum;
enum {
LIT_FAST_BITS = OTA_INFLATE_LIT_FAST_BITS,
DIST_FAST_BITS = OTA_INFLATE_DIST_FAST_BITS,
/* a lookup table entry is (code length << SYM_BITS) | symbol; 0 marks a longer code */
SYM_BITS = 9,
SYM_MASK = (1 << SYM_BITS) - 1,
/* input bytes one pass of the fast loop may take: three refills of up to 4 bytes */
FAST_IN_MIN = 12,
};
/* given an array of code lengths, build a tree and its lookup table */
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num, unsigned int fast_bits) {
const unsigned int fast_size = 1u << fast_bits;
unsigned short offs[16], next[16];
unsigned int i, sum, code;
/* clear code length count table */
for (i = 0; i < 16; ++i)
t->table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i)
t->table[lengths[i]]++;
/* In the lengths array, 0 means unused code. So, t->table[0] now contains
number of unused codes. But table's purpose is to contain # of codes of
particular length, and there're 0 codes of length 0. */
t->table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
for (sum = 0, code = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t->table[i];
if (i > 0)
code = (code + t->table[i - 1]) << 1;
next[i] = code;
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < fast_size; ++i)
t->fast[i] = 0;
for (i = 0; i < num; ++i) {
if (lengths[i])
t->trans[offs[lengths[i]]++] = i;
unsigned int len = lengths[i];
if (!len)
continue;
t->trans[offs[len]++] = i;
if (len <= fast_bits) {
/* codes arrive MSB first, the bit buffer is LSB first */
unsigned int c = next[len]++, rev = 0, j;
for (j = 0; j < len; ++j, c >>= 1)
rev = (rev << 1) | (c & 1);
for (j = rev; j < fast_size; j += 1u << len)
t->fast[j] = (unsigned short) ((len << SYM_BITS) | i);
}
}
}
@@ -117,89 +128,78 @@ static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned
* -- decode functions -- *
* ---------------------- */
static unsigned char uzlib_get_byte(TINF_DATA *d) {
/* If end of source buffer is not reached, return next byte from source
buffer. */
if (d->source < d->source_limit) {
return *d->source++;
/* top the bit buffer up from the input buffer, without blocking for more */
static inline void tinf_refill(TINF_DATA *d) {
while (d->bitcount <= 24 && d->source < d->source_limit) {
d->tag |= (unsigned int) *d->source++ << d->bitcount;
d->bitcount += 8;
}
/* Otherwise if there's callback and we haven't seen EOF yet, try to
read next byte using it. (Note: the callback can also update ->source
and ->source_limit). */
if (!d->eof) {
int val = d->source_read_cb(d);
if (val >= 0) {
return (unsigned char) val;
}
}
/* Otherwise, we hit EOF (either from ->source_read_cb() or from exhaustion
of the buffer), and it will be "sticky", i.e. further calls to this
function will end up here too. */
d->eof = true;
return 0;
}
/* get one bit from source stream */
static int tinf_getbit(TINF_DATA *d) {
unsigned int bit;
/* check if tag is empty */
if (!d->bitcount--) {
/* load next tag */
d->tag = uzlib_get_byte(d);
d->bitcount = 7;
/* make at least num (<= 16) bits available; past the end of input reads zeros and sets eof */
static inline void tinf_need(TINF_DATA *d, unsigned int num) {
while (d->bitcount < num) {
int val = -1;
if (d->source < d->source_limit) {
val = *d->source++;
} else if (!d->eof) {
val = d->source_read_cb(d);
}
if (val < 0) {
d->eof = true;
val = 0;
}
d->tag |= (unsigned int) val << d->bitcount;
d->bitcount += 8;
}
}
/* shift bit out of tag */
bit = d->tag & 0x01;
d->tag >>= 1;
return bit;
static inline unsigned int tinf_take(TINF_DATA *d, unsigned int num) {
unsigned int val = d->tag & ((1u << num) - 1);
d->tag >>= num;
d->bitcount -= num;
return val;
}
/* read a num bit value from a stream and add base */
static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) {
unsigned int val = 0;
/* read num bits */
if (num) {
unsigned int limit = 1 << (num);
unsigned int mask;
for (mask = 1; mask < limit; mask *= 2)
if (tinf_getbit(d))
val += mask;
}
return val + base;
static inline unsigned int tinf_read_bits(TINF_DATA *d, unsigned int num, unsigned int base) {
tinf_need(d, num);
return tinf_take(d, num) + base;
}
/* given a data stream and a tree, decode a symbol */
static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) {
int sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
do {
cur = 2 * cur + tinf_getbit(d);
if (++len == TINF_ARRAY_SIZE(t->table)) {
return TINF_DATA_ERROR;
/* canonical decode of the code at the bottom of tag; len gets the code length.
The trans index stays below the tree's symbol count, which trans always holds */
static int tinf_decode_long(const TINF_TREE *t, unsigned int tag, unsigned int *len) {
unsigned int code = 0, first = 0, index = 0, l;
for (l = 1; l < 16; ++l, tag >>= 1) {
unsigned int count = t->table[l];
code |= tag & 1;
if (code - first < count) {
*len = l;
return t->trans[index + code - first];
}
sum += t->table[len];
cur -= t->table[len];
} while (cur >= 0);
sum += cur;
if (sum < 0 || sum >= t->size) {
return TINF_DATA_ERROR;
index += count;
first = (first + count) << 1;
code <<= 1;
}
return TINF_DATA_ERROR;
}
return t->trans[sum];
/* decode a symbol, reading more input only while the code is not all in the bit
buffer; bits above bitcount are zero, so a code no longer than bitcount is real */
static int tinf_decode_symbol(TINF_DATA *d, const TINF_TREE *t) {
tinf_refill(d);
for (;;) {
unsigned int len;
int sym = tinf_decode_long(t, d->tag, &len);
if (sym >= 0 && len <= d->bitcount) {
tinf_take(d, len);
return sym;
}
if (d->bitcount >= 15)
return TINF_DATA_ERROR;
tinf_need(d, d->bitcount + 1);
}
}
/* given a data stream, decode dynamic trees from it */
@@ -230,7 +230,7 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
}
/* build code length tree, temporarily use length tree */
tinf_build_tree(lt, lengths, 19);
tinf_build_tree(lt, lengths, 19, LIT_FAST_BITS);
/* decode code lengths for the dynamic trees */
hlimit = hlit + hdist;
@@ -282,8 +282,8 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
}
/* build dynamic trees */
tinf_build_tree(lt, lengths, hlit);
tinf_build_tree(dt, lengths + hlit, hdist);
tinf_build_tree(lt, lengths, hlit, LIT_FAST_BITS);
tinf_build_tree(dt, lengths + hlit, hdist, DIST_FAST_BITS);
return TINF_OK;
}
@@ -302,84 +302,189 @@ static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) {
lengths[i] = 7;
for (; i < 288; ++i)
lengths[i] = 8;
tinf_build_tree(lt, lengths, 288);
tinf_build_tree(lt, lengths, 288, LIT_FAST_BITS);
for (i = 0; i < 32; ++i)
lengths[i] = 5;
tinf_build_tree(dt, lengths, 32);
tinf_build_tree(dt, lengths, 32, DIST_FAST_BITS);
}
/* inflate with the bit buffer and both cursors in locals while the input holds a
whole symbol; a match that runs past the end of the ring is left in curlen */
static int tinf_inflate_fast(TINF_DATA *d, const TINF_TREE *lt, const TINF_TREE *dt) {
const unsigned char *src = d->source;
const unsigned char *const src_end = d->source_limit;
unsigned char *const ring = d->dict_ring;
unsigned char *const ring_end = ring + d->dict_size;
unsigned char *out = d->dest;
const uint16_t *const lfast = lt->fast;
const uint16_t *const dfast = dt->fast;
unsigned int tag = d->tag, bits = d->bitcount;
int res = TINF_OK;
#define REFILL() \
while (bits <= 24) { \
tag |= (unsigned int) *src++ << bits; \
bits += 8; \
}
#define DROP(n) (tag >>= (n), bits -= (n))
while (src_end - src >= FAST_IN_MIN && out < ring_end) {
unsigned int e, len;
int sym;
REFILL();
e = lfast[tag & ((1u << LIT_FAST_BITS) - 1)];
if (e) {
len = e >> SYM_BITS;
sym = e & SYM_MASK;
} else {
sym = tinf_decode_long(lt, tag, &len);
if (sym < 0) {
res = sym;
break;
}
}
DROP(len);
if (sym < 256) {
*out++ = sym;
continue;
}
if (sym == 256) {
res = TINF_DONE;
break;
}
sym -= 257;
if (sym >= 29) {
res = TINF_DATA_ERROR;
break;
}
/* the lit/len code and its extra bits fit the refilled buffer together */
unsigned int length = LENGTH_BASE[sym] + (tag & ((1u << LENGTH_BITS[sym]) - 1));
DROP(LENGTH_BITS[sym]);
REFILL();
e = dfast[tag & ((1u << DIST_FAST_BITS) - 1)];
if (e) {
len = e >> SYM_BITS;
sym = e & SYM_MASK;
} else {
sym = tinf_decode_long(dt, tag, &len);
}
if (sym < 0 || sym >= 30) {
res = TINF_DATA_ERROR;
break;
}
DROP(len);
REFILL();
unsigned int offs = DIST_BASE[sym] + (tag & ((1u << DIST_BITS[sym]) - 1));
DROP(DIST_BITS[sym]);
if (offs > d->dict_size) {
res = TINF_DICT_ERROR;
break;
}
/* wrap as an index: a pointer before the ring would be undefined */
unsigned int at = out - ring;
const unsigned char *from = ring + (at >= offs ? at - offs : at + d->dict_size - offs);
unsigned int n = ring_end - out;
if (n > length)
n = length;
length -= n;
/* at most two runs: up to the end of the ring, then on from its start */
while (n) {
unsigned int run = ring_end - from;
if (run > n)
run = n;
n -= run;
do {
*out++ = *from++;
} while (--run);
if (from == ring_end)
from = ring;
}
if (length) {
d->curlen = length;
d->lz_off = from - ring;
}
}
#undef REFILL
#undef DROP
d->source = src;
d->dest = out;
d->tag = tag;
d->bitcount = bits;
return res;
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate next chunk of output (a byte or more) */
/* given a stream and two trees, inflate until the ring is full or the block ends */
static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
if (d->curlen == 0) {
unsigned char *const ring_end = d->dict_ring + d->dict_size;
while (d->dest < ring_end) {
if (d->curlen != 0) {
unsigned int n = ring_end - d->dest;
if (n > d->curlen)
n = d->curlen;
d->curlen -= n;
do {
*d->dest++ = d->dict_ring[d->lz_off];
if ((unsigned) ++d->lz_off == d->dict_size)
d->lz_off = 0;
} while (--n);
continue;
}
if (d->source_limit - d->source >= FAST_IN_MIN) {
int res = tinf_inflate_fast(d, lt, dt);
if (res != TINF_OK)
return res;
continue;
}
unsigned int offs;
int dist;
int sym = tinf_decode_symbol(d, lt);
if (d->eof) {
if (d->eof)
return TINF_DATA_ERROR;
}
if (sym < 0) {
if (sym < 0)
return sym;
}
/* literal byte */
if (sym < 256) {
TINF_PUT(d, sym);
return TINF_OK;
*d->dest++ = sym;
continue;
}
/* end of block */
if (sym == 256) {
if (sym == 256)
return TINF_DONE;
}
/* substring from sliding dictionary */
sym -= 257;
if (sym >= 29) {
if (sym >= 29)
return TINF_DATA_ERROR;
}
/* possibly get more bits from length code */
d->curlen = tinf_read_bits(d, LENGTH_BITS[sym], LENGTH_BASE[sym]);
dist = tinf_decode_symbol(d, dt);
if (dist < 0 || dist >= 30) {
if (dist < 0 || dist >= 30)
return TINF_DATA_ERROR;
}
/* possibly get more bits from distance code */
offs = tinf_read_bits(d, DIST_BITS[dist], DIST_BASE[dist]);
/* calculate and validate actual LZ offset to use */
if (offs > d->dict_size) {
if (offs > d->dict_size)
return TINF_DICT_ERROR;
}
/* Note: we don't try to catch offset which points to not yet filled
part of the dictionary here. Doing so would require keeping another
variable to track "filled in" size of the dictionary. Appearance of
such an offset cannot lead to accessing memory outside of the
dictionary buffer, and clients which don't want to leak unrelated
information, should explicitly initialize dictionary buffer passed
to uzlib. */
d->lz_off = d->dict_idx - offs;
if (d->lz_off < 0) {
d->lz_off = (int) (d->dest - d->dict_ring) - (int) offs;
if (d->lz_off < 0)
d->lz_off += d->dict_size;
}
}
/* copy next byte from dict substring */
TINF_PUT(d, d->dict_ring[d->lz_off]);
if ((unsigned) ++d->lz_off == d->dict_size) {
d->lz_off = 0;
}
d->curlen--;
return TINF_OK;
}
@@ -388,30 +493,22 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
if (d->curlen == 0) {
unsigned int length, invlength;
/* get length */
length = uzlib_get_byte(d);
length += 256 * uzlib_get_byte(d);
/* get one's complement of length */
invlength = uzlib_get_byte(d);
invlength += 256 * uzlib_get_byte(d);
/* check length */
/* the block starts on a byte boundary */
tinf_take(d, d->bitcount & 7);
length = tinf_read_bits(d, 16, 0);
invlength = tinf_read_bits(d, 16, 0);
if (length != (~invlength & 0x0000ffff))
return TINF_DATA_ERROR;
/* increment length to properly return TINF_DONE below, without
producing data at the same time */
d->curlen = length + 1;
/* make sure we start next block on a byte boundary */
d->bitcount = 0;
}
if (--d->curlen == 0) {
if (--d->curlen == 0)
return TINF_DONE;
}
unsigned char c = uzlib_get_byte(d);
TINF_PUT(d, c);
/* read first: a read that needs input may flush the output up to dest */
unsigned char c = tinf_read_bits(d, 8, 0);
*d->dest++ = c;
return TINF_OK;
}
@@ -419,10 +516,10 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
* -- public functions -- *
* ---------------------- */
/* initialize decompression structure */
void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len) {
d->source = NULL;
d->source_limit = NULL;
d->dest = dict;
d->tag = 0;
d->eof = 0;
d->bitcount = 0;
@@ -431,68 +528,56 @@ void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len)
d->btype = -1;
d->dict_size = dict_len;
d->dict_ring = dict;
d->dict_idx = 0;
d->curlen = 0;
d->ltree.trans = d->ltrans;
d->ltree.size = TINF_ARRAY_SIZE(d->ltrans);
d->ltree.fast = d->lfast;
d->dtree.trans = d->dtrans;
d->dtree.size = TINF_ARRAY_SIZE(d->dtrans);
d->dtree.fast = d->dfast;
}
/* inflate next output bytes from compressed stream */
int ota_inflate(TINF_DATA *d) {
/* the caller has taken the previous ringful */
if (d->dest == d->dict_ring + d->dict_size)
d->dest = d->dict_ring;
do {
int res;
/* start a new block */
if (d->btype == -1) {
int old_btype;
next_blk:
old_btype = d->btype;
/* read final block flag */
d->bfinal = tinf_getbit(d);
/* read block type (2 bits) */
d->bfinal = tinf_read_bits(d, 1, 0);
d->btype = tinf_read_bits(d, 2, 0);
if (d->btype == 1 && old_btype != 1) {
/* build fixed huffman trees */
tinf_build_fixed_trees(&d->ltree, &d->dtree);
} else if (d->btype == 2) {
/* decode trees from stream */
res = tinf_decode_trees(d, &d->ltree, &d->dtree);
if (res != TINF_OK) {
if (res != TINF_OK)
return res;
}
}
}
/* process current block */
switch (d->btype) {
case 0:
/* decompress uncompressed block */
res = tinf_inflate_uncompressed_block(d);
break;
case 1:
case 2:
/* decompress block with fixed/dynamic huffman trees */
/* trees were decoded previously, so it's the same routine for both */
res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
break;
default:
return TINF_DATA_ERROR;
}
if (res == TINF_DONE && !d->bfinal) {
/* the block has ended (without producing more data), but we
can't return without data, so start procesing next block */
if (res == TINF_DONE && !d->bfinal)
goto next_blk;
}
if (res != TINF_OK) {
if (res != TINF_OK)
return res;
}
} while (d->dest < d->dest_limit);
} while (d->dest < d->dict_ring + d->dict_size);
return TINF_OK;
}
@@ -16,10 +16,14 @@ enum OtaInflateResult {
OTA_INFLATE_DICT_ERROR = -5,
};
/* Code bits each lookup table resolves; longer codes take the canonical decode.
The distance alphabet has 30 symbols, so its table is kept small */
enum { OTA_INFLATE_LIT_FAST_BITS = 9, OTA_INFLATE_DIST_FAST_BITS = 6 };
struct OtaInflateTree {
uint16_t table[16]; /* table of code length counts */
uint16_t *trans; /* code -> symbol translation table, size entries */
uint16_t size;
uint16_t *fast; /* (len << 9) | symbol by the next input bits, 0 = longer code */
uint16_t *trans; /* code -> symbol translation table */
};
struct OtaInflateState {
@@ -29,9 +33,8 @@ struct OtaInflateState {
/* Called when source is exhausted; returns the next byte or -1 at EOF.
It may refill source/source_limit for buffered operation. */
int (*source_read_cb)(struct OtaInflateState *d);
/* Output cursor and one past the end of the output buffer */
/* Output cursor in dict_ring: each call fills the ring from here */
unsigned char *dest;
unsigned char *dest_limit;
/* Ring window holding the last dict_size output bytes for back references */
unsigned char *dict_ring;
@@ -39,9 +42,8 @@ struct OtaInflateState {
unsigned int curlen;
int lz_off;
unsigned int dict_size;
unsigned int dict_idx;
/* One word: btype is -1 between blocks, bitcount never exceeds 7 */
/* btype is -1 between blocks; bitcount is the number of valid bits in tag (<= 32) */
int8_t btype;
uint8_t bfinal;
uint8_t bitcount;
@@ -51,13 +53,15 @@ struct OtaInflateState {
struct OtaInflateTree dtree; /* dynamic distance tree */
uint16_t ltrans[288];
uint16_t dtrans[32]; /* the distance alphabet has 30 symbols, so the tree is kept small */
uint16_t lfast[1 << OTA_INFLATE_LIT_FAST_BITS];
uint16_t dfast[1 << OTA_INFLATE_DIST_FAST_BITS];
};
/* dict must cover the encoder's window (its largest back reference) */
void ota_inflate_init(struct OtaInflateState *d, unsigned char *dict, unsigned int dict_len);
/* Fills dest up to dest_limit (OK) or to the end of the stream (DONE). dest may
alias dict only if dest_limit - dest == dict_len and dest is reset to dict
exactly when a call returns OK, so the ring index and dest stay in lockstep */
/* Inflates into dict until it is full (OK) or the stream ends (DONE); the output
is dict up to dest. The next call after OK starts dict over, so take its
contents first */
int ota_inflate(struct OtaInflateState *d);
#ifdef __cplusplus
@@ -65,16 +65,15 @@ ota::OTAResponseTypes ESPHomeOTAComponent::inflate_data_(uint8_t *in, size_t ima
};
int res;
session.flushed = 0;
do {
// The ring index wrapped to 0 exactly when the window filled
session.dest = session.window;
session.dest_limit = session.window + OTA_INFLATE_WINDOW_SIZE;
session.flushed = 0;
res = ota_inflate(&session);
// A stored block keeps emitting zeros after a failed read, hence eof
if (res < 0 || session.eof)
break;
session.error = this->inflate_flush_(session);
// A full window is all in flash; the next call starts it over
session.flushed = 0;
} while (res != OTA_INFLATE_DONE && session.error == ota::OTA_RESPONSE_OK);
// Transport and flash failures are logged where they happen
+1 -12
View File
@@ -34,10 +34,7 @@ void FT63X6Touchscreen::setup() {
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_ANY_EDGE);
}
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->hard_reset_();
}
pulse_reset(this->reset_pin_, {.low_ms = 10});
// Get touch resolution
if (this->x_raw_max_ == this->x_raw_min_) {
@@ -57,14 +54,6 @@ void FT63X6Touchscreen::setup() {
this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E);
}
void FT63X6Touchscreen::hard_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
}
void FT63X6Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "FT63X6 Touchscreen:");
LOG_I2C_DEVICE(this);
-1
View File
@@ -27,7 +27,6 @@ class FT63X6Touchscreen final : public Touchscreen, public i2c::I2CDevice {
void set_threshold(uint8_t threshold) { this->threshold_ = threshold; }
protected:
void hard_reset_();
void update_touches() override;
InternalGPIOPin *interrupt_pin_{nullptr};
@@ -83,13 +83,7 @@ void GSL3670Touchscreen::update_touches() {
void GSL3670Touchscreen::clear_reg_() {
ESP_LOGD(TAG, "clear_reg");
// GPIO reset pulse
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(5);
}
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
this->write_reg8_(0x88, 0x01);
// delay(5);
@@ -106,12 +100,7 @@ void GSL3670Touchscreen::clear_reg_() {
void GSL3670Touchscreen::reset_() {
ESP_LOGD(TAG, "reset");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(1);
this->reset_pin_->digital_write(true);
delay(5);
}
pulse_reset(this->reset_pin_, {.low_ms = 1, .high_after_ms = 5});
this->write_reg8_(0xe4, 0x04);
+1 -6
View File
@@ -19,12 +19,7 @@ void ICNT86Touchscreen::setup() {
}
// Perform reset if necessary
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
}
pulse_reset(this->reset_pin_, {.low_ms = 10});
if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width();
+4 -3
View File
@@ -1,8 +1,7 @@
"""Shared base for the infrared and radio_frequency entity components."""
import esphome.codegen as cg
from esphome.components import remote_base
from esphome.components.api import DOMAIN as API_DOMAIN
from esphome.const import CONF_API
from esphome.core import CORE
from esphome.core.entity_helpers import queue_entity_register
from esphome.types import ConfigType
@@ -24,8 +23,10 @@ async def attach_transmitter(var: cg.MockObj, config: ConfigType, key: str) -> N
does. Without this call anywhere in the build a request is answered as
soon as the frame is handed over.
"""
from esphome.components import remote_base
await remote_base.register_transmittable(var, config, key)
if API_DOMAIN in CORE.config:
if CONF_API in CORE.config:
cg.add_define("USE_IR_RF_TRANSMIT_COMPLETE")
+21 -1
View File
@@ -26,7 +26,7 @@ from esphome.const import (
)
from esphome.core import CORE
from esphome.core.config import BOARD_MAX_LENGTH
from esphome.helpers import copy_file_if_changed
from esphome.helpers import copy_file_if_changed, write_file
from esphome.platformio.toolchain import copy_ccache_script
from esphome.storage_json import StorageJSON
@@ -656,6 +656,26 @@ async def component_to_code(config):
await cg.register_component(var, config)
# Called by the OTA upload through the platforms' ota_image hook
def device_ota_image(firmware: Path) -> Path:
"""The file to send for an app update: the UF2 without the images the
device skips, written next to the build output, or ``firmware`` itself
when there is nothing to drop."""
from .ota_uf2 import device_ota_uf2
data = firmware.read_bytes()
if (trimmed := device_ota_uf2(data)) is None:
return firmware
image = CORE.relative_pioenvs_path(CORE.name, "firmware.ota.uf2")
write_file(image, trimmed)
_LOGGER.info(
"Sending the images this device writes: %s of %s bytes",
len(trimmed),
len(data),
)
return image
# Called by writer.py
def copy_files() -> None:
script_dir = Path(__file__).parent
@@ -91,6 +91,10 @@ async def pin_to_code(config: ConfigType) -> MockObj:
# Called by writer.py; delegates to the shared libretiny implementation.
def copy_files() -> None:
libretiny.copy_files()
# Called by the OTA upload; the shared libretiny implementation.
ota_image = libretiny.device_ota_image
'''
BASE_CODE_BOARDS = '''
+98
View File
@@ -0,0 +1,98 @@
"""Trim a LibreTiny UF2 package to the images a device writes over OTA.
A LibreTiny UF2 holds one image per OTA scheme: the device schemes (single,
dual slot 1, dual slot 2) and the flasher schemes used over UART. The device
skips every block of an image whose scheme it does not run (uf2ota), so an
image no device scheme writes is only sent to be thrown away. On single OTA
chips (BK72xx, LN882H) that is the whole flasher image, more than half the
package. The legacy OTA format names the same device partitions, so devices
that read it write the same images.
"""
import struct
UF2_BLOCK_SIZE = 512
UF2_MAGIC_HEAD = struct.pack("<2I", 0x0A324655, 0x9E5D5157)
UF2_MAGIC_TAIL = struct.pack("<I", 0x0AB16F30)
UF2_FLAG_HAS_TAGS = 0x8000
# Header words after the magic: flags, target address, payload length, block
# number, block count
_HEADER_WORDS = struct.Struct("<5I")
_SEQ_OFFSET = 20
_DATA_START = 32
_DATA_END = UF2_BLOCK_SIZE - len(UF2_MAGIC_TAIL)
# One partition index nibble per scheme, high nibble first: device single,
# dual 1 and dual 2 are the first three, the flasher schemes the next three
UF2_TAG_OTA_PART_INFO = 0xC0EE0C
# Tags uf2ota acts on beyond the image they sit in; one in an image that
# would be dropped leaves the package untrimmed
_KEEP_TAGS = frozenset(
{
0x5D57D0, # OTA_FORMAT_1
0x6C8492, # OTA_FORMAT_2
0x6EC68A, # OTA_PART_LIST
0xB948DE, # BINPATCH
0x8288ED, # FAL_PTABLE
}
)
def _tags(block: bytes, length: int) -> dict[int, bytes] | None:
"""The extension tags after a block's payload, or None if malformed."""
tags: dict[int, bytes] = {}
pos = _DATA_START + length
while pos + 4 <= _DATA_END and (size := block[pos]):
if size < 4 or pos + size > _DATA_END:
return None
tags[int.from_bytes(block[pos + 1 : pos + 4], "little")] = block[
pos + 4 : pos + size
]
pos += (size + 3) & ~3
return tags
def device_ota_uf2(data: bytes) -> bytes | None:
"""``data`` without the images no device scheme writes.
None when there is nothing to drop, or when ``data`` is not a well
formed UF2, so the caller sends the file as it is.
"""
if len(data) % UF2_BLOCK_SIZE:
return None
count = len(data) // UF2_BLOCK_SIZE
kept: list[bytes] = []
keep = True
for seq in range(count):
block = data[seq * UF2_BLOCK_SIZE : (seq + 1) * UF2_BLOCK_SIZE]
flags, _, length, block_seq, block_count = _HEADER_WORDS.unpack_from(block, 8)
# A missing or repeated block must stay visible to the device, which
# takes the update size from the block count
if (
block[:8] != UF2_MAGIC_HEAD
or block[_DATA_END:] != UF2_MAGIC_TAIL
or _DATA_START + length > _DATA_END
or (block_seq, block_count) != (seq, count)
):
return None
tags = _tags(block, length) if flags & UF2_FLAG_HAS_TAGS else {}
if tags is None:
return None
if (part_info := tags.get(UF2_TAG_OTA_PART_INFO)) is not None:
# uf2ota rejects a selector without the whole scheme map
if len(part_info) < 3:
return None
# Any of the three device scheme nibbles names a partition
keep = int.from_bytes(part_info[:2], "big") >> 4 != 0
if keep:
kept.append(block)
elif not _KEEP_TAGS.isdisjoint(tags):
# Without its image's OTA_PART_INFO the device would write this
# block to the partition of the image before it
return None
if len(kept) == count:
return None
out = bytearray(b"".join(kept))
for seq in range(len(kept)):
struct.pack_into("<2I", out, seq * UF2_BLOCK_SIZE + _SEQ_OFFSET, seq, len(kept))
return bytes(out)
+4
View File
@@ -77,3 +77,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
# Called by writer.py; delegates to the shared libretiny implementation.
def copy_files() -> None:
libretiny.copy_files()
# Called by the OTA upload; the shared libretiny implementation.
ota_image = libretiny.device_ota_image
+4 -1
View File
@@ -2,7 +2,6 @@ from collections.abc import Callable
from esphome import automation
import esphome.codegen as cg
from esphome.components import audio
from esphome.components.const import CONF_BITRATE
import esphome.config_validation as cv
from esphome.const import (
@@ -116,6 +115,8 @@ def validate_preferred_format(
"""
def validator(config: ConfigType) -> ConfigType:
from esphome.components import audio
# Inherit settings from audio device if not manually set
inherit_property_from(CONF_NUM_CHANNELS, audio_device_key)(config)
inherit_property_from(CONF_SAMPLE_RATE, audio_device_key)(config)
@@ -155,6 +156,8 @@ def request_codecs_for_format_configs(
Public API for external components. Do not remove.
"""
from esphome.components import audio
needed_formats: set[str] = set()
need_all = False
+1 -6
View File
@@ -107,12 +107,7 @@ void MipiDsi::setup() {
}
#endif
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
} else {
esp_lcd_panel_io_tx_param(this->io_handle_, SW_RESET_CMD, nullptr, 0);
}
+1 -8
View File
@@ -33,14 +33,7 @@ void MipiRgb::setup_enables_() {
}
delay(10);
}
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
}
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
}
#ifdef USE_SPI
+2 -9
View File
@@ -151,15 +151,8 @@ class MipiSpi : public display::Display,
pin->setup();
pin->digital_write(true);
}
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
// required delay after reset is already in the init sequence, don't duplicate
}
// required delay after reset is already in the init sequence, don't duplicate
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
// need to know when the display is ready for SLPOUT command - will be 120ms after reset
auto when = millis() + 120;
@@ -226,6 +226,12 @@ def set_sdkconfig_options(config: ConfigType) -> None:
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", not border_router)
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", not border_router)
if not border_router:
# The DNS64 hook breaks IPv6 literal lookups; see openthread_esp.cpp.
# Fixed upstream on ESP-IDF master, remove this once a release includes
# "fix(openthread): bypass DNS64 synthesis for IPv6 literals",
# https://github.com/espressif/esp-idf/commit/87b133ee1a7e91c1ee95176b4733b7f72236e5da
# (merged in https://github.com/espressif/esp-idf/commit/83239509832bf5d427935cd365f4b62a320e8765)
cg.add_build_flag("-Wl,--wrap=lwip_hook_dns_external_resolve")
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
if border_router:
@@ -35,6 +35,10 @@
#include "esp_vfs_eventfd.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifndef USE_OPENTHREAD_BORDER_ROUTER
#include "lwip/dns.h"
#include "lwip/ip_addr.h"
#endif
ESPHOME_LOG_TAG(TAG, "openthread");
@@ -489,4 +493,26 @@ InstanceLock::~InstanceLock() {
}
} // namespace esphome::openthread
#ifndef USE_OPENTHREAD_BORDER_ROUTER
// ESP-IDF's DNS64 hook runs before lwIP's literal address check and retries every lookup as IPv4,
// so an IPv6 literal never resolves. Let lwIP handle those; linked with --wrap from __init__.py.
// Fixed upstream on ESP-IDF master, remove this once a release includes
// "fix(openthread): bypass DNS64 synthesis for IPv6 literals",
// https://github.com/espressif/esp-idf/commit/87b133ee1a7e91c1ee95176b4733b7f72236e5da
// (merged in https://github.com/espressif/esp-idf/commit/83239509832bf5d427935cd365f4b62a320e8765)
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" {
int __real_lwip_hook_dns_external_resolve(const char *name, ip_addr_t *addr, dns_found_callback found,
void *callback_arg, u8_t addrtype, err_t *err);
int __wrap_lwip_hook_dns_external_resolve(const char *name, ip_addr_t *addr, dns_found_callback found,
void *callback_arg, u8_t addrtype, err_t *err) {
ip_addr_t literal;
if (ipaddr_aton(name, &literal) && IP_IS_V6_VAL(literal))
return 0;
return __real_lwip_hook_dns_external_resolve(name, addr, found, callback_arg, addrtype, err);
}
} // extern "C"
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
#endif // USE_OPENTHREAD_BORDER_ROUTER
#endif
+1 -14
View File
@@ -9,23 +9,10 @@ ESPHOME_LOG_TAG(TAG, "pcd_8544");
void PCD8544::setup_pins_() {
this->spi_setup();
this->init_reset_();
pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10});
this->dc_pin_->setup();
}
void PCD8544::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void PCD8544::initialize() {
this->init_internal_(this->get_buffer_length_());
-2
View File
@@ -57,8 +57,6 @@ class PCD8544 final : public display::DisplayBuffer,
void setup_pins_();
void init_reset_();
size_t get_buffer_length_();
void start_command_();
+4
View File
@@ -77,3 +77,7 @@ async def pin_to_code(config: ConfigType) -> MockObj:
# Called by writer.py; delegates to the shared libretiny implementation.
def copy_files() -> None:
libretiny.copy_files()
# Called by the OTA upload; the shared libretiny implementation.
ota_image = libretiny.device_ota_image
+1 -12
View File
@@ -122,18 +122,7 @@ void SH1122::fill(Color color) {
const uint8_t fill = (color4 & SH1122_COLORMASK) | ((color4 & SH1122_COLORMASK) << SH1122_COLORSHIFT);
memset(this->buffer_, fill, this->get_buffer_length_());
}
void SH1122::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SH1122::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const LogString *SH1122::model_str_() {
switch (this->model_) {
case SH1122_MODEL_256_64:
@@ -355,18 +355,7 @@ void SSD1306::fill(Color color) {
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1306::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1306::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const LogString *SSD1306::model_str_() {
return ModelStrings::get_log_str(static_cast<uint8_t>(this->model_), ModelStrings::LAST_INDEX);
}
@@ -184,18 +184,7 @@ void SSD1322::fill(Color color) {
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1322::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1322::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const char *SSD1322::model_str_() {
switch (this->model_) {
case SSD1322_MODEL_256_64:
@@ -211,18 +211,7 @@ void SSD1325::fill(Color color) {
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1325::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1325::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const char *SSD1325::model_str_() {
switch (this->model_) {
case SSD1325_MODEL_128_32:
@@ -160,18 +160,7 @@ void SSD1327::fill(Color color) {
for (uint32_t i = 0; i < this->get_buffer_length_(); i++)
this->buffer_[i] = fill;
}
void SSD1327::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1327::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const char *SSD1327::model_str_() {
switch (this->model_) {
case SSD1327_MODEL_128_128:
@@ -142,17 +142,6 @@ void SSD1331::fill(Color color) {
}
}
}
void SSD1331::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1331::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
} // namespace esphome::ssd1331_base
@@ -174,18 +174,7 @@ void SSD1351::fill(Color color) {
}
}
}
void SSD1351::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void SSD1351::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const char *SSD1351::model_str_() {
switch (this->model_) {
case SSD1351_MODEL_128_96:
@@ -9,12 +9,8 @@ ESPHOME_LOG_TAG(TAG, "st7123.touchscreen");
void ST7123Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false); // TP_RESX is active low, assert for at least tRSTW (2ms)
delay(5);
this->reset_pin_->digital_write(true);
// TP_RESX is active low, assert for at least tRSTW (2ms)
pulse_reset(this->reset_pin_, {.high_before_ms = 5, .low_ms = 5});
// The controller needs up to 20ms to initialize after reset before it can be accessed.
this->setup_time_ = millis() + 30;
}
+1 -12
View File
@@ -137,18 +137,7 @@ void ST7567::fill(Color color) {
memset(buffer_, fill, this->get_buffer_length_());
}
void ST7567::init_reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(1);
// Trigger Reset
this->reset_pin_->digital_write(false);
delay(10);
// Wake up
this->reset_pin_->digital_write(true);
}
}
void ST7567::init_reset_() { pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}); }
const char *ST7567::model_str_() { return "ST7567 128x64"; }
@@ -54,10 +54,7 @@ void TT21100Touchscreen::setup() {
}
// Perform reset if necessary
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_();
}
pulse_reset(this->reset_pin_, {.low_ms = 10, .high_after_ms = 10});
// Update display dimensions if they were updated during display setup
if (this->display_ != nullptr) {
@@ -122,15 +119,6 @@ void TT21100Touchscreen::update_touches() {
}
}
void TT21100Touchscreen::reset_() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->digital_write(false);
delay(10);
this->reset_pin_->digital_write(true);
delay(10);
}
}
void TT21100Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG, "TT21100 Touchscreen:");
LOG_I2C_DEVICE(this);
@@ -28,8 +28,6 @@ class TT21100Touchscreen final : public Touchscreen, public i2c::I2CDevice {
void register_button_listener(TT21100ButtonListener *listener) { this->button_listeners_.push_back(listener); }
protected:
void reset_();
void update_touches() override;
InternalGPIOPin *interrupt_pin_;
+18 -2
View File
@@ -245,6 +245,22 @@ WiFiConfigureAction = wifi_ns.class_(
)
# The supplicant parses the bootstrapping key as a DER encoded P-256 private key in hex; these wrap the
# 32 byte key the way the ESP-IDF enrollee example does
DPP_KEY_DER_PREFIX = "30310201010420"
DPP_KEY_DER_SUFFIX = "a00a06082a8648ce3d030107"
def _validate_dpp_private_key(value: object) -> str:
"""The key is 32 bytes written as 64 hexadecimal characters."""
value = cv.string_strict(value)
if len(value) != 64 or any(c not in "0123456789abcdefABCDEF" for c in value):
raise cv.Invalid(
"private_key must be 32 bytes written as 64 hexadecimal characters"
)
return value.lower()
def _validate_dpp_device_info(value: object) -> str:
"""The supplicant puts the text on a space separated command line and reads it up to the next space."""
value = cv.string_strict(value)
@@ -322,7 +338,7 @@ EAP_AUTH_SCHEMA = cv.All(
# Wi-Fi Easy Connect (DPP) enrollee; the ESP-IDF supplicant supports the QR code method
DPP_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(cv.string_strict),
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(_validate_dpp_private_key),
cv.Optional(CONF_DEVICE_INFO): _validate_dpp_device_info,
cv.Optional(CONF_CHANNELS, default=[6]): cv.ensure_list(
cv.int_range(min=1, max=14)
@@ -904,7 +920,7 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DPP_SUPPORT", True)
add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_HOST_FEAT_WIFI_EXT_DPP", True)
if (key := dpp.get(CONF_PRIVATE_KEY)) is not None:
cg.add(var.set_dpp_key(key))
cg.add(var.set_dpp_key(f"{DPP_KEY_DER_PREFIX}{key}{DPP_KEY_DER_SUFFIX}"))
if (info := dpp.get(CONF_DEVICE_INFO)) is not None:
cg.add(var.set_dpp_device_info(info))
cg.add(var.set_dpp_channels(",".join(str(c) for c in dpp[CONF_CHANNELS])))
+15
View File
@@ -1,4 +1,5 @@
#include "esphome/core/gpio.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -18,4 +19,18 @@ void log_pin(const char *tag, const char *prefix, GPIOPin *pin) {
}
#endif
namespace internal {
void pulse_reset_ms(GPIOPin *pin, uint32_t high_before_ms, uint32_t low_ms, uint32_t high_after_ms) {
if (pin == nullptr)
return;
pin->setup();
const uint32_t waits[] = {high_before_ms, low_ms, high_after_ms};
// Level for each step is high, low, high; a zero high_before_ms skips the first step
for (size_t i = high_before_ms == 0; i < 3; i++) {
pin->digital_write(i != 1);
delay(waits[i]);
}
}
} // namespace internal
} // namespace esphome
+20
View File
@@ -144,4 +144,24 @@ void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin);
void log_pin(const char *tag, const char *prefix, GPIOPin *pin);
#endif
/// Waveform for pulse_reset(): high, low, then high again. Name the fields at the call site, e.g.
/// pulse_reset(this->reset_pin_, {.high_before_ms = 1, .low_ms = 10}).
struct ResetPulse {
uint32_t high_before_ms{0}; ///< Drive high this long first; skipped when 0
uint32_t low_ms{0}; ///< Hold the pin low (in reset) this long
uint32_t high_after_ms{0}; ///< Wait this long after releasing the pin
};
namespace internal {
/// Out-of-line body of pulse_reset(); call that instead.
void pulse_reset_ms(GPIOPin *pin, uint32_t high_before_ms, uint32_t low_ms, uint32_t high_after_ms);
} // namespace internal
/// Set up an active-low reset pin and drive it through \p pulse. Blocks for the whole sequence;
/// does nothing when pin is null. Inline so the fields reach pulse_reset_ms() in registers
/// (RISC-V would otherwise pass the struct by reference).
ESPHOME_ALWAYS_INLINE inline void pulse_reset(GPIOPin *pin, ResetPulse pulse) {
internal::pulse_reset_ms(pin, pulse.high_before_ms, pulse.low_ms, pulse.high_after_ms);
}
} // namespace esphome
+9 -4
View File
@@ -26,17 +26,20 @@ from esphome.const import (
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2,
PLATFORM_RTL87XX,
Platform,
)
_LOGGER = logging.getLogger(__name__)
# Hooks whose loss only degrades diagnostics; skipping one of these is
# logged at debug, while skipping a hook that changes what the CLI does
# (upload method, log transport) warns. A new hook is loud by default.
COSMETIC_HOOKS: Final = frozenset({"process_stacktrace"})
# Hooks whose loss only degrades diagnostics or upload size; skipping one
# of these is logged at debug, while skipping a hook that changes what the
# CLI does (upload method, log transport) warns. A new hook is loud by
# default.
COSMETIC_HOOKS: Final = frozenset({"process_stacktrace", "ota_image"})
# Platforms whose firmware prints the crash handler report share one gate.
_CRASH_HANDLER_GATE = r"0x[0-9a-fA-F]{3,}\b|CRASH DETECTED ON PREVIOUS BOOT"
@@ -71,6 +74,8 @@ PLATFORM_HOOKS: Final[dict[str, frozenset[str]]] = {
"show_logs": frozenset({PLATFORM_NRF52}),
"upload_program": frozenset({PLATFORM_NRF52}),
"process_stacktrace": frozenset(STACKTRACE_GATES),
# The file an app update sends, when it is not the build output itself
"ota_image": frozenset({PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RTL87XX}),
}
+218 -17
View File
@@ -9,7 +9,8 @@ from pathlib import Path
import re
import stat
from esphome.core import EsphomeError
from esphome import compiled_config, yaml_util
from esphome.core import CORE, EsphomeError
from esphome.helpers import write_file
from esphome.types import ConfigType
@@ -20,6 +21,10 @@ PLAIN_SCALAR = r"[^\s#\"'>|&*!%@`\[\]{},]+"
TRAILER = r"(?P<quote>[\"']?){value}(?P=quote)(?P<trail>(?:\s+#.*)?\s*)$"
class RestoreError(EsphomeError):
"""A rollback left at least one file as it was written."""
def read_text(path: Path) -> str:
"""The file as written, line endings included; read_file would fold them."""
try:
@@ -30,12 +35,25 @@ def read_text(path: Path) -> str:
@dataclass
class LineEdit:
"""One line to rewrite; ``old_line`` is what it held when located."""
"""One line to rewrite, or with ``insert_after`` a line to add right
after it; ``old_line`` is what the line held when it was located."""
path: Path
line: int
old_line: str
new_line: str
insert_after: bool = False
@dataclass
class Snapshot:
"""A rewritten file's text before and after, and its mode before; the
restore only puts ``original`` back over ``written``, never over the
user's own edit."""
original: str
written: str
mode: int | None = None
def field_line_re(
@@ -63,17 +81,66 @@ def line_at(doc: Path, line_no: int) -> str:
return lines[line_no]
_DOMAIN = "yaml_edit"
def own_documents() -> set[Path]:
"""Every file the loader read for this configuration, found once per run.
A file behind an include path with a substitution that gave nothing but
substitutions is the one kind this misses."""
if (documents := CORE.data.get(_DOMAIN)) is None:
documents = CORE.data[_DOMAIN] = _own_documents()
return documents
def _own_documents() -> set[Path]:
documents = {str(CORE.config_path)}
def walk(node: object) -> None:
if (rng := getattr(node, "esp_range", None)) is not None:
documents.add(rng.start_mark.document)
if isinstance(node, dict):
for key, value in node.items():
walk(key)
walk(value)
elif isinstance(node, list):
for value in node:
walk(value)
walk(CORE.raw_config or {})
# A file that only gave substitutions leaves no node behind; the tracker
# also lists a secrets file the loader only tried, hence the is_file check
found = yaml_util.discover_user_yaml_files(CORE.config_path, clear_secrets=False)
if found.load_errors: # an incomplete set would misplace a key
raise EsphomeError(
"Could not read every file of the configuration: "
+ "; ".join(found.load_errors)
)
documents.update(map(str, found.files))
return {p.resolve() for d in documents if (p := Path(d)).is_file()}
def source_of(mapping: ConfigType, name: str) -> tuple[Path, int] | None:
"""The file and line ``name:`` was read from, None when validation added
it or a merge key brought it in from an anchor elsewhere. Mapping keys
keep their range through validation, values may not."""
"""The file and line ``name:`` was read from; None when validation added
it or a merge key brought it in from an anchor elsewhere."""
rng = getattr(next((k for k in mapping if k == name), None), "esp_range", None)
if rng is None:
return None
# An included mapping carries the `!include` line of its parent, so only
# a key in the same document can be placed against the mapping
if (own := getattr(mapping, "esp_range", None)) is None:
return None # a mapping built in code, its keys are not on its lines
# merge_config keeps the first mapping's keys with the last one's
# values, so only the value's range names the line that won, and
# only when the key is on it (a block value starts on a line of its own)
rng = getattr(mapping.get(name), "esp_range", None)
if rng is None:
return None
doc, line = Path(rng.start_mark.document), rng.start_mark.line
if doc.resolve() not in own_documents() or not field_line_re(name).match(
line_at(doc, line)
):
return None
return doc, line
# An included mapping carries its parent's `!include` line, so only a
# key in the same document can be checked against the mapping's lines
if (
rng.start_mark.document == own.start_mark.document
and not own.start_mark.line <= rng.start_mark.line <= own.end_mark.line
@@ -82,13 +149,46 @@ def source_of(mapping: ConfigType, name: str) -> tuple[Path, int] | None:
return Path(rng.start_mark.document), rng.start_mark.line
def write_keeping_mode(path: Path, text: str, like: Path | None = None) -> None:
"""Write with the mode of ``like`` (default: the file itself) rather
than write_file's 0644; a 0600 secrets file stays 0600."""
def editable_file(path: Path) -> Path:
"""``path`` resolved, so a symlink's target is edited; refuses a file
outside the config dir or under the build data."""
resolved = path.resolve()
if (
not path.is_file()
or not resolved.is_relative_to(CORE.config_dir.resolve())
or resolved.is_relative_to(CORE.data_dir.resolve())
):
raise EsphomeError(
f"{path} is not an editable file in the configuration directory"
)
return resolved
def source_line(mapping: ConfigType, name: str) -> tuple[Path, int, str]:
"""The file, line number and text ``name:`` was read from."""
if (source := source_of(mapping, name)) is None:
raise EsphomeError(f"'{name}' was not read from a file")
doc, line_no = source
# Returned as the loader saw it: a `!secret` in a symlinked include
# resolves beside the link
editable_file(doc)
return doc, line_no, line_at(doc, line_no)
def mode_of(path: Path) -> int:
try:
mode = stat.S_IMODE((like or path).stat().st_mode)
return stat.S_IMODE(path.stat().st_mode)
except OSError as err:
raise EsphomeError(f"Could not read the mode of {like or path}: {err}") from err
raise EsphomeError(f"Could not read the mode of {path}: {err}") from err
def write_keeping_mode(
path: Path, text: str, like: Path | None = None, mode: int | None = None
) -> None:
"""Write with ``mode``, else the mode of ``like`` (default: the file
itself), rather than write_file's 0644; a 0600 secrets file stays 0600."""
if mode is None:
mode = mode_of(like or path)
try:
write_file(path, text, private=True)
except EsphomeError as err:
@@ -103,12 +203,113 @@ def write_keeping_mode(path: Path, text: str, like: Path | None = None) -> None:
def rewritten_text(original: str, edits: list[LineEdit]) -> str:
"""``original`` with the edits applied; every edit must still find the
line it was located on."""
"""``original`` with the edits applied; a line that changed is refused."""
lines = original.splitlines(keepends=True)
for edit in edits:
file_newline = "\r\n" if "\r\n" in original else "\n"
# Bottom up, so an insertion never shifts a later edit; insertions under
# one line keep their order
order = sorted(
enumerate(edits), key=lambda ie: (ie[1].line, ie[1].insert_after, ie[0])
)
for _, edit in reversed(order):
text = lines[edit.line].rstrip("\r\n") if edit.line < len(lines) else None
if text != edit.old_line:
raise EsphomeError(f"{edit.path}:{edit.line + 1} changed since it was read")
lines[edit.line] = edit.new_line + lines[edit.line][len(text) :]
ending = lines[edit.line][len(text) :]
if edit.insert_after:
# A last line without a newline gets the file's own kind
lines[edit.line] = text + (ending or file_newline)
lines.insert(edit.line + 1, edit.new_line + ending)
else:
lines[edit.line] = edit.new_line + ending
return "".join(lines)
def apply_line_edits(edits: list[LineEdit]) -> dict[Path, Snapshot]:
"""Rewrite the located lines in place; returns each file's text before
and after for restore_files. Nothing is written while any line is stale,
and a file that no longer loads is put back here."""
by_path: dict[Path, list[LineEdit]] = {}
seen_as: dict[Path, Path] = {} # the path the loader read a file by
for edit in edits:
resolved = editable_file(edit.path)
by_path.setdefault(resolved, []).append(edit)
seen_as.setdefault(resolved, edit.path)
originals = {}
for path, own in by_path.items():
original = read_text(path)
originals[path] = Snapshot(
original, rewritten_text(original, own), mode_of(path)
)
try:
for path, snapshot in originals.items():
write_keeping_mode(path, snapshot.written)
# Reloaded as the loader saw them: a relative !include inside a
# symlinked file resolves beside the link
reloads = [seen_as[path] for path in originals]
if CORE.config_path.resolve() not in originals:
# Its !secret tags register a value written elsewhere
reloads.append(CORE.config_path)
for path in reloads:
try:
# The secret registry stays: later output still redacts
yaml_util.load_yaml(
path, clear_secrets=False, track_document_range=False
)
except EsphomeError as err:
raise EsphomeError(f"{path} no longer loads: {err}") from err
compiled_config.invalidate_compiled_config()
except BaseException as err:
try:
restore_files(originals)
except RestoreError as restore_err:
if not isinstance(err, Exception):
err.add_note(str(restore_err)) # an interrupt stays one
raise err from None
raise RestoreError(f"{err}; {restore_err}") from err
raise
return originals
def restore_files(originals: dict[Path, Snapshot]) -> None:
"""Put back what apply_line_edits wrote, mode included; a file that
failed or changed meanwhile is left alone and reported. Every file is
tried whatever went wrong before; an interrupt or exit is raised again
at the end with the report as a note."""
failed: list[str] = []
stop: BaseException | None = None
def report(what: str, err: BaseException) -> None:
nonlocal stop
if isinstance(err, Exception):
why = str(err) or type(err).__name__
else: # an interrupt or exit says what it is, not its code
why = (
"interrupted"
if isinstance(err, KeyboardInterrupt)
else type(err).__name__
)
stop = err
failed.append(f"{what}: {why}")
for path, snapshot in originals.items():
try:
current = read_text(path)
if current == snapshot.original:
continue # never written, or already put back
if current != snapshot.written:
raise EsphomeError("changed since it was written, left as is")
write_keeping_mode(path, snapshot.original, mode=snapshot.mode)
except BaseException as err: # noqa: BLE001 # pylint: disable=broad-except
report(str(path), err)
try:
compiled_config.invalidate_compiled_config()
except BaseException as err: # noqa: BLE001 # pylint: disable=broad-except
report("the validated caches", err)
if not failed:
return
message = "Could not restore " + "; ".join(failed)
if stop is not None:
stop.add_note(message)
raise stop
raise RestoreError(message)
+6 -2
View File
@@ -656,9 +656,13 @@ def named_path(path: Path) -> Path:
return path.parent.resolve() / path.name
def discover_user_yaml_files(config_path: Path) -> DiscoveredYamlFiles:
def discover_user_yaml_files(
config_path: Path, clear_secrets: bool = True
) -> DiscoveredYamlFiles:
"""Fresh-re-parse ``config_path`` and report every file the YAML loader
pulled in, plus which of them came in under a secrets filename.
``clear_secrets`` is passed to the load; False keeps the redaction
registry of a configuration loaded earlier in the run.
Does NOT run schema validation, substitutions, or package resolution — so
component-internal YAML loaded by validators (LVGL helpers, dashboard
@@ -702,7 +706,7 @@ def discover_user_yaml_files(config_path: Path) -> DiscoveredYamlFiles:
load_listeners.append(_on_load)
try:
try:
data = load_yaml(config_path)
data = load_yaml(config_path, clear_secrets=clear_secrets)
except EsphomeError as err:
load_errors = [f"{config_path}: {err}"]
else:
@@ -0,0 +1,12 @@
---
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
dpp:
private_key: 00112233445566778899AABBCCDDEEFF00112233445566778899aabbccddeeff
@@ -0,0 +1,42 @@
"""The DPP bootstrapping key is given as 32 raw bytes in hex and handed to the supplicant DER encoded."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components import wifi
import esphome.config_validation as cv
RAW_KEY = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
@pytest.mark.parametrize(
"value",
[
RAW_KEY[:-2],
RAW_KEY + "00",
"zz" + RAW_KEY[2:],
wifi.DPP_KEY_DER_PREFIX + RAW_KEY + wifi.DPP_KEY_DER_SUFFIX,
],
)
def test_key_must_be_64_hex_digits(value: str) -> None:
with pytest.raises(cv.Invalid, match="64 hexadecimal characters"):
wifi._validate_dpp_private_key(value)
def test_key_is_lower_cased() -> None:
assert wifi._validate_dpp_private_key(RAW_KEY.upper()) == RAW_KEY
def test_generated_key_is_der_encoded(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The ESP-IDF supplicant expects the P-256 DER header around the raw key."""
main_cpp = generate_main(component_config_path("dpp_key.yaml"))
assert (
f'set_dpp_key("{wifi.DPP_KEY_DER_PREFIX}{RAW_KEY}{wifi.DPP_KEY_DER_SUFFIX}")'
in main_cpp
)
@@ -0,0 +1,51 @@
#include <gtest/gtest.h>
#include <string>
#include <vector>
#include "esphome/core/gpio.h"
#include "esphome/core/hal.h"
namespace esphome::core::testing {
class RecordingPin : public GPIOPin {
public:
void setup() override { this->events += 'S'; }
void pin_mode(gpio::Flags /*flags*/) override {}
gpio::Flags get_flags() const override { return gpio::FLAG_OUTPUT; }
bool digital_read() override { return false; }
void digital_write(bool value) override {
this->events += value ? 'H' : 'L';
this->write_ms.push_back(millis());
}
std::string events;
std::vector<uint32_t> write_ms;
};
TEST(PulseReset, NullPinIsIgnored) { pulse_reset(nullptr, {.low_ms = 1}); }
TEST(PulseReset, LowThenHigh) {
RecordingPin pin;
pulse_reset(&pin, {.low_ms = 1});
EXPECT_EQ(pin.events, "SLH");
}
TEST(PulseReset, PreHighDrivesHighFirst) {
RecordingPin pin;
pulse_reset(&pin, {.high_before_ms = 1, .low_ms = 1, .high_after_ms = 1});
EXPECT_EQ(pin.events, "SHLH");
}
// Distinct waits, so dropping or swapping one shows up; only lower bounds, a busy host can run long
TEST(PulseReset, HoldsEachLevelForItsTime) {
RecordingPin pin;
pulse_reset(&pin, {.high_before_ms = 10, .low_ms = 20, .high_after_ms = 30});
const uint32_t end = millis();
ASSERT_EQ(pin.events, "SHLH");
EXPECT_GE(pin.write_ms[1] - pin.write_ms[0], 10u);
EXPECT_GE(pin.write_ms[2] - pin.write_ms[1], 20u);
EXPECT_GE(end - pin.write_ms[2], 30u);
}
} // namespace esphome::core::testing
@@ -0,0 +1,14 @@
import esphome.codegen as cg
from esphome.types import ConfigType
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# One empty config entry so to_code runs and sets the define that keeps
# core/line_reader.cpp in the test build.
manifest.multi_conf = False
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_LINE_READER")
manifest.to_code = to_code
+616 -10
View File
@@ -11,6 +11,14 @@ namespace esphome::testing {
// build_plain() compressed with the CLI's window (espota2.DEFLATE_WINDOW_BITS):
// DEFLATED = zlib.compress(plain, 9, wbits=-12)
// STORED = zlib.compress(plain[:300], 0, wbits=-12)
// c = zlib.compressobj(9, zlib.DEFLATED, -12, 9, zlib.Z_FIXED)
// FIXED = c.compress(plain) + c.flush()
// and build_skewed(), whose rare bytes get codes longer than the lookup tables:
// SKEWED = zlib.compress(skewed, 9, wbits=-12)
// and build_mixed() in three parts, so stored blocks follow Huffman blocks:
// c = zlib.compressobj(9, zlib.DEFLATED, -12)
// MIXED = (c.compress(text) + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(noise)
// + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(tail) + c.flush())
static const uint8_t DEFLATED[] = {
0xed, 0xc8, 0xf7, 0x3f, 0xd4, 0x0f, 0x03, 0x00, 0x70, 0x67, 0xaf, 0x4b, 0x67, 0x66, 0x9f, 0x90, 0x91, 0x11, 0xc2,
0x11, 0x91, 0xb8, 0xb3, 0xf7, 0x3a, 0xd9, 0x5f, 0x4e, 0x99, 0x67, 0x1e, 0xce, 0x8a, 0xac, 0xec, 0x59, 0xb8, 0xc2,
@@ -201,6 +209,532 @@ static const uint8_t STORED[] = {
0x68, 0x6f, 0x6d, 0x65, 0x20, 0x6f, 0x74, 0x61, 0x20, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x20,
};
static const uint8_t FIXED[] = {
0x4b, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b,
0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15,
0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x23, 0x49, 0xec, 0x58, 0x5d, 0x63, 0xb6, 0xf7, 0xef,
0x47, 0xbf, 0x43, 0xbe, 0xed, 0xbd, 0x5f, 0x23, 0xf3, 0xb0, 0x9d, 0x71, 0xbf, 0xe1, 0xbd, 0xb0, 0x22, 0x7e, 0xf7,
0xf4, 0xb4, 0xf6, 0xc8, 0x55, 0x1d, 0x36, 0x91, 0xaf, 0xc2, 0x84, 0xab, 0x2f, 0xb5, 0x2e, 0xbc, 0x61, 0x13, 0x12,
0xaa, 0x6f, 0xbe, 0x2e, 0x35, 0xfa, 0x16, 0x53, 0xe5, 0x8c, 0x33, 0x8f, 0xa5, 0xca, 0xfa, 0xe2, 0x6f, 0xce, 0xec,
0x97, 0xb7, 0x37, 0x7b, 0xe7, 0x5c, 0xe1, 0xcb, 0xfb, 0x6b, 0xdf, 0xb2, 0x5b, 0x4f, 0xda, 0xfa, 0xee, 0x68, 0xe6,
0xfa, 0xfd, 0x0f, 0x7b, 0x58, 0xa0, 0xf0, 0xbb, 0x7f, 0x63, 0x04, 0xeb, 0x84, 0xa3, 0x9c, 0x77, 0x82, 0xcf, 0xae,
0xb2, 0xf6, 0x98, 0x19, 0x74, 0x39, 0x68, 0x2e, 0xdb, 0xfc, 0x57, 0x5b, 0x0f, 0xb1, 0x09, 0xcf, 0xf0, 0xdc, 0xc4,
0x28, 0xb7, 0xc6, 0xa8, 0xc3, 0x70, 0x4e, 0x90, 0xdb, 0xd4, 0x42, 0xb3, 0xfe, 0xf0, 0x6f, 0x96, 0xb2, 0x62, 0xbf,
0x3a, 0x4a, 0xbe, 0xce, 0xa8, 0x11, 0x8f, 0x71, 0xdc, 0x9d, 0x5b, 0xd8, 0xc7, 0x5f, 0x10, 0x79, 0x9c, 0x51, 0x5a,
0xdf, 0xd8, 0x76, 0xe2, 0x01, 0xd9, 0xa5, 0xbc, 0xbc, 0xab, 0x8d, 0x7b, 0xeb, 0xe2, 0xfa, 0xed, 0x9e, 0x65, 0x94,
0x2c, 0xb3, 0xda, 0x78, 0x78, 0xb2, 0xe0, 0x8a, 0x94, 0xe3, 0xb7, 0x4f, 0x3d, 0x48, 0x78, 0xf8, 0x79, 0x3f, 0x27,
0x43, 0xfa, 0xa2, 0xc7, 0xaa, 0x0b, 0x14, 0x0d, 0xdb, 0xaf, 0x26, 0x1d, 0x5d, 0xe1, 0x5f, 0xa7, 0xc7, 0x99, 0x3d,
0x65, 0xfe, 0x86, 0xdc, 0x95, 0xf3, 0xa2, 0xb8, 0xdd, 0x0a, 0x1a, 0xb6, 0x9d, 0x77, 0xe7, 0x59, 0xb6, 0x54, 0xeb,
0xc6, 0x9a, 0xdf, 0x0b, 0x5e, 0x6f, 0xaf, 0x54, 0x29, 0x52, 0x9e, 0xe4, 0xd1, 0x70, 0x74, 0xd9, 0xf2, 0xd6, 0xed,
0xd7, 0x7b, 0x26, 0x3c, 0x59, 0x9d, 0xec, 0x12, 0x94, 0xf6, 0x78, 0x8e, 0xb1, 0xea, 0xcf, 0xb8, 0x55, 0xbb, 0x8a,
0x13, 0x62, 0xbd, 0x0b, 0xeb, 0xf6, 0xad, 0xec, 0x09, 0x97, 0x2c, 0x3c, 0x7c, 0x2a, 0xee, 0xa9, 0x96, 0xf1, 0x9a,
0x8e, 0xc0, 0xb4, 0x85, 0xd5, 0xa5, 0xe9, 0x29, 0xb3, 0x32, 0xdf, 0xe7, 0x87, 0x39, 0x2d, 0x90, 0x0d, 0x3c, 0xca,
0xf4, 0x7d, 0xf7, 0x24, 0xd7, 0x7d, 0xf9, 0xbc, 0xdb, 0x2c, 0xce, 0x08, 0xfc, 0xdd, 0x1d, 0x12, 0x28, 0x53, 0xcd,
0x3e, 0x45, 0x7d, 0x72, 0xed, 0xa4, 0xc3, 0x57, 0x8e, 0x2d, 0x4d, 0x0c, 0x64, 0xb4, 0xb0, 0x58, 0xbe, 0xff, 0x23,
0x0b, 0xaf, 0xe8, 0xec, 0x06, 0xf9, 0xe6, 0xab, 0x4b, 0x32, 0x3b, 0x6a, 0xe6, 0x6f, 0x63, 0xbc, 0x35, 0x59, 0xdc,
0xb5, 0x5b, 0x68, 0x13, 0x93, 0x71, 0x4c, 0xc0, 0xb5, 0x87, 0x61, 0x4b, 0xd6, 0x3a, 0x79, 0xc5, 0xdc, 0x6d, 0x4b,
0x7c, 0xc9, 0x2c, 0xf4, 0x90, 0x7f, 0xf6, 0x2b, 0x35, 0x9d, 0xc4, 0x3b, 0x49, 0x1e, 0xd9, 0xb9, 0x22, 0x0f, 0x98,
0x5b, 0xbd, 0x8a, 0xdc, 0x6e, 0x4d, 0x3b, 0x51, 0x2b, 0x73, 0x91, 0xd5, 0xee, 0xe9, 0xa4, 0x02, 0xe7, 0xf8, 0x1c,
0x66, 0xd6, 0xcd, 0xaf, 0x37, 0x2b, 0x98, 0xfa, 0xd6, 0xa5, 0x05, 0x30, 0x9a, 0x1d, 0x30, 0x7e, 0xc8, 0x7f, 0xd2,
0x42, 0xef, 0xa5, 0xa6, 0xa4, 0x7f, 0xdc, 0xc6, 0x8b, 0x9e, 0xdd, 0xd6, 0xc1, 0x7f, 0xe7, 0xdb, 0xbf, 0x53, 0x55,
0x35, 0xad, 0xe6, 0x15, 0x5c, 0xef, 0x23, 0xd8, 0x63, 0x74, 0xe5, 0x56, 0xfd, 0x0d, 0xb1, 0xf0, 0x87, 0xcb, 0xce,
0xd5, 0x1e, 0x5c, 0x97, 0xb4, 0x5f, 0xf8, 0x49, 0xbb, 0x8f, 0xd5, 0xc1, 0xcd, 0x3c, 0x91, 0x33, 0xdd, 0x23, 0xa2,
0xf6, 0x56, 0xd4, 0xec, 0x0a, 0x60, 0x7c, 0x2b, 0xfd, 0xaa, 0xcb, 0xb3, 0xe3, 0xdd, 0x35, 0x91, 0xd6, 0xd5, 0x1b,
0x74, 0xee, 0x99, 0x4e, 0x16, 0xd4, 0x65, 0x94, 0xb9, 0xae, 0xe1, 0x6c, 0xf0, 0x7c, 0x03, 0xc7, 0xdb, 0xca, 0x99,
0xc2, 0x81, 0x97, 0xac, 0xca, 0xd7, 0xda, 0x6e, 0xf9, 0x71, 0xfc, 0x14, 0xb3, 0xd2, 0xa5, 0x93, 0xc7, 0xd4, 0xf9,
0x59, 0xce, 0x55, 0xd9, 0x1f, 0xc8, 0xd0, 0x39, 0x5f, 0x94, 0xc5, 0x79, 0xc8, 0x89, 0xa1, 0x28, 0xce, 0xd1, 0xe4,
0xc7, 0xb4, 0x4c, 0xfb, 0xbd, 0x56, 0x11, 0x13, 0xbb, 0x1f, 0x9e, 0x59, 0xb4, 0x71, 0xd2, 0xdd, 0xf2, 0x85, 0xa6,
0xff, 0x3e, 0x7b, 0xef, 0xd9, 0xf8, 0xd8, 0x5c, 0x90, 0xf7, 0x78, 0xea, 0xbe, 0x8f, 0x89, 0x4f, 0xe3, 0xd8, 0xfe,
0x9b, 0x1e, 0x2f, 0xeb, 0x8c, 0xfd, 0x92, 0xe7, 0x75, 0x66, 0x6b, 0x48, 0xdd, 0x47, 0xd1, 0x13, 0x0b, 0x66, 0xf6,
0xc7, 0x14, 0x70, 0xbf, 0x17, 0x39, 0xf6, 0x94, 0x6b, 0x8e, 0xe4, 0x16, 0x59, 0x9f, 0x73, 0x61, 0x6c, 0x77, 0x9c,
0x04, 0x55, 0x9f, 0x4f, 0xcb, 0xe7, 0x57, 0xb4, 0xbd, 0xff, 0x33, 0xdc, 0x9d, 0xf7, 0xbe, 0x76, 0xd6, 0x9f, 0xf2,
0xde, 0xab, 0x2f, 0x6f, 0xfe, 0xdc, 0xfa, 0xe0, 0x75, 0x51, 0x8b, 0x54, 0x9f, 0x93, 0x88, 0x6c, 0x57, 0x5e, 0xfc,
0x24, 0xab, 0xdf, 0xdc, 0x4f, 0xbf, 0x3d, 0x39, 0x30, 0xdf, 0xf5, 0x9a, 0x56, 0xf3, 0xfe, 0x8d, 0x67, 0xb3, 0x8e,
0xec, 0xef, 0xb9, 0x77, 0x7f, 0xd3, 0xf7, 0xca, 0xef, 0x09, 0xe1, 0x7f, 0xac, 0x6d, 0xab, 0xf5, 0x4e, 0xcf, 0x71,
0xac, 0x56, 0x5f, 0xfa, 0xd8, 0x23, 0x42, 0x94, 0x5d, 0xfc, 0xc1, 0xce, 0xd6, 0xf8, 0xe4, 0x15, 0xdf, 0x34, 0x85,
0x9c, 0x19, 0xb2, 0x6e, 0xbf, 0x4b, 0x51, 0x09, 0xea, 0x3e, 0x62, 0x1d, 0xbb, 0xdb, 0x54, 0x62, 0xd1, 0xe5, 0xce,
0xff, 0x9b, 0x16, 0x44, 0x1a, 0x7c, 0xba, 0x7d, 0xf5, 0xa0, 0x6f, 0x96, 0xb7, 0xd9, 0x9c, 0xd8, 0x8a, 0x67, 0x17,
0x16, 0x4f, 0xe2, 0x7d, 0x3a, 0x41, 0x70, 0x43, 0x1b, 0xbf, 0xa3, 0x49, 0xc3, 0xb2, 0xce, 0xbd, 0x2f, 0xf5, 0x2b,
0xdc, 0x79, 0x03, 0xa6, 0xb6, 0x4b, 0xef, 0x7f, 0x5c, 0x3f, 0xc5, 0xdc, 0xec, 0x49, 0xbe, 0xa5, 0x85, 0x3e, 0x4f,
0xb3, 0x55, 0xeb, 0xfd, 0xc0, 0x3d, 0x1e, 0x37, 0xc3, 0x76, 0x57, 0x4e, 0xad, 0xdc, 0x7b, 0xc5, 0x23, 0x60, 0xee,
0xca, 0xd4, 0x58, 0xf1, 0x1a, 0x61, 0x6e, 0xa1, 0x18, 0xff, 0xf4, 0x0d, 0x2c, 0x0f, 0xe7, 0x49, 0x6c, 0x66, 0xb0,
0xfa, 0x77, 0xfa, 0x88, 0xcc, 0x77, 0xed, 0x00, 0x8b, 0x3a, 0xbf, 0xdd, 0xc2, 0x47, 0x15, 0x0e, 0xff, 0xb3, 0x5d,
0x62, 0xc0, 0xff, 0xc4, 0x9d, 0xeb, 0x49, 0x10, 0x63, 0x95, 0x78, 0xa3, 0x61, 0x43, 0x43, 0xbc, 0x69, 0x94, 0xae,
0xe8, 0x99, 0x0d, 0x4a, 0xa2, 0xba, 0x0d, 0x17, 0x9f, 0x3d, 0x39, 0x13, 0xb1, 0x3e, 0x9f, 0xb5, 0xb6, 0x75, 0x8e,
0x69, 0x56, 0xc9, 0x82, 0x0f, 0x1a, 0xfe, 0xdf, 0x7f, 0xde, 0xb1, 0x60, 0xd8, 0x7c, 0xe4, 0x5d, 0x88, 0xdf, 0xc7,
0x9b, 0xaf, 0xd6, 0x1e, 0xba, 0xfe, 0x5a, 0x52, 0xe5, 0x48, 0xd8, 0x8a, 0xee, 0xd3, 0x21, 0xd9, 0xeb, 0x0b, 0x22,
0xa2, 0xd8, 0x23, 0xff, 0x31, 0xb0, 0xdd, 0x5f, 0xf8, 0x4c, 0x22, 0x72, 0xd7, 0xc1, 0x68, 0xe5, 0x3f, 0xd1, 0x72,
0x05, 0x06, 0x4e, 0x52, 0x57, 0x2e, 0x18, 0x15, 0x4d, 0x48, 0x73, 0xca, 0x99, 0xbb, 0xe8, 0xe2, 0xdb, 0x72, 0x3b,
0x83, 0x6d, 0xeb, 0x26, 0xf1, 0x26, 0xea, 0x7d, 0x5b, 0x24, 0xe5, 0x79, 0x7b, 0xa1, 0x6c, 0xe7, 0x8a, 0x7b, 0x9f,
0x2c, 0xc2, 0x76, 0x65, 0xaf, 0x3e, 0x15, 0x1c, 0x15, 0xfc, 0x2d, 0x57, 0xb8, 0x71, 0x9d, 0xfc, 0xd2, 0x3f, 0x5e,
0xb6, 0xd7, 0x5d, 0x19, 0x3b, 0x9f, 0x2c, 0x61, 0x98, 0xf1, 0xed, 0xb7, 0x6f, 0x5b, 0x8a, 0x5b, 0x7c, 0xe4, 0x9a,
0xaf, 0x95, 0x66, 0xfa, 0xaf, 0x4e, 0xb9, 0xad, 0x0f, 0x70, 0x4b, 0xeb, 0x54, 0x74, 0x9a, 0xb8, 0xb1, 0xec, 0x12,
0x6f, 0x51, 0xef, 0xe3, 0x88, 0xc7, 0x73, 0xc4, 0x2f, 0x6a, 0x44, 0x24, 0xab, 0xe7, 0xb9, 0x64, 0x37, 0x2d, 0xd9,
0x35, 0xa3, 0xf8, 0xd7, 0xee, 0xff, 0x73, 0xa4, 0xca, 0x3e, 0xc9, 0x6b, 0xce, 0x4c, 0x3a, 0x51, 0x13, 0xfd, 0xfb,
0xa7, 0x94, 0xdb, 0xdf, 0xc8, 0x6f, 0x47, 0x6f, 0xdb, 0xbc, 0x2c, 0x9c, 0x76, 0xa1, 0x50, 0xe6, 0x06, 0xaf, 0xce,
0xcc, 0x0b, 0x51, 0x42, 0x81, 0x17, 0x18, 0x4a, 0xdb, 0x57, 0xf8, 0xef, 0x4a, 0x3b, 0x30, 0xe9, 0xea, 0x85, 0xef,
0x5b, 0xda, 0x9e, 0xda, 0xaf, 0x0f, 0x0d, 0xfd, 0xba, 0xc3, 0x2f, 0x8d, 0x51, 0xa7, 0xf6, 0xc8, 0x26, 0x0b, 0x0d,
0x9e, 0x30, 0xef, 0xf3, 0xe2, 0x73, 0x6c, 0x9f, 0xb0, 0xaf, 0xb6, 0xf1, 0x12, 0xfa, 0x57, 0x3d, 0x41, 0x90, 0x6d,
0xe6, 0xab, 0xe3, 0xb5, 0x17, 0x3f, 0x7f, 0xea, 0x79, 0xae, 0x2a, 0x32, 0xe7, 0x9c, 0xc8, 0xbf, 0x3f, 0x92, 0xb9,
0x8a, 0xe6, 0x1a, 0x9b, 0xa6, 0x18, 0xf3, 0x6f, 0x7e, 0xc2, 0xe5, 0x7a, 0x7a, 0xfe, 0x0a, 0xc1, 0x07, 0xf3, 0x35,
0xb7, 0x48, 0x88, 0xbf, 0x0f, 0x8f, 0x89, 0x6f, 0xdb, 0xdc, 0x5b, 0x6f, 0xd9, 0xd4, 0x59, 0x5b, 0xb8, 0xf2, 0x4e,
0xfa, 0x05, 0x25, 0x37, 0x79, 0xc1, 0xd5, 0x1f, 0x5f, 0xce, 0x33, 0xc8, 0xdf, 0xf6, 0xee, 0x67, 0xa9, 0xde, 0xd2,
0x29, 0x91, 0xf5, 0x99, 0x0d, 0xbe, 0x2f, 0x5a, 0xe7, 0x45, 0xb2, 0x38, 0x44, 0x48, 0x5d, 0xff, 0xdd, 0x67, 0x33,
0x8b, 0xd7, 0x75, 0xa7, 0x5b, 0x3c, 0xdf, 0xb9, 0x47, 0xc7, 0x2c, 0x0e, 0xf5, 0xaa, 0x6c, 0x0d, 0xf3, 0xb6, 0x3d,
0xcb, 0xdd, 0x1f, 0xd9, 0x22, 0xd6, 0x33, 0xff, 0x8c, 0x8a, 0x8d, 0x4e, 0xf6, 0x39, 0xdd, 0x6f, 0xab, 0x6e, 0xf1,
0xa5, 0x1c, 0x36, 0xff, 0xbb, 0x92, 0x63, 0x7b, 0xdf, 0x93, 0xcb, 0x5d, 0xb3, 0x7f, 0x1a, 0xd6, 0x9d, 0xd3, 0xf5,
0xfd, 0xf1, 0xbe, 0x79, 0xde, 0xc6, 0x77, 0xb7, 0x2e, 0x18, 0x6d, 0x38, 0x5c, 0xcc, 0x3b, 0x4b, 0x25, 0xed, 0xe1,
0xbd, 0x3e, 0x26, 0xee, 0x8e, 0x58, 0x36, 0x1d, 0xf7, 0xa9, 0xae, 0xf1, 0x7f, 0xca, 0x05, 0xcd, 0x59, 0x9e, 0xa5,
0x55, 0xbb, 0xd5, 0x5e, 0x5b, 0xf8, 0x3b, 0xd7, 0x82, 0xdb, 0x41, 0x5c, 0x80, 0x39, 0x36, 0x77, 0x6f, 0xc5, 0x65,
0xce, 0xd4, 0x32, 0x75, 0xae, 0x85, 0xe9, 0x85, 0x9b, 0x9e, 0x73, 0x2f, 0x3e, 0xb0, 0xdb, 0x72, 0x56, 0xdf, 0xd4,
0xe0, 0x67, 0xaf, 0x27, 0x76, 0x45, 0x6d, 0xbb, 0x79, 0x3d, 0xc8, 0xfe, 0xd2, 0x96, 0xe3, 0xb1, 0x9c, 0xf3, 0x44,
0xfc, 0xef, 0xf8, 0xb4, 0x06, 0xbf, 0x58, 0xb3, 0x94, 0xc3, 0x6c, 0x91, 0x4b, 0x8b, 0x4a, 0x83, 0x97, 0xa9, 0xa8,
0xb3, 0x7d, 0xc5, 0x8d, 0xc9, 0xd3, 0x7e, 0xdf, 0xac, 0xdc, 0x73, 0x99, 0xeb, 0xde, 0xd3, 0x98, 0xfe, 0xe3, 0x13,
0xaf, 0xe8, 0x04, 0x3d, 0xd8, 0x9e, 0x5f, 0x30, 0xfb, 0xc6, 0xdc, 0x84, 0x7f, 0x2e, 0xb1, 0xef, 0xdd, 0xaf, 0xa9,
0x15, 0xfe, 0x9f, 0x95, 0x55, 0xcb, 0xfd, 0xa8, 0x3e, 0xa7, 0x50, 0x2b, 0x68, 0xc2, 0xeb, 0xb5, 0xa7, 0x4c, 0xf5,
0x0e, 0xff, 0x8d, 0xfc, 0xce, 0x28, 0xaa, 0x75, 0x8b, 0x9f, 0xd1, 0xe5, 0x94, 0xfb, 0xed, 0xe5, 0xe9, 0xc2, 0x32,
0x55, 0xdc, 0xcc, 0x4d, 0x8d, 0x93, 0x37, 0xee, 0x49, 0x78, 0x1b, 0x7a, 0xbb, 0x37, 0x4d, 0xbd, 0x52, 0x6c, 0x63,
0xc5, 0x72, 0x89, 0x6d, 0xfd, 0x33, 0x7e, 0x2b, 0xb8, 0xf0, 0xe5, 0x2d, 0x8d, 0xeb, 0x50, 0x13, 0x59, 0xa7, 0x11,
0xa6, 0xf0, 0x22, 0xed, 0xed, 0x3b, 0x97, 0xf2, 0x49, 0x09, 0x37, 0xaa, 0x13, 0xe4, 0xb7, 0x64, 0x26, 0x66, 0xef,
0xde, 0x7d, 0x66, 0x91, 0xcb, 0xcd, 0x7f, 0x13, 0x4b, 0xdc, 0xac, 0xea, 0x22, 0x7b, 0x14, 0x3f, 0x1e, 0x7f, 0xf1,
0xc1, 0xed, 0xf3, 0xb6, 0xea, 0x2f, 0x17, 0x67, 0xbf, 0x9d, 0xad, 0x5b, 0x62, 0x7b, 0xbe, 0x9b, 0x31, 0x7f, 0xf9,
0x81, 0xc0, 0x7e, 0x16, 0xdf, 0xb7, 0x79, 0x0b, 0xeb, 0x8e, 0x48, 0xdf, 0x77, 0xbf, 0xa5, 0xe0, 0x77, 0x73, 0x7d,
0xd3, 0x6f, 0xf6, 0x82, 0xb2, 0x9a, 0x98, 0x07, 0x8f, 0xf7, 0xfc, 0x60, 0x99, 0xd3, 0xae, 0x3f, 0x31, 0xea, 0xca,
0x03, 0xb1, 0xaa, 0x6b, 0x53, 0x5f, 0xd6, 0x1c, 0x8c, 0xbf, 0x6c, 0x1e, 0x93, 0x5f, 0x7d, 0xd7, 0xbb, 0x64, 0xf2,
0x66, 0xa9, 0x1d, 0x47, 0x7a, 0x39, 0x7f, 0x45, 0x71, 0xcd, 0xe2, 0x5c, 0x3f, 0xf5, 0xb6, 0x6a, 0xa4, 0x78, 0x89,
0xa8, 0x70, 0x4d, 0x3e, 0x47, 0xce, 0x9b, 0x53, 0x3a, 0x86, 0xc7, 0xf6, 0x09, 0xcc, 0x8e, 0x39, 0xbb, 0xcb, 0xb2,
0xb0, 0xaf, 0x63, 0x4e, 0xb1, 0xc5, 0xf1, 0x23, 0x15, 0x1f, 0x44, 0x7d, 0x7f, 0x5f, 0x2a, 0x8f, 0x0c, 0x3e, 0x68,
0x69, 0xf3, 0xfd, 0x7d, 0xe7, 0xab, 0x62, 0xed, 0x4b, 0x8a, 0x9a, 0xef, 0x6e, 0x86, 0x9d, 0x50, 0x99, 0x95, 0xd8,
0xb7, 0xeb, 0xc1, 0x0b, 0xdb, 0xd7, 0xcb, 0xbb, 0xef, 0x79, 0x1b, 0x5e, 0xb1, 0x9c, 0xf0, 0xea, 0x2e, 0xbf, 0xf2,
0xdf, 0x7d, 0x72, 0xd9, 0x9b, 0xf6, 0x5e, 0xba, 0xd2, 0x67, 0x7a, 0x7a, 0xa1, 0xa6, 0x93, 0x70, 0xf9, 0x59, 0x23,
0xe9, 0xa5, 0x97, 0x57, 0x57, 0x99, 0xec, 0x9b, 0x93, 0xb6, 0x49, 0xe4, 0xc9, 0xbb, 0xfd, 0x0c, 0x47, 0xff, 0x86,
0xac, 0x64, 0xe7, 0xbf, 0x16, 0xf0, 0x66, 0xc3, 0x7d, 0x9d, 0xeb, 0x3b, 0x8f, 0xb3, 0xee, 0xf6, 0xfa, 0x32, 0xc9,
0xa5, 0xed, 0xd9, 0x94, 0x13, 0x82, 0x8c, 0xeb, 0xdf, 0x78, 0x4b, 0x72, 0x8b, 0x79, 0x1e, 0x58, 0x37, 0x6d, 0xba,
0xff, 0xf4, 0xc9, 0x1b, 0xb6, 0xce, 0xde, 0x6e, 0xc5, 0xc4, 0x38, 0x8b, 0x69, 0xd3, 0x9d, 0x0f, 0xbb, 0x76, 0x69,
0x17, 0x96, 0x84, 0x6c, 0xec, 0xbe, 0xdb, 0x3d, 0xf5, 0x94, 0x55, 0xeb, 0x2e, 0x66, 0x95, 0x29, 0x77, 0x5c, 0x98,
0x7f, 0xe7, 0x57, 0xfb, 0x34, 0x58, 0xbc, 0xac, 0xda, 0xb6, 0xc2, 0xe5, 0x1c, 0xfb, 0xef, 0xf5, 0xc7, 0x9a, 0x45,
0xa3, 0x62, 0x73, 0xc4, 0x7f, 0x72, 0xd4, 0x1e, 0x79, 0x96, 0xfb, 0x6f, 0xba, 0xe8, 0xc3, 0xce, 0x05, 0xbb, 0x57,
0xcc, 0x54, 0xda, 0xf7, 0xe6, 0xc6, 0xbb, 0xdb, 0x95, 0xaa, 0x75, 0x33, 0xed, 0xd2, 0x2f, 0x7c, 0x6c, 0x11, 0xe1,
0xd8, 0xf5, 0xba, 0xe1, 0xe8, 0x35, 0xcd, 0x67, 0xf6, 0xf2, 0x4d, 0x16, 0xaa, 0xfc, 0xec, 0xcb, 0x2c, 0x0c, 0xfb,
0x3e, 0x2c, 0xf7, 0x2e, 0xcd, 0xd1, 0xf4, 0x10, 0xbb, 0x76, 0xf7, 0x05, 0xc7, 0xed, 0x87, 0x6a, 0xd2, 0x29, 0x5b,
0x72, 0x84, 0x16, 0xfb, 0x54, 0xca, 0xdd, 0xfb, 0xc6, 0x27, 0xff, 0xef, 0x53, 0xcc, 0xac, 0x6b, 0xa7, 0x74, 0x2b,
0x4c, 0xcb, 0xae, 0xb4, 0xac, 0x32, 0xbc, 0xb6, 0x48, 0x4a, 0x32, 0xf4, 0x02, 0x73, 0xa7, 0xc3, 0xbd, 0x5e, 0x73,
0x9b, 0xb7, 0xa1, 0x3c, 0x81, 0x2b, 0x7f, 0x1e, 0x0b, 0x75, 0x4b, 0x0e, 0x90, 0xb4, 0xb9, 0x76, 0x27, 0xf4, 0xe0,
0xae, 0x63, 0xc1, 0xdc, 0xea, 0x9a, 0x71, 0x4e, 0xc6, 0xb6, 0xaf, 0xdc, 0xff, 0x94, 0x37, 0xa8, 0x97, 0xc4, 0x15,
0xc4, 0xbe, 0xd7, 0x37, 0x39, 0x9d, 0x67, 0xb0, 0xac, 0x7c, 0xe5, 0x95, 0x47, 0x6c, 0xf7, 0xa6, 0xfc, 0xfc, 0x19,
0xd3, 0x11, 0xb5, 0xf2, 0xdc, 0x71, 0x46, 0x66, 0x8f, 0x96, 0x58, 0x16, 0xe1, 0xa7, 0x4c, 0x9f, 0x2d, 0x17, 0x09,
0x27, 0x9d, 0x4f, 0xca, 0x7d, 0xc4, 0x7a, 0x6d, 0x4a, 0xe7, 0xfb, 0x89, 0x71, 0xaa, 0x02, 0xeb, 0x12, 0x6d, 0x57,
0x2b, 0xc8, 0x9e, 0x3e, 0x75, 0xfd, 0xd5, 0x1e, 0xee, 0x19, 0x0b, 0x94, 0x75, 0x67, 0x72, 0x38, 0xc6, 0xdd, 0x67,
0x35, 0x73, 0x8a, 0x68, 0x6a, 0x3e, 0xbc, 0x43, 0xda, 0x7d, 0xb6, 0x48, 0xb7, 0xe9, 0x62, 0xfb, 0x1b, 0x11, 0x37,
0x5f, 0x38, 0xb4, 0x19, 0x15, 0x3c, 0x5c, 0xaa, 0xda, 0xa3, 0xb3, 0xf0, 0xcb, 0x7c, 0x0e, 0xcd, 0x1d, 0x57, 0x8e,
0xea, 0x98, 0xfc, 0x3f, 0x2e, 0x1e, 0x66, 0xf8, 0xfe, 0x74, 0x8f, 0xec, 0x89, 0x84, 0xb3, 0x7a, 0x65, 0x73, 0x92,
0x52, 0xe2, 0x0d, 0x2b, 0x3e, 0x4d, 0xdb, 0x95, 0x3e, 0x93, 0xa7, 0xe4, 0xc0, 0xa1, 0xd2, 0x3d, 0x92, 0x71, 0xbf,
0x7d, 0xa6, 0xd7, 0x2d, 0x33, 0x75, 0xd8, 0xd8, 0x36, 0xe7, 0x9f, 0xe2, 0x52, 0x93, 0xa2, 0x9c, 0x0d, 0xf5, 0x75,
0xce, 0xf1, 0x47, 0x26, 0x1e, 0x5d, 0x75, 0x7e, 0xe3, 0xcc, 0x55, 0xd9, 0x5e, 0x67, 0xfd, 0x17, 0xec, 0x28, 0x3a,
0xbe, 0x76, 0xda, 0x97, 0x95, 0x47, 0x8e, 0x58, 0x3d, 0xed, 0xb0, 0x6a, 0xcc, 0x75, 0x9f, 0xf0, 0x63, 0xfe, 0x77,
0x4f, 0x99, 0xca, 0x4f, 0x8f, 0x2f, 0x55, 0x17, 0xce, 0x77, 0x8b, 0x3e, 0x25, 0xd0, 0x9a, 0x9d, 0x99, 0x76, 0xe7,
0xf4, 0x84, 0x8a, 0x0f, 0x7e, 0xe7, 0x26, 0xcf, 0xd2, 0x75, 0x60, 0xe9, 0xc8, 0xeb, 0x4e, 0x9f, 0x2a, 0x34, 0x75,
0xdd, 0x63, 0x46, 0xb6, 0x0f, 0xfb, 0xb6, 0x35, 0x7d, 0x37, 0x58, 0xb5, 0xb8, 0x23, 0xa5, 0x69, 0x47, 0xc1, 0xee,
0xef, 0xf6, 0xc1, 0x1b, 0x3a, 0x55, 0x4c, 0x72, 0x1e, 0xef, 0x38, 0xac, 0xc1, 0x5b, 0x90, 0xe5, 0x1e, 0x92, 0x24,
0xa6, 0xff, 0xb3, 0xfe, 0xd8, 0xeb, 0x39, 0x13, 0xaf, 0x34, 0xa5, 0x7d, 0x61, 0x13, 0xf9, 0x29, 0x12, 0xb2, 0x4b,
0x72, 0x55, 0xb9, 0xb4, 0xf8, 0x56, 0xb3, 0x73, 0x8c, 0x56, 0x05, 0x25, 0xdd, 0x7b, 0x5e, 0x4c, 0xd8, 0x53, 0x7d,
0x59, 0x37, 0x22, 0x47, 0x59, 0x4f, 0xf0, 0xf7, 0xc4, 0xe2, 0x9c, 0xe6, 0xdc, 0x8d, 0x25, 0x9d, 0xaa, 0x7c, 0x4a,
0x7b, 0xa6, 0xd7, 0xb7, 0xaf, 0x15, 0x7b, 0xb4, 0xdf, 0xcf, 0xee, 0xd6, 0x34, 0x9d, 0x8a, 0xbc, 0xef, 0x39, 0x3e,
0x89, 0x92, 0xed, 0x59, 0xfe, 0xe9, 0x47, 0xcb, 0x7a, 0x8c, 0xab, 0xa7, 0xed, 0x91, 0x66, 0xd9, 0x6b, 0x64, 0xde,
0xb0, 0x8d, 0x93, 0xc3, 0xff, 0x03, 0xab, 0x82, 0x0f, 0x8f, 0xfa, 0xc4, 0x93, 0xea, 0x42, 0x62, 0xfe, 0xcf, 0x15,
0x58, 0x84, 0xdc, 0x9d, 0xdf, 0x99, 0x29, 0xcf, 0x93, 0x3e, 0xd2, 0x7c, 0x7f, 0x65, 0xd6, 0xf2, 0xa4, 0xda, 0x6f,
0xd7, 0xd8, 0x3f, 0x4d, 0x7c, 0x7e, 0xde, 0x76, 0x77, 0x8f, 0xe5, 0x22, 0xae, 0xa4, 0xf8, 0x88, 0x45, 0x3b, 0xcf,
0xdb, 0x16, 0x2b, 0x6b, 0x59, 0x99, 0xed, 0xab, 0x2f, 0x51, 0x75, 0x4c, 0x35, 0xaf, 0x3d, 0x79, 0x6b, 0xf1, 0x96,
0xc4, 0xfb, 0x22, 0x55, 0xc1, 0xe7, 0x83, 0x8b, 0x0c, 0x9b, 0xeb, 0xd6, 0x2e, 0xd2, 0xd8, 0xfe, 0xfa, 0xdd, 0xed,
0xfe, 0x09, 0xee, 0x9c, 0x86, 0xe9, 0x77, 0x3b, 0xfd, 0xa4, 0x16, 0x94, 0x9d, 0xc9, 0xbd, 0x7c, 0x4e, 0xcf, 0x2e,
0xf4, 0xcc, 0x41, 0xdb, 0x75, 0xac, 0x3d, 0xd9, 0x77, 0x5c, 0xbe, 0x86, 0xdc, 0x51, 0x37, 0xe6, 0xfe, 0x5e, 0x56,
0xfe, 0xea, 0xa2, 0xdb, 0x2d, 0x76, 0xe3, 0x4b, 0xac, 0xa5, 0x93, 0x45, 0x16, 0xd9, 0x67, 0xaa, 0xb7, 0xa9, 0x1a,
0xc6, 0xcc, 0x16, 0xb1, 0x93, 0xeb, 0x4f, 0x77, 0x6d, 0xb3, 0x7b, 0xb0, 0x74, 0xef, 0x83, 0x94, 0x72, 0xd1, 0x4f,
0x55, 0x51, 0xcf, 0x9e, 0x36, 0xfd, 0xd6, 0xea, 0xdc, 0x9a, 0x7e, 0xc3, 0xe2, 0x81, 0x00, 0xe3, 0xa1, 0x07, 0x0b,
0x56, 0xcb, 0xdc, 0x69, 0x2c, 0x3c, 0xee, 0xaa, 0x79, 0x9d, 0xa9, 0x5f, 0x2d, 0x6f, 0xfd, 0x9a, 0xb7, 0xab, 0x59,
0x7d, 0x93, 0x3a, 0x7e, 0x5f, 0x57, 0xbf, 0x51, 0xa0, 0xbe, 0x34, 0x5b, 0x8a, 0xed, 0xf6, 0x53, 0xd3, 0xec, 0x97,
0xf1, 0xcf, 0x8b, 0xa3, 0x77, 0x97, 0x2a, 0x9a, 0x1d, 0xd7, 0xb8, 0x7a, 0x54, 0xf2, 0x6f, 0x9f, 0xc2, 0x12, 0xfe,
0xfb, 0x8d, 0x72, 0x47, 0x37, 0xb9, 0x09, 0xed, 0x3e, 0x70, 0xf4, 0x48, 0xd1, 0x99, 0x2b, 0x27, 0xb6, 0x2d, 0x12,
0x4e, 0xbc, 0x7c, 0x99, 0x61, 0x56, 0xb9, 0xee, 0x0e, 0xb5, 0xd4, 0xbf, 0x7d, 0x3f, 0xef, 0xb3, 0xf8, 0x2e, 0xbe,
0x60, 0xe5, 0x9b, 0xbf, 0xd7, 0xc5, 0xc2, 0x3e, 0x6b, 0xfb, 0x2c, 0x55, 0x1f, 0x2e, 0xad, 0x43, 0x9b, 0x62, 0x6f,
0xc5, 0x6f, 0x7c, 0xb3, 0xe0, 0xec, 0x95, 0x40, 0xa6, 0x3f, 0x0d, 0xb5, 0x29, 0x71, 0x9a, 0xa5, 0x82, 0x3f, 0xcd,
0xcf, 0x70, 0xfb, 0x6d, 0x7b, 0x32, 0xf9, 0xdd, 0x81, 0xc7, 0xe5, 0x57, 0x57, 0x95, 0xcb, 0xf5, 0x7e, 0xf4, 0x0a,
0x61, 0x7e, 0xec, 0x72, 0x4d, 0x83, 0xb7, 0x65, 0xae, 0x7b, 0x86, 0xcc, 0x6b, 0x4d, 0xa7, 0x3e, 0xb1, 0x57, 0xef,
0xb8, 0xc4, 0x67, 0x6a, 0xa7, 0x5e, 0x9a, 0x1c, 0x7a, 0x53, 0x79, 0x8e, 0x6f, 0xfd, 0x9a, 0x43, 0x9a, 0xbd, 0x89,
0x9f, 0xe6, 0xf8, 0x5f, 0x08, 0x57, 0x79, 0x6e, 0x58, 0xf6, 0xed, 0x88, 0x11, 0xf7, 0xca, 0xc7, 0xf5, 0x0b, 0x97,
0x9c, 0x10, 0xfe, 0xec, 0x69, 0xed, 0xc8, 0x7a, 0xef, 0x70, 0x23, 0x93, 0xc3, 0x93, 0xdf, 0x57, 0xdc, 0xc4, 0xaf,
0xb6, 0x7e, 0xeb, 0xb5, 0x8d, 0xcc, 0x5a, 0x62, 0xba, 0xbc, 0x41, 0x4d, 0xa9, 0xda, 0xd5, 0x45, 0xe8, 0x9d, 0x7c,
0xe8, 0x05, 0x87, 0x3d, 0x7e, 0xab, 0x76, 0xed, 0x63, 0xf8, 0xeb, 0xa6, 0x2c, 0xda, 0x7a, 0xaa, 0xec, 0x44, 0x9a,
0xe7, 0xf6, 0xf4, 0x9f, 0x3b, 0xb9, 0xbb, 0xc2, 0x17, 0x29, 0x0b, 0xae, 0xa8, 0xce, 0xbb, 0xaf, 0x79, 0xe4, 0x5f,
0xc4, 0xbf, 0x47, 0xd6, 0xb3, 0xf7, 0xee, 0xf7, 0xaa, 0xdf, 0x26, 0x67, 0xed, 0xcf, 0xb2, 0xef, 0xab, 0x5b, 0xa8,
0xd9, 0xc3, 0x93, 0x95, 0xdd, 0x91, 0x69, 0xab, 0x27, 0x9d, 0x9f, 0xb0, 0x88, 0x73, 0xaf, 0x44, 0x78, 0x71, 0x40,
0xf6, 0xd4, 0xed, 0x57, 0xfa, 0x9c, 0xca, 0xef, 0xdd, 0xca, 0x37, 0xb4, 0x2e, 0x57, 0x94, 0x98, 0x33, 0xf3, 0xee,
0x86, 0xbf, 0xb5, 0x5f, 0x75, 0x6e, 0x29, 0x4d, 0x6d, 0x17, 0x3c, 0xd1, 0x9b, 0x32, 0x55, 0xeb, 0xce, 0xf2, 0x9b,
0x55, 0x0c, 0xe7, 0xbf, 0x29, 0x30, 0x48, 0x2d, 0x32, 0x36, 0x0d, 0xbf, 0xb8, 0xe5, 0x4e, 0xa7, 0x46, 0x6f, 0xac,
0x83, 0x9a, 0x4f, 0xe4, 0x76, 0xa1, 0x79, 0x65, 0xeb, 0x16, 0xf5, 0xbd, 0x62, 0x2b, 0xa8, 0x3e, 0x21, 0x27, 0x7b,
0x9b, 0xf7, 0x83, 0xd3, 0xa6, 0xac, 0x57, 0xd2, 0x17, 0xce, 0xf7, 0x7e, 0x3a, 0xbb, 0xe7, 0xb8, 0xef, 0x51, 0x77,
0xc1, 0xeb, 0x57, 0xde, 0x78, 0xf0, 0x07, 0x88, 0xfb, 0x6c, 0x3a, 0x59, 0x1c, 0xfd, 0xf2, 0xd0, 0x07, 0x6d, 0x23,
0x9d, 0x69, 0x27, 0x05, 0x96, 0xf1, 0x9a, 0x14, 0x95, 0xbf, 0xd8, 0xf1, 0xd4, 0x7d, 0x2b, 0x4f, 0xc5, 0x82, 0xa3,
0xda, 0xc5, 0x1a, 0x7b, 0x3e, 0x6f, 0x9f, 0x73, 0xbd, 0xee, 0x73, 0x0f, 0x83, 0x4b, 0xcf, 0xcd, 0xc9, 0x6f, 0x3d,
0xdf, 0xbd, 0xfb, 0x7e, 0xe8, 0xe0, 0x6e, 0x9e, 0xb6, 0x8e, 0xbf, 0x0a, 0xfc, 0x36, 0xb7, 0xfd, 0xd2, 0xce, 0xeb,
0x17, 0x4c, 0xd3, 0x39, 0xd7, 0x70, 0xe9, 0xd3, 0xd4, 0x4d, 0xa9, 0x1f, 0x2c, 0x7c, 0xe3, 0x12, 0x4e, 0xf9, 0x7e,
0x7f, 0xc2, 0xb3, 0xeb, 0xa4, 0x53, 0xe9, 0xb1, 0x4d, 0xf7, 0x58, 0x57, 0x36, 0xfd, 0x3e, 0x65, 0x9b, 0xb1, 0x91,
0x61, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30,
0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x40, 0x73, 0x90, 0x5a, 0x5c, 0x90, 0x91,
0x9f, 0x9b, 0xaa, 0x90, 0x5f, 0x92, 0xa8, 0x90, 0x92, 0x9a, 0x96, 0x93, 0x58, 0x92, 0xaa, 0x30, 0x2a, 0x36, 0x2a,
0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0xb4, 0xc4, 0x00,
};
static const uint8_t SKEWED[] = {
0x2d, 0x97, 0x79, 0xf0, 0x97, 0x55, 0x15, 0xc6, 0x2d, 0x0a, 0xc5, 0x25, 0x25, 0x1b, 0x9d, 0x6c, 0xcc, 0xe3, 0xd8,
0xf0, 0x07, 0x4c, 0x8e, 0x96, 0x63, 0xe2, 0x16, 0x90, 0x26, 0x6a, 0x35, 0x14, 0x2a, 0xc5, 0xe8, 0xc8, 0x35, 0xaf,
0x7a, 0x5d, 0xce, 0x51, 0xba, 0x1a, 0x82, 0x4c, 0x63, 0x2a, 0x69, 0xa6, 0xe0, 0xa8, 0x8c, 0xb8, 0x11, 0x1a, 0x03,
0x29, 0x2e, 0x29, 0xa2, 0x69, 0x2e, 0x8c, 0x1a, 0x05, 0x3f, 0xad, 0x46, 0x74, 0xc4, 0x22, 0xb7, 0x18, 0xd3, 0x16,
0x74, 0x10, 0xd1, 0x98, 0xac, 0xe7, 0x73, 0xbe, 0xf2, 0x63, 0xbe, 0xdf, 0xef, 0xfb, 0xbe, 0xf7, 0xbd, 0xf7, 0x2c,
0xcf, 0x79, 0xce, 0x73, 0xa2, 0x37, 0xf3, 0x62, 0xad, 0xd6, 0xe2, 0xbd, 0x4d, 0xa8, 0x2d, 0xfa, 0x3e, 0xde, 0x7b,
0x6b, 0xd6, 0xad, 0xb8, 0x7b, 0x09, 0xd7, 0xf3, 0x28, 0xad, 0x78, 0x44, 0xf3, 0x68, 0x25, 0xf4, 0x82, 0x45, 0xd7,
0xb3, 0xda, 0x43, 0x1f, 0xc5, 0x9e, 0xd0, 0x97, 0x5d, 0x5a, 0xb4, 0x4b, 0x58, 0x78, 0xdb, 0xb7, 0x9b, 0xf5, 0x28,
0xd6, 0x3b, 0xaf, 0x15, 0xf3, 0x1e, 0xb5, 0xd9, 0x5c, 0x8f, 0xd7, 0xa2, 0x0f, 0xd5, 0xf2, 0x78, 0xf7, 0x56, 0xce,
0x2a, 0x5e, 0x6a, 0xad, 0xad, 0x1e, 0xe3, 0x75, 0xad, 0x47, 0xd1, 0xda, 0xd6, 0xf4, 0x15, 0xd3, 0x2d, 0x22, 0x7a,
0x75, 0x5d, 0x55, 0x1d, 0x12, 0xd5, 0x8e, 0x73, 0x0f, 0xed, 0x50, 0xb4, 0x58, 0xdb, 0x37, 0x9d, 0x58, 0xe3, 0x56,
0xad, 0xd7, 0x06, 0xa1, 0x45, 0xd3, 0x4b, 0x2d, 0xda, 0x5f, 0x26, 0xb4, 0xae, 0x9d, 0xab, 0x75, 0xad, 0xd3, 0x6b,
0xf2, 0xa6, 0xd7, 0xa8, 0xdd, 0xc7, 0x58, 0x95, 0xd9, 0xba, 0x3b, 0x24, 0xdf, 0x46, 0xba, 0x95, 0x16, 0xb2, 0x4b,
0xe7, 0x45, 0x9b, 0x6e, 0xde, 0xfc, 0xf6, 0x43, 0xd7, 0x85, 0xc9, 0xab, 0xf6, 0x57, 0x59, 0xed, 0xc3, 0x65, 0x83,
0xce, 0x97, 0x2d, 0xda, 0x42, 0xeb, 0x88, 0x40, 0x8f, 0xc7, 0xe4, 0xbb, 0x36, 0x6d, 0xae, 0xa8, 0x5c, 0x25, 0xc3,
0xaa, 0x36, 0xd0, 0x6f, 0xfd, 0x23, 0x78, 0x66, 0x55, 0x6e, 0x1e, 0xd1, 0x8b, 0x2c, 0xb2, 0x1f, 0xd6, 0xbd, 0xcc,
0x1e, 0x96, 0xe9, 0x17, 0xd6, 0x3e, 0x59, 0x2b, 0x4d, 0xd1, 0xd2, 0x4a, 0xc5, 0xad, 0x17, 0x1f, 0x5a, 0xad, 0x6d,
0x0c, 0x7f, 0x74, 0xb7, 0xee, 0xf8, 0xf6, 0x07, 0xeb, 0xe3, 0x06, 0xed, 0x2b, 0x03, 0xf1, 0xae, 0x6a, 0xaf, 0xbf,
0xeb, 0xd0, 0x62, 0xda, 0x68, 0x92, 0x5e, 0xf3, 0xd2, 0x74, 0x4f, 0x6e, 0x34, 0x57, 0x30, 0xc2, 0x8f, 0xd4, 0x16,
0x79, 0x75, 0x4f, 0x9d, 0x17, 0x25, 0xec, 0xe4, 0x6a, 0x6b, 0x6b, 0xd7, 0xa2, 0x8a, 0xf7, 0x85, 0xe8, 0xcb, 0x74,
0x9d, 0x75, 0x80, 0x0e, 0xab, 0x3b, 0xe8, 0x51, 0x89, 0x47, 0x9b, 0x55, 0xe5, 0x58, 0x36, 0x12, 0x54, 0x65, 0xf0,
0x4c, 0x25, 0xd7, 0xdb, 0x24, 0x8f, 0xcc, 0xb0, 0x16, 0x7b, 0xb5, 0x6a, 0xed, 0x70, 0xc5, 0xa5, 0x96, 0x6e, 0x3a,
0x82, 0x5c, 0x2b, 0xca, 0xfd, 0xc5, 0xee, 0x33, 0x5d, 0x89, 0x5e, 0x14, 0x32, 0x6e, 0x8a, 0xde, 0x7f, 0x49, 0x56,
0x0a, 0x14, 0xe5, 0x90, 0x57, 0x3d, 0xd6, 0x44, 0xf8, 0x6d, 0x5d, 0x89, 0x7b, 0x5b, 0x10, 0x50, 0x34, 0x2f, 0x2d,
0x44, 0x5e, 0xdb, 0x3d, 0x7b, 0x7f, 0x08, 0x4e, 0x0a, 0x6c, 0x74, 0x23, 0x64, 0xe5, 0x12, 0xf9, 0x6f, 0x02, 0xcb,
0x2b, 0x3e, 0x96, 0xa0, 0xd6, 0xae, 0x84, 0x68, 0x1d, 0x59, 0xd1, 0x8b, 0x24, 0x64, 0xa1, 0x1c, 0x2f, 0x56, 0xff,
0xe6, 0xb1, 0x8d, 0x70, 0xb1, 0x61, 0x85, 0x96, 0x09, 0x72, 0x7e, 0x94, 0x37, 0x6b, 0x06, 0x0e, 0x14, 0x7d, 0xfd,
0x92, 0x8f, 0x02, 0x80, 0x11, 0x97, 0x32, 0xbb, 0x5d, 0xa8, 0x3c, 0x29, 0x09, 0x72, 0xf2, 0xa1, 0xe2, 0xb7, 0x2a,
0xd0, 0x4a, 0xaf, 0x7d, 0x17, 0x58, 0xcc, 0x27, 0x9d, 0x2c, 0x55, 0x4c, 0x46, 0xc9, 0x26, 0x85, 0x4c, 0xc0, 0xe8,
0x3b, 0xd7, 0x2a, 0x70, 0xe9, 0x52, 0x5b, 0x96, 0x8f, 0x57, 0x21, 0xb4, 0xbc, 0x25, 0x2c, 0xb5, 0xf2, 0x9c, 0xff,
0x51, 0xe9, 0x39, 0x5e, 0x89, 0x57, 0xfe, 0xea, 0xeb, 0x42, 0xb6, 0x82, 0x25, 0xe0, 0xcb, 0x4a, 0x61, 0x8f, 0x57,
0x0a, 0xa0, 0x53, 0xd2, 0xff, 0x22, 0x8b, 0xfa, 0xdd, 0x72, 0x40, 0xc7, 0x78, 0x4c, 0xee, 0xd6, 0xfa, 0xaf, 0x65,
0x41, 0xd4, 0xe3, 0x48, 0x30, 0xc1, 0xaf, 0x8d, 0x78, 0xbb, 0x71, 0xbe, 0x16, 0x0d, 0x70, 0x54, 0xfa, 0x52, 0x59,
0x17, 0x53, 0x74, 0x5f, 0xb1, 0x68, 0xf2, 0xb5, 0x39, 0x15, 0xa2, 0xed, 0xbb, 0xa0, 0xa3, 0x20, 0xe6, 0xb7, 0x1e,
0x29, 0xdc, 0x05, 0xa4, 0x7b, 0xfb, 0x9f, 0xf2, 0xd8, 0x95, 0x5b, 0x25, 0x24, 0xfa, 0xfe, 0xbe, 0x1f, 0x96, 0xd4,
0xb4, 0xa4, 0xb7, 0x45, 0xed, 0x04, 0x53, 0xaa, 0x04, 0x13, 0xcb, 0xc8, 0x28, 0x91, 0x9d, 0xca, 0xd8, 0x55, 0x38,
0x71, 0x30, 0xe7, 0x27, 0x5a, 0x98, 0x91, 0xda, 0xe7, 0x14, 0x34, 0x6a, 0xa9, 0xe0, 0x80, 0x5d, 0x5f, 0x41, 0xae,
0xd0, 0xe8, 0x32, 0xec, 0x88, 0xba, 0x84, 0x0a, 0x97, 0xa7, 0xbc, 0x4b, 0xa2, 0x7d, 0x27, 0x2a, 0xa6, 0x6f, 0x92,
0x05, 0x06, 0x20, 0x95, 0x6a, 0xb9, 0xda, 0xff, 0xa5, 0x7a, 0xac, 0xfd, 0x61, 0x2d, 0x15, 0xc2, 0xde, 0x95, 0xad,
0xde, 0xee, 0x28, 0xb6, 0xc8, 0x6f, 0x96, 0xcd, 0xb7, 0x41, 0x0b, 0x20, 0xba, 0x53, 0x57, 0x0d, 0xcb, 0xbb, 0xbd,
0xaf, 0x13, 0x40, 0x78, 0x91, 0xed, 0x72, 0x6f, 0x34, 0x29, 0x93, 0x8f, 0xa5, 0x43, 0x33, 0x8b, 0x65, 0xd9, 0x5a,
0xd5, 0xbe, 0x3f, 0x2f, 0x83, 0x74, 0x2b, 0xca, 0x51, 0x14, 0x97, 0x78, 0x42, 0x68, 0xa1, 0x98, 0x14, 0x7e, 0xaf,
0xfb, 0xc3, 0x49, 0xf2, 0x5e, 0x99, 0x14, 0x88, 0x47, 0x0a, 0x26, 0x7d, 0xc2, 0x09, 0x8a, 0x4d, 0x5c, 0x11, 0xa7,
0xc8, 0xb1, 0x7b, 0xea, 0x5b, 0x02, 0x77, 0x07, 0xc4, 0x03, 0x94, 0x41, 0x20, 0xfe, 0x61, 0xc7, 0x9a, 0x58, 0x63,
0x13, 0xb4, 0x9e, 0xa4, 0xab, 0x30, 0x15, 0xa2, 0x42, 0xc9, 0xad, 0x57, 0x88, 0x27, 0xea, 0x96, 0x58, 0xc1, 0x6d,
0x7b, 0x45, 0xc4, 0xa1, 0x88, 0x0a, 0xe0, 0x15, 0xe0, 0xe3, 0xbb, 0x3c, 0x6d, 0xf6, 0x16, 0x27, 0x1c, 0xff, 0x4d,
0xed, 0xa5, 0xd7, 0xbe, 0xe6, 0xd4, 0xa4, 0x00, 0xac, 0x10, 0x29, 0x2a, 0xba, 0xb5, 0x97, 0x98, 0x61, 0x8d, 0xa2,
0x2e, 0x5c, 0x89, 0x01, 0xcb, 0x37, 0x14, 0x59, 0x5d, 0xc8, 0x42, 0x31, 0x4e, 0xf2, 0x64, 0x83, 0x7b, 0xa6, 0xba,
0x70, 0x11, 0x5b, 0x29, 0x58, 0x41, 0x41, 0xa1, 0xd3, 0xe9, 0x04, 0xe6, 0xd9, 0xcf, 0x6a, 0xb9, 0x4a, 0x4d, 0x0f,
0xeb, 0x07, 0x3a, 0xff, 0x0c, 0x3f, 0x6c, 0xc4, 0x91, 0xef, 0x4d, 0x2d, 0x49, 0x06, 0xd0, 0xa4, 0x7c, 0x50, 0x14,
0xb5, 0xb9, 0xf2, 0x13, 0x00, 0xfb, 0x5e, 0x08, 0xb0, 0x2b, 0xec, 0xbe, 0xa0, 0x1c, 0xf4, 0x7b, 0xef, 0x38, 0x13,
0x31, 0x8a, 0x78, 0x2b, 0x7e, 0x49, 0x54, 0x54, 0xc7, 0x54, 0x59, 0xd7, 0x37, 0x34, 0x10, 0x58, 0x4f, 0x25, 0xce,
0x61, 0x73, 0xec, 0x41, 0x85, 0x06, 0x8e, 0xcd, 0x5c, 0xaa, 0xd0, 0x55, 0x6e, 0xc2, 0xa8, 0x8a, 0x4d, 0x41, 0x16,
0x5b, 0xe8, 0xae, 0x51, 0x84, 0x14, 0xc7, 0x17, 0xa1, 0x0e, 0x51, 0x9d, 0x5c, 0x15, 0xaa, 0xb4, 0xae, 0xe8, 0x42,
0x68, 0xb6, 0xef, 0x28, 0x09, 0xa7, 0xcd, 0x50, 0x00, 0x94, 0x93, 0x00, 0xd9, 0x09, 0x4f, 0x19, 0xa8, 0xe0, 0x89,
0x08, 0xc0, 0xa7, 0x56, 0xee, 0xec, 0x6d, 0x27, 0xfb, 0x4d, 0x87, 0x44, 0x28, 0x93, 0x78, 0x5a, 0x34, 0x54, 0x49,
0x8d, 0x90, 0x29, 0xb7, 0x2d, 0x46, 0x97, 0xb2, 0x8e, 0x3e, 0x21, 0x9f, 0xaf, 0x55, 0xc8, 0xa8, 0x0c, 0x19, 0x14,
0xb7, 0xc3, 0x9b, 0xca, 0xc0, 0x79, 0x25, 0x39, 0x8c, 0x7e, 0x22, 0x20, 0x2b, 0x5e, 0x43, 0x24, 0x75, 0x48, 0xc7,
0xb6, 0x8b, 0x2b, 0x99, 0x80, 0xe1, 0xe5, 0x6c, 0xeb, 0xe7, 0x89, 0x69, 0x74, 0xa8, 0xf2, 0x6f, 0x97, 0x90, 0x59,
0x48, 0xb8, 0x7d, 0x0b, 0xc2, 0x83, 0xc6, 0x8c, 0x8e, 0x62, 0x47, 0x44, 0x8c, 0x59, 0xdb, 0xab, 0x7d, 0x99, 0x1e,
0x46, 0xfe, 0xa8, 0x61, 0x71, 0xa8, 0x01, 0x08, 0xd5, 0x5f, 0xc1, 0x85, 0x68, 0xf7, 0x0a, 0x59, 0x22, 0xe9, 0x42,
0xd4, 0x5a, 0x9b, 0x53, 0xff, 0xdb, 0x62, 0x83, 0x2e, 0x5a, 0x6c, 0xce, 0x9a, 0xac, 0x90, 0x81, 0x12, 0xd3, 0x6f,
0x86, 0x94, 0xa9, 0x90, 0xce, 0x16, 0x01, 0xc3, 0x41, 0x66, 0xad, 0xff, 0xc9, 0xe1, 0x69, 0xba, 0x0c, 0xdc, 0x65,
0x49, 0x39, 0xb0, 0xa3, 0x9c, 0x9f, 0x50, 0x20, 0xf9, 0x3d, 0x45, 0x73, 0x76, 0x1f, 0xcc, 0x01, 0x78, 0x94, 0xaa,
0x37, 0xf4, 0x65, 0xdb, 0x38, 0xf1, 0x83, 0x26, 0xba, 0xbd, 0x53, 0xa8, 0xaf, 0xfa, 0x3b, 0xac, 0x52, 0x68, 0x2b,
0x9b, 0xe0, 0x1b, 0xb4, 0x12, 0x2d, 0xe9, 0x60, 0xb9, 0xa2, 0xaa, 0x2a, 0x73, 0x18, 0xbe, 0x72, 0x50, 0xc0, 0x87,
0xb2, 0x99, 0x66, 0x4b, 0x16, 0x1d, 0x24, 0xf6, 0x3a, 0xc2, 0xc1, 0xd6, 0xe7, 0x68, 0xd5, 0x72, 0x79, 0x17, 0xed,
0x73, 0xb7, 0xd8, 0x87, 0x64, 0x8a, 0x80, 0xee, 0xa1, 0xcd, 0x71, 0x42, 0x9b, 0x5a, 0xe9, 0x2a, 0x7a, 0x00, 0x0b,
0x0b, 0x4a, 0x9f, 0xc2, 0xba, 0xb0, 0x9b, 0x08, 0x30, 0x68, 0x10, 0x54, 0xa7, 0x7d, 0x66, 0xdd, 0x83, 0x59, 0x39,
0x49, 0xb7, 0xf2, 0x50, 0x49, 0x2c, 0xf8, 0x79, 0x83, 0xac, 0xd7, 0x2a, 0x6d, 0x9f, 0x0c, 0x40, 0x3f, 0xa9, 0xfe,
0x3a, 0x8c, 0x5f, 0x29, 0x9d, 0xa3, 0xe9, 0x1c, 0xda, 0x53, 0x15, 0xb0, 0x27, 0x85, 0x29, 0x9b, 0x15, 0x7a, 0xca,
0x83, 0xcc, 0x68, 0x47, 0xc5, 0xe4, 0x2c, 0xa1, 0x8a, 0xfa, 0x1b, 0x05, 0x97, 0xc9, 0x21, 0x03, 0x4c, 0x0e, 0x9a,
0x74, 0xdb, 0xe3, 0x05, 0xc2, 0x57, 0xa7, 0x72, 0x9e, 0xf6, 0x39, 0x5c, 0xd4, 0x01, 0x3e, 0xb2, 0xa9, 0x26, 0x11,
0x5b, 0xbf, 0xd1, 0x07, 0xbc, 0x1a, 0x20, 0x85, 0xf8, 0x08, 0xa7, 0xe9, 0x18, 0x0d, 0x43, 0x49, 0xd0, 0xa3, 0x8d,
0x49, 0xe9, 0xf4, 0xe6, 0xcc, 0x8a, 0xbe, 0xe9, 0x6a, 0x38, 0x22, 0x82, 0xb0, 0x91, 0x9d, 0xf2, 0x4b, 0x97, 0x94,
0x7d, 0x2a, 0x4f, 0xb8, 0x85, 0x04, 0x10, 0x11, 0x75, 0x58, 0xb4, 0x5b, 0xe0, 0x28, 0x80, 0xd2, 0xe9, 0xd2, 0x73,
0x4b, 0x79, 0xb9, 0xb3, 0x99, 0x32, 0x55, 0x00, 0xfc, 0x53, 0xc9, 0x71, 0x85, 0x64, 0xf4, 0x8d, 0x2a, 0xc8, 0x96,
0x04, 0xa6, 0xe3, 0x75, 0xe4, 0xae, 0x75, 0x97, 0x93, 0x14, 0xb6, 0x5c, 0x2b, 0x32, 0x85, 0xf1, 0xaf, 0x6d, 0x74,
0x25, 0x79, 0x48, 0xcf, 0x53, 0x52, 0xc7, 0xc9, 0x7a, 0x10, 0x93, 0x92, 0x45, 0x31, 0xfc, 0x27, 0x0c, 0xa4, 0xe0,
0x42, 0x42, 0x60, 0x44, 0xb1, 0x2a, 0xd9, 0xec, 0x00, 0x66, 0x87, 0xcd, 0xb2, 0x22, 0x9d, 0x3d, 0x69, 0xe9, 0xa5,
0x3e, 0x04, 0xa9, 0xb6, 0x74, 0xce, 0x11, 0x1c, 0x5b, 0xb5, 0xab, 0x4a, 0x18, 0xaa, 0x69, 0x3b, 0x8b, 0x82, 0x5f,
0xe6, 0x80, 0x0a, 0x92, 0x9d, 0xaa, 0x26, 0x4d, 0x22, 0xd2, 0xb9, 0x7a, 0x33, 0x61, 0xcc, 0x8b, 0x9e, 0xdf, 0xfd,
0xa3, 0xb6, 0x28, 0x03, 0xf6, 0x03, 0x85, 0x08, 0xb2, 0xd9, 0xaa, 0xf2, 0x02, 0xa3, 0xd8, 0x35, 0x4e, 0x14, 0x3b,
0xf2, 0xad, 0x95, 0xbd, 0x91, 0x2e, 0x78, 0x0f, 0x83, 0xaf, 0x90, 0x3c, 0xd0, 0x0f, 0x27, 0x6d, 0x47, 0x09, 0x9a,
0x72, 0x58, 0x2d, 0x93, 0x76, 0xad, 0xc3, 0xfe, 0x1c, 0x33, 0x54, 0xf9, 0x2d, 0x93, 0x83, 0xa4, 0xa9, 0x10, 0x38,
0x50, 0xa5, 0x70, 0x2f, 0x30, 0xff, 0xe4, 0x1d, 0xc5, 0x97, 0x2d, 0x2e, 0xbf, 0x2c, 0x33, 0xe5, 0xce, 0x23, 0xdd,
0x16, 0x54, 0xb4, 0x03, 0x10, 0x8a, 0x64, 0x2a, 0x42, 0x1f, 0x64, 0x9f, 0x28, 0x25, 0x21, 0xb5, 0xd8, 0xcd, 0x7c,
0x42, 0xd0, 0x34, 0x91, 0x83, 0x3f, 0xc2, 0x1e, 0x05, 0x53, 0xb6, 0xa8, 0x5c, 0xfe, 0xd1, 0x32, 0xc1, 0x14, 0x21,
0x25, 0xa1, 0x07, 0xb3, 0xa1, 0xd7, 0x8c, 0x61, 0xf3, 0xff, 0xe8, 0x65, 0x98, 0xab, 0xc5, 0x39, 0xbe, 0x55, 0x61,
0xae, 0x3d, 0x63, 0x5a, 0xb6, 0xa8, 0x21, 0x5c, 0xde, 0x92, 0xc1, 0x2b, 0x42, 0x47, 0xd8, 0x38, 0x87, 0x2e, 0x58,
0x3f, 0x86, 0x0e, 0xdb, 0x6c, 0x75, 0x5e, 0x6d, 0xe7, 0x88, 0x5e, 0xeb, 0x89, 0x9c, 0x22, 0xc3, 0x6f, 0x27, 0xe5,
0x38, 0x08, 0xe3, 0x02, 0x47, 0xfe, 0xb7, 0xc5, 0x6d, 0x2c, 0x85, 0xb4, 0xad, 0x21, 0x95, 0xfd, 0x5c, 0x7b, 0xc5,
0x4f, 0x87, 0xf6, 0xba, 0xbf, 0xc1, 0x2a, 0xa4, 0x09, 0x1d, 0x11, 0x2a, 0x4f, 0xc7, 0x94, 0x8f, 0xd1, 0x54, 0xaf,
0x12, 0x80, 0xfe, 0x2a, 0x94, 0x90, 0xdb, 0x95, 0x15, 0x7a, 0x48, 0x23, 0x56, 0xf9, 0xd5, 0x0d, 0xa8, 0xa2, 0xc6,
0x9e, 0x04, 0x7f, 0x14, 0x3e, 0x02, 0xa8, 0x53, 0xe0, 0x70, 0x06, 0x0d, 0xa5, 0xd7, 0xe5, 0xb6, 0xfc, 0x8a, 0xf2,
0x25, 0x2e, 0xe1, 0xcf, 0x98, 0x1c, 0x7d, 0x8b, 0xb6, 0x98, 0xee, 0x71, 0x12, 0xe6, 0x86, 0x1d, 0x27, 0x98, 0xdf,
0x58, 0x10, 0x43, 0x1d, 0x1e, 0x1d, 0x26, 0x42, 0xda, 0xaf, 0x67, 0xd3, 0x97, 0x04, 0x5e, 0x2d, 0x8c, 0x4d, 0x84,
0x7f, 0x55, 0x7a, 0x00, 0x8f, 0x0c, 0x93, 0x38, 0xa9, 0x21, 0x52, 0x10, 0x68, 0x36, 0xbf, 0x18, 0xe3, 0x75, 0xe0,
0x2a, 0x3e, 0x07, 0x55, 0xe5, 0xd9, 0x58, 0x69, 0x4c, 0x28, 0x51, 0x79, 0x0f, 0x05, 0x06, 0x10, 0xf3, 0x3e, 0x1f,
0xf5, 0x90, 0x35, 0x78, 0x97, 0x70, 0xb4, 0x5e, 0x71, 0xfe, 0x05, 0xd5, 0xa8, 0x3b, 0x47, 0xd5, 0x14, 0x1e, 0x34,
0x13, 0x8a, 0x96, 0xb0, 0xf8, 0x19, 0xb1, 0x14, 0xe9, 0x95, 0x22, 0x5a, 0x31, 0xa5, 0x71, 0xd3, 0xcd, 0xe4, 0xa1,
0xb5, 0x6b, 0x8c, 0x93, 0xea, 0x99, 0xd1, 0x95, 0x9f, 0x71, 0x14, 0x8e, 0x63, 0x8f, 0xc2, 0x5a, 0x76, 0x03, 0xcb,
0xf4, 0xf3, 0x2b, 0xe9, 0x8f, 0x11, 0x29, 0x0c, 0x5b, 0x1b, 0x66, 0x30, 0xb2, 0x4f, 0xcf, 0xf6, 0xc1, 0x5f, 0xc9,
0x41, 0xc6, 0x53, 0x49, 0x44, 0x96, 0xb5, 0xce, 0x38, 0x15, 0x6e, 0xe8, 0xe3, 0x22, 0x99, 0x38, 0x45, 0xaa, 0x0a,
0x17, 0x25, 0x4a, 0x57, 0xf9, 0x1e, 0x42, 0x0f, 0x78, 0x7b, 0x5d, 0xc2, 0xe9, 0xc8, 0x64, 0x18, 0x09, 0x25, 0x84,
0x16, 0x8e, 0x24, 0xf6, 0x65, 0x17, 0x55, 0x94, 0x37, 0x08, 0xc2, 0xf9, 0x82, 0x2a, 0xef, 0x67, 0xab, 0xb5, 0x55,
0x14, 0x7a, 0x63, 0x9a, 0x50, 0xf2, 0x52, 0x71, 0x8b, 0x55, 0xd0, 0x99, 0x46, 0xa7, 0x3d, 0x2b, 0x05, 0x59, 0x50,
0xb3, 0xe0, 0x77, 0xa3, 0x4c, 0x2f, 0x91, 0x93, 0x05, 0x3c, 0x4a, 0x8f, 0x41, 0x6d, 0xc6, 0xd2, 0x24, 0xa8, 0x8e,
0x92, 0x54, 0x43, 0x15, 0xd8, 0x53, 0x79, 0x56, 0x4b, 0x06, 0xa8, 0x36, 0x5b, 0x76, 0xb4, 0xd7, 0x10, 0x82, 0x8c,
0x63, 0x5f, 0xad, 0xcf, 0x2b, 0x67, 0x4b, 0xe4, 0xff, 0xb7, 0xa3, 0xbc, 0xaf, 0x9b, 0x2a, 0xaf, 0x1c, 0x80, 0xbe,
0x1f, 0x7e, 0x05, 0xf4, 0x8a, 0xb8, 0x13, 0x10, 0xa8, 0x02, 0x05, 0xf7, 0xee, 0xc1, 0x89, 0x54, 0x7d, 0xc1, 0x4e,
0x5a, 0x80, 0x38, 0x80, 0x09, 0x49, 0x86, 0xd6, 0xe1, 0x0b, 0x2b, 0x73, 0x13, 0x9a, 0xb0, 0xd0, 0xe6, 0x15, 0xa8,
0xb9, 0x6e, 0x89, 0xbc, 0x8e, 0x2c, 0x73, 0x95, 0x9c, 0xe4, 0x91, 0x3c, 0xde, 0x74, 0x26, 0x49, 0xac, 0x7b, 0x24,
0xa3, 0x50, 0x61, 0xa7, 0x88, 0xb6, 0xb5, 0xe5, 0x3c, 0xcb, 0xde, 0x94, 0x7d, 0x0a, 0x91, 0x91, 0x50, 0x3a, 0xd8,
0x50, 0xed, 0x2b, 0xc8, 0x84, 0xcc, 0x11, 0x3d, 0xaf, 0x67, 0xd0, 0x84, 0x9a, 0x0a, 0x95, 0xe1, 0xc8, 0x87, 0xed,
0xe5, 0xff, 0xaf, 0x12, 0xc6, 0x57, 0x5d, 0x27, 0x2a, 0x2a, 0x28, 0x93, 0xb6, 0x03, 0x1c, 0xb0, 0x97, 0x36, 0x3b,
0xa6, 0xd3, 0x57, 0x82, 0x49, 0x42, 0x5f, 0x9b, 0xd4, 0x7b, 0x37, 0xdb, 0x16, 0x71, 0x1d, 0xec, 0xd7, 0xe2, 0x52,
0x20, 0x93, 0xfa, 0x27, 0xd1, 0xd4, 0xfd, 0x19, 0x21, 0x46, 0xc7, 0x5c, 0x8c, 0x1a, 0x56, 0x3e, 0x0f, 0xf4, 0x36,
0xda, 0x99, 0x55, 0xc1, 0x74, 0xaa, 0xf5, 0xdd, 0x0d, 0x4e, 0xfb, 0x50, 0x3e, 0xa5, 0x16, 0x52, 0xe4, 0x67, 0x20,
0x38, 0xd4, 0x0a, 0x0e, 0xa9, 0x7e, 0xf6, 0xf9, 0xe8, 0x1f, 0xa8, 0xf7, 0xeb, 0xaa, 0xfc, 0x41, 0x93, 0xe8, 0xe5,
0xae, 0xda, 0x6f, 0x53, 0x90, 0xb6, 0x3c, 0xb5, 0x0c, 0x6f, 0x7e, 0x2c, 0xbf, 0x17, 0x40, 0xd4, 0x3d, 0xfb, 0x85,
0xaa, 0x78, 0xb8, 0xf8, 0x6d, 0x5f, 0xc6, 0x4c, 0x79, 0x5e, 0x9e, 0xce, 0x96, 0xc7, 0xe8, 0x0a, 0x39, 0xfa, 0x81,
0x25, 0xcf, 0x6b, 0xe8, 0xd8, 0x3d, 0x60, 0x64, 0xc4, 0x73, 0x6a, 0x94, 0x96, 0x1d, 0x0c, 0xfe, 0xfc, 0x4a, 0x49,
0xdd, 0xf3, 0x80, 0x32, 0x4f, 0x47, 0xb4, 0xf1, 0x72, 0x59, 0x79, 0xfc, 0x81, 0xcc, 0xe8, 0x39, 0xbe, 0x4a, 0xc2,
0x3c, 0x3e, 0x4d, 0xe9, 0xbb, 0x2c, 0x36, 0x32, 0xed, 0xc2, 0xc2, 0x29, 0x4d, 0xfa, 0xad, 0x88, 0xb2, 0x1c, 0x25,
0x9c, 0x91, 0x64, 0xd0, 0x61, 0x69, 0x97, 0x66, 0x57, 0xfb, 0xb3, 0x43, 0xfa, 0x75, 0x2c, 0x22, 0x06, 0x0c, 0xd2,
0xac, 0xcb, 0x41, 0x07, 0x0e, 0x14, 0x45, 0x1a, 0x81, 0xf6, 0x43, 0x19, 0xe5, 0xdf, 0x24, 0x00, 0xac, 0x20, 0x5e,
0x17, 0xc9, 0x01, 0x28, 0xe6, 0x9c, 0x9e, 0x88, 0x29, 0x9d, 0xc7, 0x52, 0xda, 0x72, 0xa5, 0x7d, 0x90, 0x31, 0x22,
0xe0, 0x29, 0xc2, 0xb3, 0x50, 0x40, 0xff, 0x70, 0xde, 0xb1, 0xc8, 0xe1, 0x65, 0xd2, 0x95, 0x07, 0x97, 0x0d, 0x6d,
0x26, 0xda, 0x1e, 0x61, 0xf2, 0x9a, 0x80, 0x07, 0x71, 0x32, 0xee, 0xd5, 0xd8, 0xda, 0x08, 0x35, 0xda, 0x8a, 0x72,
0x40, 0x91, 0xff, 0x5c, 0x8e, 0x31, 0xce, 0x40, 0x6d, 0x32, 0xff, 0x32, 0xec, 0xaf, 0x4c, 0x79, 0x42, 0xd0, 0x6f,
0xb3, 0xcd, 0x8b, 0xca, 0x73, 0x42, 0xc0, 0x03, 0x85, 0x78, 0xf5, 0x89, 0x93, 0x99, 0x19, 0x7d, 0x39, 0x13, 0xe1,
0x4b, 0x63, 0xc0, 0x90, 0x10, 0xb5, 0x8f, 0xd3, 0xf2, 0x8e, 0x1d, 0x05, 0x55, 0xb5, 0xfa, 0xcc, 0x9c, 0xec, 0xee,
0x6a, 0x6c, 0xe6, 0x39, 0x61, 0xda, 0x9d, 0x6d, 0xd9, 0x4d, 0x0c, 0x48, 0xd3, 0x30, 0xbe, 0xc2, 0x61, 0xac, 0x97,
0x07, 0xa4, 0x12, 0x7f, 0xca, 0x23, 0xc0, 0xb7, 0x33, 0xe8, 0x65, 0xf7, 0x98, 0xd8, 0x16, 0x67, 0x8a, 0x21, 0x5b,
0xea, 0x96, 0x1e, 0xa7, 0x64, 0xa2, 0xdc, 0xcb, 0x4a, 0xfa, 0xb0, 0xd8, 0xbc, 0xa6, 0x6c, 0x44, 0x77, 0x0f, 0xc4,
0x98, 0xbc, 0x9f, 0xf5, 0x62, 0x20, 0x81, 0xa9, 0xab, 0xeb, 0xeb, 0xcf, 0x50, 0x67, 0x8c, 0x35, 0x3d, 0x27, 0x2a,
0x79, 0xda, 0x11, 0xbb, 0x6d, 0xf4, 0x2a, 0x55, 0xcc, 0xd0, 0x11, 0x8b, 0xf5, 0x73, 0x72, 0xf8, 0x58, 0x26, 0x9e,
0x32, 0xa4, 0x8a, 0x38, 0x0d, 0x14, 0x6b, 0xc3, 0x4a, 0x67, 0xf5, 0x72, 0x7f, 0xed, 0x2b, 0x4b, 0x8a, 0x26, 0x54,
0xcb, 0x44, 0xb2, 0x45, 0x50, 0xc9, 0xb2, 0x22, 0x76, 0xaa, 0x82, 0xae, 0xa7, 0x57, 0x09, 0x11, 0x36, 0x8b, 0x99,
0x32, 0x49, 0xa5, 0x51, 0xe6, 0x9c, 0x14, 0xc9, 0x4e, 0x9e, 0x33, 0x5e, 0x30, 0xdb, 0xc9, 0xa4, 0x43, 0x54, 0xdc,
0x0f, 0x28, 0xed, 0xa7, 0xe9, 0x41, 0x4f, 0xd0, 0xe4, 0x54, 0x58, 0xdf, 0xe5, 0x95, 0x31, 0x35, 0x91, 0x10, 0x82,
0xf0, 0xb0, 0xd8, 0x1b, 0x19, 0xdf, 0xb7, 0xb6, 0xb5, 0xad, 0xaf, 0x93, 0xff, 0x4c, 0xb3, 0xf2, 0x7e, 0x3b, 0xd1,
0xea, 0x13, 0x36, 0x07, 0x09, 0x5c, 0x6d, 0xdb, 0x6c, 0xef, 0xb2, 0x05, 0x31, 0x4a, 0xff, 0x32, 0x3e, 0x2e, 0x8a,
0x96, 0xbd, 0x1b, 0x01, 0x24, 0xac, 0x9c, 0x2b, 0x60, 0x8c, 0x64, 0x36, 0x64, 0x0a, 0x55, 0xa0, 0xb7, 0x11, 0x64,
0xda, 0x82, 0x5a, 0x2e, 0xa8, 0x34, 0x13, 0x5f, 0xfd, 0x66, 0x1c, 0x8d, 0x2c, 0xe0, 0x55, 0xc5, 0xf6, 0x0f, 0x1d,
0x9d, 0x8d, 0xed, 0x71, 0xfa, 0x8e, 0x1e, 0x2f, 0x7b, 0x4d, 0x72, 0xbe, 0xb9, 0x41, 0xc1, 0x0a, 0xde, 0x7c, 0x5d,
0x31, 0x5d, 0xd1, 0x47, 0x4a, 0xb6, 0x6e, 0x06, 0x83, 0xb5, 0xde, 0xde, 0x24, 0x65, 0xed, 0x30, 0x79, 0xfb, 0xf8,
0xa0, 0x23, 0xd4, 0x72, 0x2f, 0x9b, 0x36, 0xba, 0x05, 0x50, 0x0b, 0xbf, 0x3f, 0x72, 0xb0, 0xd1, 0x3b, 0x8f, 0xf9,
0x1a, 0xf8, 0x38, 0x67, 0xb2, 0x32, 0xe0, 0xc1, 0x27, 0x43, 0x42, 0x1d, 0x0d, 0x29, 0x79, 0x54, 0x88, 0xbe, 0x98,
0x02, 0xc5, 0xe5, 0xf1, 0x53, 0x44, 0x0d, 0xcc, 0xad, 0x3b, 0x10, 0x86, 0xbd, 0xdf, 0x6c, 0xdb, 0x7e, 0x17, 0x4d,
0xec, 0x1a, 0x2d, 0xa5, 0x09, 0xa8, 0x60, 0xc6, 0x78, 0x2a, 0x0f, 0xed, 0xc9, 0xa5, 0x12, 0x0e, 0xfc, 0x8e, 0xd5,
0x79, 0x75, 0xba, 0xb8, 0x2f, 0xa9, 0x5c, 0x0b, 0x27, 0x19, 0x5c, 0x2b, 0x07, 0x56, 0x45, 0x49, 0x31, 0x55, 0xb9,
0x8f, 0x0a, 0xa5, 0x22, 0x05, 0xb4, 0x4f, 0x35, 0x5f, 0xa7, 0x84, 0x30, 0xc7, 0x6a, 0xab, 0xf1, 0x34, 0x9a, 0x32,
0x3e, 0xc6, 0x56, 0xdb, 0xb1, 0x93, 0xaf, 0x93, 0x99, 0x35, 0x10, 0x85, 0xf7, 0x31, 0xee, 0x22, 0xd4, 0xf5, 0x81,
0xe8, 0xf1, 0x3d, 0x39, 0xcf, 0x62, 0x8a, 0x00, 0xaf, 0xb4, 0x97, 0x32, 0x2f, 0xd5, 0x44, 0x5d, 0x9d, 0xa5, 0x64,
0xa9, 0x6d, 0x65, 0x52, 0x6a, 0x70, 0x79, 0x6b, 0x7e, 0x19, 0x40, 0xb1, 0x9d, 0x98, 0x6a, 0x8b, 0x2d, 0x41, 0x82,
0xd0, 0x97, 0x67, 0x31, 0x09, 0xf9, 0x41, 0xb1, 0xc9, 0x63, 0x4b, 0xd4, 0x5d, 0x27, 0xaa, 0x27, 0xd5, 0xcf, 0x3f,
0x11, 0xf3, 0x0d, 0xc9, 0x81, 0x9b, 0xb2, 0xe7, 0x55, 0x27, 0xbe, 0x72, 0xe3, 0xd3, 0xd9, 0x9e, 0x64, 0x06, 0xcf,
0x56, 0xde, 0xd2, 0x61, 0x3f, 0xa4, 0x68, 0x4b, 0xda, 0x22, 0x43, 0x8c, 0xb9, 0xba, 0xfb, 0x62, 0x4f, 0x7e, 0x13,
0x0c, 0xed, 0x27, 0x08, 0x88, 0x54, 0x11, 0x71, 0x3e, 0x30, 0xd5, 0xff, 0x19, 0x49, 0x20, 0xb3, 0x0c, 0x09, 0xdc,
0x46, 0xc4, 0x10, 0xb2, 0xd6, 0x60, 0xb7, 0xde, 0xdf, 0x73, 0x92, 0xc3, 0x4f, 0x6d, 0xe4, 0xe3, 0x69, 0x56, 0xda,
0xa8, 0x6e, 0x96, 0x1b, 0xb4, 0x35, 0x32, 0xa8, 0xfb, 0x1b, 0x6a, 0x56, 0x7e, 0x21, 0x6e, 0xdc, 0xeb, 0xb0, 0x51,
0x36, 0x49, 0x4d, 0x99, 0x35, 0x72, 0xe6, 0xea, 0x54, 0xf8, 0x64, 0x56, 0x33, 0x73, 0x22, 0x4b, 0x20, 0x33, 0x01,
0x56, 0xb6, 0xee, 0xa3, 0xc8, 0x13, 0x16, 0xf9, 0xa2, 0x58, 0x28, 0x2b, 0x1f, 0xfa, 0x17, 0x7c, 0x4b, 0x1f, 0xa7,
0xe2, 0xf0, 0xfb, 0xe4, 0x21, 0xbd, 0x59, 0x5e, 0x26, 0x6d, 0x54, 0x3a, 0x97, 0xdd, 0x04, 0x86, 0x6b, 0x8c, 0x49,
0x9a, 0x1c, 0xa8, 0x68, 0xac, 0xa9, 0x31, 0x98, 0x52, 0xe5, 0xea, 0x47, 0x03, 0xaa, 0x68, 0x1c, 0x4a, 0x69, 0x14,
0x8a, 0xd3, 0x84, 0x5a, 0xbb, 0x9c, 0x79, 0x92, 0x81, 0xcc, 0x92, 0x31, 0x73, 0xb0, 0x75, 0x5a, 0xb7, 0xaf, 0x04,
0x26, 0x00, 0x16, 0x92, 0x69, 0x03, 0x7a, 0x61, 0x5a, 0x41, 0xd5, 0x97, 0x63, 0x00, 0x5f, 0x63, 0x30, 0x85, 0xcc,
0x6b, 0x8e, 0x8a, 0xbb, 0xa3, 0xff, 0x25, 0x15, 0x54, 0x49, 0x76, 0x0d, 0x9a, 0xb2, 0xd9, 0x70, 0x04, 0x9f, 0xbe,
0xfd, 0x25, 0x81, 0x50, 0x87, 0xcc, 0x54, 0x8d, 0x34, 0x32, 0x2b, 0x10, 0xc8, 0x82, 0x43, 0x11, 0x3a, 0xe0, 0x91,
0xb4, 0xc7, 0xa3, 0x8c, 0x98, 0xfa, 0x59, 0x72, 0x9a, 0x92, 0x15, 0x0b, 0x03, 0x93, 0x0c, 0xee, 0xd0, 0x2e, 0x15,
0xa7, 0x19, 0xcc, 0x62, 0x84, 0xce, 0xab, 0x19, 0x62, 0xa5, 0xf3, 0x01, 0x6d, 0xd4, 0x21, 0x6e, 0xe5, 0x9d, 0xe6,
0x87, 0x56, 0xa5, 0x43, 0x40, 0x93, 0xa8, 0x09, 0xbd, 0x95, 0xfa, 0x87, 0xd6, 0x30, 0xb9, 0xf4, 0x65, 0x35, 0x07,
0x93, 0xd4, 0x85, 0x9e, 0xa3, 0x98, 0x62, 0xb7, 0x0d, 0x81, 0xc4, 0xf2, 0xca, 0x1c, 0x02, 0x70, 0x73, 0x5c, 0x11,
0xae, 0xac, 0x1e, 0x20, 0x4c, 0xac, 0x18, 0xe8, 0xa4, 0x14, 0xbf, 0x0a, 0x09, 0x3e, 0xf7, 0xd8, 0x2e, 0x07, 0xc1,
0x9e, 0x13, 0x90, 0xce, 0xa6, 0x69, 0xdb, 0x5a, 0xf8, 0x47, 0x60, 0x1c, 0x4c, 0x71, 0xef, 0xd8, 0x8d, 0x7d, 0x11,
0xbd, 0xce, 0x10, 0x1f, 0x8e, 0xf8, 0xa4, 0x58, 0x30, 0xad, 0xa1, 0xe3, 0xca, 0xb3, 0x2d, 0xc7, 0xd4, 0x52, 0xfe,
0x8d, 0x18, 0xab, 0xd3, 0x22, 0x49, 0x6b, 0xa5, 0x8a, 0xcd, 0xec, 0x5d, 0x66, 0x2e, 0x1d, 0x88, 0xa6, 0xef, 0x03,
0x15, 0x4c, 0x47, 0x47, 0x27, 0xd1, 0x9c, 0x91, 0x27, 0x8d, 0x2f, 0x14, 0x15, 0x0a, 0x8f, 0x03, 0x69, 0xba, 0xed,
0x4e, 0x6b, 0x4b, 0x0b, 0x83, 0xec, 0xf9, 0x54, 0x08, 0x9a, 0xaa, 0xbc, 0x50, 0x18, 0x65, 0xd1, 0x37, 0x23, 0xda,
0x28, 0x44, 0x7e, 0xb7, 0x4f, 0xe4, 0x80, 0xa8, 0xf8, 0x2f, 0xdc, 0xad, 0xaf, 0xb4, 0xfa, 0x7f,
};
static const uint8_t MIXED[] = {
0x4a, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x84, 0x62, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0x00, 0xb8, 0x0b, 0x47, 0xf4, 0x53, 0xc3, 0x7d, 0x78, 0x8e, 0xb4, 0x4d, 0xb7, 0x48, 0x2f, 0x6d, 0x46, 0x3d, 0x19,
0xe5, 0x70, 0x24, 0x4c, 0xbb, 0xa0, 0xe3, 0x58, 0xfc, 0x78, 0x74, 0xfa, 0x8c, 0xb1, 0x95, 0x5c, 0xaf, 0xb5, 0x32,
0x12, 0x53, 0xfe, 0x93, 0xd1, 0x23, 0x2c, 0x45, 0xed, 0x4c, 0xe9, 0xc9, 0x99, 0x0d, 0x7d, 0xff, 0xdc, 0x01, 0x30,
0x51, 0x55, 0x2c, 0x63, 0xa0, 0xb0, 0xc7, 0x6d, 0xee, 0xe4, 0xcc, 0x36, 0xd0, 0x32, 0x40, 0x96, 0x91, 0xdd, 0x43,
0x6b, 0x26, 0xaa, 0xd8, 0x7c, 0xd6, 0x16, 0x75, 0x11, 0xa6, 0x5a, 0x4a, 0x4e, 0x86, 0x1f, 0x51, 0x53, 0x3c, 0x01,
0x1a, 0x16, 0x14, 0xc6, 0x54, 0xfb, 0x44, 0x5b, 0x1a, 0x38, 0x21, 0x92, 0x03, 0xeb, 0x04, 0x9d, 0xe8, 0xf8, 0xfa,
0x4a, 0x73, 0xa4, 0x2f, 0xfc, 0x6d, 0xf3, 0x18, 0x6d, 0xc4, 0xc1, 0x62, 0x25, 0x5d, 0xa3, 0x9d, 0xb9, 0x9f, 0x7b,
0xa8, 0xc4, 0xbb, 0xdd, 0xdb, 0xa7, 0xbd, 0x25, 0xf7, 0x00, 0x54, 0x54, 0xce, 0xeb, 0x61, 0xaf, 0xb2, 0xfb, 0x42,
0x16, 0x9f, 0xf7, 0xdb, 0x25, 0x28, 0x54, 0x68, 0x0c, 0x22, 0x76, 0x06, 0x2f, 0x12, 0xa7, 0xfa, 0x7c, 0x57, 0xd3,
0xc8, 0x90, 0x17, 0x09, 0xf5, 0x89, 0xea, 0xb2, 0x96, 0xa9, 0x49, 0x8f, 0xa1, 0xb0, 0xb5, 0x74, 0xf0, 0xf6, 0xa7,
0xc6, 0x14, 0x4a, 0x3a, 0xb6, 0xdf, 0x8d, 0x9a, 0x3a, 0xb0, 0x0e, 0x2c, 0xd0, 0x7c, 0xa5, 0x7b, 0xf2, 0xa1, 0x8e,
0xe5, 0x58, 0x6b, 0x0b, 0x0a, 0xf0, 0x62, 0xb8, 0xf0, 0x9e, 0x59, 0xac, 0xf6, 0xb3, 0x38, 0x54, 0x7f, 0x2f, 0x84,
0x10, 0x5a, 0xb7, 0xb3, 0x8a, 0xf3, 0x54, 0x32, 0xdb, 0x3b, 0xf1, 0x32, 0x5c, 0x59, 0x93, 0x36, 0x4c, 0x0d, 0x56,
0x5e, 0x26, 0xe8, 0x2b, 0x71, 0xc0, 0x2e, 0x53, 0xab, 0x23, 0x86, 0x9a, 0x4c, 0x2e, 0x68, 0x54, 0xdd, 0xe9, 0x43,
0x19, 0x40, 0xaa, 0x71, 0x40, 0x7f, 0xea, 0xdb, 0x1c, 0x51, 0xe4, 0x6c, 0xf9, 0x6b, 0xf3, 0x37, 0xd4, 0x8d, 0xa9,
0x67, 0xde, 0x48, 0xae, 0xea, 0xaf, 0x8f, 0x5f, 0xdd, 0x4a, 0x05, 0x22, 0xb6, 0xd5, 0x00, 0x8b, 0x33, 0x15, 0x60,
0x30, 0x06, 0xab, 0x13, 0x4c, 0x3d, 0x10, 0x63, 0x16, 0x73, 0x52, 0x06, 0xdf, 0xb8, 0x8c, 0xe4, 0xee, 0x41, 0x26,
0xa6, 0x1c, 0xd0, 0xd2, 0x03, 0x2e, 0xe2, 0x41, 0xc9, 0xf5, 0x9a, 0xa9, 0xad, 0x27, 0x70, 0xf8, 0xd4, 0xfc, 0x99,
0xd5, 0x1d, 0x88, 0x35, 0x5b, 0x35, 0xdb, 0x3b, 0x94, 0x58, 0xda, 0x24, 0xb7, 0xe7, 0xdc, 0x5a, 0x22, 0xaf, 0x04,
0x5f, 0x16, 0xf3, 0x61, 0xbd, 0x34, 0x3f, 0xc6, 0xb2, 0xbb, 0xec, 0x6a, 0x97, 0x7f, 0x72, 0x40, 0x14, 0xa8, 0x70,
0x15, 0x06, 0x30, 0x7d, 0x6b, 0x2b, 0x1a, 0xac, 0x17, 0x93, 0x73, 0xe1, 0x9c, 0xc7, 0x0a, 0xd7, 0x46, 0xb6, 0xee,
0xed, 0x06, 0x63, 0xac, 0xb3, 0x11, 0x50, 0x45, 0x38, 0x37, 0xa9, 0xf1, 0x11, 0x04, 0x31, 0x97, 0x2a, 0x98, 0xed,
0x07, 0x4c, 0x46, 0x92, 0xfd, 0x72, 0xef, 0xb0, 0xd6, 0xcb, 0x1d, 0x9b, 0x10, 0x8f, 0xde, 0x1c, 0x83, 0x82, 0x66,
0xdc, 0x61, 0x49, 0x64, 0x8c, 0xef, 0x70, 0x20, 0xd5, 0x00, 0x37, 0x59, 0x03, 0x36, 0x1f, 0xe1, 0x71, 0xf8, 0x39,
0xf4, 0xb2, 0x32, 0x29, 0x5d, 0x84, 0xcf, 0xfd, 0x47, 0x75, 0xf8, 0xb0, 0xe4, 0x53, 0x09, 0x7c, 0xbf, 0x55, 0x58,
0xe3, 0xe2, 0x0a, 0xa5, 0x7c, 0xa7, 0x05, 0x5f, 0x7e, 0x5e, 0x45, 0x9c, 0x0e, 0xa4, 0x67, 0xd6, 0x48, 0x7e, 0x8b,
0x66, 0x0f, 0x38, 0x06, 0xc2, 0x95, 0x00, 0xcc, 0x77, 0xa9, 0x41, 0x9b, 0xe4, 0xc3, 0xc1, 0xe8, 0xbc, 0xd1, 0x79,
0x8a, 0x39, 0xbf, 0x38, 0x29, 0x21, 0xc9, 0xf6, 0x65, 0x7a, 0x80, 0xf9, 0xc6, 0x0c, 0x9d, 0xaa, 0x66, 0x9b, 0x89,
0x34, 0x60, 0xe1, 0xa8, 0xe2, 0xf7, 0x4d, 0x69, 0x35, 0x86, 0x7e, 0x0f, 0xc8, 0x2c, 0x7e, 0x3c, 0xe4, 0x33, 0xbf,
0x42, 0xe3, 0x44, 0xd6, 0x29, 0x45, 0x4d, 0xf9, 0x80, 0x94, 0x2e, 0x3a, 0xe4, 0xf8, 0x26, 0x80, 0x52, 0x99, 0x00,
0x4a, 0x2c, 0xb7, 0xfd, 0x24, 0x0c, 0x8b, 0x80, 0xe5, 0x4c, 0xdd, 0x7c, 0xad, 0x52, 0x62, 0x9e, 0xa2, 0x9c, 0xdb,
0x3a, 0x65, 0x8f, 0xee, 0xf6, 0x56, 0xff, 0xc3, 0x38, 0x8d, 0x7d, 0x5e, 0x50, 0x93, 0x64, 0xbf, 0x65, 0xe4, 0xa2,
0xbb, 0x21, 0x25, 0x47, 0x98, 0x1c, 0x25, 0xcf, 0x6b, 0x09, 0x95, 0x30, 0x32, 0xa3, 0x87, 0xa2, 0xb1, 0x55, 0xa9,
0x30, 0x0d, 0xac, 0x3e, 0x03, 0x29, 0x7d, 0xe0, 0x69, 0xc4, 0xd5, 0xf7, 0xa8, 0x4f, 0xa5, 0xf8, 0x85, 0x8f, 0x2a,
0x5e, 0x93, 0xf7, 0x1e, 0x69, 0x3c, 0xc3, 0xa0, 0x99, 0x91, 0xd4, 0xe4, 0xe9, 0xd1, 0x4f, 0x9a, 0xcc, 0xbb, 0xf4,
0x1f, 0xe8, 0x94, 0x3e, 0x66, 0x95, 0xf7, 0x3f, 0xa5, 0x14, 0x5f, 0x3a, 0xbf, 0x8f, 0x75, 0xfe, 0x74, 0xd5, 0x1a,
0x05, 0x4b, 0xd2, 0x34, 0x50, 0x3b, 0xcf, 0x39, 0x00, 0x1c, 0x6f, 0x7d, 0xb4, 0xd0, 0x65, 0x3b, 0xa6, 0x30, 0xf6,
0x65, 0x84, 0xb8, 0x32, 0x2c, 0x10, 0xf3, 0xf2, 0x4e, 0x76, 0xa5, 0x90, 0x22, 0x71, 0xba, 0x69, 0x64, 0x1c, 0xf1,
0x14, 0xc1, 0x98, 0x48, 0x5f, 0x21, 0x63, 0x26, 0x0d, 0xb8, 0x73, 0x4b, 0x52, 0xcc, 0x16, 0x78, 0x0a, 0x44, 0x89,
0xdd, 0xbd, 0xf4, 0x5a, 0x47, 0x54, 0xaa, 0x7e, 0x06, 0x97, 0xf8, 0x31, 0x9f, 0x6e, 0xbf, 0x91, 0x36, 0xd4, 0x80,
0xf9, 0xba, 0x9a, 0x62, 0x1e, 0xce, 0xe1, 0x03, 0xec, 0x7a, 0x55, 0xff, 0x1e, 0x96, 0x2a, 0x41, 0x9f, 0xf2, 0xd7,
0x0c, 0xa4, 0x46, 0x95, 0xc8, 0x86, 0xdd, 0x96, 0x8d, 0x60, 0x00, 0x06, 0x71, 0x6d, 0x23, 0xc2, 0x91, 0x1e, 0xd2,
0xdb, 0xe2, 0xfe, 0x59, 0xc9, 0xb5, 0x46, 0x36, 0x6f, 0x0b, 0x3f, 0x3f, 0xd2, 0x9e, 0x5c, 0x93, 0x99, 0xac, 0x13,
0x42, 0x44, 0x82, 0x7f, 0xbc, 0x2c, 0xcc, 0xb7, 0xfa, 0x3f, 0x9e, 0xec, 0x6e, 0x77, 0x41, 0x44, 0xab, 0x1f, 0xbe,
0x7a, 0xd4, 0xc5, 0x2c, 0x32, 0xc6, 0xfe, 0xba, 0x1d, 0x05, 0xdc, 0x7c, 0xd1, 0x67, 0x30, 0xf6, 0x65, 0x83, 0x80,
0x69, 0xd0, 0xd7, 0xc7, 0xdf, 0xff, 0xbb, 0xef, 0xf8, 0xb8, 0x09, 0x0f, 0x73, 0x31, 0x89, 0x64, 0x7c, 0x6f, 0x1f,
0x11, 0xb8, 0x60, 0xb4, 0x97, 0x11, 0xb7, 0xe4, 0x12, 0x41, 0x8b, 0x6c, 0x39, 0x20, 0xaf, 0x51, 0x13, 0xae, 0xcf,
0xd1, 0x7a, 0xe0, 0x4f, 0x0b, 0x51, 0x2b, 0xe3, 0x6d, 0x84, 0xd1, 0xee, 0xe1, 0xc4, 0xee, 0x9c, 0xb7, 0x00, 0x41,
0xbe, 0x51, 0xa2, 0xbb, 0x68, 0x90, 0x8e, 0xca, 0x4c, 0x02, 0xdb, 0x62, 0xd5, 0xc8, 0x4f, 0xd0, 0x03, 0x48, 0x9d,
0x3e, 0x09, 0x98, 0x5d, 0x05, 0xa8, 0xa5, 0xcf, 0xb6, 0x4a, 0x27, 0x50, 0xe6, 0xdd, 0x45, 0x98, 0xbd, 0x75, 0x07,
0xb9, 0x3d, 0xa0, 0x7f, 0xd7, 0x53, 0x88, 0xb7, 0xa8, 0xa0, 0xcc, 0x59, 0xab, 0x04, 0x1b, 0x21, 0x27, 0xee, 0x94,
0x03, 0xea, 0x66, 0xe1, 0x1b, 0xbf, 0xd7, 0x0d, 0x59, 0xac, 0x9e, 0x29, 0xe9, 0xf0, 0x53, 0xa3, 0x63, 0xbf, 0xda,
0x61, 0xb7, 0xbb, 0x28, 0xa0, 0xd6, 0x32, 0xd5, 0x90, 0x21, 0x1a, 0x66, 0xab, 0x92, 0xcd, 0x58, 0xd9, 0xad, 0x85,
0xec, 0x45, 0x24, 0x95, 0xb6, 0xfd, 0x4d, 0x6d, 0xe3, 0x4c, 0x44, 0x4b, 0x4d, 0xd7, 0xe0, 0xc2, 0x40, 0x7a, 0x56,
0xec, 0x0a, 0xe0, 0xaf, 0x6e, 0x45, 0xe4, 0xeb, 0x30, 0xe3, 0xab, 0xf3, 0xd9, 0xfe, 0x7e, 0x40, 0x0d, 0x5d, 0x68,
0x4c, 0x66, 0xc5, 0x93, 0xbb, 0x33, 0x06, 0x63, 0x49, 0xf3, 0xc1, 0x4f, 0x73, 0x90, 0xfa, 0x84, 0xe5, 0x5b, 0x61,
0x38, 0xa6, 0xcb, 0x60, 0xce, 0x05, 0xf5, 0xe3, 0xaf, 0x43, 0xcb, 0xeb, 0xe2, 0xfd, 0x14, 0xd2, 0x71, 0x60, 0x2f,
0x5c, 0x7c, 0x0b, 0x87, 0x81, 0x1a, 0xc2, 0xd5, 0xfd, 0xf3, 0xd9, 0x19, 0x93, 0x9e, 0x5b, 0x56, 0x25, 0xbb, 0xfd,
0x10, 0x75, 0xd0, 0xc5, 0x86, 0xb9, 0xe4, 0x7a, 0xb0, 0xe0, 0xdd, 0xca, 0xf7, 0xa7, 0x9a, 0x3e, 0xb6, 0x5a, 0xd0,
0xde, 0x75, 0x6b, 0xa5, 0xa3, 0xdf, 0xb4, 0x0e, 0xa1, 0x89, 0x8c, 0xe9, 0xf1, 0x62, 0x8b, 0xda, 0x5f, 0xf8, 0x1d,
0xe6, 0xd9, 0x70, 0xc9, 0xd0, 0x92, 0x42, 0x3f, 0x36, 0xf9, 0xb9, 0x23, 0x6b, 0x5d, 0x26, 0x9c, 0x14, 0x5d, 0xe7,
0x9b, 0x81, 0x11, 0x1b, 0xec, 0xd1, 0xf9, 0x22, 0xfe, 0xc4, 0xa9, 0xd7, 0x71, 0x5e, 0x32, 0x8d, 0x64, 0x41, 0x50,
0xca, 0x7c, 0xc8, 0x55, 0xc6, 0xb5, 0xe3, 0xd8, 0xa1, 0xe8, 0xb5, 0x1c, 0x68, 0x20, 0xef, 0x42, 0xc6, 0x9d, 0xea,
0x2f, 0x8a, 0xa4, 0x87, 0x3b, 0xdc, 0x0c, 0xc7, 0x9f, 0x3f, 0x89, 0x29, 0x9b, 0x46, 0xb7, 0xc7, 0x6a, 0x16, 0xe6,
0x2f, 0xe6, 0x9f, 0xa8, 0x02, 0xfb, 0x99, 0x1b, 0x4f, 0xcd, 0x40, 0xf7, 0xad, 0xa9, 0xd0, 0x56, 0x1a, 0xad, 0xc0,
0x47, 0x96, 0x36, 0xbe, 0x11, 0x7f, 0x0c, 0x6d, 0x47, 0xab, 0x4f, 0xe1, 0x90, 0x47, 0x21, 0xd6, 0x33, 0x72, 0x11,
0xb4, 0x65, 0x99, 0x4a, 0x43, 0x52, 0x90, 0x80, 0x2e, 0xed, 0x75, 0x96, 0x39, 0x78, 0x45, 0x0f, 0x42, 0xd2, 0x3b,
0x3b, 0xf9, 0x84, 0xed, 0x1d, 0x5e, 0x8a, 0x24, 0x1f, 0xff, 0xe1, 0x7e, 0x75, 0xef, 0xd4, 0xc5, 0x51, 0x85, 0x07,
0x0e, 0xcb, 0xda, 0x40, 0x92, 0x8e, 0x00, 0xf9, 0x4c, 0x44, 0x03, 0xa6, 0x56, 0xbf, 0xa0, 0xc6, 0x04, 0xec, 0xc7,
0xac, 0xbd, 0x78, 0xd2, 0x0a, 0x9d, 0x3f, 0x83, 0x42, 0xcc, 0x79, 0xc0, 0x6d, 0x9d, 0xa3, 0x0d, 0x27, 0x66, 0x2f,
0xcd, 0x21, 0x22, 0xeb, 0x38, 0x64, 0xc7, 0x31, 0x1b, 0x66, 0x68, 0xd4, 0x53, 0xce, 0x0b, 0xc5, 0x07, 0x07, 0x48,
0x89, 0xa9, 0x86, 0xa6, 0x5b, 0xaa, 0xdc, 0xb5, 0x20, 0x00, 0x7b, 0xeb, 0x0f, 0x58, 0x48, 0x3f, 0x1d, 0x1b, 0xe4,
0x87, 0x29, 0x23, 0xba, 0x92, 0xec, 0x3a, 0xc8, 0x05, 0x9e, 0x56, 0x2e, 0xf6, 0x69, 0x97, 0x79, 0x4a, 0x8f, 0xfc,
0xac, 0xcf, 0x5a, 0xe9, 0x12, 0x18, 0xc7, 0x59, 0xc6, 0x1c, 0xf1, 0xe3, 0xfe, 0x8d, 0x3f, 0x6e, 0x1c, 0xfa, 0x4d,
0xb3, 0x73, 0xa4, 0x9b, 0x77, 0xd9, 0x20, 0xf8, 0x86, 0xf1, 0xb0, 0xac, 0x6e, 0x35, 0x62, 0x68, 0x01, 0xc6, 0xe6,
0xee, 0x88, 0xf3, 0x30, 0x80, 0xc7, 0x8b, 0xd2, 0x0a, 0xb5, 0xe3, 0x17, 0x0e, 0xf8, 0xa6, 0xdf, 0x53, 0x22, 0x5f,
0x3d, 0x4d, 0xae, 0xc7, 0xd1, 0x4b, 0xb9, 0xd0, 0xe6, 0x63, 0x80, 0x6e, 0x4c, 0x1d, 0x9d, 0x28, 0xf4, 0xe2, 0x01,
0x62, 0x4d, 0x23, 0xb0, 0x67, 0x61, 0x49, 0x3f, 0x39, 0xae, 0x5f, 0x0f, 0x18, 0xc5, 0x81, 0x47, 0xd4, 0xa5, 0x04,
0x93, 0xd3, 0xd5, 0x63, 0x9e, 0xe2, 0x43, 0xec, 0x50, 0xbc, 0xdf, 0x2e, 0xc7, 0x5a, 0xf7, 0x21, 0x17, 0x00, 0x51,
0x14, 0xc2, 0x04, 0x0a, 0x57, 0xf6, 0x5d, 0x31, 0xac, 0x89, 0xef, 0x22, 0x6e, 0xb5, 0x98, 0xa4, 0x7f, 0x93, 0xfa,
0xc2, 0xf3, 0x58, 0x07, 0x6a, 0x7e, 0x53, 0x4e, 0xc7, 0x9e, 0xa2, 0x07, 0x5e, 0x87, 0xb7, 0xdf, 0xea, 0xd3, 0x8f,
0x84, 0xf8, 0xe6, 0xac, 0xf0, 0x42, 0x22, 0xd7, 0x76, 0xcd, 0xd0, 0x97, 0x8e, 0xc1, 0xc9, 0x6f, 0x61, 0x0c, 0xe8,
0xdb, 0x13, 0xb5, 0x2f, 0x09, 0xd2, 0x00, 0xbc, 0x58, 0xa3, 0x47, 0x6a, 0x91, 0xe7, 0x93, 0x46, 0xe7, 0xb0, 0xfd,
0xb8, 0xb0, 0xc6, 0xc7, 0x6d, 0xe1, 0x77, 0x39, 0x07, 0x4c, 0x22, 0x3d, 0x84, 0x98, 0x82, 0xba, 0x6f, 0x04, 0xbb,
0xb0, 0xc9, 0xdb, 0x26, 0x18, 0xb4, 0xb6, 0xdd, 0x79, 0xe5, 0x74, 0x05, 0x4a, 0xc7, 0xfa, 0xde, 0x0b, 0x70, 0xab,
0x8c, 0x22, 0x45, 0xa6, 0xa3, 0x76, 0x6b, 0xc2, 0x74, 0xcd, 0xf4, 0x4b, 0x19, 0xe6, 0xc6, 0x79, 0x42, 0x56, 0x4b,
0xb4, 0xbe, 0x23, 0xdb, 0x8a, 0xc9, 0x22, 0xe9, 0x6c, 0x18, 0x10, 0xef, 0x2c, 0xa5, 0x73, 0x1a, 0x37, 0x3a, 0x2e,
0xb5, 0x92, 0xc0, 0xe7, 0x57, 0x95, 0x45, 0x19, 0xcd, 0x6a, 0xce, 0xf8, 0xc5, 0x45, 0xe2, 0x08, 0x3f, 0x61, 0xd3,
0x7b, 0xb3, 0xbe, 0xcb, 0x4f, 0x03, 0x9b, 0x9a, 0x24, 0x1f, 0xb5, 0x41, 0xde, 0x7a, 0x3e, 0x3e, 0xff, 0x30, 0x9c,
0x95, 0xc1, 0xf4, 0xe6, 0xa3, 0xf6, 0xbb, 0x1f, 0xa2, 0xa0, 0x2a, 0x30, 0xfb, 0x50, 0xc0, 0xa7, 0x4f, 0x3f, 0xc3,
0x32, 0x43, 0x2e, 0xec, 0xd9, 0x0f, 0xfd, 0xd1, 0xff, 0xc6, 0x0d, 0x16, 0x17, 0x63, 0x57, 0x53, 0x25, 0x97, 0x49,
0xc4, 0xfb, 0x55, 0x64, 0xb4, 0x31, 0x19, 0x97, 0xa6, 0xe1, 0xfc, 0x59, 0x48, 0x27, 0x79, 0x88, 0x18, 0x5c, 0xfd,
0x04, 0xd3, 0x0d, 0x30, 0x0a, 0xa2, 0xbd, 0x06, 0x52, 0x04, 0x5f, 0x84, 0xe8, 0x7a, 0x92, 0x52, 0xcc, 0xf4, 0x9a,
0x06, 0x48, 0xfe, 0x22, 0x2a, 0x13, 0xaa, 0xb4, 0x13, 0x2a, 0x3c, 0xf4, 0x61, 0x6a, 0x98, 0xa0, 0x55, 0xee, 0x6c,
0x48, 0xd6, 0x2b, 0x1d, 0xcc, 0x41, 0xcc, 0x73, 0x9a, 0xf4, 0x4e, 0x03, 0xa4, 0x98, 0x5c, 0x60, 0x24, 0x32, 0xe8,
0x00, 0x0f, 0xe9, 0x0d, 0xf0, 0x5a, 0x95, 0xa8, 0x38, 0xef, 0x0b, 0xb0, 0x0c, 0x6e, 0x2b, 0x59, 0xb7, 0xa4, 0xa6,
0xed, 0x47, 0xcc, 0x90, 0x65, 0xf9, 0x88, 0xa9, 0xca, 0x63, 0x9d, 0x6b, 0x4e, 0x64, 0x71, 0xd3, 0x29, 0xcb, 0x46,
0xf0, 0x34, 0xca, 0x34, 0x2c, 0x48, 0x19, 0xe8, 0x5d, 0x55, 0xe9, 0x1f, 0x8b, 0x41, 0x8f, 0xe0, 0x7f, 0x86, 0xed,
0x7c, 0x10, 0x8a, 0xa0, 0x46, 0x2d, 0xbd, 0xeb, 0x13, 0xa9, 0x75, 0x66, 0x70, 0xca, 0x89, 0x66, 0xa6, 0x8f, 0xaf,
0x2b, 0x6e, 0xc5, 0x96, 0xcd, 0x74, 0xc8, 0xc2, 0xe3, 0x8e, 0x1a, 0x2b, 0xf8, 0xb9, 0xa5, 0x95, 0xaf, 0xd8, 0xd0,
0xae, 0xb0, 0x2b, 0x44, 0xe9, 0x59, 0x90, 0x7b, 0x1d, 0x26, 0x7d, 0x0d, 0xdc, 0xdc, 0xb9, 0xba, 0x94, 0x78, 0xdd,
0x36, 0x68, 0xdb, 0x8e, 0xb8, 0xcc, 0x32, 0x77, 0x41, 0xe3, 0x27, 0x09, 0xac, 0x98, 0xc9, 0xf2, 0x7a, 0x7e, 0x3c,
0x5e, 0x5b, 0x68, 0x20, 0xf0, 0x7c, 0xc8, 0xc2, 0x7f, 0x94, 0xc8, 0x05, 0x91, 0xac, 0x2a, 0x0e, 0x98, 0xb1, 0xe0,
0xb3, 0xee, 0xba, 0x6a, 0xa1, 0x92, 0x1a, 0xf3, 0x4f, 0xa0, 0x25, 0x2b, 0xa6, 0x92, 0xc3, 0xe3, 0x6d, 0x39, 0xb6,
0x6a, 0x76, 0x79, 0xab, 0xa9, 0x8a, 0xc7, 0x02, 0x00, 0x15, 0x64, 0x5d, 0xdf, 0x92, 0x56, 0xea, 0x25, 0xb5, 0x2b,
0x71, 0x39, 0x5a, 0x6d, 0x1c, 0xe9, 0x30, 0xe4, 0x0a, 0x05, 0x24, 0x8e, 0x92, 0xba, 0x90, 0x5f, 0x57, 0xb0, 0x20,
0xb9, 0x10, 0xe3, 0x88, 0xa9, 0x14, 0x42, 0xe8, 0xe7, 0xa8, 0x09, 0xfa, 0x8c, 0x09, 0xdf, 0x01, 0x91, 0x38, 0xaf,
0x71, 0x0b, 0x88, 0x46, 0x5f, 0x29, 0xe8, 0xb1, 0xa7, 0xe9, 0xf1, 0x54, 0xdf, 0x9d, 0xae, 0xca, 0xff, 0x21, 0x2f,
0x80, 0xcc, 0xd9, 0xcc, 0x11, 0x09, 0x6d, 0x3a, 0x5f, 0x5d, 0x44, 0x4f, 0x75, 0x03, 0xc1, 0x95, 0x1f, 0x33, 0x40,
0x90, 0x47, 0x54, 0xcf, 0xcf, 0x0e, 0xde, 0xac, 0xb5, 0xae, 0x11, 0x83, 0x05, 0x12, 0x57, 0x64, 0x2f, 0x32, 0x7a,
0x86, 0x5c, 0x30, 0x86, 0xbe, 0x3b, 0x34, 0x7c, 0xbd, 0x89, 0x08, 0xbb, 0x0c, 0x63, 0x72, 0x2e, 0xc9, 0x4b, 0x55,
0x36, 0x1f, 0xfc, 0x86, 0x56, 0x86, 0xed, 0x6f, 0xa1, 0xc0, 0xa7, 0x66, 0xe4, 0x8f, 0xc7, 0xcb, 0x93, 0x36, 0xd2,
0xe4, 0x74, 0xb6, 0xbd, 0x78, 0x0d, 0xc5, 0x37, 0x2d, 0xcf, 0x8e, 0xbc, 0xa1, 0xff, 0x2c, 0x5a, 0x4b, 0x68, 0xe4,
0x42, 0x10, 0xc2, 0x24, 0x0e, 0xa5, 0xe0, 0x3f, 0xd4, 0xa7, 0x9d, 0xe6, 0xee, 0x7b, 0x85, 0x66, 0x87, 0xe7, 0x42,
0xa4, 0xe6, 0x6d, 0xcd, 0xd8, 0x7a, 0x6b, 0x50, 0x0c, 0xaa, 0xf4, 0xdd, 0x53, 0x59, 0x6c, 0xd1, 0xca, 0x3b, 0xe6,
0x06, 0x50, 0xda, 0x3d, 0xbc, 0x05, 0x67, 0xf4, 0x45, 0x83, 0x41, 0xce, 0x72, 0xdf, 0x47, 0x2c, 0xc0, 0x5e, 0xde,
0x2b, 0x42, 0xf7, 0xc1, 0x7a, 0x4e, 0x8d, 0x8b, 0xfc, 0xe7, 0xe9, 0x06, 0x21, 0xee, 0x79, 0x31, 0x04, 0x07, 0xca,
0x13, 0x44, 0x4c, 0xc7, 0x42, 0xa4, 0xb4, 0xaf, 0x32, 0x62, 0x42, 0xd7, 0x3f, 0x58, 0xed, 0x25, 0x78, 0xd3, 0x52,
0x48, 0x31, 0x36, 0x1c, 0xb8, 0x47, 0x4f, 0x2f, 0x71, 0x31, 0x75, 0x97, 0x6f, 0xcc, 0x66, 0x34, 0xe8, 0xe3, 0xe2,
0x21, 0x4c, 0x7f, 0x06, 0xe9, 0xfd, 0xf4, 0x6a, 0x69, 0xce, 0xa8, 0xfa, 0x83, 0x3d, 0xa2, 0x95, 0x87, 0x70, 0xd6,
0x67, 0x62, 0xf2, 0x33, 0x06, 0x84, 0x2a, 0x68, 0x4f, 0x93, 0x9f, 0x7a, 0xff, 0xd1, 0xee, 0x07, 0x10, 0x4d, 0x86,
0x57, 0xda, 0xcd, 0x2a, 0x28, 0xe5, 0x77, 0x24, 0x36, 0x8f, 0x41, 0x4a, 0x8c, 0x62, 0x1e, 0xb2, 0x8d, 0x32, 0xe6,
0x34, 0xc1, 0xf9, 0x01, 0xa4, 0x61, 0x73, 0xdb, 0xcc, 0xa1, 0x79, 0x08, 0x2c, 0xc0, 0x1f, 0x32, 0x71, 0xd0, 0x8c,
0xc6, 0xb5, 0x45, 0x9c, 0x62, 0x7c, 0x40, 0x37, 0xfd, 0x6a, 0x99, 0x6f, 0xd4, 0xc6, 0xae, 0x5c, 0x18, 0xaf, 0xef,
0xb7, 0x59, 0x20, 0x25, 0x39, 0xb0, 0xa5, 0x85, 0xb1, 0xaf, 0xda, 0x78, 0xe8, 0x09, 0xa9, 0x28, 0x34, 0x02, 0x3f,
0xca, 0xde, 0x95, 0xd3, 0xae, 0x35, 0x67, 0x16, 0x08, 0x6f, 0x6e, 0x78, 0x7c, 0x43, 0x60, 0x21, 0x6b, 0x46, 0xde,
0xaa, 0x00, 0x09, 0x5d, 0x9f, 0xf8, 0xf0, 0xd8, 0xb6, 0x35, 0xd1, 0x38, 0xcc, 0x02, 0x50, 0x7e, 0xa3, 0x24, 0xfd,
0xb6, 0x5d, 0xa5, 0x2a, 0x9c, 0xb7, 0x60, 0x8d, 0x0b, 0x07, 0x4b, 0x9b, 0x29, 0xf4, 0x87, 0x9f, 0x54, 0xf7, 0x29,
0x9c, 0xcb, 0x94, 0xc6, 0xe8, 0x1d, 0x59, 0xc1, 0x85, 0x52, 0x93, 0x54, 0x3e, 0xda, 0xd3, 0x5f, 0x86, 0x39, 0x24,
0xf3, 0x10, 0x70, 0x09, 0x9e, 0x2d, 0x2a, 0x39, 0x74, 0xea, 0xac, 0x19, 0xe2, 0xe5, 0x79, 0xfd, 0x32, 0xf3, 0xc8,
0x1f, 0xdd, 0x74, 0xa7, 0x5b, 0x94, 0x70, 0xeb, 0xb0, 0x0d, 0x7a, 0xde, 0x77, 0x8e, 0x41, 0xa8, 0x61, 0x3a, 0xf6,
0x79, 0x32, 0x1e, 0x62, 0x7c, 0x3f, 0xfb, 0xc3, 0x7b, 0xa8, 0xd1, 0x6a, 0x52, 0x56, 0x30, 0x15, 0x15, 0xb3, 0x3e,
0x0d, 0x01, 0x2c, 0x87, 0xff, 0x23, 0x68, 0x88, 0x9b, 0xca, 0x45, 0xf9, 0xee, 0xae, 0x97, 0xde, 0x80, 0xdd, 0x9f,
0x4c, 0x2d, 0x69, 0x4d, 0xfc, 0xc3, 0xd3, 0x36, 0x91, 0x61, 0x36, 0x53, 0xa9, 0x8a, 0x33, 0x7f, 0xa5, 0x0c, 0xa0,
0xaf, 0x39, 0x93, 0xef, 0xee, 0xb2, 0x0c, 0x60, 0x20, 0x0a, 0xbb, 0xe7, 0x73, 0xad, 0x1b, 0xdb, 0xa4, 0x09, 0x79,
0x63, 0xf7, 0xd8, 0x4f, 0xa0, 0xf3, 0x2d, 0x61, 0x9a, 0xe1, 0x40, 0x99, 0xec, 0x03, 0xf9, 0xa3, 0x2f, 0x95, 0x3d,
0x48, 0x28, 0x85, 0xfc, 0x8b, 0xb1, 0xf1, 0xf7, 0x86, 0x68, 0x64, 0x52, 0x01, 0x19, 0xd0, 0xdd, 0xd7, 0xec, 0x4b,
0x22, 0x09, 0x4e, 0x8a, 0x2e, 0x9f, 0xa0, 0x1e, 0x20, 0x43, 0xa2, 0x70, 0x73, 0x9b, 0x9e, 0x7d, 0x0b, 0x69, 0x54,
0x52, 0x01, 0x65, 0xa8, 0x47, 0x2e, 0xd6, 0x0e, 0xfb, 0x79, 0xf2, 0xb1, 0x13, 0xe1, 0xfb, 0x66, 0xb1, 0x49, 0x16,
0x54, 0xb6, 0x07, 0xe9, 0x5f, 0xb8, 0x2a, 0xfc, 0x59, 0xc2, 0x8f, 0xcd, 0xe8, 0xce, 0xa3, 0xb0, 0x35, 0x03, 0xf5,
0x6d, 0x59, 0xaf, 0x83, 0x98, 0x88, 0x03, 0xc0, 0xae, 0xfb, 0x90, 0x68, 0xf4, 0x9f, 0x85, 0x04, 0x10, 0x7f, 0xa5,
0x3c, 0x64, 0xf2, 0x0b, 0xe0, 0x2e, 0x84, 0x9e, 0xb5, 0x60, 0xa5, 0x52, 0xff, 0x40, 0x14, 0x23, 0xd3, 0xc0, 0x09,
0xd2, 0x19, 0x9b, 0x25, 0x5e, 0x63, 0x34, 0x1a, 0x41, 0x71, 0x2e, 0x1a, 0x70, 0x80, 0xca, 0x01, 0x10, 0x57, 0x04,
0x41, 0xf8, 0x0f, 0x6d, 0x91, 0xea, 0xa5, 0xc7, 0x55, 0x91, 0x41, 0x62, 0xcc, 0x3a, 0x43, 0x39, 0xfb, 0x52, 0x13,
0xcb, 0x5d, 0xa4, 0xa5, 0xbb, 0x47, 0x4f, 0xce, 0x27, 0x1d, 0x88, 0x4a, 0xc8, 0x37, 0x15, 0x89, 0xce, 0x90, 0x84,
0x3e, 0x79, 0x34, 0x2f, 0xa5, 0x77, 0x68, 0xf1, 0x96, 0x01, 0x96, 0xa6, 0x4e, 0xb3, 0xff, 0x64, 0xf9, 0x2f, 0x1f,
0x7b, 0xff, 0x72, 0xdf, 0x2d, 0x13, 0xf1, 0x7d, 0x2d, 0xfc, 0x7b, 0x3c, 0xd9, 0x21, 0xce, 0xcb, 0x37, 0xd7, 0xf6,
0x76, 0xb1, 0x6b, 0x69, 0x0e, 0x90, 0xee, 0xe4, 0x99, 0x3a, 0xc3, 0x04, 0xba, 0x2b, 0xe9, 0xa3, 0xdf, 0xb5, 0x09,
0xf8, 0x2c, 0x78, 0x3f, 0x6d, 0x2b, 0x9f, 0xa8, 0x3f, 0x30, 0xdf, 0xb0, 0xd8, 0x8d, 0x30, 0x19, 0xec, 0x7b, 0x4a,
0xca, 0x55, 0xc3, 0xde, 0x5d, 0xaf, 0x2f, 0x99, 0x66, 0xb3, 0xb4, 0xda, 0x84, 0x54, 0x5c, 0x2d, 0x2a, 0x9b, 0xf5,
0x8f, 0x2b, 0x43, 0x7c, 0x61, 0x07, 0x14, 0x46, 0x28, 0xf7, 0x00, 0xf4, 0x0a, 0xba, 0xff, 0x14, 0x08, 0x1e, 0xaa,
0x97, 0xfc, 0x4d, 0x99, 0xf7, 0x52, 0xdd, 0x7e, 0x25, 0x86, 0x95, 0xfe, 0x33, 0x61, 0x00, 0x53, 0xc6, 0xf3, 0xb4,
0xa2, 0x39, 0x4f, 0x5f, 0x1d, 0x92, 0x09, 0x96, 0xd8, 0x25, 0x70, 0x5e, 0x6c, 0x0a, 0x8c, 0x76, 0x4e, 0x40, 0xd6,
0x6a, 0xed, 0x9e, 0xba, 0x59, 0x8d, 0x7d, 0xcd, 0x81, 0xe6, 0x27, 0xce, 0x8f, 0x7b, 0xfe, 0x5e, 0xa8, 0xbc, 0x6c,
0x7f, 0x37, 0xdb, 0x8e, 0xce, 0x75, 0x6e, 0xa0, 0x80, 0xb9, 0xc9, 0x27, 0x1e, 0x85, 0x19, 0xe0, 0x00, 0x4c, 0x41,
0x6b, 0x8b, 0x05, 0x74, 0xb7, 0x2a, 0x70, 0x97, 0x2c, 0x0d, 0x2b, 0x54, 0x9b, 0xa4, 0x74, 0xfe, 0xe7, 0xda, 0xbb,
0x27, 0x70, 0x12, 0xef, 0x03, 0x43, 0xe2, 0x85, 0x7b, 0x02, 0x92, 0x48, 0x11, 0x94, 0x53, 0xe4, 0x76, 0x8d, 0x3f,
0x30, 0xed, 0x5c, 0x15, 0x3e, 0x5d, 0x37, 0xb0, 0x24, 0x39, 0xc7, 0x4c, 0x89, 0x10, 0x93, 0x79, 0xef, 0xf3, 0x2d,
0xdb, 0xc2, 0x8a, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xed, 0xbb, 0x8c, 0x8a, 0x8d, 0x8a, 0xe1, 0x16, 0x03, 0x00,
};
static constexpr size_t WINDOW = 4096;
static constexpr size_t PLAIN_SIZE = 16000;
@@ -223,18 +757,55 @@ static std::vector<uint8_t> build_plain() {
return plain;
}
// Text, incompressible noise that zlib stores, then text again
static std::vector<uint8_t> build_mixed() {
std::vector<uint8_t> plain;
const char *text = "esphome ota deflate ";
for (int i = 0; i < 60; i++)
plain.insert(plain.end(), text, text + strlen(text));
uint32_t x = 3;
for (int i = 0; i < 3000; i++)
plain.push_back(lcg_next(x));
for (int i = 0; i < 40; i++)
plain.insert(plain.end(), text, text + strlen(text));
return plain;
}
static std::vector<uint8_t> build_skewed() {
std::vector<uint8_t> plain;
const char *common = "etaoin ";
uint32_t x = 7;
for (int i = 0; i < 6000; i++) {
uint8_t r = lcg_next(x);
plain.push_back(r < 230 ? common[r % 7] : lcg_next(x));
}
return plain;
}
// Mirrors the OTA session: chunked input through the read callback, window as output
struct Session : OtaInflateState {
const uint8_t *in;
size_t in_len;
size_t in_pos;
size_t chunk;
// Like the session where the ack follows the write: output is taken
// before each read, up to dest
bool flush_in_read;
size_t flushed;
std::vector<uint8_t> out;
uint8_t window[WINDOW];
};
static void take_output(Session &s) {
const size_t produced = s.dest - s.window;
s.out.insert(s.out.end(), s.window + s.flushed, s.window + produced);
s.flushed = produced;
}
static int read_cb(OtaInflateState *d) {
auto *s = static_cast<Session *>(d);
if (s->flush_in_read)
take_output(*s);
if (s->in_pos >= s->in_len)
return -1;
size_t n = std::min(s->chunk, s->in_len - s->in_pos);
@@ -245,23 +816,26 @@ static int read_cb(OtaInflateState *d) {
}
// Inflates the whole input; returns the decoder result and fills s.out
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk) {
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk, bool flush_in_read = false) {
s.in = in;
s.in_len = in_len;
s.in_pos = 0;
s.chunk = chunk;
s.flush_in_read = flush_in_read;
s.flushed = 0;
s.out.clear();
memset(s.window, 0, sizeof(s.window));
ota_inflate_init(&s, s.window, WINDOW);
s.source_read_cb = read_cb;
int res;
do {
s.dest = s.window;
s.dest_limit = s.window + WINDOW;
// Starts the window over once the previous call filled it
res = ota_inflate(&s);
if (res < 0 || s.eof)
return res < 0 ? res : OTA_INFLATE_DATA_ERROR;
s.out.insert(s.out.end(), s.window, s.dest);
take_output(s);
// A full window is taken; the next call starts it over
s.flushed = 0;
if (s.out.size() > PLAIN_SIZE)
return OTA_INFLATE_DATA_ERROR;
} while (res != OTA_INFLATE_DONE);
@@ -289,6 +863,36 @@ TEST(OtaInflate, StoredBlock) {
EXPECT_EQ(s->out, plain);
}
// Dynamic blocks, fixed Huffman blocks, codes longer than the lookup tables, and
// stored blocks that start with read ahead bits in the bit buffer
struct Stream {
const uint8_t *data;
size_t len;
std::vector<uint8_t> (*plain)();
};
static const Stream STREAMS[] = {{DEFLATED, sizeof(DEFLATED), build_plain},
{FIXED, sizeof(FIXED), build_plain},
{SKEWED, sizeof(SKEWED), build_skewed},
{MIXED, sizeof(MIXED), build_mixed}};
TEST(OtaInflate, EveryReadSizeGivesTheSameOutput) {
// 1 byte reads never leave enough input for the fast loop, 11 to 13 straddle
// its threshold, and the larger ones run it across read and window boundaries
auto s = std::make_unique<Session>();
for (const auto &stream : STREAMS) {
const auto plain = stream.plain();
for (bool flush_in_read : {false, true}) {
for (size_t chunk : {size_t{1}, size_t{11}, size_t{12}, size_t{13}, size_t{300}, size_t{1040}, stream.len}) {
ASSERT_EQ(inflate_all(*s, stream.data, stream.len, chunk, flush_in_read), OTA_INFLATE_DONE)
<< "stream of " << stream.len << " B, read size " << chunk << ", flush in read " << flush_in_read;
EXPECT_EQ(s->out, plain) << "stream of " << stream.len << " B, read size " << chunk << ", flush in read "
<< flush_in_read;
EXPECT_EQ(s->in_pos, stream.len);
}
}
}
}
TEST(OtaInflate, TruncatedStreamFails) {
auto s = std::make_unique<Session>();
for (size_t cut : {size_t{1}, size_t{100}, size_t{1000}, sizeof(DEFLATED) - 1}) {
@@ -305,12 +909,14 @@ TEST(OtaInflate, TruncatedStoredBlockFails) {
TEST(OtaInflate, CorruptStreamsNeverEscapeTheWindow) {
// Flipped bytes and garbage; the sanitizers check the decoder stays in bounds
auto s = std::make_unique<Session>();
std::vector<uint8_t> bad(DEFLATED, DEFLATED + sizeof(DEFLATED));
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
for (size_t i = 0; i < bad.size(); i += 29) {
bad[i] ^= 0x5a;
inflate_all(*s, bad.data(), bad.size(), 1040);
bad[i] ^= 0x5a;
for (const auto &stream : STREAMS) {
std::vector<uint8_t> bad(stream.data, stream.data + stream.len);
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
for (size_t i = 0; i < bad.size(); i += 29) {
bad[i] ^= 0x5a;
inflate_all(*s, bad.data(), bad.size(), 1040);
bad[i] ^= 0x5a;
}
}
uint32_t x = 99;
std::vector<uint8_t> garbage(2000);
@@ -2,6 +2,7 @@ wifi:
dpp:
channels: [1, 6, 11]
device_info: ESPHome
private_key: 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
timeout: 30s
on_dpp_uri:
- logger.log:
@@ -0,0 +1,70 @@
esphome:
name: uart-mock-dfrobot-sen0395
host:
api:
batch_delay: 0ms # Disable batching to receive all state updates
logger:
level: VERBOSE
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
# Dummy uart entry to satisfy DEPENDENCIES = ["uart"]
uart:
baud_rate: 115200
port: /dev/null
uart_mock:
id: mock_uart
baud_rate: 115200
# A small model of the module's command line: echo, answer and the prompt (no line end)
on_tx:
- then:
- lambda: |-
std::string cmd(data.begin(), data.end());
std::string reply = cmd + "\r\n";
if (cmd == "resetSystem") {
// The restart only answers with the prompt
} else {
reply += "Done\r\n";
}
reply += "leapMMW:/>";
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(reply.begin(), reply.end()));
dfrobot_sen0395:
id: sen0395
uart_id: mock_uart
binary_sensor:
- platform: dfrobot_sen0395
name: Detected
switch:
- platform: dfrobot_sen0395
type: sensor_active
name: Sensor Active
button:
- platform: template
name: Inject Present
on_press:
- lambda: |-
// '$' is kept apart so it is not read as a substitution
std::string frame = std::string(1, '$') + "JYBSS,1, , , *\r\n";
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(frame.begin(), frame.end()));
- platform: template
name: Inject Overlong Then Absent
on_press:
- lambda: |-
// An overlong line is dropped up to its line end; the next frame still parses
std::string data(300, 'x');
data += "\r\n" + std::string(1, '$') + "JYBSS,0, , , *\r\n";
id(mock_uart).inject_to_rx_buffer(std::vector<uint8_t>(data.begin(), data.end()));
- platform: template
name: Reset Sensor
on_press:
- dfrobot_sen0395.reset:
id: sen0395
@@ -0,0 +1,93 @@
"""Integration test for the DFRobot SEN0395 component against a model of the module's command line.
Checks presence frames, recovery after an overlong line, a command answered by "Done" and the
reset command, which the module answers only with its "leapMMW:/>" prompt (no line end).
"""
from __future__ import annotations
import asyncio
from collections.abc import Callable
from pathlib import Path
from aioesphomeapi import (
BinarySensorInfo,
ButtonInfo,
EntityInfo,
EntityState,
SwitchInfo,
)
import pytest
from .state_utils import require_entity
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_uart_mock_dfrobot_sen0395(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Frames parse, an overlong line is dropped and the prompt completes the reset."""
external_components_path = str(
Path(__file__).parent / "fixtures" / "external_components"
)
yaml_config = yaml_config.replace(
"EXTERNAL_COMPONENT_PATH", external_components_path
)
loop = asyncio.get_running_loop()
dropped = loop.create_future()
restarted = loop.create_future()
def line_callback(line: str) -> None:
if "Line over 252 chars dropped" in line and not dropped.done():
dropped.set_result(True)
if "Restarted sensor." in line and not restarted.done():
restarted.set_result(True)
states: dict[int, EntityState] = {}
async def wait_for(condition: Callable[[], bool], what: str) -> None:
try:
async with asyncio.timeout(10.0):
while not condition():
await asyncio.sleep(0.05)
except TimeoutError:
pytest.fail(f"Timeout waiting for {what}; states: {states}")
async with (
run_compiled(yaml_config, line_callback=line_callback),
api_client_connected() as client,
):
entities, _ = await client.list_entities_services()
detected = require_entity(entities, "detected", BinarySensorInfo)
active = require_entity(entities, "sensor_active", SwitchInfo)
present = require_entity(entities, "inject_present", ButtonInfo)
overlong = require_entity(entities, "inject_overlong_then_absent", ButtonInfo)
reset = require_entity(entities, "reset_sensor", ButtonInfo)
client.subscribe_states(lambda state: states.__setitem__(state.key, state))
def state_of(entity: EntityInfo) -> object | None:
state = states.get(entity.key)
return None if state is None or state.missing_state else state.state
client.button_command(present.key)
await wait_for(lambda: state_of(detected) is True, "presence")
client.button_command(overlong.key)
await wait_for(
lambda: state_of(detected) is False, "absence after overlong line"
)
await asyncio.wait_for(dropped, timeout=5.0)
client.switch_command(active.key, False)
await wait_for(lambda: state_of(active) is False, "sensor stopped")
client.button_command(reset.key)
try:
await asyncio.wait_for(restarted, timeout=10.0)
except TimeoutError:
pytest.fail("The reset was not completed by the prompt")
@@ -0,0 +1,125 @@
"""Tests for trimming a LibreTiny UF2 to the images a device writes over OTA."""
from pathlib import Path
import struct
import pytest
from esphome.components.libretiny import device_ota_image
from esphome.components.libretiny.ota_uf2 import UF2_TAG_OTA_PART_INFO, device_ota_uf2
from esphome.core import CORE
# Written from the UF2 and LibreTiny formats, not from the module under test
MAGIC_1, MAGIC_2, MAGIC_3 = 0x0A324655, 0x9E5D5157, 0x0AB16F30
FLAG_FAMILY, FLAG_TAGS = 0x2000, 0x8000
FAMILY = 0x7B3EF230
TAG_OTA_FORMAT_2 = 0x6C8492
TAG_BINPATCH = 0xB948DE
# OTA_PART_INFO index nibbles: device single, dual 1, dual 2, flasher single, dual 1, dual 2
DEVICE_SINGLE = b"\x10\x00\x00"
FLASHER_SINGLE = b"\x00\x01\x00"
DEVICE_DUAL_1 = b"\x01\x00\x00"
DEVICE_DUAL_2 = b"\x00\x10\x00"
def _tag(tag: int, value: bytes) -> bytes:
size = 4 + len(value)
return bytes([size]) + tag.to_bytes(3, "little") + value + bytes(-size % 4)
def _block(payload: bytes = b"", tags: dict[int, bytes] | None = None) -> bytes:
"""A block numbered 0 of 0; _package numbers it."""
flags = FLAG_FAMILY | (FLAG_TAGS if tags else 0)
header = struct.pack("<8I", MAGIC_1, MAGIC_2, flags, 0, len(payload), 0, 0, FAMILY)
body = payload + b"".join(_tag(t, v) for t, v in (tags or {}).items())
return header + body + bytes(512 - 32 - 4 - len(body)) + struct.pack("<I", MAGIC_3)
def _image(part_info: bytes, blocks: int, fill: int) -> list[bytes]:
"""An image: its first block names the partitions, the rest carry data."""
data = bytes([fill]) * 256
first = _block(data, {UF2_TAG_OTA_PART_INFO: part_info + b"part\x00"})
return [first] + [_block(data) for _ in range(blocks - 1)]
def _package(*blocks: bytes) -> bytes:
"""The blocks numbered in order, as a UF2 file."""
return b"".join(
block[:20] + struct.pack("<2I", seq, len(blocks)) + block[28:]
for seq, block in enumerate(blocks)
)
HEADER = _block(tags={TAG_OTA_FORMAT_2: b"\x02"})
FLASHER = _image(FLASHER_SINGLE, 5, 0xAA)
DEVICE = _image(DEVICE_SINGLE, 3, 0xBB)
SLOT_1 = _image(DEVICE_DUAL_1, 2, 0x11)
SLOT_2 = _image(DEVICE_DUAL_2, 2, 0x22)
PATCHED = _block(bytes([0xAA]) * 256, {TAG_BINPATCH: b"\x01\x02\x03\x04"})
@pytest.mark.parametrize(
("blocks", "expected"),
[
pytest.param([HEADER, *FLASHER, *DEVICE], [HEADER, *DEVICE], id="single_ota"),
pytest.param(
[HEADER, *SLOT_1, *FLASHER, *SLOT_2],
[HEADER, *SLOT_1, *SLOT_2],
id="dual_ota_keeps_both_slots",
),
pytest.param(
[HEADER, *FLASHER, PATCHED, *DEVICE],
None,
id="dropped_image_carries_a_tag_the_device_acts_on",
),
pytest.param([HEADER, *DEVICE], None, id="nothing_to_drop"),
],
)
def test_device_ota_uf2(blocks: list[bytes], expected: list[bytes] | None) -> None:
trimmed = device_ota_uf2(_package(*blocks))
assert trimmed == (_package(*expected) if expected is not None else None)
def test_data_that_is_not_a_well_formed_uf2_is_left_alone() -> None:
package = _package(HEADER, *FLASHER, *DEVICE)
bad_magic = package[:512] + b"\x00" + package[513:]
# A tag that runs past the data area of the first flasher block
at = 512 + 32 + 256
bad_tag = package[:at] + bytes([250]) + package[at + 1 :]
# A whole block missing, or repeated, keeps the original numbering
missing = package[:1024] + package[1536:]
repeated = package[:1536] + package[1024:]
# OTA_PART_INFO too short to hold the scheme map, which uf2ota rejects
short_selectors = [
_package(HEADER, _block(bytes(256), {UF2_TAG_OTA_PART_INFO: bytes(n)}), *DEVICE)
for n in range(3)
]
for data in (
b"",
bytes(range(256)) * 4,
package[:-1],
bad_magic,
bad_tag,
missing,
repeated,
*short_selectors,
):
assert device_ota_uf2(data) is None
def test_device_ota_image(setup_core: Path) -> None:
tmp_path = setup_core
CORE.build_path = tmp_path / "build"
CORE.name = "test"
firmware = tmp_path / "firmware.uf2"
firmware.write_bytes(_package(HEADER, *FLASHER, *DEVICE))
image = device_ota_image(firmware)
assert image == CORE.relative_pioenvs_path("test", "firmware.ota.uf2")
assert image.read_bytes() == _package(HEADER, *DEVICE)
# Nothing to drop: the firmware itself is sent
firmware.write_bytes(_package(HEADER, *DEVICE))
assert device_ota_image(firmware) == firmware
+44
View File
@@ -18,6 +18,7 @@ from esphome.__main__ import run_esphome
from esphome.compiled_config import (
_LAMBDA_KEY,
compiled_config_path,
invalidate_compiled_config,
load_compiled_config,
save_compiled_config,
save_compiled_config_and_sidecar,
@@ -937,3 +938,46 @@ def test_load_compiled_config_rejects_wizard_only_sidecar(
_set_cache_mtime(cache_path, yaml_path, offset=5)
assert load_compiled_config(yaml_path) is None
def test_invalidate_compiled_config_reports_an_unlink_failure(
tmp_path: Path,
) -> None:
"""A cache that cannot be removed would serve the old key later."""
CORE.config_path = tmp_path / "test.yaml"
cache = compiled_config_path("test.yaml")
cache.parent.mkdir(parents=True)
cache.write_text("{}")
with (
patch("pathlib.Path.unlink", side_effect=OSError("busy")),
pytest.raises(EsphomeError, match="validated config cache"),
):
invalidate_compiled_config()
def test_invalidate_compiled_config_drops_every_cache(tmp_path: Path) -> None:
"""Another configuration sharing the edited secrets.yaml or include has
a cache the mtime check would keep serving."""
CORE.config_path = tmp_path / "test.yaml"
storage = compiled_config_path("test.yaml").parent
storage.mkdir(parents=True)
for name in ("test.yaml", "other.yaml"):
(storage / f"{name}.validated.json").write_text("{}")
(storage / "other.json").write_text("{}")
invalidate_compiled_config()
assert sorted(p.name for p in storage.iterdir()) == ["other.json"]
def test_invalidate_compiled_config_reports_a_storage_dir_it_cannot_list(
tmp_path: Path,
) -> None:
CORE.config_path = tmp_path / "test.yaml"
storage = compiled_config_path("test.yaml").parent
storage.mkdir(parents=True)
with (
patch("pathlib.Path.iterdir", side_effect=OSError("denied")),
pytest.raises(EsphomeError, match="Could not list"),
):
invalidate_compiled_config()
storage.rmdir()
invalidate_compiled_config() # no storage dir yet is nothing to drop
+4 -25
View File
@@ -222,15 +222,9 @@ def test_api_client_does_not_import_heavy_modules() -> None:
# Packages a domain needs for its own schema; cheap and never an integration
_ENTITY_COMPONENT_DEPENDENCIES = {
"infrared": ("esphome.components.ir_rf_base",),
"light": ("esphome.components.power_supply", "esphome.components.const.css_colors"),
}
# Entity domains that still import integrations at top level
_ENTITY_COMPONENT_KNOWN_LEAKS = {
"infrared": "imports remote_base and api at top level",
"media_player": "audio pulls in the esp32 chain",
"radio_frequency": "imports remote_base and api at top level",
"radio_frequency": ("esphome.components.ir_rf_base",),
}
@@ -248,26 +242,11 @@ def _entity_components() -> list[str]:
def test_entity_component_tables_match_discovery() -> None:
"""Catch stale table entries and a discovery that silently finds nothing."""
found = set(_entity_components())
stale = (
_ENTITY_COMPONENT_KNOWN_LEAKS.keys() | _ENTITY_COMPONENT_DEPENDENCIES.keys()
) - found
stale = _ENTITY_COMPONENT_DEPENDENCIES.keys() - found
assert not stale, f"not discovered as entity components: {sorted(stale)}"
@pytest.mark.parametrize(
"component",
[
pytest.param(
c,
marks=pytest.mark.xfail(
reason=_ENTITY_COMPONENT_KNOWN_LEAKS[c], strict=True
),
)
if c in _ENTITY_COMPONENT_KNOWN_LEAKS
else c
for c in _entity_components()
],
)
@pytest.mark.parametrize("component", _entity_components())
def test_entity_component_does_not_import_integrations(component: str) -> None:
"""An entity component must not drag in its optional integrations.
+112
View File
@@ -2135,6 +2135,118 @@ def test_upload_program_ota_success(
)
@pytest.mark.parametrize(
("config", "uploader", "image_arg"),
[
pytest.param(
{CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 8892}]},
"mock_run_ota",
3,
id="native",
),
pytest.param(
{
CONF_OTA: [{CONF_PLATFORM: CONF_WEB_SERVER}],
CONF_WEB_SERVER: {CONF_PORT: 80},
},
"mock_run_web_server_ota",
4,
id="web_server",
),
],
)
def test_upload_program_ota_sends_the_platform_ota_image(
config: ConfigType,
uploader: str,
image_arg: int,
mock_get_port_type: Mock,
tmp_path: Path,
request: pytest.FixtureRequest,
) -> None:
"""An app update sends the file the platform's ota_image hook picks."""
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
mock_get_port_type.return_value = "NETWORK"
mock_upload = request.getfixturevalue(uploader)
mock_upload.return_value = (0, "192.168.1.100")
image = tmp_path / "firmware.ota.uf2"
with patch(
"esphome.components.bk72xx.ota_image", return_value=image
) as mock_ota_image:
exit_code, _ = upload_program(config, MockArgs(), ["192.168.1.100"])
assert exit_code == 0
mock_ota_image.assert_called_once_with(CORE.firmware_bin)
assert mock_upload.call_args.args[image_arg] == image
def test_upload_program_ota_file_goes_through_the_ota_image_hook(
mock_run_ota: Mock,
mock_get_port_type: Mock,
tmp_path: Path,
) -> None:
"""--file is the firmware the platform's ota_image hook starts from."""
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
mock_get_port_type.return_value = "NETWORK"
mock_run_ota.return_value = (0, "192.168.1.100")
other = tmp_path / "other.uf2"
image = tmp_path / "firmware.ota.uf2"
config = {CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 8892}]}
with patch(
"esphome.components.bk72xx.ota_image", return_value=image
) as mock_ota_image:
upload_program(config, MockArgs(file=str(other)), ["192.168.1.100"])
mock_ota_image.assert_called_once_with(other)
assert mock_run_ota.call_args.args[3] == image
def test_upload_program_ota_partition_table_skips_the_ota_image_hook(
mock_run_ota: Mock,
mock_get_port_type: Mock,
tmp_path: Path,
) -> None:
"""Only app updates go through the ota_image hook."""
setup_core(platform=PLATFORM_BK72XX, tmp_path=tmp_path)
mock_get_port_type.return_value = "NETWORK"
mock_run_ota.return_value = (0, "192.168.1.100")
partition_file = tmp_path / "partitions.bin"
partition_file.write_bytes(_make_partition_table_bytes())
config = {
CONF_OTA: [
{
CONF_PLATFORM: CONF_ESPHOME,
CONF_PORT: 8892,
"allow_partition_access": True,
}
]
}
args = MockArgs(file=str(partition_file), partition_table=True)
with patch("esphome.components.bk72xx.ota_image") as mock_ota_image:
upload_program(config, args, ["192.168.1.100"])
mock_ota_image.assert_not_called()
assert mock_run_ota.call_args.args[3] == partition_file
def test_upload_program_ota_without_a_hook_sends_the_firmware(
mock_run_ota: Mock,
mock_get_port_type: Mock,
tmp_path: Path,
) -> None:
"""A platform without an ota_image hook sends its build output."""
setup_core(platform=PLATFORM_ESP32, tmp_path=tmp_path)
mock_get_port_type.return_value = "NETWORK"
mock_run_ota.return_value = (0, "192.168.1.100")
config = {CONF_OTA: [{CONF_PLATFORM: CONF_ESPHOME, CONF_PORT: 3232}]}
upload_program(config, MockArgs(), ["192.168.1.100"])
assert mock_run_ota.call_args.args[3].name == "firmware.bin"
@pytest.mark.parametrize(
"old_key",
[None, "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA="],
+395 -7
View File
@@ -3,21 +3,32 @@
from __future__ import annotations
from pathlib import Path
import stat
import sys
from typing import Any
from unittest.mock import patch
import pytest
from esphome import yaml_util
from esphome.compiled_config import compiled_config_path
from esphome.config import do_substitution_pass
from esphome.const import CONF_ESPHOME, CONF_NAME
from esphome.core import CORE, EsphomeError
from esphome.yaml_edit import (
LineEdit,
RestoreError,
Snapshot,
apply_line_edits,
editable_file,
field_line_re,
line_at,
own_documents,
read_text,
restore_files,
rewrite,
rewritten_text,
source_line,
source_of,
write_keeping_mode,
)
@@ -98,13 +109,46 @@ def test_a_comment_needs_whitespace_and_a_scalar_is_not_empty() -> None:
assert field_line_re("key", "abc#def").match("key: abc#def") is not None
def test_source_of_is_none_for_a_value_validation_added(tmp_path: Path) -> None:
"""Only a key read from a file carries a range, and only a mapping read
from a file can place its keys; a mapping built in code cannot."""
def test_source_of_is_none_for_a_value_validation_added() -> None:
"""Only a key read from a file carries a range."""
assert source_of({"name": "kitchen"}, "name") is None
def test_a_mapping_built_in_code_places_only_this_configurations_keys(
tmp_path: Path,
) -> None:
"""merge_config rebuilds a mapping without a range; a value read for this
configuration is still placed, one read from elsewhere is not."""
_setup(tmp_path, YAML)
loaded_key = next(iter(CORE.raw_config[CONF_ESPHOME]))
assert source_of({loaded_key: "kitchen"}, CONF_NAME) is None
own_key, own_value = next(iter(CORE.raw_config[CONF_ESPHOME].items()))
assert source_of({own_key: own_value}, CONF_NAME) == (CORE.config_path, 1)
# Only the value names the line that won; without a range it is refused
assert source_of({own_key: "x"}, CONF_NAME) is None
(tmp_path / "elsewhere.yaml").write_bytes(YAML.encode())
other = yaml_util.load_yaml(tmp_path / "elsewhere.yaml")
other_key, other_value = next(iter(other[CONF_ESPHOME].items()))
assert source_of({other_key: other_value}, CONF_NAME) is None
# A value on a line of its own does not name the key's line
_setup(tmp_path, "esphome:\n name:\n kitchen\n")
own_key, own_value = next(iter(CORE.raw_config[CONF_ESPHOME].items()))
assert source_of({own_key: own_value}, CONF_NAME) is None
def test_own_documents_include_a_file_that_only_gives_substitutions(
tmp_path: Path,
) -> None:
(tmp_path / "subs.yaml").write_bytes(b"substitutions:\n x: y\n")
(tmp_path / "sensor.yaml").write_bytes(b"platform: template\nname: x\n")
_setup(
tmp_path,
YAML + "packages:\n subs: !include subs.yaml\nsensor:\n"
" - !include sensor.yaml\n",
)
assert (tmp_path / "sensor.yaml").resolve() in own_documents() # via a list
CORE.reset()
CORE.config_path = tmp_path / "test.yaml"
CORE.raw_config = {} # what is left after substitutions are taken out
assert (tmp_path / "subs.yaml").resolve() in own_documents()
def test_source_of_is_none_for_a_merged_key(tmp_path: Path) -> None:
@@ -152,8 +196,6 @@ def test_write_keeps_the_mode_of_the_file_or_another(tmp_path: Path) -> None:
def test_write_failures_say_which_step_and_why(tmp_path: Path) -> None:
"""A missing mode source, a write that fails, and a mode that cannot be
put back after the write are three different situations."""
from unittest.mock import patch
path = _setup(tmp_path, YAML)
with pytest.raises(EsphomeError, match="Could not read the mode of .*gone.yaml"):
write_keeping_mode(path, YAML, like=tmp_path / "gone.yaml")
@@ -180,3 +222,349 @@ def test_read_text_reports_a_file_it_cannot_decode(tmp_path: Path) -> None:
path.write_bytes(b"caf\xe9: 1\n")
with pytest.raises(EsphomeError, match="Error reading file"):
read_text(path)
def test_source_line_refuses_what_is_not_an_editable_file(tmp_path: Path) -> None:
"""A remote package is checked out under the build data and is not the
user's; a file outside the configuration directory is not either."""
(tmp_path / ".esphome").mkdir()
(tmp_path / ".esphome" / "base.yaml").write_bytes(b"name: kitchen\n")
_setup(tmp_path, "esphome: !include .esphome/base.yaml\n")
with pytest.raises(EsphomeError, match="not an editable file"):
source_line(CORE.raw_config[CONF_ESPHOME], CONF_NAME)
with pytest.raises(EsphomeError, match="was not read from a file"):
source_line({"name": "kitchen"}, CONF_NAME)
_setup(tmp_path, YAML)
assert source_line(CORE.raw_config[CONF_ESPHOME], CONF_NAME) == (
CORE.config_path,
1,
" name: kitchen # the device",
)
outside = tmp_path.parent / "outside.yaml"
outside.write_bytes(b"")
with pytest.raises(EsphomeError, match="not an editable file"):
editable_file(outside)
(tmp_path / "link.yaml").symlink_to(CORE.config_path)
assert editable_file(tmp_path / "link.yaml") == CORE.config_path.resolve()
def test_insert_after_goes_before_the_rewrite_of_the_same_line(
tmp_path: Path,
) -> None:
"""Edits apply from the bottom up so an insertion never moves a later
line; the file's own line ending is used, also on a bare last line."""
text = YAML.replace("\n", "\r\n").rstrip("\r\n")
path = _setup(tmp_path, text)
edit = _name_edit("garage")
added = LineEdit(path, 4, " ssid: kitchen", " password: x", insert_after=True)
more = LineEdit(
path, 4, " ssid: kitchen", " fast_connect: true", insert_after=True
)
assert rewritten_text(read_text(path), [added, more, edit]) == (
text.replace("name: kitchen", "name: garage")
+ "\r\n password: x\r\n fast_connect: true"
)
edit.old_line = "changed"
with pytest.raises(EsphomeError, match="changed since it was read"):
rewritten_text(read_text(path), [added, edit])
def test_own_documents_refuses_an_incomplete_set(tmp_path: Path) -> None:
_setup(tmp_path, YAML)
found = yaml_util.DiscoveredYamlFiles(load_errors=["sub.yaml: broken"])
with (
patch("esphome.yaml_util.discover_user_yaml_files", return_value=found),
pytest.raises(EsphomeError, match="Could not read every file .*sub.yaml"),
):
own_documents()
def test_apply_keeps_the_secret_registry(tmp_path: Path) -> None:
"""The reload must not clear what later output redacts as !secret."""
(tmp_path / "secrets.yaml").write_bytes(b"ssid: hidden\n")
path = _setup(tmp_path, YAML.replace("ssid: kitchen", "ssid: !secret ssid"))
assert yaml_util.is_secret("hidden") == "ssid"
apply_line_edits([_name_edit("garage")])
assert yaml_util.is_secret("hidden") == "ssid"
assert "!secret ssid" in path.read_text()
def test_apply_writes_reloads_drops_the_cache_and_restores(tmp_path: Path) -> None:
path = _setup(tmp_path, YAML)
cache = compiled_config_path(CORE.config_filename)
cache.parent.mkdir(parents=True, exist_ok=True)
cache.write_text("{}")
mode = stat.S_IMODE(path.stat().st_mode)
originals = apply_line_edits([_name_edit("garage")])
assert originals == {
path: Snapshot(YAML, YAML.replace("kitchen #", "garage #"), mode)
}
assert path.read_text() == YAML.replace("kitchen #", "garage #")
assert not cache.exists()
cache.write_text("{}")
restore_files(originals)
assert path.read_text() == YAML
assert not cache.exists()
def test_apply_reloads_a_symlinked_file_by_the_link(tmp_path: Path) -> None:
"""A relative !include inside the file resolves beside the link, as the
loader read it, not beside the target."""
shared = tmp_path / "shared"
shared.mkdir()
(shared / "device.yaml").write_bytes(
b"name: kitchen\nfriendly_name: !include fn.yaml\n"
)
(tmp_path / "fn.yaml").write_bytes(b"Kitchen\n") # beside the link only
(tmp_path / "device.yaml").symlink_to(shared / "device.yaml")
_setup(tmp_path, "esphome: !include device.yaml\n")
doc, line_no = source_of(CORE.raw_config[CONF_ESPHOME], CONF_NAME)
assert doc == tmp_path / "device.yaml"
match = field_line_re(CONF_NAME, "kitchen").match(line_at(doc, line_no))
edit = LineEdit(doc, line_no, match.string, rewrite(match, "garage"))
originals = apply_line_edits([edit])
assert list(originals) == [(shared / "device.yaml").resolve()]
assert (shared / "device.yaml").read_text() == (
"name: garage\nfriendly_name: !include fn.yaml\n"
)
def test_apply_rolls_back_a_rewrite_the_loader_refuses(tmp_path: Path) -> None:
path = _setup(tmp_path, YAML)
edit = _name_edit("garage")
edit.new_line = " name: [unterminated"
with pytest.raises(EsphomeError, match="no longer loads"):
apply_line_edits([edit])
assert path.read_text() == YAML
with (
patch("esphome.yaml_util.load_yaml", side_effect=KeyboardInterrupt),
pytest.raises(KeyboardInterrupt),
):
apply_line_edits([_name_edit("garage")])
assert path.read_text() == YAML
def test_restore_reports_every_file_it_could_not_write(tmp_path: Path) -> None:
"""A missing file, a failed write and a cache that cannot be dropped are
all reported; a file the user changed meanwhile is left alone."""
path = _setup(tmp_path, YAML)
good = tmp_path / "a.yaml"
(tmp_path / "b.yaml").write_bytes(b"")
good.write_bytes(b"")
with (
patch("esphome.yaml_edit.write_file", side_effect=[EsphomeError("disk"), None]),
pytest.raises(EsphomeError, match="Could not restore .*b.yaml: disk"),
):
restore_files({tmp_path / "b.yaml": Snapshot("x", ""), good: Snapshot("y", "")})
with pytest.raises(
EsphomeError, match="Could not restore .*gone.yaml: Error reading file"
):
restore_files({tmp_path / "gone.yaml": Snapshot("x", "x")})
with (
patch(
"esphome.compiled_config.invalidate_compiled_config",
side_effect=EsphomeError("busy"),
),
pytest.raises(
EsphomeError, match="Could not restore the validated caches: busy"
),
):
restore_files({path: Snapshot(YAML, YAML)})
originals = apply_line_edits([_name_edit("garage")])
edited = path.read_text() + "logger:\n"
path.write_bytes(edited.encode())
with pytest.raises(EsphomeError, match="changed since it was written, left as is"):
restore_files(originals)
assert path.read_text() == edited
def test_apply_rolls_back_when_the_cache_cannot_be_dropped(tmp_path: Path) -> None:
path = _setup(tmp_path, YAML)
with (
patch(
"esphome.compiled_config.invalidate_compiled_config",
side_effect=[EsphomeError("busy"), None],
),
pytest.raises(EsphomeError, match="busy"),
):
apply_line_edits([_name_edit("garage")])
assert path.read_text() == YAML
def test_apply_reports_a_rollback_that_also_failed(tmp_path: Path) -> None:
"""The rewrite lands, the reload fails, and the rollback write fails too."""
from esphome.yaml_edit import write_file
_setup(tmp_path, YAML)
writes: list[int] = []
def write_then_fail(*args: Any, **kwargs: Any) -> None:
writes.append(1)
if len(writes) > 1:
raise EsphomeError("disk")
write_file(*args, **kwargs)
with (
patch("esphome.yaml_util.load_yaml", side_effect=EsphomeError("broken")),
patch("esphome.yaml_edit.write_file", side_effect=write_then_fail),
pytest.raises(EsphomeError, match="broken; Could not restore .*disk"),
):
apply_line_edits([_name_edit("garage")])
def test_a_failed_rollback_keeps_an_interrupt_an_interrupt(tmp_path: Path) -> None:
_setup(tmp_path, YAML)
with (
patch("esphome.yaml_util.load_yaml", side_effect=KeyboardInterrupt),
patch("pathlib.Path.chmod", side_effect=[None, OSError("disk")]),
pytest.raises(KeyboardInterrupt) as info,
):
apply_line_edits([_name_edit("garage")])
assert "disk" in "".join(info.value.__notes__)
def test_rollback_after_a_failed_second_write_leaves_the_untouched_file_alone(
tmp_path: Path,
) -> None:
"""The file that was never written is not reported as changed."""
path = _setup(tmp_path, YAML)
other = tmp_path / "other.yaml"
other.write_bytes(b"x: 1\n")
edits = [_name_edit("garage"), LineEdit(other, 0, "x: 1", "x: 2")]
with (
patch(
"esphome.yaml_edit.write_keeping_mode",
side_effect=[None, EsphomeError("disk")],
),
pytest.raises(EsphomeError) as info,
):
apply_line_edits(edits)
assert "changed since it was written" not in str(info.value)
assert path.read_text() == YAML and other.read_text() == "x: 1\n"
def test_restore_goes_on_after_an_interrupt(tmp_path: Path) -> None:
"""A second Ctrl-C must not leave the other files holding the new value."""
path = _setup(tmp_path, YAML)
other = tmp_path / "other.yaml"
other.write_bytes(b"x: 1\n")
calls: list[Path] = []
def interrupt_at(n: int) -> Any:
def write(target: Path, text: str, **kwargs: Any) -> None:
calls.append(target)
if len(calls) == n:
raise KeyboardInterrupt
write_keeping_mode(target, text, **kwargs)
return write
originals = apply_line_edits(
[_name_edit("garage"), LineEdit(other, 0, "x: 1", "x: 2")]
)
with (
patch("esphome.yaml_edit.write_keeping_mode", side_effect=interrupt_at(1)),
pytest.raises(KeyboardInterrupt) as info,
):
restore_files(originals)
assert calls == [path, other] and other.read_text() == "x: 1\n"
assert f"Could not restore {path}: interrupted" in "".join(info.value.__notes__)
# The interrupt stays one inside apply as well
_setup(tmp_path, YAML)
calls.clear()
with (
patch("esphome.yaml_util.load_yaml", side_effect=EsphomeError("broken")),
patch("esphome.yaml_edit.write_keeping_mode", side_effect=interrupt_at(2)),
pytest.raises(KeyboardInterrupt) as info,
):
apply_line_edits([_name_edit("garage")])
assert "Could not restore" in "".join(info.value.__notes__)
def test_restore_finishes_after_any_failure(tmp_path: Path) -> None:
"""A raw OSError still leaves the other files put back, and an interrupt
while dropping the caches carries the report."""
path = _setup(tmp_path, YAML)
other = tmp_path / "other.yaml"
other.write_bytes(b"x: 1\n")
originals = apply_line_edits(
[_name_edit("garage"), LineEdit(other, 0, "x: 1", "x: 2")]
)
writes: list[Path] = []
def fail_first(target: Path, text: str, **kwargs: Any) -> None:
writes.append(target)
if len(writes) == 1:
raise OSError("odd")
write_keeping_mode(target, text, **kwargs)
with (
patch("esphome.yaml_edit.write_keeping_mode", side_effect=fail_first),
pytest.raises(RestoreError) as info,
):
restore_files(originals)
assert f"Could not restore {path}: odd" in str(
info.value
) # a Windows path is no regex
assert "garage" in path.read_text() and other.read_text() == "x: 1\n"
with (
patch(
"esphome.compiled_config.invalidate_compiled_config",
side_effect=KeyboardInterrupt,
),
pytest.raises(KeyboardInterrupt) as info,
):
restore_files(originals)
assert path.read_text() == YAML
assert "the validated caches: interrupted" in "".join(info.value.__notes__)
@pytest.mark.skipif(sys.platform == "win32", reason="posix file modes")
def test_restore_puts_back_the_mode_from_before_the_edit(tmp_path: Path) -> None:
"""A write whose chmod failed leaves the file 0600; the restore does not
copy that, it has the mode from before."""
path = _setup(tmp_path, YAML)
path.chmod(0o644)
originals = apply_line_edits([_name_edit("garage")])
path.chmod(0o600)
restore_files(originals)
assert path.read_text() == YAML
assert path.stat().st_mode & 0o777 == 0o644
def test_restore_finishes_after_an_exit(tmp_path: Path) -> None:
"""A SystemExit from a signal handler still leaves the other files put
back and comes out again with the report."""
path = _setup(tmp_path, YAML)
other = tmp_path / "other.yaml"
other.write_bytes(b"x: 1\n")
originals = apply_line_edits(
[_name_edit("garage"), LineEdit(other, 0, "x: 1", "x: 2")]
)
writes: list[Path] = []
def exit_first(target: Path, text: str, **kwargs: Any) -> None:
writes.append(target)
if len(writes) == 1:
raise SystemExit(1)
write_keeping_mode(target, text, **kwargs)
with (
patch("esphome.yaml_edit.write_keeping_mode", side_effect=exit_first),
pytest.raises(SystemExit) as info,
):
restore_files(originals)
assert other.read_text() == "x: 1\n"
assert f"Could not restore {path}: SystemExit" in "".join(info.value.__notes__)
def test_a_value_written_to_the_secrets_file_is_registered(tmp_path: Path) -> None:
"""Only a resolved !secret registers its value, so the main file is
loaded again after an edit elsewhere; own_documents keeps the registry."""
secrets = tmp_path / "secrets.yaml"
secrets.write_bytes(b"ssid: hidden\n")
_setup(tmp_path, YAML.replace("ssid: kitchen", "ssid: !secret ssid"))
own_documents()
assert yaml_util.is_secret("hidden") == "ssid"
apply_line_edits([LineEdit(secrets, 0, "ssid: hidden", "ssid: hidden2")])
assert yaml_util.is_secret("hidden2") == "ssid"