175 lines
5.5 KiB
C++
175 lines
5.5 KiB
C++
#ifdef ESP_PLATFORM
|
|
#include <sdkconfig.h>
|
|
#endif
|
|
|
|
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
|
|
|
#include <Tactility/app/chat/ChatProtocol.h>
|
|
#include <Tactility/service/espnow/EspNow.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
namespace tt::app::chat {
|
|
|
|
bool serializeTextMessage(uint32_t senderId, uint32_t targetId,
|
|
const std::string& senderName, const std::string& target,
|
|
const std::string& message, std::vector<uint8_t>& out) {
|
|
// Validate input lengths (min and max)
|
|
if (senderName.size() < MIN_NICKNAME_LEN || senderName.size() > MAX_NICKNAME_LEN) {
|
|
return false;
|
|
}
|
|
if (target.size() > MAX_TARGET_LEN) {
|
|
return false; // MIN_TARGET_LEN is 0, so empty (broadcast) is allowed
|
|
}
|
|
if (message.size() < MIN_MESSAGE_LEN) {
|
|
return false;
|
|
}
|
|
|
|
// Calculate payload size: nickname + null + target + null + message
|
|
size_t payloadSize = senderName.size() + 1 + target.size() + 1 + message.size();
|
|
|
|
// Check against ESP-NOW limits (guard against underflow if getMaxDataLength < HEADER_SIZE)
|
|
size_t maxData = service::espnow::getMaxDataLength();
|
|
if (maxData <= HEADER_SIZE) {
|
|
return false;
|
|
}
|
|
size_t maxPayload = maxData - HEADER_SIZE;
|
|
if (payloadSize > maxPayload || payloadSize > 255) {
|
|
return false; // payload_size is uint8_t
|
|
}
|
|
|
|
// Build header
|
|
MessageHeader header = {
|
|
.magic = CHAT_MAGIC_V2,
|
|
.protocol_version = PROTOCOL_VERSION,
|
|
.from = senderId,
|
|
.to = targetId,
|
|
.payload_type = static_cast<uint8_t>(PayloadType::TextMessage),
|
|
.payload_size = static_cast<uint8_t>(payloadSize)
|
|
};
|
|
|
|
// Allocate output buffer
|
|
out.resize(HEADER_SIZE + payloadSize);
|
|
|
|
// Copy header to output
|
|
memcpy(out.data(), &header, HEADER_SIZE);
|
|
|
|
// Build payload: nickname\0 + target\0 + message
|
|
uint8_t* payload = out.data() + HEADER_SIZE;
|
|
size_t offset = 0;
|
|
|
|
memcpy(payload + offset, senderName.c_str(), senderName.size() + 1);
|
|
offset += senderName.size() + 1;
|
|
|
|
memcpy(payload + offset, target.c_str(), target.size() + 1);
|
|
offset += target.size() + 1;
|
|
|
|
memcpy(payload + offset, message.c_str(), message.size());
|
|
// Note: message is NOT null-terminated in wire format (length is implicit)
|
|
|
|
return true;
|
|
}
|
|
|
|
bool deserializeMessage(const uint8_t* data, size_t length, ParsedMessage& out) {
|
|
// Minimum: header + min_nickname + null + min_target + null + min_message
|
|
// = 16 + 2 + 1 + 0 + 1 + 1 = 21 bytes
|
|
constexpr size_t MIN_PACKET_SIZE = HEADER_SIZE + MIN_NICKNAME_LEN + 1 + MIN_TARGET_LEN + 1 + MIN_MESSAGE_LEN;
|
|
if (length < MIN_PACKET_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
// Copy header to aligned struct
|
|
MessageHeader header;
|
|
memcpy(&header, data, HEADER_SIZE);
|
|
|
|
// Validate header
|
|
if (header.magic != CHAT_MAGIC_V2) {
|
|
return false;
|
|
}
|
|
|
|
if (header.protocol_version != PROTOCOL_VERSION) {
|
|
return false;
|
|
}
|
|
|
|
// Validate payload size
|
|
if (header.payload_size != length - HEADER_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
// Only handle text messages for now
|
|
if (header.payload_type != static_cast<uint8_t>(PayloadType::TextMessage)) {
|
|
return false;
|
|
}
|
|
|
|
// Parse payload
|
|
const uint8_t* payload = data + HEADER_SIZE;
|
|
size_t payloadLen = header.payload_size;
|
|
|
|
// Find nickname (null-terminated)
|
|
const char* nicknameStart = reinterpret_cast<const char*>(payload);
|
|
size_t nicknameLen = strnlen(nicknameStart, payloadLen);
|
|
if (nicknameLen >= payloadLen) {
|
|
return false; // No null terminator found
|
|
}
|
|
|
|
size_t offset = nicknameLen + 1;
|
|
size_t remaining = payloadLen - offset;
|
|
|
|
// Find target (null-terminated)
|
|
const char* targetStart = reinterpret_cast<const char*>(payload + offset);
|
|
size_t targetLen = strnlen(targetStart, remaining);
|
|
if (targetLen >= remaining) {
|
|
return false; // No null terminator found
|
|
}
|
|
|
|
offset += targetLen + 1;
|
|
remaining = payloadLen - offset;
|
|
|
|
// Rest is the message (not null-terminated)
|
|
const char* messageStart = reinterpret_cast<const char*>(payload + offset);
|
|
|
|
// Validate field lengths (min and max)
|
|
if (nicknameLen < MIN_NICKNAME_LEN || nicknameLen > MAX_NICKNAME_LEN) {
|
|
return false;
|
|
}
|
|
if (targetLen > MAX_TARGET_LEN) {
|
|
return false;
|
|
}
|
|
if (remaining < MIN_MESSAGE_LEN) {
|
|
return false;
|
|
}
|
|
|
|
// Populate output
|
|
out.senderId = header.from;
|
|
out.targetId = header.to;
|
|
out.senderName = std::string(nicknameStart, nicknameLen);
|
|
out.target = std::string(targetStart, targetLen);
|
|
out.message = std::string(messageStart, remaining);
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t getMaxMessageLength(size_t nicknameLen, size_t targetLen) {
|
|
// Guard against underflow if getMaxDataLength < HEADER_SIZE
|
|
size_t maxData = service::espnow::getMaxDataLength();
|
|
if (maxData <= HEADER_SIZE) {
|
|
return 0;
|
|
}
|
|
size_t maxPayload = maxData - HEADER_SIZE;
|
|
|
|
// Payload: nickname + null + target + null + message
|
|
size_t overhead = nicknameLen + 1 + targetLen + 1;
|
|
if (overhead >= maxPayload || overhead > 255) {
|
|
return 0;
|
|
}
|
|
// Cap at 255 since payload_size is uint8_t
|
|
size_t maxFromEspNow = maxPayload - overhead;
|
|
size_t maxFromPayloadSize = 255 - overhead;
|
|
return std::min(maxFromEspNow, maxFromPayloadSize);
|
|
}
|
|
|
|
} // namespace tt::app::chat
|
|
|
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|