285 lines
7.7 KiB
C

#include "str_util.h"
#include "panic.h"
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* str_dup(const char* str)
{
char* clone = calloc(strlen(str) + 1, sizeof(char));
strcpy(clone, str);
return clone;
}
StrSplitter str_splitter(const char* text, size_t text_len, const char* split)
{
return (StrSplitter) {
.text = text,
.text_len = text_len,
.i = 0,
.split = split,
.split_len = strlen(split),
};
}
StrSlice str_split_next(StrSplitter* splitter)
{
for (size_t i = splitter->i; i < splitter->text_len; ++i) {
if (strncmp(&splitter->text[i], splitter->split, splitter->split_len)
== 0) {
const char* ptr = &splitter->text[splitter->i];
size_t len = i - splitter->i;
splitter->i = i + splitter->split_len;
return (StrSlice) { ptr, len };
}
}
return (StrSlice) {
.ptr = &splitter->text[splitter->i],
.len = splitter->text_len - splitter->i,
};
}
void string_push_str(String* string, const char* str)
{
for (size_t i = 0; i < strlen(str); ++i) {
string_push(string, str[i]);
}
string_push(string, '\0');
string->size -= 1;
}
void string_push_fmt_va(String* string, const char* fmt, ...)
{
va_list args1;
va_start(args1, fmt);
va_list args2;
va_copy(args2, args1);
char buf[1 + vsnprintf(NULL, 0, fmt, args1)];
va_end(args1);
vsnprintf(buf, sizeof buf, fmt, args2);
va_end(args2);
string_push_str(string, buf);
}
char* string_copy(const String* string)
{
char* copy = malloc(string->size + 1);
strncpy(copy, string->data, string->size);
copy[string->size] = '\0';
return copy;
}
#define STR_HASH_SALT_SIZE 32
#define STR_HASH_HASH_SIZE 32
#define STR_HASH_STR_LEN 128
typedef struct {
uint8_t salt[STR_HASH_SALT_SIZE];
uint8_t hash[STR_HASH_HASH_SIZE];
} HashData;
static inline HashData hashdata_from_str_and_salt(
const char* str, const uint8_t* salt)
{
if (strlen(str) >= MAX_HASH_INPUT_LEN) {
fprintf(stderr, "error: tried to hash too long input\n");
exit(1);
}
HashData hash;
memcpy(hash.salt, salt, STR_HASH_SALT_SIZE);
uint8_t input[MAX_HASH_INPUT_LEN + 1 + STR_HASH_SALT_SIZE] = { 0 };
memcpy(input, hash.salt, STR_HASH_SALT_SIZE);
memcpy(&input[STR_HASH_SALT_SIZE], str, strlen(str));
SHA256(input, strlen((char*)input), hash.hash);
return hash;
}
static inline HashData hashdata_from_str(const char* str)
{
uint8_t salt[STR_HASH_SALT_SIZE];
RAND_bytes(salt, STR_HASH_SALT_SIZE);
return hashdata_from_str_and_salt(str, salt);
}
static inline bool hashdata_is_equal(HashData hash, const char* str)
{
HashData other = hashdata_from_str_and_salt(str, hash.salt);
return memcmp(hash.hash, other.hash, STR_HASH_HASH_SIZE) == 0;
}
static inline char* hashdata_to_string(HashData hash)
{
char* result = calloc(STR_HASH_STR_LEN + 1, sizeof(char));
for (size_t i = 0; i < STR_HASH_SALT_SIZE; ++i) {
char bytestr[3] = { 0 };
snprintf(bytestr, 3, "%02x", hash.salt[i]);
result[i * 2] = bytestr[0];
result[i * 2 + 1] = bytestr[1];
}
for (size_t i = 0; i < STR_HASH_HASH_SIZE; ++i) {
char bytestr[3] = { 0 };
snprintf(bytestr, 3, "%02x", hash.hash[i]);
result[(STR_HASH_SALT_SIZE + i) * 2] = bytestr[0];
result[(STR_HASH_SALT_SIZE + i) * 2 + 1] = bytestr[1];
}
return result;
}
static inline HashData hashdata_from_hash_string(const char* str)
{
uint8_t result[64] = { 0 };
size_t result_i = 0;
for (size_t i = 0; i < strlen(str) && result_i < 64; i += 2) {
char bytestr[3] = { 0 };
strncpy(bytestr, &str[i], 2);
uint64_t byte = strtoul(bytestr, NULL, 16);
result[result_i] = (uint8_t)byte;
result_i += 1;
}
HashData hash;
for (size_t i = 0; i < 32; ++i) {
hash.salt[i] = result[i];
hash.hash[i] = result[32 + i];
}
return hash;
}
char* str_hash(const char* input)
{
HashData data = hashdata_from_str(input);
return hashdata_to_string(data);
}
bool str_hash_equal(const char* hash, const char* input)
{
HashData data = hashdata_from_hash_string(hash);
return hashdata_is_equal(data, input);
}
static inline uint64_t chibihash64__load32le(const uint8_t* p)
{
return (uint64_t)p[0] << 0 | (uint64_t)p[1] << 8 | (uint64_t)p[2] << 16
| (uint64_t)p[3] << 24;
}
static inline uint64_t chibihash64__load64le(const uint8_t* p)
{
return chibihash64__load32le(p) | (chibihash64__load32le(p + 4) << 32);
}
static inline uint64_t chibihash64__rotl(uint64_t x, int n)
{
return (x << n) | (x >> (-n & 63));
}
static inline uint64_t chibihash64(
const void* keyIn, ptrdiff_t len, uint64_t seed)
{
const uint8_t* p = (const uint8_t*)keyIn;
ptrdiff_t l = len;
const uint64_t K = UINT64_C(0x2B7E151628AED2A7); // digits of e
uint64_t seed2
= chibihash64__rotl(seed - K, 15) + chibihash64__rotl(seed - K, 47);
uint64_t h[4] = { seed, seed + K, seed2, seed2 + (K * K ^ K) };
// depending on your system unrolling might (or might not) make things
// a tad bit faster on large strings. on my system, it actually makes
// things slower.
// generally speaking, the cost of bigger code size is usually not
// worth the trade-off since larger code-size will hinder inlinability
// but depending on your needs, you may want to uncomment the pragma
// below to unroll the loop.
// #pragma GCC unroll 2
for (; l >= 32; l -= 32) {
for (int i = 0; i < 4; ++i, p += 8) {
uint64_t stripe = chibihash64__load64le(p);
h[i] = (stripe + h[i]) * K;
h[(i + 1) & 3] += chibihash64__rotl(stripe, 27);
}
}
for (; l >= 8; l -= 8, p += 8) {
h[0] ^= chibihash64__load32le(p + 0);
h[0] *= K;
h[1] ^= chibihash64__load32le(p + 4);
h[1] *= K;
}
if (l >= 4) {
h[2] ^= chibihash64__load32le(p);
h[3] ^= chibihash64__load32le(p + l - 4);
} else if (l > 0) {
h[2] ^= p[0];
h[3] ^= p[l / 2] | ((uint64_t)p[l - 1] << 8);
}
h[0] += chibihash64__rotl(h[2] * K, 31) ^ (h[2] >> 31);
h[1] += chibihash64__rotl(h[3] * K, 31) ^ (h[3] >> 31);
h[0] *= K;
h[0] ^= h[0] >> 31;
h[1] += h[0];
uint64_t x = (uint64_t)len * K;
x ^= chibihash64__rotl(x, 29);
x += seed;
x ^= h[1];
x ^= chibihash64__rotl(x, 15) ^ chibihash64__rotl(x, 42);
x *= K;
x ^= chibihash64__rotl(x, 13) ^ chibihash64__rotl(x, 31);
return x;
}
uint64_t str_fast_hash(const char* input)
{
return chibihash64(input, sizeof(char*), 0x80085);
}
char* str_random(size_t length)
{
char* string = calloc(length + 1, sizeof(char));
size_t string_i = 0;
for (size_t i = 0; i < length; ++i) {
int r = rand() % (10 + 26 + 26);
if (r < 10) {
string[string_i++] = (char)r + '0';
} else if (r < 10 + 26) {
string[string_i++] = (char)(r - 10) + 'A';
} else {
string[string_i++] = (char)(r - 10 - 26) + 'a';
}
}
return string;
}
void str_util_test(void)
{
{
char* hash = str_hash("1234");
if (!str_hash_equal(hash, "1234")) {
PANIC("hash should be equal");
}
if (str_hash_equal(hash, "4321")) {
PANIC("hash should not be equal");
}
free(hash);
}
{
char* token_1 = str_random(16);
char* token_2 = str_random(16);
if (strcmp(token_1, token_2) == 0) {
PANIC("tokens should not be equal");
}
free(token_1);
free(token_2);
}
}