Browse Source
- ll_queue: Add dgram, dgram_free_fn, dgram_pool, len fields to ll_entry - ll_queue: Remove ref_count, simplify memory management - etcp: Add normalizer pointer to ETCP_CONN struct - pkt_normalizer: Major refactoring for queue integration - tests: Update for new ll_entry structure - cleanup: Remove backup filesnodeinfo-routing-update
11 changed files with 1477 additions and 2318 deletions
@ -1,606 +1,355 @@ |
|||||||
|
// pkt_normalizer.c - Implementation of packet normalizer for ETCP
|
||||||
#include "pkt_normalizer.h" |
#include "pkt_normalizer.h" |
||||||
#include "../lib/u_async.h" |
#include "etcp.h" // For ETCP_CONN and related structures |
||||||
|
#include "ll_queue.h" // For queue operations |
||||||
|
#include "u_async.h" // For UASYNC |
||||||
#include <stdlib.h> |
#include <stdlib.h> |
||||||
#include <string.h> |
#include <string.h> |
||||||
#include <stdint.h> |
#include <stdio.h> // For debugging (can be removed if not needed) |
||||||
#include <stdio.h> |
|
||||||
static void packer_handler(struct ll_queue* q, void* arg); |
// Internal helper to convert void* data to struct ll_entry*
|
||||||
static void unpacker_handler(struct ll_queue* q, void* arg); |
static inline struct ll_entry* data_to_entry(void* data) { |
||||||
static void send_buf(struct pn_struct* pn); |
if (!data) return NULL; |
||||||
static int get_header(uint8_t* header, size_t L); |
return (struct ll_entry*)data; |
||||||
/* Calculate fragment size from mtu */ |
} |
||||||
struct pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer, int mtu) { |
|
||||||
struct pn_struct* pn = malloc(sizeof(struct pn_struct)); |
// Forward declarations
|
||||||
|
static void packer_cb(struct ll_queue* q, void* arg); |
||||||
|
static void flush_cb(void* arg); |
||||||
|
static void input_ready_cb(struct ll_queue* q, void* arg); |
||||||
|
|
||||||
|
// Initialization
|
||||||
|
struct PKTNORM* pn_init(struct ETCP_CONN* etcp) { |
||||||
|
if (!etcp) return NULL; |
||||||
|
|
||||||
|
struct PKTNORM* pn = calloc(1, sizeof(struct PKTNORM)); |
||||||
if (!pn) return NULL; |
if (!pn) return NULL; |
||||||
pn->ua = ua; |
|
||||||
pn->input = queue_new(ua, 0); // No memory pool for now, no hash table
|
pn->etcp = etcp; |
||||||
if (!pn->input) { |
pn->ua = etcp->instance->ua; |
||||||
free(pn); |
pn->pkt_size = etcp->mtu; // Use MTU as fixed packet size (adjust if headers need subtraction)
|
||||||
return NULL; |
|
||||||
} |
pn->input = queue_new(pn->ua, 0); // No hash needed
|
||||||
pn->output = queue_new(ua, 0); // No memory pool for now, no hash table
|
pn->output = queue_new(pn->ua, 0); // No hash needed
|
||||||
if (!pn->output) { |
pn->pending = queue_new(pn->ua, 0); |
||||||
queue_free(pn->input); |
pn->send_pending = queue_new(pn->ua, 0); |
||||||
free(pn); |
|
||||||
|
if (!pn->input || !pn->output || !pn->pending || !pn->send_pending) { |
||||||
|
pn_pair_deinit(pn); |
||||||
return NULL; |
return NULL; |
||||||
} |
} |
||||||
pn->is_packer = is_packer; |
|
||||||
if (is_packer) { |
pn->in_buf = calloc(1, pn->pkt_size); |
||||||
// Calculate fragment size from mtu: fragment = mtu - 100
|
pn->cap = pn->pkt_size; // For packer buffer (fixed size)
|
||||||
int fragment_size = mtu - ETCP_OVERHEAD; |
pn->len = 0; // Current length in packer buffer
|
||||||
if (fragment_size < 256) fragment_size = 256; // Minimum sane value
|
|
||||||
pn->u.packer.cap = fragment_size; |
pn->out_buf = calloc(1, pn->pkt_size * 10); // Arbitrary large buffer for assembly
|
||||||
pn->u.packer.buf = malloc(pn->u.packer.cap); |
pn->cap = pn->pkt_size * 10; // Initial capacity for unpacker (can reallocate if needed)
|
||||||
if (!pn->u.packer.buf) { |
pn->len = 0; |
||||||
queue_free(pn->input); |
pn->total_len = 0; |
||||||
queue_free(pn->output); |
pn->in_fragment = 0; |
||||||
free(pn); |
|
||||||
return NULL; |
// Set callback for automatic processing when items are added to input
|
||||||
} |
queue_set_callback(pn->input, packer_cb, pn); |
||||||
pn->u.packer.len = 0; |
|
||||||
pn->u.packer.error_count = 0; |
pn->flush_timer = NULL; |
||||||
queue_set_callback(pn->input, packer_handler, pn); |
|
||||||
} else { |
|
||||||
pn->u.unpacker.buf = NULL; |
|
||||||
pn->u.unpacker.len = 0; |
|
||||||
pn->u.unpacker.total_len = 0; |
|
||||||
pn->u.unpacker.cap = 0; |
|
||||||
pn->u.unpacker.error_count = 0; |
|
||||||
queue_set_callback(pn->input, unpacker_handler, pn); |
|
||||||
} |
|
||||||
return pn; |
return pn; |
||||||
} |
} |
||||||
|
|
||||||
void pkt_normalizer_deinit(struct pn_struct* pn) { |
// Deinitialization
|
||||||
|
void pn_pair_deinit(struct PKTNORM* pn) { |
||||||
if (!pn) return; |
if (!pn) return; |
||||||
queue_free(pn->input); |
|
||||||
queue_free(pn->output); |
// Drain and free queues
|
||||||
if (pn->is_packer) { |
if (pn->input) { |
||||||
free(pn->u.packer.buf); |
void* data; |
||||||
} else { |
while ((data = queue_data_get(pn->input)) != NULL) { |
||||||
free(pn->u.unpacker.buf); |
queue_data_free(data); |
||||||
free(pn->u.unpacker.service_buf); |
} |
||||||
|
queue_free(pn->input); |
||||||
} |
} |
||||||
free(pn); |
if (pn->output) { |
||||||
} |
void* data; |
||||||
struct pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua, int mtu) { |
while ((data = queue_data_get(pn->output)) != NULL) { |
||||||
struct pkt_normalizer_pair* pair = malloc(sizeof(struct pkt_normalizer_pair)); |
queue_data_free(data); |
||||||
if (!pair) return NULL; |
} |
||||||
pair->packer = pkt_normalizer_init(ua, 1, mtu); |
queue_free(pn->output); |
||||||
if (!pair->packer) { |
|
||||||
free(pair); |
|
||||||
return NULL; |
|
||||||
} |
} |
||||||
pair->unpacker = pkt_normalizer_init(ua, 0, mtu); |
if (pn->pending) { |
||||||
if (!pair->unpacker) { |
void* data; |
||||||
pkt_normalizer_deinit(pair->packer); |
while ((data = queue_data_get(pn->pending)) != NULL) { |
||||||
free(pair); |
queue_data_free(data); |
||||||
return NULL; |
} |
||||||
|
queue_free(pn->pending); |
||||||
} |
} |
||||||
return pair; |
if (pn->send_pending) { |
||||||
} |
void* data; |
||||||
void pkt_normalizer_pair_deinit(struct pkt_normalizer_pair* pair) { |
while ((data = queue_data_get(pn->send_pending)) != NULL) { |
||||||
if (!pair) return; |
struct ETCP_FRAGMENT* frag = data_to_entry(data); |
||||||
pkt_normalizer_deinit(pair->packer); |
if (frag->ll.dgram) memory_pool_free(pn->etcp->instance->data_pool, frag->ll.dgram); |
||||||
pkt_normalizer_deinit(pair->unpacker); |
memory_pool_free(pn->etcp->rx_pool, frag); |
||||||
free(pair); |
} |
||||||
} |
queue_free(pn->send_pending); |
||||||
static int get_header(uint8_t* header, size_t L) { |
|
||||||
if (L > 1535) return -1; |
|
||||||
if (L <= 239) { |
|
||||||
header[0] = (uint8_t)L; |
|
||||||
return 1; |
|
||||||
} else { |
|
||||||
uint8_t high = (uint8_t)(L >> 8); |
|
||||||
if (high > 5) return -1; |
|
||||||
header[0] = 0xF0 + high; |
|
||||||
header[1] = (uint8_t)(L & 0xFF); |
|
||||||
return 2; |
|
||||||
} |
} |
||||||
} |
|
||||||
/* Сбросить состояние сборки фрагментов */ |
if (pn->flush_timer) { |
||||||
static void reset_fragment_state(struct pn_struct* pn) { |
uasync_cancel_timeout(pn->ua, pn->flush_timer); |
||||||
if (!pn->is_packer) { |
|
||||||
pn->u.unpacker.len = 0; |
|
||||||
pn->u.unpacker.total_len = 0; |
|
||||||
pn->u.unpacker.in_fragment = 0; |
|
||||||
} |
} |
||||||
|
|
||||||
|
free(pn->in_buf); |
||||||
|
free(pn->out_buf); |
||||||
|
free(pn); |
||||||
} |
} |
||||||
/* Таймаут для сборки фрагментов */ |
|
||||||
static void send_buf(struct pn_struct* pn) { |
// Reset unpacker state
|
||||||
if (pn->u.packer.len == 0) return; |
void pn_unpacker_reset_state(struct PKTNORM* pn) { |
||||||
size_t payload_len = pn->u.packer.len; |
if (!pn) return; |
||||||
uint8_t* out = queue_data_new(2 + payload_len); // Новый API
|
pn->len = 0; |
||||||
if (!out) return; |
pn->total_len = 0; |
||||||
*(uint16_t*)out = (uint16_t)payload_len; |
pn->in_fragment = 0; |
||||||
memcpy(out + 2, pn->u.packer.buf, payload_len); |
|
||||||
queue_data_put(pn->output, out, 0); // Новый API, ID=0 для внутренних пакетов
|
|
||||||
pn->u.packer.len = 0; |
|
||||||
} |
} |
||||||
static void packer_handler(struct ll_queue* q, void* arg) { |
|
||||||
struct pn_struct* pn = arg; |
// Send data to packer (copies and adds to input queue or pending, triggering callback)
|
||||||
size_t max = (size_t)1400; |
void pn_packer_send(struct PKTNORM* pn, uint8_t* data, uint16_t len) { |
||||||
|
if (!pn || !data || len == 0) return; |
||||||
// Получить данные из очереди
|
|
||||||
void* pkt_data = queue_data_get(q); |
void* entry_data = queue_data_new(len); |
||||||
if (!pkt_data) { |
if (!entry_data) return; |
||||||
queue_resume_callback(q); |
|
||||||
return; |
struct ll_entry* entry = data_to_entry(entry_data); |
||||||
} |
memcpy(entry->data, data, len); |
||||||
|
entry->len = len; |
||||||
// Для определения размера нужно знать структуру, но в новом API размер в самих данных
|
entry->size = len; |
||||||
// В pkt_normalizer все данные имеют 2-байтовый заголовок с размером
|
|
||||||
uint16_t* size_ptr = (uint16_t*)pkt_data; |
// To minimize delay, add to input only if empty, else to pending and set waiter if not set
|
||||||
size_t L = *size_ptr; |
if (queue_entry_count(pn->input) == 0 && pn->input->waiter.callback == NULL) { |
||||||
uint8_t* pkt_payload = (uint8_t*)pkt_data + 2; |
queue_data_put(pn->input, entry_data, 0); |
||||||
uint8_t header[2]; |
|
||||||
int hsize = get_header(header, L); |
|
||||||
size_t needed = (size_t)hsize + L; |
|
||||||
if (hsize < 0 || needed > max) { |
|
||||||
// Fragment
|
|
||||||
if (pn->u.packer.len > 0) { |
|
||||||
send_buf(pn); |
|
||||||
} |
|
||||||
size_t remaining = L; |
|
||||||
size_t pos = 0; |
|
||||||
int fragment_count = 0; |
|
||||||
while (remaining > 0) { |
|
||||||
size_t chunk; |
|
||||||
size_t payload_len; |
|
||||||
uint8_t* fout; |
|
||||||
uint8_t* fd; |
|
||||||
uint8_t frag_header[2]; |
|
||||||
int frag_hsize; |
|
||||||
if (fragment_count == 0) { |
|
||||||
// Первый фрагмент: FF + общая длина (2 байта)
|
|
||||||
chunk = remaining > (max - 5) ? (max - 5) : remaining; // 2+1+2+chunk <= max
|
|
||||||
payload_len = 1 + 2 + chunk; // FF + total_len + data
|
|
||||||
fout = queue_data_new(2 + payload_len); |
|
||||||
if (!fout) { |
|
||||||
break; |
|
||||||
} |
|
||||||
fd = fout; |
|
||||||
*(uint16_t*)fd = (uint16_t)payload_len; |
|
||||||
fd += 2; |
|
||||||
*fd++ = 0xFF; |
|
||||||
*fd++ = (uint8_t)(L >> 8); // старший байт общей длины
|
|
||||||
*fd++ = (uint8_t)(L & 0xFF); // младший байт общей длины
|
|
||||||
} else { |
|
||||||
// Не первый фрагмент
|
|
||||||
if (remaining <= max - 3) { |
|
||||||
// Это последний возможный фрагмент (помещается в один пакет с префиксом FE)
|
|
||||||
// Пытаемся отправить как обычный блок
|
|
||||||
frag_hsize = get_header(frag_header, remaining); |
|
||||||
if (frag_hsize > 0 && (size_t)frag_hsize + remaining + 2 <= max) { |
|
||||||
// Успешно: обычный блок
|
|
||||||
payload_len = frag_hsize + remaining; |
|
||||||
chunk = remaining; |
|
||||||
fout = queue_data_new(2 + payload_len); |
|
||||||
if (!fout) { |
|
||||||
break; |
|
||||||
} |
|
||||||
fd = fout; |
|
||||||
*(uint16_t*)fd = (uint16_t)payload_len; |
|
||||||
fd += 2; |
|
||||||
memcpy(fd, frag_header, frag_hsize); |
|
||||||
fd += frag_hsize; |
|
||||||
} else { |
|
||||||
// Не удалось отправить как обычный блок - разбиваем на 2 фрагмента
|
|
||||||
// 1. FE фрагмент с частью данных
|
|
||||||
// 2. Обычный блок с оставшимися данными
|
|
||||||
// Находим максимальный размер для FE фрагмента
|
|
||||||
size_t max_fe_data = max - 3; // 2 байта длины + 0xFE
|
|
||||||
if (max_fe_data > remaining) { |
|
||||||
max_fe_data = remaining; |
|
||||||
} |
|
||||||
// Пробуем различные размеры, начиная с максимального
|
|
||||||
size_t fe_data_size = 0; |
|
||||||
for (size_t try_fe = max_fe_data; try_fe > 0; try_fe--) { |
|
||||||
size_t try_regular = remaining - try_fe; |
|
||||||
if (try_regular == 0) continue; // Нужно отправить что-то как обычный блок
|
|
||||||
uint8_t test_header[2]; |
|
||||||
int hsize = get_header(test_header, try_regular); |
|
||||||
if (hsize <= 0) continue; |
|
||||||
if ((size_t)hsize + try_regular + 2 <= max) { |
|
||||||
fe_data_size = try_fe; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
if (fe_data_size == 0) { |
|
||||||
// Не удалось найти разбиение - ошибка
|
|
||||||
pn->u.packer.error_count++; |
|
||||||
// Отправляем как FE (нарушение спецификации, но это крайний случай)
|
|
||||||
chunk = remaining > (max - 3) ? (max - 3) : remaining; |
|
||||||
payload_len = 1 + chunk; // FE + data
|
|
||||||
fout = queue_data_new(2 + payload_len); |
|
||||||
if (!fout) break; |
|
||||||
fd = (fout); |
|
||||||
*(uint16_t*)fd = (uint16_t)payload_len; |
|
||||||
fd += 2; |
|
||||||
*fd++ = 0xFE; |
|
||||||
} else { |
|
||||||
// Отправляем FE фрагмент
|
|
||||||
chunk = fe_data_size; |
|
||||||
payload_len = 1 + chunk; // FE + data
|
|
||||||
fout = queue_data_new(2 + payload_len); |
|
||||||
if (!fout) break; |
|
||||||
fd = (fout); |
|
||||||
*(uint16_t*)fd = (uint16_t)payload_len; |
|
||||||
fd += 2; |
|
||||||
*fd++ = 0xFE; |
|
||||||
memcpy(fd, pkt_data + pos, chunk); |
|
||||||
queue_data_put(pn->output, fout, 0); |
|
||||||
pos += chunk; |
|
||||||
remaining -= chunk; |
|
||||||
fragment_count++; |
|
||||||
// Обновляем оставшиеся данные для обычного блока
|
|
||||||
// (цикл продолжит обработку на следующей итерации)
|
|
||||||
continue; |
|
||||||
} |
|
||||||
} |
|
||||||
} else { |
|
||||||
// Промежуточный фрагмент, отправляем как FE
|
|
||||||
chunk = remaining > (max - 3) ? (max - 3) : remaining; |
|
||||||
payload_len = 1 + chunk; // FE + data
|
|
||||||
fout = queue_data_new(2 + payload_len); |
|
||||||
if (!fout) { |
|
||||||
break; |
|
||||||
} |
|
||||||
fd = fout; |
|
||||||
*(uint16_t*)fd = (uint16_t)payload_len; |
|
||||||
fd += 2; |
|
||||||
*fd++ = 0xFE; |
|
||||||
} |
|
||||||
} |
|
||||||
memcpy(fd, pkt_data + pos, chunk); |
|
||||||
queue_data_put(pn->output, fout, 0); |
|
||||||
pos += chunk; |
|
||||||
remaining -= chunk; |
|
||||||
fragment_count++; |
|
||||||
} |
|
||||||
} else { |
} else { |
||||||
if (pn->u.packer.len + needed > max) { |
queue_data_put(pn->pending, entry_data, 0); |
||||||
send_buf(pn); |
if (pn->input->waiter.callback == NULL) { |
||||||
|
queue_wait_threshold(pn->input, 0, 0, input_ready_cb, pn); |
||||||
} |
} |
||||||
// Add to buffer
|
|
||||||
uint8_t* p = pn->u.packer.buf + pn->u.packer.len; |
|
||||||
memcpy(p, header, (size_t)hsize); |
|
||||||
memcpy(p + hsize, pkt_data, L); |
|
||||||
pn->u.packer.len += needed; |
|
||||||
} |
} |
||||||
queue_data_free(pkt_data); |
|
||||||
if (pn->u.packer.len > 0) { |
|
||||||
send_buf(pn); |
|
||||||
} |
|
||||||
queue_resume_callback(q); |
|
||||||
} |
|
||||||
void pkt_normalizer_set_service_callback(struct pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data) { |
|
||||||
if (!pn) return; |
|
||||||
pn->service_callback = callback; |
|
||||||
pn->service_callback_user_data = user_data; |
|
||||||
} |
} |
||||||
void pkt_normalizer_reset_service_state(struct pn_struct* pn) { |
|
||||||
if (!pn || pn->is_packer) return; |
// Internal: Callback when input queue becomes empty, move next from pending
|
||||||
if (pn->u.unpacker.in_service) { |
static void input_ready_cb(struct ll_queue* q, void* arg) { |
||||||
// Deliver pending service packet
|
struct PKTNORM* pn = (struct PKTNORM*)arg; |
||||||
if (pn->service_callback) { |
|
||||||
pn->service_callback(pn->service_callback_user_data, |
void* data = queue_data_get(pn->pending); |
||||||
pn->u.unpacker.service_type, |
if (data) { |
||||||
pn->u.unpacker.service_buf, |
queue_data_put(q, data, 0); // This will trigger packer_cb async if needed
|
||||||
pn->u.unpacker.service_len); |
if (queue_entry_count(pn->pending) > 0) { |
||||||
|
queue_wait_threshold(q, 0, 0, input_ready_cb, pn); |
||||||
} |
} |
||||||
free(pn->u.unpacker.service_buf); |
|
||||||
pn->u.unpacker.service_buf = NULL; |
|
||||||
pn->u.unpacker.service_len = 0; |
|
||||||
pn->u.unpacker.service_cap = 0; |
|
||||||
pn->u.unpacker.in_service = 0; |
|
||||||
} |
} |
||||||
} |
} |
||||||
void pkt_normalizer_reset_state(struct pn_struct* pn) { |
|
||||||
if (!pn) return; |
static void etcp_input_ready_cb(struct ll_queue* q, void* arg) { |
||||||
if (pn->is_packer) { |
struct PKTNORM* pn = (struct PKTNORM*)arg; |
||||||
// Flush packer buffer
|
|
||||||
if (pn->u.packer.len > 0) { |
void* data = queue_data_get(pn->send_pending); |
||||||
send_buf(pn); |
if (data) { |
||||||
|
queue_data_put(q, data, 0); |
||||||
|
if (queue_entry_count(pn->send_pending) > 0) { |
||||||
|
queue_wait_threshold(q, 0, 0, etcp_input_ready_cb, pn); |
||||||
} |
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Internal: Send a fixed-size chunk to etcp->input_queue (or send_pending)
|
||||||
|
static void send_chunk(struct PKTNORM* pn, uint8_t* chunk_data, uint16_t chunk_len) { |
||||||
|
// Alloc ETCP_FRAGMENT for the chunk (as payload for etcp)
|
||||||
|
struct ETCP_FRAGMENT* frag = memory_pool_alloc(pn->etcp->rx_pool); |
||||||
|
if (!frag) return; |
||||||
|
|
||||||
|
frag->ll.dgram = memory_pool_alloc(pn->etcp->instance->data_pool); |
||||||
|
if (!frag->ll.dgram) { |
||||||
|
memory_pool_free(pn->etcp->rx_pool, frag); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
memcpy(frag->ll.dgram, chunk_data, chunk_len); |
||||||
|
frag->ll.size = chunk_len; |
||||||
|
frag->ll.len = chunk_len; |
||||||
|
frag->seq = 0; // Not used for input
|
||||||
|
frag->timestamp = 0; |
||||||
|
|
||||||
|
struct ll_queue* eq = pn->etcp->input_queue; |
||||||
|
|
||||||
|
// Add to etcp input_queue only if empty, else to send_pending and set waiter
|
||||||
|
if (queue_entry_count(eq) == 0 && eq->waiter.callback == NULL) { |
||||||
|
queue_data_put(eq, frag, 0); |
||||||
} else { |
} else { |
||||||
// Reset unpacker fragment state
|
queue_data_put(pn->send_pending, frag, 0); |
||||||
reset_fragment_state(pn); |
if (eq->waiter.callback == NULL) { |
||||||
// Reset service state
|
queue_wait_threshold(eq, 0, 0, etcp_input_ready_cb, pn); |
||||||
pkt_normalizer_reset_service_state(pn); |
} |
||||||
} |
} |
||||||
} |
} |
||||||
int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* data, size_t len) { |
|
||||||
if (!pn || !pn->is_packer) return -1; |
// Internal: Packer callback (aggregates small, fragments large, sends chunks)
|
||||||
// Service packet ограничен 256 байтами всего
|
static void packer_cb(struct ll_queue* q, void* arg) { |
||||||
if (len > 256 - 2) return -1; // 2 байта на заголовок (0xFC + тип)
|
struct PKTNORM* pn = (struct PKTNORM*)arg; |
||||||
size_t max = (size_t)1400; |
if (!pn) return; |
||||||
if (max < 3) return -1; |
|
||||||
// Размер сервисного пакета: 2 байта длины + 1 байт 0xFC + 1 байт тип + данные
|
pn->len = 0; // Start with empty buffer for this batch
|
||||||
// Если не помещается в один фрагмент - используем продолжение 0xFD
|
|
||||||
size_t total_service_len = 1 + 1 + len; // 0xFC + type + data
|
while (q->head) { |
||||||
size_t pos = 0; |
void* data = queue_data_get(q); |
||||||
while (total_service_len > 0) { |
struct ll_entry* entry = data_to_entry(data); |
||||||
// Определяем размер куска для этого пакета
|
uint16_t item_len = entry->len; |
||||||
size_t chunk; |
|
||||||
uint8_t service_header; |
if (item_len + 2 > pn->pkt_size) { |
||||||
if (pos == 0) { |
// Large item: fragment (must be alone in buffer)
|
||||||
// Первый пакет: 0xFC + тип + часть данных
|
if (pn->len > 0) { |
||||||
// Максимум данных в первом пакете: max - 2 (длина) - 2 (0xFC+тип)
|
// Flush current aggregated small items first
|
||||||
size_t max_first_data = max - 4; |
memset(pn->in_buf + pn->len, 0, pn->pkt_size - pn->len); // Pad
|
||||||
if (max_first_data > len) max_first_data = len; |
send_chunk(pn, pn->in_buf, pn->pkt_size); |
||||||
chunk = max_first_data; |
pn->len = 0; |
||||||
service_header = 0xFC; |
} |
||||||
|
|
||||||
|
// Fragment the large item
|
||||||
|
uint8_t* ptr = entry->data; |
||||||
|
uint32_t remaining = item_len; |
||||||
|
|
||||||
|
// First fragment
|
||||||
|
*(uint16_t*)(pn->in_buf) = 0xffff; |
||||||
|
*(uint32_t*)(pn->in_buf + 2) = remaining; |
||||||
|
uint16_t chunk = pn->pkt_size - 6; |
||||||
|
memcpy(pn->in_buf + 6, ptr, chunk); |
||||||
|
send_chunk(pn, pn->in_buf, pn->pkt_size); |
||||||
|
ptr += chunk; |
||||||
|
remaining -= chunk; |
||||||
|
|
||||||
|
// Continuation fragments
|
||||||
|
while (remaining > 0) { |
||||||
|
chunk = (remaining < pn->pkt_size - 2) ? remaining : pn->pkt_size - 2; |
||||||
|
*(uint16_t*)(pn->in_buf) = 0x0000; // Continuation marker
|
||||||
|
memcpy(pn->in_buf + 2, ptr, chunk); |
||||||
|
memset(pn->in_buf + 2 + chunk, 0, pn->pkt_size - 2 - chunk); // Pad
|
||||||
|
send_chunk(pn, pn->in_buf, pn->pkt_size); |
||||||
|
ptr += chunk; |
||||||
|
remaining -= chunk; |
||||||
|
} |
||||||
} else { |
} else { |
||||||
// Продолжение: 0xFD + данные
|
// Small item: try to aggregate
|
||||||
// Максимум данных: max - 2 (длина) - 1 (0xFD)
|
if (pn->len + item_len + 2 > pn->pkt_size) { |
||||||
size_t max_cont_data = max - 3; |
// Flush current buffer
|
||||||
size_t remaining = len - pos; |
memset(pn->in_buf + pn->len, 0, pn->pkt_size - pn->len); // Pad
|
||||||
if (max_cont_data > remaining) max_cont_data = remaining; |
send_chunk(pn, pn->in_buf, pn->pkt_size); |
||||||
chunk = max_cont_data; |
pn->len = 0; |
||||||
service_header = 0xFD; |
} |
||||||
} |
|
||||||
if (chunk == 0) break; |
// Add to buffer
|
||||||
size_t payload_len = 1 + chunk + (pos == 0 ? 1 : 0); // +1 байт типа для первого пакета
|
*(uint16_t*)(pn->in_buf + pn->len) = item_len; |
||||||
struct ll_entry* entry = queue_data_new(2 + payload_len); |
memcpy(pn->in_buf + pn->len + 2, entry->data, item_len); |
||||||
if (!entry) return -1; |
pn->len += item_len + 2; |
||||||
uint8_t* d = (entry); |
|
||||||
*(uint16_t*)d = (uint16_t)payload_len; |
|
||||||
d += 2; |
|
||||||
*d++ = service_header; |
|
||||||
if (pos == 0) { |
|
||||||
*d++ = type; |
|
||||||
} |
} |
||||||
memcpy(d, (const uint8_t*)data + pos, chunk); |
|
||||||
queue_data_put(pn->output, entry, 0); |
queue_data_free(data); // Free the entry
|
||||||
pos += chunk; |
|
||||||
total_service_len -= chunk + (pos == chunk ? 2 : 1); // корректно вычитаем заголовки
|
|
||||||
} |
} |
||||||
return 0; |
|
||||||
} |
// If remaining in buffer, set timeout to flush if queues empty
|
||||||
int pkt_normalizer_get_error_count(const struct pn_struct* pn) { |
if (pn->len > 0) { |
||||||
if (!pn) return 0; |
if (pn->flush_timer) { |
||||||
if (pn->is_packer) { |
uasync_cancel_timeout(pn->ua, pn->flush_timer); |
||||||
return pn->u.packer.error_count; |
} |
||||||
|
pn->flush_timer = uasync_set_timeout(pn->ua, 10, pn, flush_cb); // 1ms = 10 * 0.1ms
|
||||||
} |
} |
||||||
return pn->u.unpacker.error_count; |
|
||||||
|
queue_resume_callback(q); |
||||||
} |
} |
||||||
void pkt_normalizer_reset_error_count(struct pn_struct* pn) { |
|
||||||
|
// Internal: Flush callback on timeout
|
||||||
|
static void flush_cb(void* arg) { |
||||||
|
struct PKTNORM* pn = (struct PKTNORM*)arg; |
||||||
if (!pn) return; |
if (!pn) return; |
||||||
if (pn->is_packer) { |
|
||||||
pn->u.packer.error_count = 0; |
pn->flush_timer = NULL; |
||||||
} else { |
|
||||||
pn->u.unpacker.error_count = 0; |
if (pn->len > 0 && queue_entry_count(pn->input) == 0 && queue_entry_count(pn->pending) == 0) { |
||||||
|
memset(pn->in_buf + pn->len, 0, pn->pkt_size - pn->len); // Pad
|
||||||
|
send_chunk(pn, pn->in_buf, pn->pkt_size); |
||||||
|
pn->len = 0; |
||||||
} |
} |
||||||
} |
} |
||||||
void pkt_normalizer_flush(struct pn_struct* pn) { |
|
||||||
if (!pn || !pn->is_packer) return; |
// Internal: Add assembled data to etcp->output_queue as ETCP_FRAGMENT
|
||||||
if (pn->u.packer.len > 0) { |
static void add_to_output(struct PKTNORM* pn, uint8_t* app_data, uint16_t app_len) { |
||||||
send_buf(pn); |
struct ETCP_FRAGMENT* frag = memory_pool_alloc(pn->etcp->rx_pool); |
||||||
|
if (!frag) return; |
||||||
|
|
||||||
|
frag->ll.dgram = memory_pool_alloc(pn->etcp->instance->data_pool); |
||||||
|
if (!frag->ll.dgram) { |
||||||
|
memory_pool_free(pn->etcp->rx_pool, frag); |
||||||
|
return; |
||||||
} |
} |
||||||
|
|
||||||
|
memcpy(frag->ll.dgram, app_data, app_len); |
||||||
|
frag->ll.size = app_len; |
||||||
|
frag->ll.len = app_len; |
||||||
|
frag->seq = 0; |
||||||
|
frag->timestamp = 0; |
||||||
|
|
||||||
|
queue_data_put(pn->etcp->output_queue, frag, 0); |
||||||
} |
} |
||||||
static void unpacker_handler(struct ll_queue* q, void* arg) { |
|
||||||
struct pn_struct* pn = arg; |
// Internal: Process incoming fixed-size payload chunk from etcp
|
||||||
while (queue_entry_count(q) > 0) { |
void pn_unpacker_input(struct PKTNORM* pn, uint8_t* data, uint16_t len) { |
||||||
uint8_t* entry = queue_data_get(q); |
if (!pn || len != pn->pkt_size) return; |
||||||
uint8_t* data = entry; |
|
||||||
uint16_t payload_len = *(uint16_t*)data; |
uint16_t header = *(uint16_t*)data; |
||||||
if (entry != NULL && *(uint16_t*)entry != 2 + (size_t)payload_len) { |
|
||||||
queue_data_free(entry); |
if (pn->in_fragment) { |
||||||
continue; |
// Expect continuation
|
||||||
|
if (header != 0x0000) { |
||||||
|
// Error: invalid continuation
|
||||||
|
pn_unpacker_reset_state(pn); |
||||||
|
return; |
||||||
} |
} |
||||||
uint8_t* cg = data + 2; |
|
||||||
size_t cg_pos = 0; |
uint16_t chunk = pn->pkt_size - 2; |
||||||
size_t cg_len = (size_t)payload_len; |
if (pn->len + chunk > pn->cap) { |
||||||
while (cg_pos < cg_len) { |
pn->cap *= 2; |
||||||
uint8_t byte = cg[cg_pos++]; |
pn->out_buf = realloc(pn->out_buf, pn->cap); |
||||||
if (byte == 0xFF) { |
} |
||||||
/* Начало нового фрагментированного пакета */ |
memcpy(pn->out_buf + pn->len, data + 2, chunk); |
||||||
if (pn->u.unpacker.in_fragment) { |
pn->len += chunk; |
||||||
/* Не завершен предыдущий фрагмент - ошибка */ |
|
||||||
pn->u.unpacker.error_count++; |
if (pn->len >= pn->total_len) { |
||||||
reset_fragment_state(pn); |
// Assembly complete: add to output_queue
|
||||||
} |
add_to_output(pn, pn->out_buf, pn->total_len); |
||||||
/* Проверить, что есть 2 байта для общей длины */ |
pn_unpacker_reset_state(pn); |
||||||
if (cg_pos + 2 > cg_len) { |
} |
||||||
pn->u.unpacker.error_count++; |
} else { |
||||||
goto err; |
if (header == 0xffff) { |
||||||
} |
// Start of fragment
|
||||||
/* Прочитать общую длину */ |
pn->total_len = *(uint32_t*)(data + 2); |
||||||
uint16_t total_len = ((uint16_t)cg[cg_pos] << 8) | cg[cg_pos + 1]; |
uint16_t chunk = pn->pkt_size - 6; |
||||||
cg_pos += 2; |
if (pn->total_len <= chunk) { |
||||||
size_t chunk_len = cg_len - cg_pos; |
// Invalid or degenerate case
|
||||||
/* Выделить буфер при необходимости */ |
pn_unpacker_reset_state(pn); |
||||||
size_t new_len = chunk_len; |
return; |
||||||
if (new_len > pn->u.unpacker.cap) { |
|
||||||
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; |
|
||||||
if (new_cap < new_len) new_cap = new_len; |
|
||||||
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); |
|
||||||
pn->u.unpacker.cap = new_cap; |
|
||||||
} |
|
||||||
memcpy(pn->u.unpacker.buf, cg + cg_pos, chunk_len); |
|
||||||
pn->u.unpacker.len = chunk_len; |
|
||||||
pn->u.unpacker.total_len = total_len; |
|
||||||
pn->u.unpacker.in_fragment = 1; |
|
||||||
cg_pos += chunk_len; |
|
||||||
/* Проверить, не собрали ли уже весь пакет */ |
|
||||||
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { |
|
||||||
if (pn->u.unpacker.len == pn->u.unpacker.total_len) { |
|
||||||
uint8_t* out = queue_data_new(pn->u.unpacker.total_len); |
|
||||||
if (out) { |
|
||||||
memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); |
|
||||||
queue_data_put(pn->output, out, 0); |
|
||||||
} |
|
||||||
} else { |
|
||||||
/* Слишком много данных - ошибка */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
} |
|
||||||
reset_fragment_state(pn); |
|
||||||
} |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (byte == 0xFE) { |
|
||||||
/* Продолжение фрагментированного пакета */ |
|
||||||
if (!pn->u.unpacker.in_fragment) { |
|
||||||
/* Не было начала фрагмента - ошибка */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
goto err; |
|
||||||
} |
|
||||||
size_t chunk_len = cg_len - cg_pos; |
|
||||||
size_t new_len = pn->u.unpacker.len + chunk_len; |
|
||||||
if (new_len > pn->u.unpacker.cap) { |
|
||||||
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; |
|
||||||
if (new_cap < new_len) new_cap = new_len; |
|
||||||
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); |
|
||||||
pn->u.unpacker.cap = new_cap; |
|
||||||
} |
|
||||||
memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, chunk_len); |
|
||||||
pn->u.unpacker.len = new_len; |
|
||||||
cg_pos += chunk_len; |
|
||||||
/* Проверить, не собрали ли уже весь пакет */ |
|
||||||
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { |
|
||||||
if (pn->u.unpacker.len == pn->u.unpacker.total_len) { |
|
||||||
uint8_t* out = queue_data_new(pn->u.unpacker.total_len); |
|
||||||
if (out) { |
|
||||||
memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); |
|
||||||
queue_data_put(pn->output, out, 0); |
|
||||||
} |
|
||||||
} else { |
|
||||||
/* Слишком много данных - ошибка */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
} |
|
||||||
reset_fragment_state(pn); |
|
||||||
} |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (byte == 0xFC || byte == 0xFD) { |
|
||||||
/* Service packet */ |
|
||||||
if (byte == 0xFC) { |
|
||||||
/* Start of service packet */ |
|
||||||
if (pn->u.unpacker.in_service) { |
|
||||||
/* Previous service packet finished - deliver it */ |
|
||||||
if (pn->service_callback) { |
|
||||||
pn->service_callback(pn->service_callback_user_data, |
|
||||||
pn->u.unpacker.service_type, |
|
||||||
pn->u.unpacker.service_buf, |
|
||||||
pn->u.unpacker.service_len); |
|
||||||
} |
|
||||||
free(pn->u.unpacker.service_buf); |
|
||||||
pn->u.unpacker.service_buf = NULL; |
|
||||||
pn->u.unpacker.service_len = 0; |
|
||||||
pn->u.unpacker.service_cap = 0; |
|
||||||
pn->u.unpacker.in_service = 0; |
|
||||||
} |
|
||||||
/* Read service type */ |
|
||||||
if (cg_pos >= cg_len) goto err; |
|
||||||
uint8_t service_type = cg[cg_pos++]; |
|
||||||
pn->u.unpacker.service_type = service_type; |
|
||||||
pn->u.unpacker.in_service = 1; |
|
||||||
pn->u.unpacker.service_len = 0; |
|
||||||
} else { |
|
||||||
/* 0xFD - continuation */ |
|
||||||
if (!pn->u.unpacker.in_service) { |
|
||||||
/* No service packet started - error */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
goto err; |
|
||||||
} |
|
||||||
} |
|
||||||
/* Read data */ |
|
||||||
size_t data_len = cg_len - cg_pos; |
|
||||||
if (data_len > 0) { |
|
||||||
size_t new_len = pn->u.unpacker.service_len + data_len; |
|
||||||
if (new_len > 256) { |
|
||||||
/* Service packet too long - error */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
free(pn->u.unpacker.service_buf); |
|
||||||
pn->u.unpacker.service_buf = NULL; |
|
||||||
pn->u.unpacker.service_len = 0; |
|
||||||
pn->u.unpacker.service_cap = 0; |
|
||||||
pn->u.unpacker.in_service = 0; |
|
||||||
goto err; |
|
||||||
} |
|
||||||
if (new_len > pn->u.unpacker.service_cap) { |
|
||||||
size_t new_cap = pn->u.unpacker.service_cap ? pn->u.unpacker.service_cap * 2 : 256; |
|
||||||
if (new_cap < new_len) new_cap = new_len; |
|
||||||
if (new_cap > 256) new_cap = 256; |
|
||||||
uint8_t* new_buf = realloc(pn->u.unpacker.service_buf, new_cap); |
|
||||||
if (!new_buf) { |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
goto err; |
|
||||||
} |
|
||||||
pn->u.unpacker.service_buf = new_buf; |
|
||||||
pn->u.unpacker.service_cap = new_cap; |
|
||||||
} |
|
||||||
memcpy(pn->u.unpacker.service_buf + pn->u.unpacker.service_len, cg + cg_pos, data_len); |
|
||||||
pn->u.unpacker.service_len = new_len; |
|
||||||
cg_pos += data_len; |
|
||||||
} |
|
||||||
/* Check if this is the end of service packet (end of payload) */ |
|
||||||
if (cg_pos >= cg_len) { |
|
||||||
/* End of current payload, but service packet may continue in next transport packet */ |
|
||||||
continue; |
|
||||||
} else { |
|
||||||
/* There is more data in this payload after service packet - error */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
free(pn->u.unpacker.service_buf); |
|
||||||
pn->u.unpacker.service_buf = NULL; |
|
||||||
pn->u.unpacker.service_len = 0; |
|
||||||
pn->u.unpacker.service_cap = 0; |
|
||||||
pn->u.unpacker.in_service = 0; |
|
||||||
goto err; |
|
||||||
} |
|
||||||
} |
|
||||||
/* Обычная запись (не фрагмент) */ |
|
||||||
size_t L; |
|
||||||
if (byte <= 0xEF) { |
|
||||||
L = byte; |
|
||||||
} else if (byte >= 0xF0 && byte <= 0xF5) { |
|
||||||
if (cg_pos >= cg_len) goto err; |
|
||||||
uint8_t ext = cg[cg_pos++]; |
|
||||||
L = ((size_t)(byte - 0xF0) << 8) | ext; |
|
||||||
} else { |
|
||||||
/* Недопустимый байт */ |
|
||||||
goto err; |
|
||||||
} |
} |
||||||
if (cg_pos + L > cg_len) goto err; |
memcpy(pn->out_buf, data + 6, chunk); |
||||||
if (pn->u.unpacker.in_fragment) { |
pn->len = chunk; |
||||||
/* Это последний фрагмент в виде обычной записи */ |
pn->in_fragment = 1; |
||||||
size_t new_len = pn->u.unpacker.len + L; |
} else { |
||||||
if (new_len > pn->u.unpacker.cap) { |
// Normal aggregated packet: process multiple items (ignore pad)
|
||||||
size_t new_cap = pn->u.unpacker.cap ? pn->u.unpacker.cap * 2 : 4096; |
uint8_t* ptr = data; |
||||||
if (new_cap < new_len) new_cap = new_len; |
size_t processed = 0; |
||||||
pn->u.unpacker.buf = realloc(pn->u.unpacker.buf, new_cap); |
while (processed < pn->pkt_size) { |
||||||
pn->u.unpacker.cap = new_cap; |
header = *(uint16_t*)ptr; |
||||||
} |
if (header == 0 || header == 0xffff || header == 0x0000) break; // End (pad) or error
|
||||||
memcpy(pn->u.unpacker.buf + pn->u.unpacker.len, cg + cg_pos, L); |
if (processed + 2 + header > pn->pkt_size) break; |
||||||
pn->u.unpacker.len = new_len; |
|
||||||
cg_pos += L; |
add_to_output(pn, ptr + 2, header); |
||||||
/* Проверить, собрали ли весь пакет */ |
|
||||||
if (pn->u.unpacker.len >= pn->u.unpacker.total_len) { |
ptr += 2 + header; |
||||||
if (pn->u.unpacker.len == pn->u.unpacker.total_len) { |
processed += 2 + header; |
||||||
uint8_t* out = queue_data_new(pn->u.unpacker.total_len); |
|
||||||
if (out) { |
|
||||||
memcpy(out, pn->u.unpacker.buf, pn->u.unpacker.total_len); |
|
||||||
queue_data_put(pn->output, out, 0); |
|
||||||
} |
|
||||||
} else { |
|
||||||
/* Слишком много данных - ошибка */ |
|
||||||
pn->u.unpacker.error_count++; |
|
||||||
} |
|
||||||
reset_fragment_state(pn); |
|
||||||
} |
|
||||||
} else { |
|
||||||
/* Обычная запись (не часть фрагмента) */ |
|
||||||
uint8_t* out = queue_data_new(L); |
|
||||||
if (out) { |
|
||||||
memcpy(out, cg + cg_pos, L); |
|
||||||
queue_data_put(pn->output, out, 0); |
|
||||||
} |
|
||||||
cg_pos += L; |
|
||||||
} |
} |
||||||
} |
} |
||||||
err: |
|
||||||
queue_data_free(entry); |
|
||||||
} |
} |
||||||
queue_resume_callback(q); |
|
||||||
} |
} |
||||||
|
|||||||
@ -1,75 +1,59 @@ |
|||||||
// pkt_normalizer.h
|
// pkt_normalizer.h (упрощенная версия)
|
||||||
#ifndef PKT_NORMALIZER_H |
#ifndef PKT_NORMALIZER_H |
||||||
#define PKT_NORMALIZER_H |
#define PKT_NORMALIZER_H |
||||||
|
|
||||||
#include "ll_queue.h" |
#include "../lib/ll_queue.h" |
||||||
#include "../lib/u_async.h" |
#include "../lib/u_async.h" |
||||||
#include <stdint.h> |
#include <stdint.h> |
||||||
|
|
||||||
/* Default fragment reassembly timeout in uasync timebase units (0.1 ms) */ |
// Структура для packer
|
||||||
#ifndef PKT_NORMALIZER_FRAGMENT_TIMEOUT |
struct PKTNORM { |
||||||
#define PKT_NORMALIZER_FRAGMENT_TIMEOUT 5000 /* 500 ms */ |
// public:
|
||||||
#endif |
struct ll_queue* input; // Входная очередь в packer (через нее отправляем пакеты)
|
||||||
|
struct ll_queue* output; // Выходная очередь из unpacker (через нее принимаем пакеты)
|
||||||
|
|
||||||
|
// private:
|
||||||
|
struct ETCP_CONN* etcp; |
||||||
|
uint16_t pkt_size; |
||||||
|
|
||||||
|
|
||||||
|
// packer:
|
||||||
|
uasync_t* ua; // uasync instance
|
||||||
|
uint8_t* in_buf; // Буфер для упаковки
|
||||||
|
size_t len; // Текущая длина в буфере
|
||||||
|
size_t cap; // Емкость буфера (MAX_PACKET_SIZE)
|
||||||
|
void* flush_timer; // For timeout flush
|
||||||
|
struct ll_queue* pending; // For not filling input
|
||||||
|
|
||||||
|
// unpacker:
|
||||||
|
uint8_t* out_buf; // Буфер для сборки фрагментов
|
||||||
|
// size_t len; // Текущая накопленная длина
|
||||||
|
size_t total_len; // Ожидаемая общая длина (для фрагментов)
|
||||||
|
// size_t cap; // Емкость буфера
|
||||||
|
int in_fragment; // Флаг: идет сборка фрагмента (1) или нет (0)
|
||||||
|
struct ll_queue* send_pending; // For not filling etcp->input_queue
|
||||||
|
|
||||||
/* ETCP overhead for calculating fragment size from MTU */ |
|
||||||
#define ETCP_OVERHEAD 100 // Reserve 100 bytes for headers, crypto, etc.*/
|
|
||||||
|
|
||||||
/* Service packet callback type */ |
|
||||||
typedef void (*pkt_normalizer_service_callback_t)(void* user_data, uint8_t type, const uint8_t* data, size_t len); |
|
||||||
|
|
||||||
struct pn_struct { |
|
||||||
struct ll_queue* input; |
|
||||||
struct ll_queue* output; |
|
||||||
uasync_t* ua; |
|
||||||
int is_packer; |
|
||||||
union { |
|
||||||
struct { |
|
||||||
uint8_t* buf; |
|
||||||
size_t len; |
|
||||||
size_t cap; |
|
||||||
int error_count; |
|
||||||
} packer; |
|
||||||
struct { |
|
||||||
uint8_t* buf; /* буфер для сборки фрагментов */ |
|
||||||
size_t len; /* текущая накопленная длина */ |
|
||||||
size_t total_len; /* ожидаемая общая длина из первого фрагмента */ |
|
||||||
size_t cap; /* ёмкость буфера */ |
|
||||||
int error_count; /* счетчик ошибок сборки */ |
|
||||||
int in_fragment; /* флаг: идет сборка фрагментов (1) или нет (0) */ |
|
||||||
/* Service packet reassembly */ |
|
||||||
uint8_t* service_buf; /* буфер для сборки сервисных пакетов */ |
|
||||||
size_t service_len; /* текущая накопленная длина сервисного пакета */ |
|
||||||
size_t service_cap; /* ёмкость буфера сервисного пакета */ |
|
||||||
uint8_t service_type; /* тип сервисного пакета */ |
|
||||||
int in_service; /* флаг: идет сборка сервисного пакета (1) или нет (0) */ |
|
||||||
} unpacker; |
|
||||||
} u; |
|
||||||
/* Service packet callback */ |
|
||||||
pkt_normalizer_service_callback_t service_callback; |
|
||||||
void* service_callback_user_data; |
|
||||||
}; |
}; |
||||||
|
|
||||||
struct pkt_normalizer_pair { |
// Инициализация пары
|
||||||
struct pn_struct* packer; |
struct PKTNORM* pn_init(struct ETCP_CONN* etcp);// все что нужно (в т.ч. mtu и ua) берет из etcp
|
||||||
struct pn_struct* unpacker; |
|
||||||
}; |
// Деинициализация пары
|
||||||
|
void pn_pair_deinit(struct PKTNORM* pn); |
||||||
|
|
||||||
struct pn_struct* pkt_normalizer_init(uasync_t* ua, int is_packer, int mtu); // 1 for packer, 0 for unpacker, mtu for fragment size calculation
|
// Сброс состояния (для unpacker)
|
||||||
void pkt_normalizer_deinit(struct pn_struct* pn); |
void pn_unpacker_reset_state(struct PKTNORM* pn); |
||||||
|
|
||||||
struct pkt_normalizer_pair* pkt_normalizer_pair_init(uasync_t* ua, int mtu); |
// создаёт malloc data, копирует, помещает в input.
|
||||||
void pkt_normalizer_pair_deinit(struct pkt_normalizer_pair* pair); |
void pn_packer_send(struct PKTNORM* pn, uint8_t* data, uint16_t len); |
||||||
|
|
||||||
/* Error handling */ |
/* Как работает:
|
||||||
int pkt_normalizer_get_error_count(const struct pn_struct* pn); |
Формат отправки в etcp: 2 байта размер, далее данные (порезанные на куски и отправленные через etcp) |
||||||
void pkt_normalizer_reset_error_count(struct pn_struct* pn); |
собирает по возможности полные пакеты с размером pkt_size. |
||||||
|
+ timeout: неполный пакет отправляется по таймауту 1ms (если очереди пустые) |
||||||
|
+ входящая очередь etcp не должне наполняться для минимизации задержки - новый пакет отправляем только когда очередь пустая |
||||||
|
|
||||||
/* Flush internal buffer (packer only) */ |
*/ |
||||||
void pkt_normalizer_flush(struct pn_struct* pn); |
|
||||||
|
|
||||||
int pkt_normalizer_send_service(struct pn_struct* pn, uint8_t type, const void* data, size_t len); |
|
||||||
void pkt_normalizer_set_service_callback(struct pn_struct* pn, pkt_normalizer_service_callback_t callback, void* user_data); |
|
||||||
void pkt_normalizer_reset_service_state(struct pn_struct* pn); |
|
||||||
void pkt_normalizer_reset_state(struct pn_struct* pn); |
|
||||||
|
|
||||||
#endif // PKT_NORMALIZER_H
|
#endif // PKT_NORMALIZER_H
|
||||||
@ -1,369 +0,0 @@ |
|||||||
/* sc_lib.c - Secure Channel library implementation using TinyCrypt */ |
|
||||||
|
|
||||||
#include "secure_channel.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc_dh.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/aes.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ccm_mode.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/constants.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc_platform_specific.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/sha256.h" |
|
||||||
#include <string.h> |
|
||||||
#include <stddef.h> |
|
||||||
#include <sys/types.h> |
|
||||||
#include <unistd.h> |
|
||||||
#include <sys/time.h> |
|
||||||
#include <stdio.h> |
|
||||||
|
|
||||||
// Simple debug macros
|
|
||||||
#define DEBUG_CATEGORY_CRYPTO 1 |
|
||||||
#define DEBUG_ERROR(category, fmt, ...) fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) |
|
||||||
#define DEBUG_INFO(category, fmt, ...) fprintf(stdout, "INFO: " fmt "\n", ##__VA_ARGS__) |
|
||||||
#include <stdio.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include "crc32.h" |
|
||||||
|
|
||||||
static const struct uECC_Curve_t *curve = NULL; |
|
||||||
static uint8_t sc_urandom_seed[8] = {0}; |
|
||||||
static int sc_urandom_initialized = 0; |
|
||||||
|
|
||||||
static void sc_init_random_seed(void) |
|
||||||
{ |
|
||||||
int fd = open("/dev/urandom", O_RDONLY); |
|
||||||
if (fd >= 0) { |
|
||||||
ssize_t ret = read(fd, sc_urandom_seed, 8); |
|
||||||
close(fd); |
|
||||||
if (ret == 8) { |
|
||||||
sc_urandom_initialized = 1; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
static int sc_rng(uint8_t *dest, unsigned size) |
|
||||||
{ |
|
||||||
int fd = open("/dev/urandom", O_RDONLY); |
|
||||||
if (fd < 0) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
ssize_t ret = read(fd, dest, size); |
|
||||||
close(fd); |
|
||||||
if (ret != size) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/* Mix in PID and microtime for additional entropy */ |
|
||||||
pid_t pid = getpid(); |
|
||||||
struct timeval tv; |
|
||||||
gettimeofday(&tv, NULL); |
|
||||||
|
|
||||||
for (unsigned i = 0; i < size; i++) { |
|
||||||
dest[i] ^= ((pid >> (i % (sizeof(pid) * 8))) & 0xFF); |
|
||||||
dest[i] ^= ((tv.tv_sec >> (i % (sizeof(tv.tv_sec) * 8))) & 0xFF); |
|
||||||
dest[i] ^= ((tv.tv_usec >> (i % (sizeof(tv.tv_usec) * 8))) & 0xFF); |
|
||||||
} |
|
||||||
|
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
static int sc_validate_key(const uint8_t *public_key) |
|
||||||
{ |
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
int result = uECC_valid_public_key(public_key, curve); |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_validate_key: uECC_valid_public_key returned %d", result); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_generate_keypair(struct SC_MYKEYS *pk) |
|
||||||
{ |
|
||||||
if (!pk) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
/* Set custom RNG function */ |
|
||||||
uECC_set_rng(sc_rng); |
|
||||||
|
|
||||||
if (!uECC_make_key(pk->public_key, pk->private_key, curve)) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
// Конвертация hex строки в бинарный формат
|
|
||||||
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) { |
|
||||||
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1; |
|
||||||
|
|
||||||
for (size_t i = 0; i < binary_len; i++) { |
|
||||||
unsigned int byte; |
|
||||||
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1; |
|
||||||
binary[i] = (uint8_t)byte; |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key) { |
|
||||||
if (!mykeys || !public_key || !private_key) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: invalid arguments"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public_key len=%zu, private_key len=%zu",
|
|
||||||
strlen(public_key), strlen(private_key)); |
|
||||||
|
|
||||||
/* Convert hex to binary first */ |
|
||||||
if (hex_to_binary(public_key, mykeys->public_key, SC_PUBKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert public key from hex"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
if (hex_to_binary(private_key, mykeys->private_key, SC_PRIVKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert private key from hex"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Validate the converted binary public key */ |
|
||||||
if (sc_validate_key(mykeys->public_key) != 0) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public key validation failed"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: keys initialized successfully"); |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys) { |
|
||||||
|
|
||||||
ctx->pk=mykeys; |
|
||||||
ctx->initialized = 1; |
|
||||||
ctx->peer_key_set = 0; |
|
||||||
ctx->session_ready = 0; |
|
||||||
ctx->tx_counter = 0; |
|
||||||
ctx->rx_counter = 0; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key_h, int mode) { |
|
||||||
uint8_t shared_secret[SC_SHARED_SECRET_SIZE]; |
|
||||||
uint8_t peer_public_key[SC_PUBKEY_SIZE]; |
|
||||||
|
|
||||||
if (mode) { |
|
||||||
if (hex_to_binary(peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid hex key format"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
} |
|
||||||
else memcpy(peer_public_key, peer_public_key_h, SC_PUBKEY_SIZE); |
|
||||||
|
|
||||||
if (!ctx) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid ctx"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->initialized) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: ctx not initialized"); |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
/* Validate peer public key */ |
|
||||||
if (sc_validate_key(peer_public_key) != 0) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid key"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Compute shared secret using ECDH */ |
|
||||||
if (!ctx->pk) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: no private key"); |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
if (!uECC_shared_secret(peer_public_key, ctx->pk->private_key, |
|
||||||
shared_secret, curve)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: shared secret error"); |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Derive session key from shared secret (simple copy for demo) */ |
|
||||||
memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE); |
|
||||||
|
|
||||||
/* Store peer public key */ |
|
||||||
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE); |
|
||||||
ctx->peer_key_set = 1; |
|
||||||
|
|
||||||
ctx->session_ready = 1; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
// Новая функция для генерации nonce с микросекундами
|
|
||||||
static void generate_nonce_with_timer(uint8_t *nonce) { |
|
||||||
struct timeval tv; |
|
||||||
gettimeofday(&tv, NULL); |
|
||||||
uint32_t usec = (uint32_t)tv.tv_usec; |
|
||||||
|
|
||||||
// Поместить usec в первые 4 байта (little-endian)
|
|
||||||
nonce[0] = usec & 0xFF; |
|
||||||
nonce[1] = (usec >> 8) & 0xFF; |
|
||||||
nonce[2] = (usec >> 16) & 0xFF; |
|
||||||
nonce[3] = (usec >> 24) & 0xFF; |
|
||||||
|
|
||||||
// Заполнить оставшиеся 9 байт случайными данными
|
|
||||||
sc_rng(nonce + 4, SC_NONCE_SIZE - 4); |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_encrypt(sc_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t *ciphertext_len) { |
|
||||||
uint8_t nonce[SC_NONCE_SIZE]; |
|
||||||
uint8_t plaintext_with_crc[plaintext_len + SC_CRC32_SIZE]; |
|
||||||
size_t total_plaintext_len = plaintext_len + SC_CRC32_SIZE; |
|
||||||
uint8_t combined_output[total_plaintext_len + SC_TAG_SIZE]; |
|
||||||
struct tc_aes_key_sched_struct sched; |
|
||||||
struct tc_ccm_mode_struct ccm_state; |
|
||||||
|
|
||||||
if (!ctx || !plaintext || !ciphertext || !ciphertext_len) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->session_ready) { |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (plaintext_len == 0) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Добавляем CRC32 к данным */ |
|
||||||
memcpy(plaintext_with_crc, plaintext, plaintext_len); |
|
||||||
uint32_t crc = crc32_calc(plaintext, plaintext_len); |
|
||||||
plaintext_with_crc[plaintext_len] = (crc >> 0) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 1] = (crc >> 8) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 2] = (crc >> 16) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 3] = (crc >> 24) & 0xFF; |
|
||||||
|
|
||||||
/* Генерируем nonce с таймером */ |
|
||||||
generate_nonce_with_timer(nonce); |
|
||||||
|
|
||||||
/* Initialize AES key schedule */ |
|
||||||
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Configure CCM mode */ |
|
||||||
if (tc_ccm_config(&ccm_state, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Encrypt and generate tag */ |
|
||||||
if (tc_ccm_generation_encryption(combined_output, sizeof(combined_output), |
|
||||||
NULL, 0, /* no associated data */ |
|
||||||
plaintext_with_crc, total_plaintext_len, |
|
||||||
&ccm_state) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Copy nonce + ciphertext + tag to output buffer */ |
|
||||||
memcpy(ciphertext, nonce, SC_NONCE_SIZE); |
|
||||||
memcpy(ciphertext + SC_NONCE_SIZE, combined_output, total_plaintext_len + SC_TAG_SIZE); |
|
||||||
*ciphertext_len = SC_NONCE_SIZE + total_plaintext_len + SC_TAG_SIZE; |
|
||||||
|
|
||||||
ctx->tx_counter++; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_decrypt(sc_context_t *ctx, |
|
||||||
const uint8_t *ciphertext, |
|
||||||
size_t ciphertext_len, |
|
||||||
uint8_t *plaintext, |
|
||||||
size_t *plaintext_len) |
|
||||||
{ |
|
||||||
uint8_t nonce[SC_NONCE_SIZE]; |
|
||||||
struct tc_aes_key_sched_struct sched; |
|
||||||
struct tc_ccm_mode_struct ccm_state; |
|
||||||
size_t total_plaintext_len = ciphertext_len - SC_NONCE_SIZE - SC_TAG_SIZE; |
|
||||||
uint8_t plaintext_with_crc[total_plaintext_len]; |
|
||||||
|
|
||||||
if (!ctx || !ciphertext || !plaintext || !plaintext_len) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->session_ready) { |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (ciphertext_len < SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Извлекаем nonce из начала ciphertext */ |
|
||||||
memcpy(nonce, ciphertext, SC_NONCE_SIZE); |
|
||||||
|
|
||||||
/* Ciphertext для расшифровки начинается после nonce */ |
|
||||||
const uint8_t *encrypted_data = ciphertext + SC_NONCE_SIZE; |
|
||||||
size_t encrypted_len = ciphertext_len - SC_NONCE_SIZE; |
|
||||||
|
|
||||||
/* Initialize AES key schedule */ |
|
||||||
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Configure CCM mode с извлечённым nonce */ |
|
||||||
if (tc_ccm_config(&ccm_state, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Decrypt and verify tag */ |
|
||||||
if (tc_ccm_decryption_verification(plaintext_with_crc, total_plaintext_len, |
|
||||||
NULL, 0, /* no associated data */ |
|
||||||
encrypted_data, encrypted_len, |
|
||||||
&ccm_state) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_AUTH_FAILED; |
|
||||||
} |
|
||||||
|
|
||||||
/* Проверяем CRC32 */ |
|
||||||
size_t data_len = total_plaintext_len - SC_CRC32_SIZE; |
|
||||||
uint32_t expected_crc = crc32_calc(plaintext_with_crc, data_len); |
|
||||||
uint32_t received_crc = (plaintext_with_crc[data_len] << 0) | |
|
||||||
(plaintext_with_crc[data_len + 1] << 8) | |
|
||||||
(plaintext_with_crc[data_len + 2] << 16) | |
|
||||||
(plaintext_with_crc[data_len + 3] << 24); |
|
||||||
|
|
||||||
if (expected_crc != received_crc) { |
|
||||||
return SC_ERR_CRC_FAILED; |
|
||||||
} |
|
||||||
|
|
||||||
/* Копируем данные без CRC32 */ |
|
||||||
memcpy(plaintext, plaintext_with_crc, data_len); |
|
||||||
*plaintext_len = data_len; |
|
||||||
|
|
||||||
ctx->rx_counter++; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key) { |
|
||||||
if (!private_key || !public_key) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!uECC_compute_public_key(private_key, public_key, curve)) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
@ -1,388 +0,0 @@ |
|||||||
/* sc_lib.c - Secure Channel library implementation using TinyCrypt */ |
|
||||||
|
|
||||||
#include "secure_channel.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc_dh.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/aes.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ccm_mode.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/constants.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/ecc_platform_specific.h" |
|
||||||
#include "../tinycrypt/lib/include/tinycrypt/sha256.h" |
|
||||||
#include <string.h> |
|
||||||
#include <stddef.h> |
|
||||||
#include <sys/types.h> |
|
||||||
#include <unistd.h> |
|
||||||
#include <sys/time.h> |
|
||||||
#include <stdio.h> |
|
||||||
|
|
||||||
// Simple debug macros |
|
||||||
#define DEBUG_CATEGORY_CRYPTO 1 |
|
||||||
#define DEBUG_ERROR(category, fmt, ...) fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) |
|
||||||
#define DEBUG_INFO(category, fmt, ...) fprintf(stdout, "INFO: " fmt "\n", ##__VA_ARGS__) |
|
||||||
#include <stdio.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include "crc32.h" |
|
||||||
|
|
||||||
static const struct uECC_Curve_t *curve = NULL; |
|
||||||
static uint8_t sc_urandom_seed[8] = {0}; |
|
||||||
static int sc_urandom_initialized = 0; |
|
||||||
|
|
||||||
static void sc_init_random_seed(void) |
|
||||||
{ |
|
||||||
int fd = open("/dev/urandom", O_RDONLY); |
|
||||||
if (fd >= 0) { |
|
||||||
ssize_t ret = read(fd, sc_urandom_seed, 8); |
|
||||||
close(fd); |
|
||||||
if (ret == 8) { |
|
||||||
sc_urandom_initialized = 1; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
static int sc_rng(uint8_t *dest, unsigned size) |
|
||||||
{ |
|
||||||
int fd = open("/dev/urandom", O_RDONLY); |
|
||||||
if (fd < 0) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
ssize_t ret = read(fd, dest, size); |
|
||||||
close(fd); |
|
||||||
if (ret != size) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
/* Mix in PID and microtime for additional entropy */ |
|
||||||
pid_t pid = getpid(); |
|
||||||
struct timeval tv; |
|
||||||
gettimeofday(&tv, NULL); |
|
||||||
|
|
||||||
for (unsigned i = 0; i < size; i++) { |
|
||||||
dest[i] ^= ((pid >> (i % (sizeof(pid) * 8))) & 0xFF); |
|
||||||
dest[i] ^= ((tv.tv_sec >> (i % (sizeof(tv.tv_sec) * 8))) & 0xFF); |
|
||||||
dest[i] ^= ((tv.tv_usec >> (i % (sizeof(tv.tv_usec) * 8))) & 0xFF); |
|
||||||
} |
|
||||||
|
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
static int sc_validate_key(const uint8_t *public_key) |
|
||||||
{ |
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
int result = uECC_valid_public_key(public_key, curve); |
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_validate_key: uECC_valid_public_key returned %d", result); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_generate_keypair(struct SC_MYKEYS *pk) |
|
||||||
{ |
|
||||||
if (!pk) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
/* Set custom RNG function */ |
|
||||||
uECC_set_rng(sc_rng); |
|
||||||
|
|
||||||
if (!uECC_make_key(pk->public_key, pk->private_key, curve)) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
// Конвертация hex строки в бинарный формат |
|
||||||
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) { |
|
||||||
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1; |
|
||||||
|
|
||||||
for (size_t i = 0; i < binary_len; i++) { |
|
||||||
unsigned int byte; |
|
||||||
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1; |
|
||||||
binary[i] = (uint8_t)byte; |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key) { |
|
||||||
if (!mykeys || !public_key || !private_key) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: invalid arguments"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public_key len=%zu, private_key len=%zu", |
|
||||||
strlen(public_key), strlen(private_key)); |
|
||||||
|
|
||||||
/* Convert hex to binary first */ |
|
||||||
if (hex_to_binary(public_key, mykeys->public_key, SC_PUBKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert public key from hex"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
if (hex_to_binary(private_key, mykeys->private_key, SC_PRIVKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: failed to convert private key from hex"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Validate the converted binary public key */ |
|
||||||
if (sc_validate_key(mykeys->public_key) != 0) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: public key validation failed"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CRYPTO, "sc_init_local_keys: keys initialized successfully"); |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys) { |
|
||||||
|
|
||||||
ctx->pk=mykeys; |
|
||||||
ctx->initialized = 1; |
|
||||||
ctx->peer_key_set = 0; |
|
||||||
ctx->session_ready = 0; |
|
||||||
ctx->tx_counter = 0; |
|
||||||
ctx->rx_counter = 0; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key_h, int mode) { |
|
||||||
uint8_t shared_secret[SC_SHARED_SECRET_SIZE]; |
|
||||||
uint8_t peer_public_key[SC_PUBKEY_SIZE]; |
|
||||||
|
|
||||||
if (mode) { |
|
||||||
if (hex_to_binary(peer_public_key_h, peer_public_key, SC_PUBKEY_SIZE)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid hex key format"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
} |
|
||||||
else memcpy(peer_public_key, peer_public_key_h, SC_PUBKEY_SIZE); |
|
||||||
|
|
||||||
if (!ctx) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid ctx"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->initialized) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: ctx not initialized"); |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
/* Validate peer public key */ |
|
||||||
if (sc_validate_key(peer_public_key) != 0) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: invalid key"); |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Compute shared secret using ECDH */ |
|
||||||
if (!ctx->pk) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: no private key"); |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
if (!uECC_shared_secret(peer_public_key, ctx->pk->private_key, |
|
||||||
shared_secret, curve)) { |
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_set_peer_public_key: shared secret error"); |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Derive session key from shared secret (simple copy for demo) */ |
|
||||||
memcpy(ctx->session_key, shared_secret, SC_SESSION_KEY_SIZE); |
|
||||||
|
|
||||||
/* Store peer public key */ |
|
||||||
memcpy(ctx->peer_public_key, peer_public_key, SC_PUBKEY_SIZE); |
|
||||||
ctx->peer_key_set = 1; |
|
||||||
|
|
||||||
ctx->session_ready = 1; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
static void sc_build_nonce(uint64_t counter, uint8_t *nonce_out) |
|
||||||
{ |
|
||||||
struct tc_sha256_state_struct sha_ctx; |
|
||||||
uint8_t hash[32]; |
|
||||||
struct timeval tv; |
|
||||||
uint8_t data[8 + 8 + 4]; |
|
||||||
|
|
||||||
if (!sc_urandom_initialized) { |
|
||||||
sc_init_random_seed(); |
|
||||||
} |
|
||||||
|
|
||||||
gettimeofday(&tv, NULL); |
|
||||||
|
|
||||||
memcpy(data, sc_urandom_seed, 8); |
|
||||||
data[8] = (counter >> 0) & 0xFF; |
|
||||||
data[9] = (counter >> 8) & 0xFF; |
|
||||||
data[10] = (counter >> 16) & 0xFF; |
|
||||||
data[11] = (counter >> 24) & 0xFF; |
|
||||||
data[12] = (counter >> 32) & 0xFF; |
|
||||||
data[13] = (counter >> 40) & 0xFF; |
|
||||||
data[14] = (counter >> 48) & 0xFF; |
|
||||||
data[15] = (counter >> 56) & 0xFF; |
|
||||||
data[16] = (tv.tv_sec >> 0) & 0xFF; |
|
||||||
data[17] = (tv.tv_sec >> 8) & 0xFF; |
|
||||||
data[18] = (tv.tv_sec >> 16) & 0xFF; |
|
||||||
data[19] = (tv.tv_sec >> 24) & 0xFF; |
|
||||||
|
|
||||||
tc_sha256_init(&sha_ctx); |
|
||||||
tc_sha256_update(&sha_ctx, data, 20); |
|
||||||
tc_sha256_final(hash, &sha_ctx); |
|
||||||
|
|
||||||
memcpy(nonce_out, hash, SC_NONCE_SIZE); |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_encrypt(sc_context_t *ctx, |
|
||||||
const uint8_t *plaintext, |
|
||||||
size_t plaintext_len, |
|
||||||
uint8_t *ciphertext, |
|
||||||
size_t *ciphertext_len) |
|
||||||
{ |
|
||||||
uint8_t nonce[SC_NONCE_SIZE]; |
|
||||||
struct tc_aes_key_sched_struct sched; |
|
||||||
struct tc_ccm_mode_struct ccm_state; |
|
||||||
size_t total_plaintext_len = plaintext_len + SC_CRC32_SIZE; |
|
||||||
uint8_t plaintext_with_crc[total_plaintext_len]; |
|
||||||
uint8_t combined_output[total_plaintext_len + SC_TAG_SIZE]; |
|
||||||
|
|
||||||
if (!ctx || !plaintext || !ciphertext || !ciphertext_len) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->session_ready) { |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (plaintext_len == 0) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Добавляем CRC32 к данным */ |
|
||||||
memcpy(plaintext_with_crc, plaintext, plaintext_len); |
|
||||||
uint32_t crc = crc32_calc(plaintext, plaintext_len); |
|
||||||
plaintext_with_crc[plaintext_len] = (crc >> 0) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 1] = (crc >> 8) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 2] = (crc >> 16) & 0xFF; |
|
||||||
plaintext_with_crc[plaintext_len + 3] = (crc >> 24) & 0xFF; |
|
||||||
|
|
||||||
/* Initialize AES key schedule */ |
|
||||||
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Build nonce from counter */ |
|
||||||
sc_build_nonce(ctx->tx_counter, nonce); |
|
||||||
|
|
||||||
/* Configure CCM mode */ |
|
||||||
if (tc_ccm_config(&ccm_state, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Encrypt and generate tag */ |
|
||||||
if (tc_ccm_generation_encryption(combined_output, sizeof(combined_output), |
|
||||||
NULL, 0, /* no associated data */ |
|
||||||
plaintext_with_crc, total_plaintext_len, |
|
||||||
&ccm_state) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Copy ciphertext + tag to output buffer */ |
|
||||||
memcpy(ciphertext, combined_output, total_plaintext_len + SC_TAG_SIZE); |
|
||||||
*ciphertext_len = total_plaintext_len + SC_TAG_SIZE; |
|
||||||
|
|
||||||
ctx->tx_counter++; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_decrypt(sc_context_t *ctx, |
|
||||||
const uint8_t *ciphertext, |
|
||||||
size_t ciphertext_len, |
|
||||||
uint8_t *plaintext, |
|
||||||
size_t *plaintext_len) |
|
||||||
{ |
|
||||||
uint8_t nonce[SC_NONCE_SIZE]; |
|
||||||
struct tc_aes_key_sched_struct sched; |
|
||||||
struct tc_ccm_mode_struct ccm_state; |
|
||||||
TCCcmMode_t c = &ccm_state; |
|
||||||
size_t total_plaintext_len = ciphertext_len - SC_TAG_SIZE; |
|
||||||
uint8_t plaintext_with_crc[total_plaintext_len]; |
|
||||||
|
|
||||||
if (!ctx || !ciphertext || !plaintext || !plaintext_len) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!ctx->session_ready) { |
|
||||||
return SC_ERR_NOT_INITIALIZED; |
|
||||||
} |
|
||||||
|
|
||||||
if (ciphertext_len < SC_TAG_SIZE + SC_CRC32_SIZE) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
/* Initialize AES key schedule */ |
|
||||||
if (tc_aes128_set_encrypt_key(&sched, ctx->session_key) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Build nonce from counter */ |
|
||||||
sc_build_nonce(ctx->rx_counter, nonce); |
|
||||||
|
|
||||||
/* Configure CCM mode */ |
|
||||||
if (tc_ccm_config(c, &sched, nonce, SC_NONCE_SIZE, SC_TAG_SIZE) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
|
|
||||||
/* Decrypt and verify tag */ |
|
||||||
if (tc_ccm_decryption_verification(plaintext_with_crc, total_plaintext_len, |
|
||||||
NULL, 0, /* no associated data */ |
|
||||||
ciphertext, ciphertext_len, |
|
||||||
c) != TC_CRYPTO_SUCCESS) { |
|
||||||
return SC_ERR_AUTH_FAILED; |
|
||||||
} |
|
||||||
|
|
||||||
/* Проверяем CRC32 */ |
|
||||||
size_t data_len = total_plaintext_len - SC_CRC32_SIZE; |
|
||||||
uint32_t expected_crc = crc32_calc(plaintext_with_crc, data_len); |
|
||||||
uint32_t received_crc = (plaintext_with_crc[data_len] << 0) | |
|
||||||
(plaintext_with_crc[data_len + 1] << 8) | |
|
||||||
(plaintext_with_crc[data_len + 2] << 16) | |
|
||||||
(plaintext_with_crc[data_len + 3] << 24); |
|
||||||
|
|
||||||
if (expected_crc != received_crc) { |
|
||||||
return SC_ERR_CRC_FAILED; |
|
||||||
} |
|
||||||
|
|
||||||
/* Копируем данные без CRC32 */ |
|
||||||
memcpy(plaintext, plaintext_with_crc, data_len); |
|
||||||
*plaintext_len = data_len; |
|
||||||
|
|
||||||
ctx->rx_counter++; |
|
||||||
|
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
|
|
||||||
sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key) { |
|
||||||
if (!private_key || !public_key) { |
|
||||||
return SC_ERR_INVALID_ARG; |
|
||||||
} |
|
||||||
|
|
||||||
if (!curve) { |
|
||||||
curve = uECC_secp256r1(); |
|
||||||
} |
|
||||||
|
|
||||||
if (!uECC_compute_public_key(private_key, public_key, curve)) { |
|
||||||
return SC_ERR_CRYPTO; |
|
||||||
} |
|
||||||
return SC_OK; |
|
||||||
} |
|
||||||
@ -1,68 +0,0 @@ |
|||||||
// secure_channel.h |
|
||||||
#ifndef SECURE_CHANNEL_H |
|
||||||
#define SECURE_CHANNEL_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <stddef.h> |
|
||||||
|
|
||||||
// Размеры ключей |
|
||||||
#define SC_PRIVKEY_SIZE 32 |
|
||||||
#define SC_PUBKEY_SIZE 64 |
|
||||||
#define SC_HASH_SIZE 32 |
|
||||||
#define SC_NONCE_SIZE 13 // CCM requires exactly 13 bytes |
|
||||||
#define SC_SHARED_SECRET_SIZE SC_HASH_SIZE |
|
||||||
#define SC_SESSION_KEY_SIZE 16 |
|
||||||
#define SC_TAG_SIZE 8 |
|
||||||
#define SC_CRC32_SIZE 4 |
|
||||||
|
|
||||||
// Коды возврата |
|
||||||
#define SC_OK 0 |
|
||||||
#define SC_ERR_INVALID_ARG -1 |
|
||||||
#define SC_ERR_CRYPTO -2 |
|
||||||
#define SC_ERR_NOT_INITIALIZED -3 |
|
||||||
#define SC_ERR_AUTH_FAILED -4 |
|
||||||
#define SC_ERR_CRC_FAILED -5 |
|
||||||
|
|
||||||
#define SC_PEER_PUBKEY_BIN 0 |
|
||||||
#define SC_PEER_PUBKEY_HEX 1 |
|
||||||
|
|
||||||
// Типы |
|
||||||
typedef int sc_status_t; |
|
||||||
typedef struct secure_channel sc_context_t; |
|
||||||
|
|
||||||
struct SC_MYKEYS { |
|
||||||
/* Локальные ключи */ |
|
||||||
uint8_t private_key[SC_PRIVKEY_SIZE]; |
|
||||||
uint8_t public_key[SC_PUBKEY_SIZE]; |
|
||||||
}; |
|
||||||
|
|
||||||
// Контекст защищенного канала |
|
||||||
struct secure_channel { |
|
||||||
struct SC_MYKEYS* pk; |
|
||||||
/* Ключи пира (после key exchange) */ |
|
||||||
uint8_t peer_public_key[SC_PUBKEY_SIZE]; |
|
||||||
uint8_t session_key[SC_SESSION_KEY_SIZE]; /* Derived session key */ |
|
||||||
|
|
||||||
/* Nonces для отправки и приема */ |
|
||||||
uint8_t send_nonce[SC_NONCE_SIZE]; |
|
||||||
uint8_t recv_nonce[SC_NONCE_SIZE]; |
|
||||||
|
|
||||||
uint8_t initialized; |
|
||||||
uint8_t peer_key_set; |
|
||||||
uint8_t session_ready; |
|
||||||
uint64_t tx_counter; |
|
||||||
uint64_t rx_counter; |
|
||||||
}; |
|
||||||
|
|
||||||
// Функции инициализации |
|
||||||
sc_status_t sc_init_ctx(sc_context_t *ctx, struct SC_MYKEYS *mykeys); |
|
||||||
sc_status_t sc_generate_keypair(struct SC_MYKEYS *keys); |
|
||||||
sc_status_t sc_init_local_keys(struct SC_MYKEYS *mykeys, const char *public_key, const char *private_key); |
|
||||||
sc_status_t sc_set_peer_public_key(sc_context_t *ctx, const char *peer_public_key, int mode);// mode: 0-bin 1-hex key format |
|
||||||
sc_status_t sc_compute_public_key_from_private(const uint8_t *private_key, uint8_t *public_key); |
|
||||||
|
|
||||||
// Криптографические операции |
|
||||||
sc_status_t sc_encrypt(sc_context_t *ctx, const uint8_t *plaintext, size_t plaintext_len, uint8_t *ciphertext, size_t *ciphertext_len); |
|
||||||
sc_status_t sc_decrypt(sc_context_t *ctx, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *plaintext, size_t *plaintext_len); |
|
||||||
|
|
||||||
#endif // SECURE_CHANNEL_H |
|
||||||
Loading…
Reference in new issue