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

Code before:
                    p--;
                    l--;
                }
                if (*p == '.') l--;
            }
            if (l == 2 && buf[0] == '-' && buf[1] == '0') {
                buf[0] = '0';
                l = 1;
            }
            break;
        default: goto err; /* Invalid mode. */
        }
    }
    buf[l] = '\0';
    return l;
err:
    /* force add Null termination */
    if (len > 0) buf[0] = '\0';
    return 0;
}

/* Populate the provided seed array by deterministically folding the input value.
 * The function repeats the value across the seed array and XORs each byte with
 * the current seed content.
 */
void getHashSeedFromValue(unsigned char *seed_array, size_t len, const char *value) {
    size_t input_len = strlen(value);
    memset(seed_array, 0, len);
    if (input_len == 0) return;
    size_t max_len = len > input_len ? len : input_len;
    for (size_t i = 0; i < max_len; i++) {
        seed_array[i % len] = value[i % input_len] ^ seed_array[i % len];
    }
}

/* Parses a version string on the form "major.minor.patch" and returns an
 * integer on the form 0xMMmmpp. Returns -1 on parse error. */
int version2num(const char *version) {
    int v = 0, part = 0, numdots = 0;
    const char *p = version;
    do {
        if (*p >= '0' && *p <= '9') {
            part = part * 10 + (unsigned)(*p - '0');
            if (part > 255) return -1;
        } else if (*p == '.') {
            if (++numdots > 2) return -1;
            v = (v << 8) | part;
            part = 0;
        } else {
            return -1;
        }

Review comment (suggestion):
FromString seems more descriptive than FromValue. (A value can mean anything.)

Perhaps more importantly: Do we need to invent a new hashing algorithm just for this or shall we reuse an existing algorithm instead? This algorithm along with siphash as the hash function will become fixed from now on. Until now, we could replace siphash if we'd wanted, but from now on, if we change any of this, it breaks the scan cursor compatibility between nodes.

We can hard-code the output seed length to 16 bytes, which is what the hash salt needs to be. No need to make it dynamic here.

I suggest we use either SHA1 or SHA256 that we already have in the repo. SHA1 generates an 16 bytes string, which is what we need for siphash. If we use SHA256 instead, we only need half of the output, but we can simply take the first or the second half.

Code after:
                    p--;
                    l--;
                }
                if (*p == '.') l--;
            }
            if (l == 2 && buf[0] == '-' && buf[1] == '0') {
                buf[0] = '0';
                l = 1;
            }
            break;
        default: goto err; /* Invalid mode. */
        }
    }
    buf[l] = '\0';
    return l;
err:
    /* force add Null termination */
    if (len > 0) buf[0] = '\0';
    return 0;
}

/* Populate the provided seed array by hashing the provided string with SHA256
 * and copying the first outlen bytes of the digest into the seed buffer. */
void getHashSeedFromString(unsigned char *seed_array, size_t outlen, const char *value) {
    SHA256_CTX ctx;
    unsigned char digest[SHA256_BLOCK_SIZE];

    sha256_init(&ctx);
    sha256_update(&ctx, (const BYTE *)value, strlen(value));
    sha256_final(&ctx, digest);

    if (outlen > SHA256_BLOCK_SIZE) outlen = SHA256_BLOCK_SIZE;
    memcpy(seed_array, digest, outlen);
}


/* Parses a version string on the form "major.minor.patch" and returns an
 * integer on the form 0xMMmmpp. Returns -1 on parse error. */
int version2num(const char *version) {
    int v = 0, part = 0, numdots = 0;
    const char *p = version;
    do {
        if (*p >= '0' && *p <= '9') {
            part = part * 10 + (unsigned)(*p - '0');
            if (part > 255) return -1;
        } else if (*p == '.') {
            if (++numdots > 2) return -1;
            v = (v << 8) | part;
            part = 0;
        } else {
            return -1;