mirror of
https://github.com/esphome/esphome.git
synced 2026-10-15 10:42:52 +00:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
091afaad0c | ||
|
|
2ea993ef23 | ||
|
|
4db1124a38 | ||
|
|
1532c11a0a | ||
|
|
4e3a9e055e | ||
|
|
3848e8e4de | ||
|
|
ac91ad5b47 | ||
|
|
2304594c6c | ||
|
|
029d4bdce2 | ||
|
|
5110f51f87 | ||
|
|
ed21d15389 | ||
|
|
0db1c0376a | ||
|
|
e7bf815b36 | ||
|
|
3520561282 | ||
|
|
ff9a26c814 | ||
|
|
0a8e31c18d | ||
|
|
c5e320a977 | ||
|
|
7a7268da23 | ||
|
|
8004bfb184 | ||
|
|
de1616d0cc | ||
|
|
c3da1af20e | ||
|
|
d61e4ca85b | ||
|
|
8151e61bf3 | ||
|
|
e693c48086 | ||
|
|
66be2805e4 | ||
|
|
c9fb996758 | ||
|
|
6aa6b682df | ||
|
|
5cc758dea3 | ||
|
|
bf718a28b9 | ||
|
|
a595590386 | ||
|
|
a0dc5a8d14 | ||
|
|
edec6aaf5e | ||
|
|
55804e6e16 | ||
|
|
228f8894a9 | ||
|
|
866574ca14 | ||
|
|
5747c736c2 | ||
|
|
0f6c266cd7 | ||
|
|
05dbc5ee59 | ||
|
|
29f7439154 | ||
|
|
89cd183a9f | ||
|
|
370cfb8898 |
@@ -13,6 +13,7 @@ from esphome.components.logger import request_log_listener
|
||||
from esphome.components.noise import ( # noqa: F401
|
||||
ENCRYPTION_SCHEMA,
|
||||
decode_encryption_key,
|
||||
enable_spare_ephemeral,
|
||||
encryption_schema,
|
||||
new_psk_progmem,
|
||||
validate_encryption_key,
|
||||
@@ -614,6 +615,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
# and plaintext disabled. Only a factory reset can remove it.
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
enable_spare_ephemeral()
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
|
||||
@@ -317,9 +317,16 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg);
|
||||
#endif
|
||||
|
||||
// How long a new connection holds off the spare ephemeral refill
|
||||
static constexpr uint32_t CONNECT_GRACE_MS = 1000;
|
||||
bool is_authenticated() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
|
||||
}
|
||||
// Unauthenticated and within its grace period; an older unauthenticated
|
||||
// connection is a stale half open client and no longer counts
|
||||
bool is_still_connecting(uint32_t now) {
|
||||
return !this->is_authenticated() && now - this->last_traffic_ < CONNECT_GRACE_MS;
|
||||
}
|
||||
bool is_connection_setup() {
|
||||
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
|
||||
this->is_authenticated();
|
||||
|
||||
@@ -165,6 +165,15 @@ void APIServer::loop() {
|
||||
this->accept_new_connections_();
|
||||
}
|
||||
|
||||
// Checked once per pass for the refill and for the clients below
|
||||
const bool connected = network::is_connected();
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
// Only the flag test is inline; refilling is the rare path
|
||||
if (connected && !noise::has_spare_ephemeral()) {
|
||||
this->refill_spare_ephemeral_();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->api_connection_count_ == 0) {
|
||||
// Check reboot timeout - done in loop to avoid scheduler heap churn
|
||||
// (cancelled scheduler items sit in heap memory until their scheduled time).
|
||||
@@ -181,8 +190,7 @@ void APIServer::loop() {
|
||||
}
|
||||
|
||||
// Process clients and remove disconnected ones in a single pass
|
||||
// Check network connectivity once for all clients
|
||||
if (!network::is_connected()) {
|
||||
if (!connected) {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->active_clients()) {
|
||||
client->on_fatal_error();
|
||||
@@ -210,6 +218,21 @@ void APIServer::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
// Called with the network up; refill only while no api client is still
|
||||
// connecting (an OTA handshake is not visible here and just pays the refill
|
||||
// it triggered).
|
||||
void APIServer::refill_spare_ephemeral_() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
for (auto &client : this->active_clients()) {
|
||||
if (client->is_still_connecting(now)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
noise::prepare_spare_ephemeral();
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServer::remove_client_(uint8_t client_index) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "api_buffer.h"
|
||||
// Must precede clients_ so APIConnection is complete for default_delete (libc++).
|
||||
#include "api_connection.h"
|
||||
#ifdef USE_API_NOISE
|
||||
#if defined(USE_API_NOISE) || defined(USE_NOISE_SPARE_EPHEMERAL)
|
||||
// Only present in the build when the noise component is loaded
|
||||
#include "esphome/components/noise/noise.h"
|
||||
#endif
|
||||
@@ -368,6 +368,9 @@ class APIServer final : public Component,
|
||||
uint8_t provisioning_source_{0};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
void refill_spare_ephemeral_();
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
noise::NoiseContext noise_ctx_;
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
|
||||
@@ -92,6 +92,11 @@ def encryption_schema(config: ConfigType | None) -> ConfigType:
|
||||
return ENCRYPTION_SCHEMA(config)
|
||||
|
||||
|
||||
def enable_spare_ephemeral() -> None:
|
||||
"""Compile the spare ephemeral key slot; the component that refills it calls this."""
|
||||
cg.add_define("USE_NOISE_SPARE_EPHEMERAL")
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.30")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "noise.h"
|
||||
#ifdef USE_NOISE
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
@@ -8,6 +9,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include <noise/protocol.h>
|
||||
#include <sodium.h>
|
||||
|
||||
namespace esphome::noise {
|
||||
|
||||
@@ -21,6 +23,39 @@ void NoiseContext::load_psk(psk_t &out) const {
|
||||
progmem_memcpy(out.data(), this->psk_, out.size());
|
||||
}
|
||||
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
static constexpr size_t PRIVATE_KEY_SIZE = SPARE_EPHEMERAL_KEY_SIZE;
|
||||
static constexpr size_t PUBLIC_KEY_SIZE = SPARE_EPHEMERAL_KEY_SIZE;
|
||||
uint8_t spare_ephemeral[SPARE_EPHEMERAL_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
void prepare_spare_ephemeral() {
|
||||
uint8_t *private_key = spare_ephemeral;
|
||||
uint8_t *public_key = spare_ephemeral + PRIVATE_KEY_SIZE;
|
||||
// Same steps as noise-c's curve25519 keygen; the clamp sets the ready bit,
|
||||
// a failure wipes the slot so the handshake generates its own key
|
||||
if (!random_bytes(private_key, PRIVATE_KEY_SIZE)) {
|
||||
sodium_memzero(spare_ephemeral, sizeof(spare_ephemeral));
|
||||
return;
|
||||
}
|
||||
private_key[0] &= 0xF8;
|
||||
private_key[PRIVATE_KEY_SIZE - 1] = (private_key[PRIVATE_KEY_SIZE - 1] & 0x7F) | 0x40;
|
||||
if (crypto_scalarmult_curve25519_base(public_key, private_key) != 0) {
|
||||
sodium_memzero(spare_ephemeral, sizeof(spare_ephemeral));
|
||||
}
|
||||
}
|
||||
|
||||
int consume_spare_ephemeral(NoiseHandshakeState *state) {
|
||||
if (!has_spare_ephemeral()) {
|
||||
return 0;
|
||||
}
|
||||
// noise-c keeps its own copy, so the slot is wiped either way
|
||||
int err = noise_handshakestate_set_local_ephemeral(state, spare_ephemeral, PRIVATE_KEY_SIZE,
|
||||
spare_ephemeral + PRIVATE_KEY_SIZE, PUBLIC_KEY_SIZE);
|
||||
sodium_memzero(spare_ephemeral, sizeof(spare_ephemeral));
|
||||
return err;
|
||||
}
|
||||
#endif // USE_NOISE_SPARE_EPHEMERAL
|
||||
|
||||
const LogString *noise_err_to_logstr(int err) {
|
||||
if (err == NOISE_ERROR_NO_MEMORY)
|
||||
return LOG_STR("NO_MEMORY");
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// noise-c handshake state; the full definition lives in <noise/protocol.h>
|
||||
using NoiseHandshakeState = struct NoiseHandshakeState_s;
|
||||
|
||||
namespace esphome::noise {
|
||||
|
||||
using psk_t = std::array<uint8_t, 32>;
|
||||
@@ -38,6 +41,26 @@ class NoiseContext {
|
||||
/// Convert a noise error code to a readable error
|
||||
const LogString *noise_err_to_logstr(int err);
|
||||
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
// One responder ephemeral key pair generated ahead of time (about 60 ms on
|
||||
// ESP8266), refilled by the api server while idle and consumed by the next
|
||||
// handshake of any noise transport; an empty slot means the handshake
|
||||
// generates its own key. The private key stays in RAM until consumed; it is
|
||||
// not wiped on shutdown.
|
||||
// Private key then public key; zero when empty
|
||||
static constexpr size_t SPARE_EPHEMERAL_KEY_SIZE = 32;
|
||||
static constexpr size_t SPARE_EPHEMERAL_SIZE = 2 * SPARE_EPHEMERAL_KEY_SIZE;
|
||||
extern uint8_t spare_ephemeral[SPARE_EPHEMERAL_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
// Polled every api loop tick, so it must inline. A clamped X25519 private key
|
||||
// always has bit 254 set, so that byte doubles as the ready flag.
|
||||
inline bool has_spare_ephemeral() { return (spare_ephemeral[SPARE_EPHEMERAL_KEY_SIZE - 1] & 0x40) != 0; }
|
||||
/// Fill the slot; blocks for the base point multiply
|
||||
void prepare_spare_ephemeral();
|
||||
/// Hand the slot's key pair to a handshake that has not started and wipe the
|
||||
/// slot; 0 when the slot was empty or the key was taken, else the noise-c error
|
||||
int consume_spare_ephemeral(NoiseHandshakeState *state);
|
||||
#endif
|
||||
|
||||
// Shared wire format for the noise transports (api and ota): every frame is
|
||||
// FRAME_INDICATOR, a 16-bit big-endian payload length, then the payload.
|
||||
// Handshake payloads start with a status byte; transport payloads end with
|
||||
|
||||
@@ -8,6 +8,24 @@ namespace esphome::noise {
|
||||
|
||||
static const char *const TAG = "noise";
|
||||
|
||||
// Test branch only: a copy of CpuFrequencyBoost from esphome/core/helpers.h so this
|
||||
// component works through external_components against a dev that lacks it.
|
||||
#if defined(USE_ESP8266) && F_CPU != 160000000L
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
extern "C" bool system_update_cpu_freq(uint8_t freq);
|
||||
class HandshakeCpuBoost {
|
||||
public:
|
||||
HandshakeCpuBoost() { system_update_cpu_freq(160); }
|
||||
~HandshakeCpuBoost() { system_update_cpu_freq(80); }
|
||||
};
|
||||
#else
|
||||
class HandshakeCpuBoost {
|
||||
public:
|
||||
HandshakeCpuBoost() {}
|
||||
~HandshakeCpuBoost() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
// Log the failing noise-c call at the same verbosity the api helper used
|
||||
// before this class existed; callers only see one collapsed error code.
|
||||
#define HANDSHAKE_STEP_LOG(func_name, err_code) \
|
||||
@@ -57,6 +75,13 @@ int NoiseResponderHandshake::init(const NoiseContext &ctx, const uint8_t *prolog
|
||||
HANDSHAKE_STEP_LOG("noise_handshakestate_set_prologue", err);
|
||||
return this->fail_init_(err);
|
||||
}
|
||||
#ifdef USE_NOISE_SPARE_EPHEMERAL
|
||||
err = consume_spare_ephemeral(this->handshake_);
|
||||
// Not fatal: the handshake generates its own key instead
|
||||
if (err != 0) {
|
||||
HANDSHAKE_STEP_LOG("noise_handshakestate_set_local_ephemeral", err);
|
||||
}
|
||||
#endif
|
||||
err = noise_handshakestate_start(this->handshake_);
|
||||
if (err != 0) {
|
||||
HANDSHAKE_STEP_LOG("noise_handshakestate_start", err);
|
||||
@@ -95,6 +120,7 @@ NoiseResponderHandshake::Action NoiseResponderHandshake::action() const {
|
||||
}
|
||||
|
||||
int NoiseResponderHandshake::read_message(uint8_t *data, size_t len) {
|
||||
HandshakeCpuBoost boost;
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_input(mbuf, data, len);
|
||||
@@ -103,6 +129,7 @@ int NoiseResponderHandshake::read_message(uint8_t *data, size_t len) {
|
||||
|
||||
int NoiseResponderHandshake::write_message(uint8_t *out, size_t capacity, size_t &out_len) {
|
||||
out_len = 0;
|
||||
HandshakeCpuBoost boost;
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_output(mbuf, out, capacity);
|
||||
|
||||
@@ -37,7 +37,8 @@ class NoiseResponderHandshake {
|
||||
NoiseResponderHandshake &operator=(const NoiseResponderHandshake &) = delete;
|
||||
|
||||
/// Create and start the handshake with the context's PSK and the prologue.
|
||||
/// A repeated call frees the previous handshake state and starts over.
|
||||
/// A repeated call frees the previous handshake state and starts over. A
|
||||
/// spare ephemeral key, when one is ready, is used instead of generating.
|
||||
[[nodiscard]] int init(const NoiseContext &ctx, const uint8_t *prologue, size_t prologue_len);
|
||||
/// ACTION_FAILED is the catch-all: returned before init(), after split()
|
||||
/// has released the state, and when noise-c reports a failed handshake.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#ifdef USE_ESP8266
|
||||
#include "uart_component_esp8266.h"
|
||||
#include <esp8266_peri.h>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -251,23 +252,35 @@ void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_p
|
||||
gpio_rx_pin_->attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
}
|
||||
// A byte can arrive while a CpuFrequencyBoost has an 80 MHz build at 160 MHz; the clock select bit doubles
|
||||
// the bit time then. The whole byte is read inside the ISR, so the clock cannot change partway through.
|
||||
__attribute__((always_inline)) static inline uint32_t rx_bit_time(uint32_t bit_time) {
|
||||
#if F_CPU != 160000000L
|
||||
// NOLINTNEXTLINE(clang-analyzer-core.FixedAddressDereference) -- CPU2X is MMIO at a fixed address
|
||||
return bit_time << (CPU2X & 1);
|
||||
#else
|
||||
return bit_time;
|
||||
#endif
|
||||
}
|
||||
|
||||
void IRAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) {
|
||||
uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500;
|
||||
const uint32_t bit_time = rx_bit_time(arg->bit_time_);
|
||||
uint32_t wait = bit_time + bit_time / 3 - 500;
|
||||
const uint32_t start = arch_get_cpu_cycle_count();
|
||||
uint8_t rec = 0;
|
||||
// Manually unroll the loop
|
||||
for (int i = 0; i < arg->data_bits_; i++)
|
||||
rec |= arg->read_bit_(&wait, start) << i;
|
||||
rec |= arg->read_bit_(&wait, start, bit_time) << i;
|
||||
|
||||
/* If parity is enabled, just read it and ignore it. */
|
||||
/* TODO: Should we check parity? Or is it too slow for nothing added..*/
|
||||
if (arg->parity_ == UART_CONFIG_PARITY_EVEN || arg->parity_ == UART_CONFIG_PARITY_ODD)
|
||||
arg->read_bit_(&wait, start);
|
||||
arg->read_bit_(&wait, start, bit_time);
|
||||
|
||||
// Stop bit
|
||||
arg->wait_(&wait, start);
|
||||
arg->wait_(&wait, start, bit_time);
|
||||
if (arg->stop_bits_ == 2)
|
||||
arg->wait_(&wait, start);
|
||||
arg->wait_(&wait, start, bit_time);
|
||||
|
||||
arg->rx_buffer_[arg->rx_in_pos_] = rec;
|
||||
arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_;
|
||||
@@ -296,37 +309,39 @@ void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
|
||||
}
|
||||
|
||||
{
|
||||
// Transmit runs from the main loop and never overlaps a CpuFrequencyBoost
|
||||
InterruptLock lock;
|
||||
uint32_t wait = this->bit_time_;
|
||||
const uint32_t bit_time = this->bit_time_;
|
||||
uint32_t wait = bit_time;
|
||||
const uint32_t start = arch_get_cpu_cycle_count();
|
||||
// Start bit
|
||||
this->write_bit_(false, &wait, start);
|
||||
this->write_bit_(false, &wait, start, bit_time);
|
||||
for (int i = 0; i < this->data_bits_; i++) {
|
||||
bool bit = data & (1 << i);
|
||||
this->write_bit_(bit, &wait, start);
|
||||
this->write_bit_(bit, &wait, start, bit_time);
|
||||
if (need_parity_bit)
|
||||
parity_bit ^= bit;
|
||||
}
|
||||
if (need_parity_bit)
|
||||
this->write_bit_(parity_bit, &wait, start);
|
||||
this->write_bit_(parity_bit, &wait, start, bit_time);
|
||||
// Stop bit
|
||||
this->write_bit_(true, &wait, start);
|
||||
this->write_bit_(true, &wait, start, bit_time);
|
||||
if (this->stop_bits_ == 2)
|
||||
this->wait_(&wait, start);
|
||||
this->wait_(&wait, start, bit_time);
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) {
|
||||
void IRAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
|
||||
while (arch_get_cpu_cycle_count() - start < *wait)
|
||||
;
|
||||
*wait += this->bit_time_;
|
||||
*wait += bit_time;
|
||||
}
|
||||
bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start) {
|
||||
this->wait_(wait, start);
|
||||
bool IRAM_ATTR ESP8266SoftwareSerial::read_bit_(uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
|
||||
this->wait_(wait, start, bit_time);
|
||||
return this->rx_pin_.digital_read();
|
||||
}
|
||||
void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start) {
|
||||
void IRAM_ATTR ESP8266SoftwareSerial::write_bit_(bool bit, uint32_t *wait, const uint32_t &start, uint32_t bit_time) {
|
||||
this->tx_pin_.digital_write(bit);
|
||||
this->wait_(wait, start);
|
||||
this->wait_(wait, start, bit_time);
|
||||
}
|
||||
uint8_t ESP8266SoftwareSerial::read_byte() {
|
||||
if (this->rx_in_pos_ == this->rx_out_pos_)
|
||||
|
||||
@@ -28,9 +28,9 @@ class ESP8266SoftwareSerial {
|
||||
protected:
|
||||
static void gpio_intr(ESP8266SoftwareSerial *arg);
|
||||
|
||||
void wait_(uint32_t *wait, const uint32_t &start);
|
||||
bool read_bit_(uint32_t *wait, const uint32_t &start);
|
||||
void write_bit_(bool bit, uint32_t *wait, const uint32_t &start);
|
||||
void wait_(uint32_t *wait, const uint32_t &start, uint32_t bit_time);
|
||||
bool read_bit_(uint32_t *wait, const uint32_t &start, uint32_t bit_time);
|
||||
void write_bit_(bool bit, uint32_t *wait, const uint32_t &start, uint32_t bit_time);
|
||||
|
||||
uint32_t bit_time_{0};
|
||||
uint8_t *rx_buffer_{nullptr};
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
#define USE_IMPROV_NEXT_URL
|
||||
#define USE_MD5
|
||||
#define USE_NOISE
|
||||
#define USE_NOISE_SPARE_EPHEMERAL
|
||||
#define USE_SHA256
|
||||
#ifndef USE_RP2 // no MQTT backend or esp_wireguard library on RP2
|
||||
#define USE_MQTT
|
||||
|
||||
@@ -2066,6 +2066,32 @@ class LwIPLock {
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(USE_ESP8266) && F_CPU != 160000000L
|
||||
// Forward decl from <user_interface.h>
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
extern "C" bool system_update_cpu_freq(uint8_t freq);
|
||||
#endif
|
||||
|
||||
/** Runs the CPU at 160 MHz while alive. ESP8266 built for 80 MHz only; elsewhere it compiles to nothing.
|
||||
*
|
||||
* The core resets the clock before every loop() pass, so a scope must stay within one pass, must not nest and
|
||||
* must not yield to the main loop. Peripheral clocks are unchanged, but the cycle counter runs twice as fast, so
|
||||
* code that times itself against F_CPU, including ISRs that fire while a scope is open, must read CPU2X.
|
||||
*/
|
||||
class CpuFrequencyBoost {
|
||||
public:
|
||||
CpuFrequencyBoost(const CpuFrequencyBoost &) = delete;
|
||||
CpuFrequencyBoost &operator=(const CpuFrequencyBoost &) = delete;
|
||||
#if defined(USE_ESP8266) && F_CPU != 160000000L
|
||||
CpuFrequencyBoost() { system_update_cpu_freq(160); }
|
||||
~CpuFrequencyBoost() { system_update_cpu_freq(80); }
|
||||
#else
|
||||
// Not = default, so clang-tidy does not flag unused variables at call sites
|
||||
CpuFrequencyBoost() {}
|
||||
~CpuFrequencyBoost() {}
|
||||
#endif
|
||||
};
|
||||
|
||||
/** Helper class to request `loop()` to be called as fast as possible.
|
||||
*
|
||||
* Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import esphome.codegen as cg
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
@@ -5,3 +6,10 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# to_code must run: it defines USE_NOISE and adds the noise-c library
|
||||
# the component sources under test need.
|
||||
manifest.enable_codegen()
|
||||
real_to_code = manifest.to_code
|
||||
|
||||
async def to_code_testing(config):
|
||||
await real_to_code(config)
|
||||
cg.add_define("USE_NOISE_SPARE_EPHEMERAL")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
|
||||
@@ -157,6 +157,74 @@ TEST(NoiseResponderHandshakeTest, FullHandshakeAndTransportRoundTrip) {
|
||||
noise_cipherstate_free(recv_cipher);
|
||||
}
|
||||
|
||||
// Drive one full NNpsk0 handshake between a fresh initiator and responder;
|
||||
// responder_e receives the ephemeral public key the responder put on the
|
||||
// wire (the clear text start of its message, taken before the initiator
|
||||
// consumes the buffer in place)
|
||||
static void run_handshake(NoiseResponderHandshake &responder, uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE]) {
|
||||
const psk_t psk = make_psk(7);
|
||||
ASSERT_EQ(responder.init(ctx_for(psk), PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
Initiator initiator(psk, PROLOGUE, sizeof(PROLOGUE));
|
||||
uint8_t msg[MAX_HANDSHAKE_SIZE];
|
||||
size_t msg_len = initiator.write_message(msg, sizeof(msg));
|
||||
ASSERT_EQ(responder.read_message(msg, msg_len), 0);
|
||||
size_t reply_len = 0;
|
||||
ASSERT_EQ(responder.write_message(msg, sizeof(msg), reply_len), 0);
|
||||
ASSERT_GE(reply_len, SPARE_EPHEMERAL_KEY_SIZE);
|
||||
std::memcpy(responder_e, msg, SPARE_EPHEMERAL_KEY_SIZE);
|
||||
ASSERT_EQ(initiator.read_message(msg, reply_len), 0);
|
||||
ASSERT_EQ(responder.action(), Action::ACTION_SPLIT);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, EmptySlotLeavesHandshakeToGenerate) {
|
||||
ASSERT_FALSE(has_spare_ephemeral());
|
||||
NoiseResponderHandshake responder;
|
||||
uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE];
|
||||
run_handshake(responder, responder_e);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, ConsumeHandsTheKeyToANewState) {
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
const NoiseProtocolId nid = {
|
||||
.prefix_id = NOISE_PREFIX_STANDARD,
|
||||
.pattern_id = NOISE_PATTERN_NN,
|
||||
.modifier_ids = {NOISE_MODIFIER_PSK0},
|
||||
.dh_id = NOISE_DH_CURVE25519,
|
||||
.cipher_id = NOISE_CIPHER_CHACHAPOLY,
|
||||
.hash_id = NOISE_HASH_SHA256,
|
||||
.hybrid_id = NOISE_DH_NONE,
|
||||
};
|
||||
NoiseHandshakeState *state = nullptr;
|
||||
ASSERT_EQ(noise_handshakestate_new_by_id(&state, &nid, NOISE_ROLE_RESPONDER), 0);
|
||||
const psk_t psk = make_psk(7);
|
||||
ASSERT_EQ(noise_handshakestate_set_pre_shared_key(state, psk.data(), psk.size()), 0);
|
||||
ASSERT_EQ(noise_handshakestate_set_prologue(state, PROLOGUE, sizeof(PROLOGUE)), 0);
|
||||
EXPECT_EQ(consume_spare_ephemeral(state), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
noise_handshakestate_free(state);
|
||||
}
|
||||
|
||||
TEST(SpareEphemeralTest, SlotKeyIsOnTheWireAndConsumedOnce) {
|
||||
prepare_spare_ephemeral();
|
||||
ASSERT_TRUE(has_spare_ephemeral());
|
||||
uint8_t expected_pub[SPARE_EPHEMERAL_KEY_SIZE];
|
||||
std::memcpy(expected_pub, spare_ephemeral + SPARE_EPHEMERAL_KEY_SIZE, sizeof(expected_pub));
|
||||
|
||||
NoiseResponderHandshake first;
|
||||
uint8_t responder_e[SPARE_EPHEMERAL_KEY_SIZE];
|
||||
run_handshake(first, responder_e);
|
||||
// The spare, not a generated key, went out; and it went out once
|
||||
EXPECT_EQ(std::memcmp(responder_e, expected_pub, sizeof(expected_pub)), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
|
||||
NoiseResponderHandshake second;
|
||||
run_handshake(second, responder_e);
|
||||
EXPECT_NE(std::memcmp(responder_e, expected_pub, sizeof(expected_pub)), 0);
|
||||
EXPECT_FALSE(has_spare_ephemeral());
|
||||
}
|
||||
|
||||
TEST(NoiseResponderHandshakeTest, ReInitRestartsHandshake) {
|
||||
// The documented retry shape: a repeated init() frees the previous state
|
||||
// and starts over. The first message under the new key authenticating
|
||||
|
||||
Reference in New Issue
Block a user