Fix OTA resume, CLI identity, radio persistence, and network sessions
Distinguish OTA read failures from missing blocks and select unique raw-flash checkpoints by MID and target. Keep deferred CLI requests and replies bound to immutable command identity and full authenticated keys. Gate radio mutations on their exact reply delivery, recover coding-rate restoration, and roll back failed Companion preference writes. Preserve accepted low duty cycles across reboot. Retain partial network writes, cancel displaced client operations, and reject malformed or stalled framed input. Add regression and CI coverage, and size-optimize the RAK4631 Ethernet build without dropping features.
This commit is contained in:
1 parent
931cb4928f
commit
a88ced23b6
64 files changed
+3504
-369
No files matched your search
@@ -6,6 +6,10 @@
|
||||
#define RECV_STATE_LEN1_FOUND 2
|
||||
#define RECV_STATE_LEN2_FOUND 3
|
||||
|
||||
#if !ETHERNET_RAW_LINE
|
||||
static constexpr uint32_t ETHERNET_FRAME_TIMEOUT_MS = 5000;
|
||||
#endif
|
||||
|
||||
bool SerialEthernetInterface::begin() {
|
||||
return true;
|
||||
}
|
||||
@@ -18,21 +22,32 @@ void SerialEthernetInterface::enable() {
|
||||
|
||||
void SerialEthernetInterface::disable() {
|
||||
_isEnabled = false;
|
||||
disconnectClient();
|
||||
onClientDisconnected();
|
||||
clearBuffers();
|
||||
}
|
||||
|
||||
bool SerialEthernetInterface::isConnected() const {
|
||||
return _isEnabled && session_active;
|
||||
}
|
||||
|
||||
size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
if (len > MAX_FRAME_SIZE) {
|
||||
ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isConnected() && len > 0) {
|
||||
if (!mesh::enqueueCompanionFrame(send_queue, send_queue_len, FRAME_QUEUE_SIZE,
|
||||
if (_isEnabled && isConnected() && src != nullptr && len > 0) {
|
||||
// A partly written frame must remain at the head even if a new response
|
||||
// displaces or reorders lower-priority frames in the waiting tail.
|
||||
const int pinned = send_offset != 0 ? 1 : 0;
|
||||
int tail_len = send_queue_len - pinned;
|
||||
if (!mesh::enqueueCompanionFrame(send_queue + pinned, tail_len, FRAME_QUEUE_SIZE - pinned,
|
||||
src, len)) {
|
||||
ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
|
||||
return 0;
|
||||
}
|
||||
send_queue_len = tail_len + pinned;
|
||||
return len;
|
||||
}
|
||||
return 0;
|
||||
@@ -43,37 +58,66 @@ bool SerialEthernetInterface::isWriteBusy() const {
|
||||
}
|
||||
|
||||
bool SerialEthernetInterface::isReadBusy() const {
|
||||
return _state != RECV_STATE_IDLE;
|
||||
return _state != RECV_STATE_IDLE || _rx_len > 0;
|
||||
}
|
||||
|
||||
bool SerialEthernetInterface::hasPendingIO() const {
|
||||
// Ethernet is polled, so an active connection must keep receiving loop
|
||||
// service even when no complete frame is queued yet.
|
||||
return isConnected() || send_queue_len > 0 || _state != RECV_STATE_IDLE;
|
||||
return _isEnabled && isConnected();
|
||||
}
|
||||
|
||||
void SerialEthernetInterface::onClientConnected() {
|
||||
clearBuffers();
|
||||
void SerialEthernetInterface::onClientConnected(uint32_t remote_ip) {
|
||||
const bool different_owner = queue_has_ip && queue_ip != remote_ip;
|
||||
if (different_owner) {
|
||||
// Cancel response producers while the displaced route is unavailable.
|
||||
// Ethernet has only its original four-frame queue: unlike WiFi there is
|
||||
// no spare queue for a different IP's backlog, so discard it on takeover.
|
||||
onClientDisconnected();
|
||||
send_queue_len = 0;
|
||||
}
|
||||
resetInput();
|
||||
send_offset = 0; // Replay a complete frame on a fresh TCP stream.
|
||||
queue_ip = remote_ip;
|
||||
queue_has_ip = true;
|
||||
session_active = true;
|
||||
}
|
||||
|
||||
void SerialEthernetInterface::onClientDisconnected() {
|
||||
clearBuffers();
|
||||
const bool was_active = session_active;
|
||||
session_active = false;
|
||||
resetInput();
|
||||
send_offset = 0;
|
||||
if (was_active && session_changed != nullptr) session_changed(session_context);
|
||||
}
|
||||
|
||||
void SerialEthernetInterface::rejectInput() {
|
||||
disconnectClient();
|
||||
onClientDisconnected();
|
||||
}
|
||||
|
||||
size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
|
||||
if (!_isEnabled) return 0;
|
||||
if (isConnected()) {
|
||||
#if !ETHERNET_RAW_LINE
|
||||
if (isReadBusy() && (uint32_t)((uint32_t)millis() - _rx_started) >= ETHERNET_FRAME_TIMEOUT_MS) {
|
||||
rejectInput();
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (send_queue_len > 0) { // first, check send queue
|
||||
|
||||
_last_write = millis();
|
||||
int len = send_queue[0].len;
|
||||
|
||||
uint8_t pkt[MAX_FRAME_SIZE + 3];
|
||||
#if ETHERNET_RAW_LINE
|
||||
ETHERNET_DEBUG_PRINTLN("TX line len=%d", len);
|
||||
write(send_queue[0].buf, len);
|
||||
write((const uint8_t *)"\r\n", 2);
|
||||
memcpy(pkt, send_queue[0].buf, len);
|
||||
pkt[len] = '\r';
|
||||
pkt[len + 1] = '\n';
|
||||
const size_t total = len + 2U;
|
||||
#else
|
||||
uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames
|
||||
pkt[0] = '>';
|
||||
pkt[1] = (len & 0xFF); // LSB
|
||||
pkt[2] = (len >> 8); // MSB
|
||||
@@ -82,8 +126,13 @@ size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
#if ETHERNET_DEBUG_LOGGING && ARDUINO
|
||||
ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len);
|
||||
#endif
|
||||
write(pkt, 3 + len);
|
||||
const size_t total = len + 3U;
|
||||
#endif
|
||||
const size_t remaining = total - send_offset;
|
||||
const size_t written = write(pkt + send_offset, remaining);
|
||||
send_offset += written < remaining ? written : remaining;
|
||||
if (send_offset < total) return 0;
|
||||
send_offset = 0;
|
||||
send_queue_len--;
|
||||
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
|
||||
send_queue[i] = send_queue[i + 1];
|
||||
@@ -99,20 +148,25 @@ size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
continue;
|
||||
}
|
||||
uint16_t out_len = _rx_len;
|
||||
if (out_len > MAX_FRAME_SIZE) out_len = MAX_FRAME_SIZE;
|
||||
if (dest == nullptr) { rejectInput(); return 0; }
|
||||
memcpy(dest, _rx_buf, out_len);
|
||||
_rx_len = 0;
|
||||
resetInput();
|
||||
return out_len;
|
||||
}
|
||||
if (_rx_len < MAX_FRAME_SIZE) {
|
||||
if (_rx_len == 0) _rx_started = (uint32_t)millis();
|
||||
_rx_buf[_rx_len] = (uint8_t)c;
|
||||
_rx_len++;
|
||||
} else {
|
||||
rejectInput(); // Never execute the prefix of an overlong command.
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
switch (_state) {
|
||||
case RECV_STATE_IDLE:
|
||||
if (c == '<') {
|
||||
_state = RECV_STATE_HDR_FOUND;
|
||||
_rx_started = (uint32_t)millis();
|
||||
}
|
||||
break;
|
||||
case RECV_STATE_HDR_FOUND:
|
||||
@@ -121,8 +175,12 @@ size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
break;
|
||||
case RECV_STATE_LEN1_FOUND:
|
||||
_frame_len |= ((uint16_t)c) << 8;
|
||||
if (_frame_len == 0 || _frame_len > MAX_FRAME_SIZE) {
|
||||
rejectInput();
|
||||
return 0;
|
||||
}
|
||||
_rx_len = 0;
|
||||
_state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE;
|
||||
_state = RECV_STATE_LEN2_FOUND;
|
||||
break;
|
||||
default:
|
||||
if (_rx_len < MAX_FRAME_SIZE) {
|
||||
@@ -130,15 +188,14 @@ size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
}
|
||||
_rx_len++;
|
||||
if (_rx_len >= _frame_len) {
|
||||
if (_frame_len > MAX_FRAME_SIZE) {
|
||||
_frame_len = MAX_FRAME_SIZE;
|
||||
}
|
||||
#if ETHERNET_DEBUG_LOGGING && ARDUINO
|
||||
ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len);
|
||||
#endif
|
||||
memcpy(dest, _rx_buf, _frame_len);
|
||||
_state = RECV_STATE_IDLE;
|
||||
return _frame_len;
|
||||
if (dest == nullptr) { rejectInput(); return 0; }
|
||||
const size_t out_len = _frame_len;
|
||||
memcpy(dest, _rx_buf, out_len);
|
||||
resetInput();
|
||||
return out_len;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user