test: add lora frame and chunk reassembly logic suite
This commit is contained in:
28
lib/dd3_transport_logic/include/batch_reassembly_logic.h
Normal file
28
lib/dd3_transport_logic/include/batch_reassembly_logic.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
struct BatchReassemblyState {
|
||||
bool active;
|
||||
uint16_t batch_id;
|
||||
uint8_t next_index;
|
||||
uint8_t expected_chunks;
|
||||
uint16_t total_len;
|
||||
uint16_t received_len;
|
||||
uint32_t last_rx_ms;
|
||||
uint32_t timeout_ms;
|
||||
};
|
||||
|
||||
enum class BatchReassemblyStatus : uint8_t {
|
||||
InProgress = 0,
|
||||
Complete = 1,
|
||||
ErrorReset = 2
|
||||
};
|
||||
|
||||
void batch_reassembly_reset(BatchReassemblyState &state);
|
||||
|
||||
BatchReassemblyStatus batch_reassembly_push(BatchReassemblyState &state, uint16_t batch_id, uint8_t chunk_index,
|
||||
uint8_t chunk_count, uint16_t total_len, const uint8_t *chunk_data,
|
||||
size_t chunk_len, uint32_t now_ms, uint32_t timeout_ms_for_new_batch,
|
||||
uint16_t max_total_len, uint8_t *buffer, size_t buffer_cap,
|
||||
uint16_t &out_complete_len);
|
||||
19
lib/dd3_transport_logic/include/lora_frame_logic.h
Normal file
19
lib/dd3_transport_logic/include/lora_frame_logic.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
enum class LoraFrameDecodeStatus : uint8_t {
|
||||
Ok = 0,
|
||||
LengthMismatch = 1,
|
||||
CrcFail = 2,
|
||||
InvalidMsgKind = 3
|
||||
};
|
||||
|
||||
uint16_t lora_crc16_ccitt(const uint8_t *data, size_t len);
|
||||
|
||||
bool lora_build_frame(uint8_t msg_kind, uint16_t device_id_short, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *out_frame, size_t out_cap, size_t &out_len);
|
||||
|
||||
LoraFrameDecodeStatus lora_parse_frame(const uint8_t *frame, size_t frame_len, uint8_t max_msg_kind, uint8_t *out_msg_kind,
|
||||
uint16_t *out_device_id_short, uint8_t *out_payload, size_t payload_cap,
|
||||
size_t *out_payload_len);
|
||||
75
lib/dd3_transport_logic/src/batch_reassembly_logic.cpp
Normal file
75
lib/dd3_transport_logic/src/batch_reassembly_logic.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "batch_reassembly_logic.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void batch_reassembly_reset(BatchReassemblyState &state) {
|
||||
state.active = false;
|
||||
state.batch_id = 0;
|
||||
state.next_index = 0;
|
||||
state.expected_chunks = 0;
|
||||
state.total_len = 0;
|
||||
state.received_len = 0;
|
||||
state.last_rx_ms = 0;
|
||||
state.timeout_ms = 0;
|
||||
}
|
||||
|
||||
BatchReassemblyStatus batch_reassembly_push(BatchReassemblyState &state, uint16_t batch_id, uint8_t chunk_index,
|
||||
uint8_t chunk_count, uint16_t total_len, const uint8_t *chunk_data,
|
||||
size_t chunk_len, uint32_t now_ms, uint32_t timeout_ms_for_new_batch,
|
||||
uint16_t max_total_len, uint8_t *buffer, size_t buffer_cap,
|
||||
uint16_t &out_complete_len) {
|
||||
out_complete_len = 0;
|
||||
if (!buffer || !chunk_data) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
if (chunk_len > 0 && total_len == 0) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
|
||||
bool expired = state.timeout_ms > 0 && (now_ms - state.last_rx_ms > state.timeout_ms);
|
||||
if (!state.active || batch_id != state.batch_id || expired) {
|
||||
if (chunk_index != 0) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
if (total_len == 0 || total_len > max_total_len || chunk_count == 0) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
state.active = true;
|
||||
state.batch_id = batch_id;
|
||||
state.expected_chunks = chunk_count;
|
||||
state.total_len = total_len;
|
||||
state.received_len = 0;
|
||||
state.next_index = 0;
|
||||
state.last_rx_ms = now_ms;
|
||||
state.timeout_ms = timeout_ms_for_new_batch;
|
||||
}
|
||||
|
||||
if (!state.active || chunk_index != state.next_index || chunk_count != state.expected_chunks) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
|
||||
if (state.received_len + chunk_len > state.total_len ||
|
||||
state.received_len + chunk_len > max_total_len ||
|
||||
state.received_len + chunk_len > buffer_cap) {
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::ErrorReset;
|
||||
}
|
||||
|
||||
memcpy(&buffer[state.received_len], chunk_data, chunk_len);
|
||||
state.received_len += static_cast<uint16_t>(chunk_len);
|
||||
state.next_index++;
|
||||
state.last_rx_ms = now_ms;
|
||||
|
||||
if (state.next_index == state.expected_chunks && state.received_len == state.total_len) {
|
||||
out_complete_len = state.received_len;
|
||||
batch_reassembly_reset(state);
|
||||
return BatchReassemblyStatus::Complete;
|
||||
}
|
||||
|
||||
return BatchReassemblyStatus::InProgress;
|
||||
}
|
||||
88
lib/dd3_transport_logic/src/lora_frame_logic.cpp
Normal file
88
lib/dd3_transport_logic/src/lora_frame_logic.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "lora_frame_logic.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
uint16_t lora_crc16_ccitt(const uint8_t *data, size_t len) {
|
||||
if (!data && len > 0) {
|
||||
return 0;
|
||||
}
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
crc ^= static_cast<uint16_t>(data[i]) << 8;
|
||||
for (uint8_t b = 0; b < 8; ++b) {
|
||||
if (crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool lora_build_frame(uint8_t msg_kind, uint16_t device_id_short, const uint8_t *payload, size_t payload_len,
|
||||
uint8_t *out_frame, size_t out_cap, size_t &out_len) {
|
||||
out_len = 0;
|
||||
if (!out_frame) {
|
||||
return false;
|
||||
}
|
||||
if (payload_len > 0 && !payload) {
|
||||
return false;
|
||||
}
|
||||
if (payload_len > (SIZE_MAX - 5)) {
|
||||
return false;
|
||||
}
|
||||
size_t needed = payload_len + 5;
|
||||
if (needed > out_cap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
out_frame[idx++] = msg_kind;
|
||||
out_frame[idx++] = static_cast<uint8_t>(device_id_short >> 8);
|
||||
out_frame[idx++] = static_cast<uint8_t>(device_id_short & 0xFF);
|
||||
if (payload_len > 0) {
|
||||
memcpy(&out_frame[idx], payload, payload_len);
|
||||
idx += payload_len;
|
||||
}
|
||||
uint16_t crc = lora_crc16_ccitt(out_frame, idx);
|
||||
out_frame[idx++] = static_cast<uint8_t>(crc >> 8);
|
||||
out_frame[idx++] = static_cast<uint8_t>(crc & 0xFF);
|
||||
out_len = idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
LoraFrameDecodeStatus lora_parse_frame(const uint8_t *frame, size_t frame_len, uint8_t max_msg_kind, uint8_t *out_msg_kind,
|
||||
uint16_t *out_device_id_short, uint8_t *out_payload, size_t payload_cap,
|
||||
size_t *out_payload_len) {
|
||||
if (!frame || !out_msg_kind || !out_device_id_short || !out_payload_len) {
|
||||
return LoraFrameDecodeStatus::LengthMismatch;
|
||||
}
|
||||
if (frame_len < 5) {
|
||||
return LoraFrameDecodeStatus::LengthMismatch;
|
||||
}
|
||||
|
||||
size_t payload_len = frame_len - 5;
|
||||
if (payload_len > payload_cap || (payload_len > 0 && !out_payload)) {
|
||||
return LoraFrameDecodeStatus::LengthMismatch;
|
||||
}
|
||||
|
||||
uint16_t crc_calc = lora_crc16_ccitt(frame, frame_len - 2);
|
||||
uint16_t crc_rx = static_cast<uint16_t>(frame[frame_len - 2] << 8) | frame[frame_len - 1];
|
||||
if (crc_calc != crc_rx) {
|
||||
return LoraFrameDecodeStatus::CrcFail;
|
||||
}
|
||||
|
||||
uint8_t msg_kind = frame[0];
|
||||
if (msg_kind > max_msg_kind) {
|
||||
return LoraFrameDecodeStatus::InvalidMsgKind;
|
||||
}
|
||||
|
||||
*out_msg_kind = msg_kind;
|
||||
*out_device_id_short = static_cast<uint16_t>(frame[1] << 8) | frame[2];
|
||||
if (payload_len > 0) {
|
||||
memcpy(out_payload, &frame[3], payload_len);
|
||||
}
|
||||
*out_payload_len = payload_len;
|
||||
return LoraFrameDecodeStatus::Ok;
|
||||
}
|
||||
Reference in New Issue
Block a user