Repository: valkey-io/valkey
File: src/cluster_legacy.c (C)

Code before:
/* 8 bytes mstime + 8 bytes cluster node */
#define FAILURE_REPORT_KEYLEN 16
#define SEC_IN_MS 1000ULL
#define CEIL_OFFSET_MS (SEC_IN_MS - 1)

/* The report time is rounded up (ceiled) to the nearest second in order to bucket
 * timestamps and keep the radix‐tree keys compact.  The resulting timestamp is stored
 * in big‑endian format, followed by the pointer to the clusterNode. */
static void encodeFailureReportKey(unsigned char *buf, mstime_t report_time, clusterNode *node) {
    const size_t node_ptr_pad_bytes = (sizeof(clusterNode *) == 4) ? 4 : 0; // pad on 32-bit

    /* Round up to the next second for fewer key splits and quorum grace */
    mstime_t bucketed_time = ((report_time + CEIL_OFFSET_MS) / SEC_IN_MS) * SEC_IN_MS;

    /* Big endian timestamp for numeric time order in rax */
    uint64_t big_endian_time = htonu64((uint64_t)bucketed_time);
    memcpy(buf, &big_endian_time, sizeof(uint64_t));

    /* Append the node pointer, plus padding if necessary */
    memcpy(buf + sizeof(uint64_t), &node, sizeof(node));
    if (node_ptr_pad_bytes) memset(buf + sizeof(uint64_t) + sizeof(node), 0, node_ptr_pad_bytes);
}

/* Reverses the operation of encodeFailureReportKey, reading back a timestamp
 * and the pointer to the associated clusterNode. */
static void decodeFailureReportKey(unsigned char *buf, mstime_t *report_time, clusterNode **node_ptr) {
    uint64_t big_endian_time;
    memcpy(&big_endian_time, buf, sizeof(uint64_t));
    *report_time = (mstime_t)ntohu64(big_endian_time);
    memcpy(node_ptr, buf + sizeof(uint64_t), sizeof(*node_ptr));
}

/* This function is called every time we get a failure report from a node.
 * We maintain fail_reports as a radix tree keyed by bucketed timestamp
 * (ceiled to the next second) and the sender pointer, which naturally orders
 * entries by time.
 *
 * 'failing' is the node that is in failure state according to the
 * 'sender' node.
 *
 * The function returns 0 if it early‐exits (same sender & time bucket)
 * or updates a timestamp of an existing failure report from the same sender.
 * 1 is returned if a new failure report is created. */
int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) {
    unsigned char buf[FAILURE_REPORT_KEYLEN];
    mstime_t now = mstime();
    const mstime_t bucketed_time = ((now + CEIL_OFFSET_MS) / SEC_IN_MS) * SEC_IN_MS;
    int is_new = 1;

    /* Look for any existing entry from this sender and remove it */
    raxIterator ri;

Review comment (bug):
```suggestion
static void decodeFailureReportKey(unsigned char *buf, mstime_t *report_time, clusterNode **node_ptr) {
```

Code after:
/* 8 bytes mstime + 8 bytes cluster node pointer (padded with zeros on 32-bit systems). */
#define FAILURE_REPORT_KEYLEN 16
#define SEC_IN_MS 1000ULL
#define CEIL_OFFSET_MS (SEC_IN_MS - 1)

/* The report time is rounded up (ceiled) to the nearest second in order to bucket
 * timestamps and keep the radix‐tree keys compact.  The resulting timestamp is stored
 * in big‑endian format, followed by the pointer to the clusterNode. */
static void encodeFailureReportKey(clusterNode *node, mstime_t report_time, unsigned char *buf_out) {
    const size_t node_ptr_pad_bytes = (sizeof(clusterNode *) == 4) ? 4 : 0; // pad on 32-bit

    /* Round up to the next second for fewer key splits and quorum grace */
    mstime_t bucketed_time = (report_time / SEC_IN_MS) * SEC_IN_MS + SEC_IN_MS;

    /* Big endian timestamp for numeric time order in rax */
    uint64_t big_endian_time = htonu64((uint64_t)bucketed_time);
    memcpy(buf_out, &big_endian_time, sizeof(uint64_t));

    /* Append the node pointer, plus padding if necessary */
    memcpy(buf_out + sizeof(uint64_t), &node, sizeof(node));
    if (node_ptr_pad_bytes) memset(buf_out + sizeof(uint64_t) + sizeof(node), 0, node_ptr_pad_bytes);
}

/* Reverses the operation of encodeFailureReportKey, reading back a timestamp
 * and the pointer to the associated clusterNode. */
static void decodeFailureReportKey(unsigned char *buf, mstime_t *report_time, clusterNode **node_ptr) {
    uint64_t big_endian_time;
    memcpy(&big_endian_time, buf, sizeof(uint64_t));
    *report_time = (mstime_t)ntohu64(big_endian_time);
    memcpy(node_ptr, buf + sizeof(uint64_t), sizeof(*node_ptr));
}

/* This function is called every time we get a failure report from a node.
 * We maintain fail_reports as a radix tree keyed by bucketed timestamp
 * (ceiled to the next second) and the sender pointer, which naturally orders
 * entries by time.
 *
 * 'failing' is the node that is in failure state according to the
 * 'sender' node.
 *
 * The function returns 0 if it early‐exits (same sender & time bucket)
 * or updates a timestamp of an existing failure report from the same sender.
 * 1 is returned if a new failure report is created. */
int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) {
    unsigned char buf[FAILURE_REPORT_KEYLEN];
    mstime_t now = mstime();
    const mstime_t bucketed_time = (now / SEC_IN_MS) * SEC_IN_MS + SEC_IN_MS;
    int is_new = 1;

    /* Look for any existing entry from this sender and remove it */
    raxIterator ri;