Refine Reticulum payload memory and chat status UI

This commit is contained in:
liu weikai
2026-07-17 01:48:28 +08:00
parent dd5b6425a8
commit 18aed9abdf
33 changed files with 412 additions and 128 deletions
@@ -25,6 +25,12 @@ struct DecodedField
std::vector<uint8_t> encoded_value;
};
struct ByteSpan
{
const uint8_t* data = nullptr;
size_t size = 0;
};
struct DecodedEnvelope
{
uint8_t destination_hash[reticulum::kTruncatedHashSize] = {};
@@ -241,6 +247,10 @@ bool encodePropagationBatch(double remote_timebase,
const std::vector<std::vector<uint8_t>>& messages,
uint8_t* out_payload,
size_t* inout_len);
bool encodePropagationBatch(double remote_timebase,
const std::vector<ByteSpan>& messages,
uint8_t* out_payload,
size_t* inout_len);
bool decodePropagationBatch(const uint8_t* data, size_t len,
DecodedPropagationBatch* out_batch);
@@ -280,6 +290,9 @@ bool encodePropagationIdListPayload(const std::vector<std::vector<uint8_t>>& ids
bool encodePropagationMessageListPayload(const std::vector<std::vector<uint8_t>>& messages,
uint8_t* out_payload,
size_t* inout_len);
bool encodePropagationMessageListPayload(const std::vector<ByteSpan>& messages,
uint8_t* out_payload,
size_t* inout_len);
void computeMessageHash(const uint8_t destination_hash[reticulum::kTruncatedHashSize],
const uint8_t source_hash[reticulum::kTruncatedHashSize],
@@ -569,6 +569,32 @@ bool appendArrayOfBins(const std::vector<std::vector<uint8_t>>& items,
return true;
}
bool appendArrayOfBinSpans(const std::vector<ByteSpan>& items,
uint8_t* out,
size_t out_len,
size_t& used)
{
if (items.size() > kMaxPropagationWireItems)
{
return false;
}
if (!appendArrayHeader(static_cast<uint8_t>(items.size()), out, out_len, used))
{
return false;
}
for (const auto& item : items)
{
if ((!item.data && item.size != 0U) ||
!appendBin(item.data, item.size, out, out_len, used))
{
return false;
}
}
return true;
}
bool readArrayOfBins(Cursor& cursor, std::vector<std::vector<uint8_t>>* out_items)
{
if (!out_items)
@@ -1465,6 +1491,28 @@ bool encodePropagationBatch(double remote_timebase,
return true;
}
bool encodePropagationBatch(double remote_timebase,
const std::vector<ByteSpan>& messages,
uint8_t* out_payload,
size_t* inout_len)
{
if (!out_payload || !inout_len)
{
return false;
}
size_t used = 0;
if (!appendArrayHeader(2, out_payload, *inout_len, used) ||
!appendFloat64(remote_timebase, out_payload, *inout_len, used) ||
!appendArrayOfBinSpans(messages, out_payload, *inout_len, used))
{
return false;
}
*inout_len = used;
return true;
}
bool decodePropagationBatch(const uint8_t* data, size_t len,
DecodedPropagationBatch* out_batch)
{
@@ -1838,6 +1886,25 @@ bool encodePropagationMessageListPayload(const std::vector<std::vector<uint8_t>>
return encodePropagationIdListPayload(messages, out_payload, inout_len);
}
bool encodePropagationMessageListPayload(const std::vector<ByteSpan>& messages,
uint8_t* out_payload,
size_t* inout_len)
{
if (!out_payload || !inout_len)
{
return false;
}
size_t used = 0;
if (!appendArrayOfBinSpans(messages, out_payload, *inout_len, used))
{
return false;
}
*inout_len = used;
return true;
}
void computeMessageHash(const uint8_t destination_hash[reticulum::kTruncatedHashSize],
const uint8_t source_hash[reticulum::kTruncatedHashSize],
const uint8_t* packed_payload,
@@ -182,7 +182,7 @@ class ChatConversationScreen
lv_obj_t* source_label = nullptr;
lv_obj_t* text_label = nullptr; // inside bubble
lv_obj_t* time_label = nullptr; // inside meta row
lv_obj_t* status_label = nullptr; // reserved (not used)
lv_obj_t* status_label = nullptr; // inside meta row
std::unique_ptr<MessageActionContext> retry_ctx;
bool retry_enabled = false;
};
@@ -38,9 +38,6 @@ lv_obj_t* create_bubble_text(lv_obj_t* bubble_parent);
// Create bubble time label
lv_obj_t* create_bubble_time(lv_obj_t* bubble_parent);
// Create bubble status label
lv_obj_t* create_bubble_status(lv_obj_t* bubble_parent);
// Layout-only helpers to align message row left/right
void align_message_row(lv_obj_t* row, bool is_self);
@@ -21,6 +21,5 @@ void apply_message_row(lv_obj_t* row);
void apply_bubble(lv_obj_t* bubble, bool is_self, bool source_unverified = false);
void apply_bubble_text(lv_obj_t* label);
void apply_bubble_time(lv_obj_t* label);
void apply_bubble_status(lv_obj_t* label);
} // namespace chat::ui::conversation::styles
+4
View File
@@ -478,6 +478,10 @@ bool ui_present_interruption_app(AppScreen* app, lv_obj_t* parent)
s_interruption_app = app;
ui_set_overlay_active(true);
ui::menu_layout::setMenuVisible(false);
if (main_screen != nullptr)
{
lv_tileview_set_tile_by_index(main_screen, 0, 1, LV_ANIM_OFF);
}
ui_switch_to_app(app, parent);
return s_active_app == app;
}
@@ -274,6 +274,82 @@ bool model_fits_location_overlay(lv_obj_t* viewport_root,
return ::ui::chat::MessageDeliveryState::Unknown;
}
const char* delivery_status_text_key(::ui::chat::MessageDeliveryState delivery)
{
switch (delivery)
{
case ::ui::chat::MessageDeliveryState::Queued:
return "Queued";
case ::ui::chat::MessageDeliveryState::Sending:
case ::ui::chat::MessageDeliveryState::Sent:
return "Sending...";
case ::ui::chat::MessageDeliveryState::Delivered:
return "Delivered";
case ::ui::chat::MessageDeliveryState::Failed:
return "Failed";
case ::ui::chat::MessageDeliveryState::Draft:
case ::ui::chat::MessageDeliveryState::Received:
case ::ui::chat::MessageDeliveryState::Unknown:
return nullptr;
}
return nullptr;
}
lv_color_t delivery_status_chip_color(::ui::chat::MessageDeliveryState delivery)
{
switch (delivery)
{
case ::ui::chat::MessageDeliveryState::Queued:
return lv_color_hex(0xF4E2B0);
case ::ui::chat::MessageDeliveryState::Sending:
case ::ui::chat::MessageDeliveryState::Sent:
return lv_color_hex(0xCFE4FF);
case ::ui::chat::MessageDeliveryState::Delivered:
return lv_color_hex(0xD4F0D2);
case ::ui::chat::MessageDeliveryState::Failed:
return lv_color_hex(0xF1B8AA);
case ::ui::chat::MessageDeliveryState::Draft:
case ::ui::chat::MessageDeliveryState::Received:
case ::ui::chat::MessageDeliveryState::Unknown:
break;
}
return lv_color_hex(0xE8E0D8);
}
void update_delivery_status_chip(lv_obj_t* status_label,
::ui::chat::MessageDeliveryState delivery)
{
if (!status_label)
{
return;
}
lv_obj_t* chip = lv_obj_get_parent(status_label);
const char* text_key = delivery_status_text_key(delivery);
if (!text_key || text_key[0] == '\0')
{
lv_label_set_text(status_label, "");
if (chip)
{
lv_obj_add_flag(chip, LV_OBJ_FLAG_HIDDEN);
}
return;
}
if (chip)
{
lv_obj_set_style_bg_color(chip,
delivery_status_chip_color(delivery),
LV_PART_MAIN);
lv_obj_clear_flag(chip, LV_OBJ_FLAG_HIDDEN);
}
::ui::i18n::set_label_text(status_label, text_key);
::ui::fonts::apply_localized_font(
status_label,
lv_label_get_text(status_label),
::ui::page_profile::resolve_caption_font());
}
bool message_ref_matches_id(const ::ui::chat::MessageRef& ref,
chat::MessageId msg_id)
{
@@ -683,20 +759,13 @@ bool ChatConversationScreen::updateMessageStatus(const chat::MessageId msg_id,
return true;
}
update_delivery_status_chip(item.status_label, item.delivery);
if (status == MessageStatus::Failed)
{
::ui::i18n::set_label_text(item.status_label, "Failed");
::ui::fonts::apply_localized_font(
item.status_label, lv_label_get_text(item.status_label), ::ui::fonts::ui_chrome_font());
lv_obj_clear_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
enableRetryAction(item);
}
else
{
lv_label_set_text(item.status_label, "");
::ui::fonts::apply_localized_font(
item.status_label, lv_label_get_text(item.status_label), ::ui::fonts::ui_chrome_font());
lv_obj_add_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
disableRetryAction(item);
}
return true;
@@ -1283,6 +1352,14 @@ void ChatConversationScreen::createMessageItem(const ::ui::chat::MessageRow& row
}
item.time_label =
create_meta_chip(item.meta_row, time_buf, lv_color_hex(0xD4F0D2), max_meta_w);
if (is_self)
{
item.status_label = create_meta_chip(item.meta_row,
"Sending...",
delivery_status_chip_color(row.delivery),
max_meta_w);
update_delivery_status_chip(item.status_label, row.delivery);
}
item.text_label = chat::ui::layout::create_bubble_text(bubble);
chat::ui::conversation::styles::apply_bubble_text(item.text_label);
@@ -1331,23 +1408,10 @@ void ChatConversationScreen::createMessageItem(const ::ui::chat::MessageRow& row
lv_obj_set_width(item.text_label, LV_SIZE_CONTENT);
}
item.status_label = chat::ui::layout::create_bubble_status(bubble);
chat::ui::conversation::styles::apply_bubble_status(item.status_label);
if (row.delivery == ::ui::chat::MessageDeliveryState::Failed)
{
::ui::i18n::set_label_text(item.status_label, "Failed");
::ui::fonts::apply_localized_font(
item.status_label, lv_label_get_text(item.status_label), ::ui::fonts::ui_chrome_font());
lv_obj_clear_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
enableRetryAction(item);
}
else
{
lv_label_set_text(item.status_label, "");
::ui::fonts::apply_localized_font(
item.status_label, lv_label_get_text(item.status_label), ::ui::fonts::ui_chrome_font());
lv_obj_add_flag(item.status_label, LV_OBJ_FLAG_HIDDEN);
}
// Align row based on sender (same behavior)
chat::ui::layout::align_message_row(item.container, is_self);
@@ -206,13 +206,6 @@ lv_obj_t* create_bubble_time(lv_obj_t* bubble_parent)
return label;
}
lv_obj_t* create_bubble_status(lv_obj_t* bubble_parent)
{
lv_obj_t* label = lv_label_create(bubble_parent);
lv_obj_set_width(label, LV_SIZE_CONTENT);
return label;
}
void align_message_row(lv_obj_t* row, bool is_self)
{
// Match original behavior:
@@ -24,7 +24,6 @@ static lv_style_t s_bubble_other;
static lv_style_t s_bubble_unverified;
static lv_style_t s_bubble_text;
static lv_style_t s_bubble_time;
static lv_style_t s_bubble_status;
static constexpr lv_coord_t kPadX = 8;
static constexpr lv_coord_t kPadY = 6;
@@ -138,11 +137,6 @@ void init_once()
lv_style_set_text_color(&s_bubble_time, lv_color_hex(0x6A5646));
lv_style_set_text_align(&s_bubble_time, LV_TEXT_ALIGN_LEFT);
lv_style_set_text_font(&s_bubble_time, meta_font);
lv_style_init(&s_bubble_status);
lv_style_set_text_color(&s_bubble_status, lv_color_hex(0xCC0000));
lv_style_set_text_align(&s_bubble_status, LV_TEXT_ALIGN_LEFT);
lv_style_set_text_font(&s_bubble_status, meta_font);
}
void apply_root(lv_obj_t* root)
@@ -204,12 +198,6 @@ void apply_bubble_time(lv_obj_t* label)
lv_obj_add_style(label, &s_bubble_time, 0);
}
void apply_bubble_status(lv_obj_t* label)
{
init_once();
lv_obj_add_style(label, &s_bubble_status, 0);
}
} // namespace chat::ui::conversation::styles
#endif
+2
View File
@@ -248,6 +248,8 @@ Send ID Broadcast إرسال بث الهوية
Send ID Local إرسال هوية محلي
Sending... جار الإرسال...
Sent تم الإرسال
Queued في قائمة الانتظار
Delivered تم التسليم
Settings الإعدادات
Setting إعداد
Share Position Marker مشاركة علامة الموقع
1 Action إجراء
248 Send ID Local إرسال هوية محلي
249 Sending... جار الإرسال...
250 Sent تم الإرسال
251 Queued في قائمة الانتظار
252 Delivered تم التسليم
253 Settings الإعدادات
254 Setting إعداد
255 Share Position Marker مشاركة علامة الموقع
@@ -248,6 +248,8 @@ Send ID Broadcast Отправить ID трансляцию
Send ID Local Отправить ID локальный
Sending... Отправка...
Sent Отправлено
Queued В очереди
Delivered Доставлено
Settings Настройки
Setting Настройка
Share Position Marker Маркер позиции акции
1 Action Действие
248 Send ID Local Отправить ID локальный
249 Sending... Отправка...
250 Sent Отправлено
251 Queued В очереди
252 Delivered Доставлено
253 Settings Настройки
254 Setting Настройка
255 Share Position Marker Маркер позиции акции
@@ -248,6 +248,8 @@ Send ID Broadcast ID Broadcast senden
Send ID Local Senden Sie ID Local
Sending... Wird gesendet...
Sent Gesendet
Queued In Warteschlange
Delivered Zugestellt
Settings Einstellungen
Setting Einstellung
Share Position Marker Positionsmarkierung teilen
1 Action Aktion
248 Send ID Local Senden Sie ID Local
249 Sending... Wird gesendet...
250 Sent Gesendet
251 Queued In Warteschlange
252 Delivered Zugestellt
253 Settings Einstellungen
254 Setting Einstellung
255 Share Position Marker Positionsmarkierung teilen
@@ -248,6 +248,8 @@ Send ID Broadcast Enviar ID Difusión
Send ID Local Enviar ID Local
Sending... Enviando...
Sent Enviado
Queued En cola
Delivered Entregado
Settings Configuración
Setting Configuración
Share Position Marker Compartir marcador de posición
1 Action Acción
248 Send ID Local Enviar ID Local
249 Sending... Enviando...
250 Sent Enviado
251 Queued En cola
252 Delivered Entregado
253 Settings Configuración
254 Setting Configuración
255 Share Position Marker Compartir marcador de posición
@@ -248,6 +248,8 @@ Send ID Broadcast Envoyer la diffusion ID
Send ID Local Envoyer ID Local
Sending... Envoi en cours...
Sent Envoyé
Queued En file
Delivered Livré
Settings Paramètres
Setting Paramètre
Share Position Marker Partager le marqueur de position
1 Action Action
248 Send ID Local Envoyer ID Local
249 Sending... Envoi en cours...
250 Sent Envoyé
251 Queued En file
252 Delivered Livré
253 Settings Paramètres
254 Setting Paramètre
255 Share Position Marker Partager le marqueur de position
@@ -248,6 +248,8 @@ Send ID Broadcast Invia ID Trasmetti
Send ID Local Invia ID Locale
Sending... Invio...
Sent Inviato
Queued In coda
Delivered Consegnato
Settings Impostazioni
Setting Impostazione
Share Position Marker Condividi indicatore di posizione
1 Action Azione
248 Send ID Local Invia ID Locale
249 Sending... Invio...
250 Sent Inviato
251 Queued In coda
252 Delivered Consegnato
253 Settings Impostazioni
254 Setting Impostazione
255 Share Position Marker Condividi indicatore di posizione
@@ -248,6 +248,8 @@ Send ID Broadcast Verzend ID uitzending
Send ID Local Verzend ID Lokaal
Sending... Verzenden...
Sent Verzonden
Queued In wachtrij
Delivered Afgeleverd
Settings Instellingen
Setting Instelling
Share Position Marker Aandeelpositiemarkering
1 Action Actie
248 Send ID Local Verzend ID Lokaal
249 Sending... Verzenden...
250 Sent Verzonden
251 Queued In wachtrij
252 Delivered Afgeleverd
253 Settings Instellingen
254 Setting Instelling
255 Share Position Marker Aandeelpositiemarkering
@@ -248,6 +248,8 @@ Send ID Broadcast Wyślij ID Transmisja
Send ID Local Wyślij ID lokalnie
Sending... Wysyłanie...
Sent Wysłano
Queued W kolejce
Delivered Dostarczono
Settings Ustawienia
Setting Ustawienie
Share Position Marker Udostępnij znacznik pozycji
1 Action Akcja
248 Send ID Local Wyślij ID lokalnie
249 Sending... Wysyłanie...
250 Sent Wysłano
251 Queued W kolejce
252 Delivered Dostarczono
253 Settings Ustawienia
254 Setting Ustawienie
255 Share Position Marker Udostępnij znacznik pozycji
@@ -248,6 +248,8 @@ Send ID Broadcast Enviar ID Transmissão
Send ID Local Enviar ID Local
Sending... Enviando...
Sent Enviado
Queued Na fila
Delivered Entregue
Settings Configurações
Setting Configuração
Share Position Marker Marcador de posição de compartilhamento
1 Action Ação
248 Send ID Local Enviar ID Local
249 Sending... Enviando...
250 Sent Enviado
251 Queued Na fila
252 Delivered Entregue
253 Settings Configurações
254 Setting Configuração
255 Share Position Marker Marcador de posição de compartilhamento
+2
View File
@@ -248,6 +248,8 @@ Send ID Broadcast ID ブロードキャストを送信
Send ID Local ID をローカルに送信
Sending... 送信中...
Sent 送信済み
Queued キュー済み
Delivered 配信済み
Settings 設定
Setting 設定
Share Position Marker シェアポジションマーカー
1 Action アクション
248 Send ID Local ID をローカルに送信
249 Sending... 送信中...
250 Sent 送信済み
251 Queued キュー済み
252 Delivered 配信済み
253 Settings 設定
254 Setting 設定
255 Share Position Marker シェアポジションマーカー
+2
View File
@@ -248,6 +248,8 @@ Send ID Broadcast ID 브로드캐스트 보내기
Send ID Local ID 로컬 보내기
Sending... 보내는 중...
Sent 보냄
Queued 대기 중
Delivered 전달됨
Settings 설정
Setting 설정
Share Position Marker 위치 마커 공유
1 Action 액션
248 Send ID Local ID 로컬 보내기
249 Sending... 보내는 중...
250 Sent 보냄
251 Queued 대기 중
252 Delivered 전달됨
253 Settings 설정
254 Setting 설정
255 Share Position Marker 위치 마커 공유
@@ -248,6 +248,8 @@ Send ID Broadcast 广播发送 ID
Send ID Local 本地发送 ID
Sending... 发送中...
Sent 已发送
Queued 已排队
Delivered 已送达
Settings 设置
Setting 设置
Share Position Marker 分享位置标记
1 Action 操作
248 Send ID Local 本地发送 ID
249 Sending... 发送中...
250 Sent 已发送
251 Queued 已排队
252 Delivered 已送达
253 Settings 设置
254 Setting 设置
255 Share Position Marker 分享位置标记
@@ -248,6 +248,8 @@ Send ID Broadcast 廣播傳送 ID
Send ID Local 本地傳送 ID
Sending... 傳送中...
Sent 已傳送
Queued 已排隊
Delivered 已送達
Settings 設定
Setting 設定
Share Position Marker 分享位置標記
1 Action 操作
248 Send ID Local 本地傳送 ID
249 Sending... 傳送中...
250 Sent 已傳送
251 Queued 已排隊
252 Delivered 已送達
253 Settings 設定
254 Setting 設定
255 Share Position Marker 分享位置標記
@@ -423,7 +423,7 @@ class LxmfAdapter : public IMeshAdapter
uint8_t* out_payload, size_t* inout_len) const;
bool decryptLinkPayload(const LinkSession& session,
const uint8_t* payload, size_t payload_len,
std::vector<uint8_t>* out_plaintext) const;
runtime::ResourcePayloadBuffer* out_plaintext) const;
bool sendLinkPacket(LinkSession& session,
reticulum::PacketType packet_type,
reticulum::PacketContext context,
@@ -0,0 +1,96 @@
/**
* @file lxmf_memory.h
* @brief Memory ownership helpers for embedded LXMF runtime buffers.
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
#include <new>
#include <vector>
#if defined(ESP_PLATFORM)
#include <esp_heap_caps.h>
#else
#include <cstdlib>
#endif
namespace chat::lxmf::runtime
{
template <typename T>
class PsramAllocator
{
public:
using value_type = T;
PsramAllocator() noexcept = default;
template <typename U>
PsramAllocator(const PsramAllocator<U>&) noexcept
{
}
T* allocate(std::size_t count)
{
if (count > std::numeric_limits<std::size_t>::max() / sizeof(T))
{
throw std::bad_alloc();
}
const std::size_t bytes = count * sizeof(T);
if (bytes == 0)
{
return nullptr;
}
#if defined(ESP_PLATFORM)
void* ptr = heap_caps_malloc(bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
void* ptr = std::malloc(bytes);
#endif
if (!ptr)
{
throw std::bad_alloc();
}
return static_cast<T*>(ptr);
}
void deallocate(T* ptr, std::size_t) noexcept
{
if (!ptr)
{
return;
}
#if defined(ESP_PLATFORM)
heap_caps_free(ptr);
#else
std::free(ptr);
#endif
}
template <typename U>
struct rebind
{
using other = PsramAllocator<U>;
};
};
template <typename T, typename U>
bool operator==(const PsramAllocator<T>&, const PsramAllocator<U>&) noexcept
{
return true;
}
template <typename T, typename U>
bool operator!=(const PsramAllocator<T>&, const PsramAllocator<U>&) noexcept
{
return false;
}
using ResourcePayloadBuffer = std::vector<uint8_t, PsramAllocator<uint8_t>>;
using ResourcePayloadList = std::vector<ResourcePayloadBuffer>;
} // namespace chat::lxmf::runtime
@@ -26,7 +26,7 @@ struct PropagationRuntimeLimits
struct PropagationMessageSelection
{
std::vector<std::vector<uint8_t>> messages;
std::vector<ByteSpan> messages;
uint32_t served_count = 0;
};
@@ -42,7 +42,7 @@ struct PropagationServiceResponse
bool send_response = false;
bool response_data_is_nil = false;
bool offer_validated = false;
std::vector<uint8_t> packed_response;
ResourcePayloadBuffer packed_response;
};
enum class PropagationMessageAction : uint8_t
@@ -69,7 +69,7 @@ struct PropagationMessageAcceptance
PropagationMessageAction action = PropagationMessageAction::Rejected;
uint8_t transient_id[reticulum::kFullHashSize] = {};
uint8_t destination_hash[reticulum::kTruncatedHashSize] = {};
std::vector<uint8_t> local_delivery_payload;
ResourcePayloadBuffer local_delivery_payload;
};
struct PropagationBatchContext
@@ -107,7 +107,7 @@ bool recordResourcePart(LinkResourceTransfer& resource,
ResourceAssemblyResult appendResourceAssemblySegment(
LinkSession& session,
LinkResourceTransfer& resource,
std::vector<uint8_t>& payload_data,
ResourcePayloadBuffer& payload_data,
uint32_t now_ms);
void markResourceComplete(LinkResourceTransfer& resource, uint32_t now_ms);
@@ -9,6 +9,7 @@
#include "chat/infra/lxmf/lxmf_wire.h"
#include "chat/infra/reticulum/lxst_call_state_machine.h"
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_identity.h"
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_memory.h"
#include <array>
#include <cstddef>
@@ -142,7 +143,7 @@ struct LinkPendingRequest
struct DeferredLinkPayload
{
std::vector<uint8_t> payload;
ResourcePayloadBuffer payload;
std::vector<uint8_t> request_id;
uint32_t message_id = 0;
uint8_t resource_flags = 0;
@@ -165,7 +166,7 @@ struct LinkResourceTransfer
std::vector<uint8_t> hashmap;
std::vector<std::array<uint8_t, 4>> map_hashes;
std::vector<uint8_t> map_hash_known;
std::vector<std::vector<uint8_t>> parts;
ResourcePayloadList parts;
std::vector<uint8_t> received_bitmap;
uint32_t data_size = 0;
uint32_t transfer_size = 0;
@@ -194,7 +195,7 @@ struct LinkResourceAssembly
{
uint8_t original_hash[reticulum::kFullHashSize] = {};
std::vector<uint8_t> request_id;
std::vector<uint8_t> payload;
ResourcePayloadBuffer payload;
uint32_t next_segment_index = 1;
uint32_t total_segments = 1;
uint32_t last_activity_ms = 0;
@@ -250,7 +251,7 @@ struct PropagationEntry
{
uint8_t transient_id[reticulum::kFullHashSize] = {};
uint8_t destination_hash[reticulum::kTruncatedHashSize] = {};
std::vector<uint8_t> lxmf_data;
ResourcePayloadBuffer lxmf_data;
uint32_t created_s = 0;
uint32_t served_count = 0;
};
@@ -278,7 +279,7 @@ struct PendingPropagationUpload
uint8_t destination_hash[reticulum::kTruncatedHashSize] = {};
uint8_t message_hash[reticulum::kFullHashSize] = {};
uint8_t transient_id[reticulum::kFullHashSize] = {};
std::vector<uint8_t> transient_data;
ResourcePayloadBuffer transient_data;
uint32_t created_ms = 0;
uint32_t message_id = 0;
uint8_t stamp_cost = 0;
@@ -18,6 +18,7 @@
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_propagation_service_runtime.h"
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_resource_runtime.h"
#include "platform/esp/arduino_common/chat/infra/lxmf/lxmf_transport_runtime.h"
#include "platform/esp/common/mbedtls_sha256_compat.h"
#include "platform/esp/common/reticulum_crypto_compat.h"
#include "platform/esp/common/reticulum_runtime_compat.h"
#include "platform/ui/gps_runtime.h"
@@ -47,6 +48,7 @@ namespace rtdir = ::platform::ui::reticulum_directory;
namespace rtnet = ::platform::ui::reticulum_network_config;
namespace rtpage = ::platform::ui::reticulum_page;
namespace screen_runtime = ::platform::ui::screen;
namespace sha_compat = ::platform::esp::common::crypto;
using PageFailureKind = rtpage::RequestProgress::FailureKind;
constexpr size_t kMaxPacketLen = reticulum::kReticulumMtu;
@@ -636,6 +638,40 @@ bool hashesEqual(const uint8_t* a, const uint8_t* b, size_t len)
return true;
}
bool fullHashJoined(const uint8_t* first,
size_t first_len,
const uint8_t* second,
size_t second_len,
uint8_t out_hash[reticulum::kFullHashSize])
{
if (!out_hash || (!first && first_len != 0) || (!second && second_len != 0))
{
return false;
}
mbedtls_sha256_context sha;
mbedtls_sha256_init(&sha);
bool ok = sha_compat::sha256_starts(&sha, 0) == 0;
if (ok && first_len != 0)
{
ok = sha_compat::sha256_update(&sha, first, first_len) == 0;
}
if (ok && second_len != 0)
{
ok = sha_compat::sha256_update(&sha, second, second_len) == 0;
}
if (ok)
{
ok = sha_compat::sha256_finish(&sha, out_hash) == 0;
}
mbedtls_sha256_free(&sha);
if (!ok)
{
std::memset(out_hash, 0, reticulum::kFullHashSize);
}
return ok;
}
bool unpackRnsLxmfEnvelope(const uint8_t expected_destination_hash[reticulum::kTruncatedHashSize],
const uint8_t* payload, size_t payload_len,
DecodedEnvelope* out_envelope,
@@ -891,7 +927,7 @@ void bzip2PsramFree(void*, void* address)
bool decompressBzip2Payload(const uint8_t* compressed,
size_t compressed_len,
size_t expected_size,
std::vector<uint8_t>* out_payload,
runtime::ResourcePayloadBuffer* out_payload,
int* out_status)
{
if (out_status)
@@ -905,7 +941,7 @@ bool decompressBzip2Payload(const uint8_t* compressed,
return false;
}
std::vector<uint8_t> output(expected_size, 0);
runtime::ResourcePayloadBuffer output(expected_size, 0);
bz_stream stream{};
stream.bzalloc = bzip2PsramAlloc;
stream.bzfree = bzip2PsramFree;
@@ -1515,9 +1551,10 @@ bool LxmfAdapter::queueReadyPropagationUpload(
return false;
}
std::vector<std::vector<uint8_t>> messages;
messages.push_back(upload.transient_data);
std::vector<uint8_t> batch(upload.transient_data.size() + 32U, 0);
std::vector<ByteSpan> messages;
messages.push_back(ByteSpan{upload.transient_data.data(),
upload.transient_data.size()});
runtime::ResourcePayloadBuffer batch(upload.transient_data.size() + 32U, 0);
size_t batch_len = batch.size();
if (!encodePropagationBatch(static_cast<double>(currentTimestampSeconds()),
messages,
@@ -5394,7 +5431,7 @@ bool LxmfAdapter::handleLinkDataPacket(LinkSession& session,
return false;
}
std::vector<uint8_t> plaintext;
runtime::ResourcePayloadBuffer plaintext;
const uint8_t context = packet.context;
const bool raw_payload = packetContextUsesRawLinkPayload(context);
if (!raw_payload)
@@ -6366,14 +6403,14 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
saw_incoming_resource = true;
uint8_t full_hash[reticulum::kFullHashSize] = {};
std::vector<uint8_t> hash_material(packet.payload_len + sizeof(resource.random_hash), 0);
memcpy(hash_material.data(), packet.payload, packet.payload_len);
memcpy(hash_material.data() + packet.payload_len,
resource.random_hash,
sizeof(resource.random_hash));
reticulum::fullHash(hash_material.data(),
hash_material.size(),
full_hash);
if (!fullHashJoined(packet.payload,
packet.payload_len,
resource.random_hash,
sizeof(resource.random_hash),
full_hash))
{
return false;
}
bool complete = false;
std::size_t matched_index = resource.part_count;
@@ -6476,7 +6513,7 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
return true;
}
std::vector<uint8_t> assembled;
runtime::ResourcePayloadBuffer assembled;
assembled.reserve(resource.transfer_size);
for (const auto& part : resource.parts)
{
@@ -6487,7 +6524,7 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
assembled.resize(resource.transfer_size);
}
std::vector<uint8_t> resource_stream;
runtime::ResourcePayloadBuffer resource_stream;
if (resource.encrypted)
{
if (!decryptLinkPayload(session,
@@ -6518,7 +6555,7 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
resource_stream.data() + kResourceDataPrefixLen;
const size_t resource_payload_len =
resource_stream.size() - kResourceDataPrefixLen;
std::vector<uint8_t> payload_data;
runtime::ResourcePayloadBuffer payload_data;
if (resource.compressed)
{
int bz_status = BZ_OK;
@@ -6565,19 +6602,15 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
return false;
}
std::vector<uint8_t> resource_hash_material(payload_data.size() + sizeof(resource.random_hash), 0);
if (!payload_data.empty())
{
memcpy(resource_hash_material.data(), payload_data.data(), payload_data.size());
}
memcpy(resource_hash_material.data() + payload_data.size(),
resource.random_hash,
sizeof(resource.random_hash));
uint8_t expected_resource_hash[reticulum::kFullHashSize] = {};
reticulum::fullHash(resource_hash_material.data(),
resource_hash_material.size(),
expected_resource_hash);
if (!fullHashJoined(payload_data.data(),
payload_data.size(),
resource.random_hash,
sizeof(resource.random_hash),
expected_resource_hash))
{
return false;
}
if (!hashesEqual(expected_resource_hash,
resource.resource_hash,
reticulum::kFullHashSize))
@@ -6585,17 +6618,14 @@ bool LxmfAdapter::handleLinkResourcePart(LinkSession& session,
return false;
}
std::vector<uint8_t> proof_material(payload_data.size() + reticulum::kFullHashSize, 0);
if (!payload_data.empty())
if (!fullHashJoined(payload_data.data(),
payload_data.size(),
resource.resource_hash,
reticulum::kFullHashSize,
resource.expected_proof))
{
memcpy(proof_material.data(), payload_data.data(), payload_data.size());
return false;
}
memcpy(proof_material.data() + payload_data.size(),
resource.resource_hash,
reticulum::kFullHashSize);
reticulum::fullHash(proof_material.data(),
proof_material.size(),
resource.expected_proof);
std::array<uint8_t, reticulum::kFullHashSize * 2> proof_payload{};
memcpy(proof_payload.data(), resource.resource_hash, reticulum::kFullHashSize);
@@ -8574,14 +8604,16 @@ bool LxmfAdapter::encryptLinkPayload(const LinkSession& session,
bool LxmfAdapter::decryptLinkPayload(const LinkSession& session,
const uint8_t* payload, size_t payload_len,
std::vector<uint8_t>* out_plaintext) const
runtime::ResourcePayloadBuffer* out_plaintext) const
{
if (!payload || payload_len == 0 || !out_plaintext)
{
return false;
}
std::vector<uint8_t> plaintext(reticulum::paddedTokenPlaintextSize(payload_len), 0);
runtime::ResourcePayloadBuffer plaintext(
reticulum::paddedTokenPlaintextSize(payload_len),
0);
size_t plaintext_len = plaintext.size();
if (!reticulum::tokenDecrypt(session.derived_key,
payload,
@@ -9450,12 +9482,12 @@ bool LxmfAdapter::queueOutgoingResource(LinkSession& session,
return false;
}
std::vector<uint8_t> stream(kResourceDataPrefixLen + len, 0);
runtime::ResourcePayloadBuffer stream(kResourceDataPrefixLen + len, 0);
fillRandomBytes(stream.data(), kResourceDataPrefixLen);
memcpy(stream.data() + kResourceDataPrefixLen, data, len);
const size_t encrypted_capacity = reticulum::tokenSizeForPlaintext(stream.size());
std::vector<uint8_t> encrypted_stream(encrypted_capacity, 0);
runtime::ResourcePayloadBuffer encrypted_stream(encrypted_capacity, 0);
size_t encrypted_len = encrypted_stream.size();
uint8_t iv[reticulum::kTokenIvSize] = {};
fillRandomBytes(iv, sizeof(iv));
@@ -9500,22 +9532,24 @@ bool LxmfAdapter::queueOutgoingResource(LinkSession& session,
{
fillRandomBytes(resource.random_hash, sizeof(resource.random_hash));
std::vector<uint8_t> hash_material(len + sizeof(resource.random_hash), 0);
memcpy(hash_material.data(), data, len);
memcpy(hash_material.data() + len, resource.random_hash, sizeof(resource.random_hash));
reticulum::fullHash(hash_material.data(),
hash_material.size(),
resource.resource_hash);
if (!fullHashJoined(data,
len,
resource.random_hash,
sizeof(resource.random_hash),
resource.resource_hash))
{
return false;
}
memcpy(resource.original_hash, resource.resource_hash, sizeof(resource.original_hash));
std::vector<uint8_t> proof_material(len + reticulum::kFullHashSize, 0);
memcpy(proof_material.data(), data, len);
memcpy(proof_material.data() + len,
resource.resource_hash,
reticulum::kFullHashSize);
reticulum::fullHash(proof_material.data(),
proof_material.size(),
resource.expected_proof);
if (!fullHashJoined(data,
len,
resource.resource_hash,
reticulum::kFullHashSize,
resource.expected_proof))
{
return false;
}
resource.hashmap.clear();
std::vector<std::array<uint8_t, kResourceMapHashLen>> recent_hashes;
@@ -9531,14 +9565,15 @@ bool LxmfAdapter::queueOutgoingResource(LinkSession& session,
resource.parts[index].assign(encrypted_stream.begin() + offset,
encrypted_stream.begin() + offset + chunk_len);
std::vector<uint8_t> map_material(chunk_len + sizeof(resource.random_hash), 0);
memcpy(map_material.data(), resource.parts[index].data(), chunk_len);
memcpy(map_material.data() + chunk_len,
resource.random_hash,
sizeof(resource.random_hash));
uint8_t full_hash[reticulum::kFullHashSize] = {};
reticulum::fullHash(map_material.data(), map_material.size(), full_hash);
if (!fullHashJoined(resource.parts[index].data(),
chunk_len,
resource.random_hash,
sizeof(resource.random_hash),
full_hash))
{
return false;
}
std::array<uint8_t, kResourceMapHashLen> map_hash{};
memcpy(map_hash.data(), full_hash, map_hash.size());
@@ -571,7 +571,8 @@ PropagationMessageSelection collectPropagationMessagesForWants(
break;
}
selection.messages.push_back(entry->lxmf_data);
selection.messages.push_back(ByteSpan{entry->lxmf_data.data(),
entry->lxmf_data.size()});
cumulative_size = next_size;
entry->served_count += 1;
selection.served_count += 1;
@@ -48,7 +48,7 @@ bool packUintResponse(uint32_t value, PropagationServiceResponse* out_response)
return false;
}
std::vector<uint8_t> packed(8, 0);
ResourcePayloadBuffer packed(8, 0);
std::size_t packed_len = packed.size();
if (!encodeMsgpackUint(value, packed.data(), &packed_len))
{
@@ -68,7 +68,7 @@ bool packBoolResponse(bool value, PropagationServiceResponse* out_response)
return false;
}
std::vector<uint8_t> packed(4, 0);
ResourcePayloadBuffer packed(4, 0);
std::size_t packed_len = packed.size();
if (!encodeMsgpackBool(value, packed.data(), &packed_len))
{
@@ -91,7 +91,7 @@ bool packIdListResponse(const std::vector<std::vector<uint8_t>>& items,
const std::size_t response_capacity =
4 + (items.size() * (reticulum::kFullHashSize + 3));
std::vector<uint8_t> packed(response_capacity, 0);
ResourcePayloadBuffer packed(response_capacity, 0);
std::size_t packed_len = packed.size();
if (!encodePropagationIdListPayload(items, packed.data(), &packed_len))
{
@@ -104,7 +104,7 @@ bool packIdListResponse(const std::vector<std::vector<uint8_t>>& items,
return true;
}
bool packMessageListResponse(const std::vector<std::vector<uint8_t>>& items,
bool packMessageListResponse(const std::vector<ByteSpan>& items,
PropagationServiceResponse* out_response)
{
if (!out_response)
@@ -115,9 +115,9 @@ bool packMessageListResponse(const std::vector<std::vector<uint8_t>>& items,
std::size_t response_capacity = 4;
for (const auto& item : items)
{
response_capacity += item.size() + 3;
response_capacity += item.size + 3;
}
std::vector<uint8_t> packed(response_capacity, 0);
ResourcePayloadBuffer packed(response_capacity, 0);
std::size_t packed_len = packed.size();
if (!encodePropagationMessageListPayload(items, packed.data(), &packed_len))
{
@@ -438,7 +438,7 @@ bool recordResourcePart(LinkResourceTransfer& resource,
ResourceAssemblyResult appendResourceAssemblySegment(
LinkSession& session,
LinkResourceTransfer& resource,
std::vector<uint8_t>& payload_data,
ResourcePayloadBuffer& payload_data,
uint32_t now_ms)
{
if (!resource.split && resource.total_segments <= 1)
@@ -562,7 +562,7 @@ int main()
true,
600,
1));
std::vector<uint8_t> split_payload_1{0x11, 0x12};
ResourcePayloadBuffer split_payload_1{0x11, 0x12};
assert(appendResourceAssemblySegment(resource_session,
split_segment_1,
split_payload_1,
@@ -574,7 +574,7 @@ int main()
LinkResourceTransfer split_segment_2 = split_segment_1;
split_segment_2.segment_index = 2;
split_segment_2.last_activity_ms = 620;
std::vector<uint8_t> split_payload_2{0x13};
ResourcePayloadBuffer split_payload_2{0x13};
assert(appendResourceAssemblySegment(resource_session,
split_segment_2,
split_payload_2,