Skip to content

Commit

Permalink
Calldata compression
Browse files Browse the repository at this point in the history
  • Loading branch information
apaillier-ledger committed Jan 20, 2025
1 parent 69bd672 commit d8487d6
Show file tree
Hide file tree
Showing 15 changed files with 465 additions and 261 deletions.
178 changes: 161 additions & 17 deletions src_features/generic_tx_parser/calldata.c
Original file line number Diff line number Diff line change
@@ -1,65 +1,209 @@
#ifdef HAVE_GENERIC_TX_PARSER

#include <string.h>
#include "os_math.h" // MIN
#include "calldata.h"
#include "os_print.h"
#include "mem.h"

typedef struct {
uint8_t *ptr;
size_t size;
uint8_t selector[CALLDATA_SELECTOR_SIZE];
// chunk_info (8 bit):
// ........
// ^^^^^^ byte size
// ^ unused
// ^ strip direction : 0 LEFT, 1 RIGHT
//
// chunk_info (1) | chunk (n) | chunk info (1) | chunk (n) | ...
uint8_t *chunks;
size_t chunks_size;
size_t expected_size;
size_t received_size;
uint8_t chunk[CALLDATA_CHUNK_SIZE];
size_t chunk_size;
} s_calldata;

static s_calldata g_calldata = {0};
static s_calldata *g_calldata = NULL;

typedef enum {
CHUNK_STRIP_LEFT = 0,
CHUNK_STRIP_RIGHT = 1,
} e_chunk_strip_dir;

#define CHUNK_INFO_SIZE_MASK 0x3f
#define CHUNK_INFO_SIZE_OFFSET 0

#define CHUNK_INFO_DIR_MASK 0x01
#define CHUNK_INFO_DIR_OFFSET 7

#define CHUNK_INFO_SIZE(v) \
((v & (CHUNK_INFO_SIZE_MASK << CHUNK_INFO_SIZE_OFFSET)) >> CHUNK_INFO_SIZE_OFFSET)
#define CHUNK_INFO_DIR(v) \
((v & (CHUNK_INFO_DIR_MASK << CHUNK_INFO_DIR_OFFSET)) >> CHUNK_INFO_DIR_OFFSET)

typedef uint8_t chunk_info_t;

bool calldata_init(size_t size) {
if ((g_calldata.ptr = mem_alloc(size)) == NULL) {
if (g_calldata != NULL) {
calldata_cleanup();
}
if ((g_calldata = mem_alloc(sizeof(*g_calldata))) == NULL) {
return false;
}
explicit_bzero(g_calldata, sizeof(*g_calldata));
g_calldata->expected_size = size;
return true;
}

static bool compress_chunk(s_calldata *calldata) {
uint8_t strip_left = 0;
uint8_t strip_right = 0;
chunk_info_t chunk_info = 0;
e_chunk_strip_dir strip_dir;
uint8_t stripped_size;
uint8_t start_idx;
uint8_t *ptr;

for (int i = 0; (i < CALLDATA_CHUNK_SIZE) && (calldata->chunk[i] == 0x00); ++i) {
strip_left += 1;
}
for (int i = CALLDATA_CHUNK_SIZE - 1; (i >= 0) && (calldata->chunk[i] == 0x00); --i) {
strip_right += 1;
}

if (strip_left >= strip_right) {
strip_dir = CHUNK_STRIP_LEFT;
stripped_size = CALLDATA_CHUNK_SIZE - strip_left;
start_idx = strip_left;
} else {
strip_dir = CHUNK_STRIP_RIGHT;
stripped_size = strip_right;
start_idx = 0;
}
chunk_info |= strip_dir << CHUNK_INFO_DIR_OFFSET;
chunk_info |= stripped_size << CHUNK_INFO_SIZE_OFFSET;
if ((ptr = mem_alloc(sizeof(chunk_info) + stripped_size)) == NULL) {
return false;
}
g_calldata.expected_size = size;
if (calldata->chunks == NULL) {
calldata->chunks = ptr;
} else {
if ((calldata->chunks + calldata->chunks_size) != ptr) {
// something was allocated in between two compressed chunks
return false;
}
}
calldata->chunks_size += sizeof(chunk_info) + stripped_size;
memcpy(ptr, &chunk_info, sizeof(chunk_info));
memcpy(ptr + sizeof(chunk_info), calldata->chunk + start_idx, stripped_size);
return true;
}

static bool decompress_chunk(s_calldata *calldata, size_t offset) {
chunk_info_t chunk_info = calldata->chunks[offset];
const uint8_t *compressed_chunk = &calldata->chunks[offset + sizeof(chunk_info)];
size_t diff;

diff = CALLDATA_CHUNK_SIZE - CHUNK_INFO_SIZE(chunk_info);
if (CHUNK_INFO_DIR(chunk_info) == CHUNK_STRIP_LEFT) {
explicit_bzero(calldata->chunk, diff);
memcpy(&calldata->chunk[diff], compressed_chunk, CHUNK_INFO_SIZE(chunk_info));
} else {
memcpy(calldata->chunk, compressed_chunk, CHUNK_INFO_SIZE(chunk_info));
explicit_bzero(&calldata->chunk[CHUNK_INFO_SIZE(chunk_info)], diff);
}
return true;
}

bool calldata_append(const uint8_t *buffer, size_t size) {
if ((g_calldata.size + size) > g_calldata.expected_size) {
uint8_t cpy_length;

if (g_calldata == NULL) return false;
if ((g_calldata->received_size + size) > g_calldata->expected_size) {
calldata_cleanup();
return false;
}
memcpy(&g_calldata.ptr[g_calldata.size], buffer, size);
g_calldata.size += size;

// selector handling
if (g_calldata->chunks == NULL) {
if (size < CALLDATA_SELECTOR_SIZE) {
// somehow getting an imcomplete selector
calldata_cleanup();
return false;
}
memcpy(g_calldata->selector, buffer, CALLDATA_SELECTOR_SIZE);
buffer += CALLDATA_SELECTOR_SIZE;
size -= CALLDATA_SELECTOR_SIZE;
g_calldata->received_size += CALLDATA_SELECTOR_SIZE;
}

// chunk handling
while (size > 0) {
if (g_calldata->received_size > g_calldata->expected_size) {
calldata_cleanup();
return false;
}
cpy_length = MIN(size, (sizeof(g_calldata->chunk) - g_calldata->chunk_size));
memcpy(&g_calldata->chunk[g_calldata->chunk_size], buffer, cpy_length);
g_calldata->chunk_size += cpy_length;
if (g_calldata->chunk_size == CALLDATA_CHUNK_SIZE) {
if (!compress_chunk(g_calldata)) {
calldata_cleanup();
return false;
}
g_calldata->chunk_size = 0;
}
buffer += cpy_length;
size -= cpy_length;
g_calldata->received_size += cpy_length;
}
if (g_calldata->received_size == g_calldata->expected_size) {
PRINTF("calldata reduced by ~%u%% with compression (%u -> %u bytes)\n",
100 - (100 * (CALLDATA_SELECTOR_SIZE + g_calldata->chunks_size) /
g_calldata->received_size),
g_calldata->received_size,
CALLDATA_SELECTOR_SIZE + g_calldata->chunks_size);
}
return true;
}

void calldata_cleanup(void) {
mem_dealloc(g_calldata.size);
explicit_bzero(&g_calldata, sizeof(g_calldata));
mem_dealloc(g_calldata->chunks_size);
mem_dealloc(sizeof(*g_calldata));
g_calldata = NULL;
}

static bool has_valid_calldata(const s_calldata *calldata) {
if (calldata->ptr == NULL) {
if (calldata == NULL) {
PRINTF("Error: no calldata!\n");
return false;
}
if (g_calldata.size != g_calldata.expected_size) {
if (calldata->received_size != calldata->expected_size) {
PRINTF("Error: incomplete calldata!\n");
return false;
}
return true;
}

const uint8_t *calldata_get_selector(void) {
if (!has_valid_calldata(&g_calldata) || (g_calldata.size < CALLDATA_SELECTOR_SIZE)) {
if (!has_valid_calldata(g_calldata)) {
return NULL;
}
return g_calldata.ptr;
return g_calldata->selector;
}

const uint8_t *calldata_get_chunk(int idx) {
if (!has_valid_calldata(&g_calldata) ||
(g_calldata.size < (CALLDATA_SELECTOR_SIZE + ((size_t) idx + 1) * CALLDATA_CHUNK_SIZE))) {
size_t offset = 0;

if (!has_valid_calldata(g_calldata) || (g_calldata->chunks == NULL)) {
return NULL;
}
return &g_calldata.ptr[CALLDATA_SELECTOR_SIZE + (idx * CALLDATA_CHUNK_SIZE)];
for (int i = 0; i < idx; ++i) {
if (offset > g_calldata->chunks_size) return NULL;
offset += sizeof(chunk_info_t) + CHUNK_INFO_SIZE(g_calldata->chunks[offset]);
}
if (!decompress_chunk(g_calldata, offset)) return NULL;
return g_calldata->chunk;
}

#endif // HAVE_GENERIC_TX_PARSER
29 changes: 23 additions & 6 deletions src_features/generic_tx_parser/gtp_data_path.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "read.h"
#include "utils.h"
#include "calldata.h"
#include "mem.h"

enum {
TAG_VERSION = 0x00,
Expand Down Expand Up @@ -141,34 +142,43 @@ static bool path_leaf(const s_leaf_args *leaf,
s_parsed_value_collection *collection) {
uint8_t buf[sizeof(uint16_t)];
const uint8_t *chunk;
uint8_t *leaf_buf;

if (collection->size > MAX_VALUE_COLLECTION_SIZE) {
return false;
}

switch (leaf->type) {
case LEAF_TYPE_STATIC:
collection->value[collection->size].length = CALLDATA_CHUNK_SIZE;
collection->value[collection->size].size = CALLDATA_CHUNK_SIZE;
break;

case LEAF_TYPE_DYNAMIC:
if ((chunk = calldata_get_chunk(*offset)) == NULL) {
return false;
}
// TODO: properly handle multi-chunk dynamic values once calldata compression
// is implemented
buf_shrink_expand(chunk, CALLDATA_CHUNK_SIZE, buf, sizeof(buf));
collection->value[collection->size].length = read_u16_be(buf, 0);
collection->value[collection->size].size = read_u16_be(buf, 0);
*offset += 1;
break;

default:
return false;
}
if ((chunk = calldata_get_chunk(*offset)) == NULL) {
collection->value[collection->size].length = collection->value[collection->size].size;
collection->value[collection->size].offset = 0;
if ((leaf_buf = mem_rev_alloc(collection->value[collection->size].length)) == NULL) {
return false;
}
collection->value[collection->size].ptr = chunk;
for (int chunk_idx = 0;
(chunk_idx * CALLDATA_CHUNK_SIZE) < collection->value[collection->size].length;
++chunk_idx) {
if ((chunk = calldata_get_chunk(*offset + chunk_idx)) == NULL) {
return false;
}
memcpy(leaf_buf + (chunk_idx * CALLDATA_CHUNK_SIZE), chunk, CALLDATA_CHUNK_SIZE);
}
collection->value[collection->size].ptr = leaf_buf;
collection->size += 1;
return true;
}
Expand Down Expand Up @@ -200,6 +210,7 @@ static bool path_slice(const s_slice_args *slice, s_parsed_value_collection *col
}
collection->value[collection->size - 1].ptr += start;
collection->value[collection->size - 1].length = (end - start);
collection->value[collection->size - 1].offset += start;
return true;
}

Expand Down Expand Up @@ -308,4 +319,10 @@ bool data_path_get(const s_data_path *data_path, s_parsed_value_collection *coll
return true;
}

void data_path_cleanup(const s_parsed_value_collection *collection) {
for (int i = 0; i < collection->size; ++i) {
mem_rev_dealloc(collection->value[i].size);
}
}

#endif // HAVE_GENERIC_TX_PARSER
1 change: 1 addition & 0 deletions src_features/generic_tx_parser/gtp_data_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ typedef struct {
} s_data_path_context;

bool handle_data_path_struct(const s_tlv_data *data, s_data_path_context *context);
void data_path_cleanup(const s_parsed_value_collection *collection);
bool data_path_get(const s_data_path *data_path, s_parsed_value_collection *collection);

#endif // GTP_DATA_PATH_H_
35 changes: 18 additions & 17 deletions src_features/generic_tx_parser/gtp_param_amount.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,32 @@ bool handle_param_amount_struct(const s_tlv_data *data, s_param_amount_context *
}

bool format_param_amount(const s_param_amount *param, const char *name) {
bool ret;
uint64_t chain_id;
const char *ticker;
s_parsed_value_collection collec;
char *buf = strings.tmp.tmp;
size_t buf_size = sizeof(strings.tmp.tmp);

if (!value_get(&param->value, &collec)) {
return false;
}
chain_id = get_tx_chain_id();
ticker = get_displayable_ticker(&chain_id, chainConfig);
for (int i = 0; i < collec.size; ++i) {
if (!amountToString(collec.value[i].ptr,
collec.value[i].length,
WEI_TO_ETHER,
ticker,
buf,
buf_size)) {
return false;
}
if (!add_to_field_table(PARAM_TYPE_AMOUNT, name, buf)) {
return false;
if ((ret = value_get(&param->value, &collec))) {
chain_id = get_tx_chain_id();
ticker = get_displayable_ticker(&chain_id, chainConfig);
for (int i = 0; i < collec.size; ++i) {
if (!(ret = amountToString(collec.value[i].ptr,
collec.value[i].length,
WEI_TO_ETHER,
ticker,
buf,
buf_size))) {
break;
}
if (!(ret = add_to_field_table(PARAM_TYPE_AMOUNT, name, buf))) {
break;
}
}
}
return true;
value_cleanup(&param->value, &collec);
return ret;
}

#endif // HAVE_GENERIC_TX_PARSER
Loading

0 comments on commit d8487d6

Please sign in to comment.