2021-11-03 18:15:46 -04:00
|
|
|
#include "util.js.h"
|
|
|
|
|
2022-06-04 13:04:51 -04:00
|
|
|
#include "mem.h"
|
2021-11-03 18:15:46 -04:00
|
|
|
#include "task.h"
|
2021-12-27 15:49:07 -05:00
|
|
|
#include "trace.h"
|
2021-11-03 18:15:46 -04:00
|
|
|
|
2023-01-28 16:59:36 -05:00
|
|
|
#include <openssl/crypto.h>
|
|
|
|
#include <openssl/sha.h>
|
2022-12-31 11:47:10 -05:00
|
|
|
#include <picohttpparser.h>
|
2022-09-28 19:52:44 -04:00
|
|
|
#include <quickjs-libc.h>
|
2023-02-13 22:15:24 -05:00
|
|
|
#include <sodium/utils.h>
|
2021-12-27 15:49:07 -05:00
|
|
|
#include <uv.h>
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
2022-01-26 20:15:54 -05:00
|
|
|
static JSValue _util_utf8_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
size_t length = 0;
|
|
|
|
const char* value = JS_ToCStringLen(context, &length, argv[0]);
|
2023-01-28 16:59:36 -05:00
|
|
|
JSValue typed_array = tf_util_new_uint8_array(context, (const uint8_t*)value, length);
|
2022-01-26 20:15:54 -05:00
|
|
|
JS_FreeCString(context, value);
|
2023-01-28 16:59:36 -05:00
|
|
|
return typed_array;
|
2022-01-26 20:15:54 -05:00
|
|
|
}
|
|
|
|
|
2021-11-03 18:15:46 -04:00
|
|
|
static JSValue _util_utf8_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
JSValue result = JS_NULL;
|
|
|
|
size_t length;
|
|
|
|
if (JS_IsString(argv[0]))
|
|
|
|
{
|
|
|
|
result = JS_DupValue(context, argv[0]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
|
|
|
|
if (array)
|
|
|
|
{
|
|
|
|
result = JS_NewStringLen(context, (const char*)array, length);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
size_t offset;
|
|
|
|
size_t element_size;
|
|
|
|
JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
|
|
|
size_t size;
|
|
|
|
if (!JS_IsException(buffer))
|
|
|
|
{
|
|
|
|
array = tf_util_try_get_array_buffer(context, &size, buffer);
|
|
|
|
if (array)
|
|
|
|
{
|
|
|
|
result = JS_NewStringLen(context, (const char*)array, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
JS_FreeValue(context, buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSValue tf_util_utf8_decode(JSContext* context, JSValue value)
|
|
|
|
{
|
|
|
|
return _util_utf8_decode(context, JS_NULL, 1, &value);
|
|
|
|
}
|
|
|
|
|
2022-09-28 19:52:44 -04:00
|
|
|
static JSValue _util_base64_encode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
JSValue result = JS_UNDEFINED;
|
|
|
|
size_t length = 0;
|
2023-01-28 16:59:36 -05:00
|
|
|
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
|
|
|
|
if (array)
|
2022-09-28 19:52:44 -04:00
|
|
|
{
|
2023-01-28 16:59:36 -05:00
|
|
|
char* encoded = tf_malloc(length * 4);
|
2023-02-13 22:15:24 -05:00
|
|
|
int r = tf_base64_encode(array, length, encoded, length * 4);
|
2023-01-28 16:59:36 -05:00
|
|
|
if (r >= 0)
|
|
|
|
{
|
|
|
|
result = JS_NewStringLen(context, encoded, r);
|
|
|
|
}
|
|
|
|
tf_free(encoded);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const char* value = JS_ToCStringLen(context, &length, argv[0]);
|
|
|
|
char* encoded = tf_malloc(length * 4);
|
2023-02-13 22:15:24 -05:00
|
|
|
int r = tf_base64_encode((const uint8_t*)value, length, encoded, length * 4);
|
2023-01-28 16:59:36 -05:00
|
|
|
if (r >= 0)
|
|
|
|
{
|
|
|
|
result = JS_NewStringLen(context, encoded, r);
|
|
|
|
}
|
|
|
|
tf_free(encoded);
|
|
|
|
JS_FreeCString(context, value);
|
2022-09-28 19:52:44 -04:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static JSValue _util_base64_decode(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
JSValue result = JS_UNDEFINED;
|
|
|
|
size_t length = 0;
|
|
|
|
const char* value = JS_ToCStringLen(context, &length, argv[0]);
|
2023-02-13 22:15:24 -05:00
|
|
|
uint8_t* encoded = tf_malloc(length);
|
2022-09-28 19:52:44 -04:00
|
|
|
|
2023-02-13 22:15:24 -05:00
|
|
|
int r = tf_base64_decode(value, length, encoded, length);
|
2022-09-28 19:52:44 -04:00
|
|
|
if (r >= 0)
|
|
|
|
{
|
2023-02-13 22:15:24 -05:00
|
|
|
result = JS_NewStringLen(context, (const char*)encoded, r);
|
2022-09-28 19:52:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
tf_free(encoded);
|
|
|
|
JS_FreeCString(context, value);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-11-03 18:28:25 -04:00
|
|
|
uint8_t* tf_util_try_get_array_buffer(JSContext* context, size_t* psize, JSValueConst obj)
|
2021-11-03 18:15:46 -04:00
|
|
|
{
|
2021-11-03 18:28:25 -04:00
|
|
|
uint8_t* result = JS_GetArrayBuffer(context, psize, obj);
|
|
|
|
JS_FreeValue(context, JS_GetException(context));
|
2021-11-03 18:15:46 -04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-11-03 18:28:25 -04:00
|
|
|
JSValue tf_util_try_get_typed_array_buffer(JSContext* context, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element)
|
2021-11-03 18:15:46 -04:00
|
|
|
{
|
2021-11-03 18:28:25 -04:00
|
|
|
JSValue result = JS_GetTypedArrayBuffer(context, obj, pbyte_offset, pbyte_length, pbytes_per_element);
|
|
|
|
JS_FreeValue(context, JS_GetException(context));
|
2021-11-03 18:15:46 -04:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSValue _util_print(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
tf_task_t* task = JS_GetContextOpaque(context);
|
|
|
|
if (task)
|
|
|
|
{
|
|
|
|
printf("Task[%p:%s]>", task, tf_task_get_name(task));
|
2022-02-13 17:39:22 -05:00
|
|
|
tf_task_print(task, argc, argv);
|
2021-11-03 18:15:46 -04:00
|
|
|
}
|
|
|
|
for (int i = 0; i < argc; ++i)
|
|
|
|
{
|
|
|
|
if (JS_IsNull(argv[i]))
|
|
|
|
{
|
|
|
|
printf(" null");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const char* value = JS_ToCString(context, argv[i]);
|
|
|
|
printf(" %s", value);
|
|
|
|
JS_FreeCString(context, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
return JS_NULL;
|
|
|
|
}
|
|
|
|
|
2021-12-27 16:48:16 -05:00
|
|
|
bool tf_util_report_error(JSContext* context, JSValue value)
|
2021-11-03 18:28:25 -04:00
|
|
|
{
|
2021-12-27 16:48:16 -05:00
|
|
|
bool is_error = false;
|
2021-11-03 18:28:25 -04:00
|
|
|
if (JS_IsError(context, value))
|
|
|
|
{
|
|
|
|
const char* string = JS_ToCString(context, value);
|
|
|
|
printf("ERROR: %s\n", string);
|
|
|
|
JS_FreeCString(context, string);
|
|
|
|
|
|
|
|
JSValue stack = JS_GetPropertyStr(context, value, "stack");
|
|
|
|
if (!JS_IsUndefined(stack))
|
|
|
|
{
|
|
|
|
const char* stack_str = JS_ToCString(context, stack);
|
|
|
|
printf("%s\n", stack_str);
|
|
|
|
JS_FreeCString(context, stack_str);
|
|
|
|
}
|
|
|
|
JS_FreeValue(context, stack);
|
|
|
|
|
|
|
|
tf_task_t* task = tf_task_get(context);
|
2022-07-26 20:27:10 -04:00
|
|
|
if (!task || !tf_task_send_error_to_parent(task, value))
|
2021-11-03 18:28:25 -04:00
|
|
|
{
|
2022-07-26 20:27:10 -04:00
|
|
|
js_std_dump_error(context);
|
2021-11-03 18:28:25 -04:00
|
|
|
}
|
2021-12-27 16:48:16 -05:00
|
|
|
is_error = true;
|
2021-11-03 18:28:25 -04:00
|
|
|
}
|
|
|
|
else if (JS_IsException(value))
|
|
|
|
{
|
|
|
|
tf_task_t* task = tf_task_get(context);
|
2022-07-26 20:27:10 -04:00
|
|
|
if (!task || !tf_task_send_error_to_parent(task, value))
|
2022-04-17 20:24:00 -04:00
|
|
|
{
|
|
|
|
js_std_dump_error(context);
|
2021-11-03 18:28:25 -04:00
|
|
|
}
|
2021-12-27 16:48:16 -05:00
|
|
|
is_error = true;
|
2021-11-03 18:28:25 -04:00
|
|
|
}
|
2021-12-27 16:48:16 -05:00
|
|
|
return is_error;
|
2021-11-03 18:28:25 -04:00
|
|
|
}
|
|
|
|
|
2021-12-27 15:49:07 -05:00
|
|
|
typedef struct _timeout_t {
|
|
|
|
tf_task_t* _task;
|
|
|
|
JSValue _callback;
|
|
|
|
} timeout_t;
|
|
|
|
|
|
|
|
static void _handle_closed(uv_handle_t* handle)
|
|
|
|
{
|
2022-06-04 13:04:51 -04:00
|
|
|
tf_free(handle);
|
2021-12-27 15:49:07 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void _util_timeoutCallback(uv_timer_t* handle)
|
|
|
|
{
|
|
|
|
timeout_t* timeout = handle->data;
|
|
|
|
tf_trace_begin(tf_task_get_trace(timeout->_task), "_util_timeoutCallback");
|
|
|
|
JSContext* context = tf_task_get_context(timeout->_task);
|
|
|
|
JSValue result = JS_Call(
|
|
|
|
context,
|
|
|
|
timeout->_callback,
|
|
|
|
JS_NULL,
|
|
|
|
0,
|
|
|
|
NULL);
|
|
|
|
tf_util_report_error(context, result);
|
|
|
|
JS_FreeValue(context, result);
|
2022-06-06 22:41:44 -04:00
|
|
|
JS_FreeValue(context, timeout->_callback);
|
2021-12-27 15:49:07 -05:00
|
|
|
tf_trace_end(tf_task_get_trace(timeout->_task));
|
2022-06-04 13:04:51 -04:00
|
|
|
tf_free(timeout);
|
2021-12-27 15:49:07 -05:00
|
|
|
uv_close((uv_handle_t*)handle, _handle_closed);
|
|
|
|
}
|
|
|
|
|
|
|
|
static JSValue _util_setTimeout(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
tf_task_t* task = JS_GetContextOpaque(context);
|
|
|
|
|
2022-06-04 13:04:51 -04:00
|
|
|
timeout_t* timeout = tf_malloc(sizeof(timeout_t));
|
2021-12-27 15:49:07 -05:00
|
|
|
*timeout = (timeout_t)
|
|
|
|
{
|
|
|
|
._task = task,
|
|
|
|
._callback = JS_DupValue(context, argv[0]),
|
|
|
|
};
|
|
|
|
|
2022-06-04 13:04:51 -04:00
|
|
|
uv_timer_t* timer = tf_malloc(sizeof(uv_timer_t));
|
2021-12-27 15:49:07 -05:00
|
|
|
memset(timer, 0, sizeof(uv_timer_t));
|
|
|
|
uv_timer_init(tf_task_get_loop(task), timer);
|
|
|
|
timer->data = timeout;
|
|
|
|
|
|
|
|
int64_t duration;
|
|
|
|
JS_ToInt64(context, &duration, argv[1]);
|
|
|
|
uv_timer_start(timer, _util_timeoutCallback, duration, 0);
|
|
|
|
return JS_NULL;
|
|
|
|
}
|
|
|
|
|
2022-12-31 11:47:10 -05:00
|
|
|
static JSValue _util_parseHttp(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
JSValue result = JS_UNDEFINED;
|
|
|
|
const char* method = NULL;
|
|
|
|
size_t method_length = 0;
|
|
|
|
const char* path = NULL;
|
|
|
|
size_t path_length = 0;
|
|
|
|
int minor_version = 0;
|
|
|
|
struct phr_header headers[100];
|
|
|
|
size_t header_count = sizeof(headers) / sizeof(*headers);
|
|
|
|
int previous_length = 0;
|
|
|
|
JS_ToInt32(context, &previous_length, argv[1]);
|
|
|
|
|
|
|
|
JSValue buffer = JS_UNDEFINED;
|
|
|
|
size_t length;
|
|
|
|
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
|
|
|
|
if (!array)
|
|
|
|
{
|
|
|
|
size_t offset;
|
|
|
|
size_t element_size;
|
|
|
|
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
|
|
|
if (!JS_IsException(buffer))
|
|
|
|
{
|
|
|
|
array = tf_util_try_get_array_buffer(context, &length, buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array)
|
|
|
|
{
|
|
|
|
int parse_result = phr_parse_request((const char*)array, length, &method, &method_length, &path, &path_length, &minor_version, headers, &header_count, 0);
|
|
|
|
if (parse_result > 0)
|
|
|
|
{
|
|
|
|
result = JS_NewObject(context);
|
|
|
|
JS_SetPropertyStr(context, result, "bytes_parsed", JS_NewInt32(context, parse_result));
|
|
|
|
JS_SetPropertyStr(context, result, "minor_version", JS_NewInt32(context, minor_version));
|
|
|
|
JS_SetPropertyStr(context, result, "method", JS_NewStringLen(context, method, method_length));
|
|
|
|
JS_SetPropertyStr(context, result, "path", JS_NewStringLen(context, path, path_length));
|
|
|
|
JSValue header_object = JS_NewObject(context);
|
|
|
|
for (int i = 0; i < (int)header_count; i++)
|
|
|
|
{
|
|
|
|
char name[256];
|
|
|
|
snprintf(name, sizeof(name), "%.*s", (int)headers[i].name_len, headers[i].name);
|
|
|
|
JS_SetPropertyStr(context, header_object, name, JS_NewStringLen(context, headers[i].value, headers[i].value_len));
|
|
|
|
}
|
|
|
|
JS_SetPropertyStr(context, result, "headers", header_object);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = JS_NewInt32(context, parse_result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result = JS_ThrowTypeError(context, "Could not convert argument to array.");
|
|
|
|
}
|
|
|
|
|
|
|
|
JS_FreeValue(context, buffer);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-01-28 16:59:36 -05:00
|
|
|
static JSValue _util_sha1_digest(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
size_t length = 0;
|
|
|
|
const char* value = JS_ToCStringLen(context, &length, argv[0]);
|
|
|
|
unsigned char digest[SHA_DIGEST_LENGTH] = { 0 };
|
|
|
|
SHA1((const unsigned char*)value, length, digest);
|
|
|
|
return JS_NewArrayBufferCopy(context, digest, sizeof(digest));
|
|
|
|
}
|
|
|
|
|
|
|
|
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
|
|
|
|
{
|
|
|
|
JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);
|
|
|
|
JSValue global = JS_GetGlobalObject(context);
|
|
|
|
JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array");
|
|
|
|
JSValue result = JS_CallConstructor(context, constructor, 1, &array_buffer);
|
|
|
|
JS_FreeValue(context, constructor);
|
|
|
|
JS_FreeValue(context, global);
|
|
|
|
JS_FreeValue(context, array_buffer);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static JSValue _util_mask_bytes(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
|
|
|
{
|
|
|
|
JSValue result = JS_UNDEFINED;
|
|
|
|
uint32_t mask = 0;
|
|
|
|
JS_ToUint32(context, &mask, argv[1]);
|
|
|
|
uint64_t double_mask = ((uint64_t)mask << 32) | mask;
|
|
|
|
|
|
|
|
size_t offset = 0;
|
|
|
|
size_t length = 0;
|
|
|
|
size_t element_size = 0;
|
|
|
|
JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
|
|
|
|
if (!JS_IsException(buffer))
|
|
|
|
{
|
|
|
|
size_t size = 0;
|
|
|
|
const uint8_t* array = tf_util_try_get_array_buffer(context, &size, buffer);
|
|
|
|
if (array)
|
|
|
|
{
|
|
|
|
uint8_t* copy = tf_malloc(size);
|
|
|
|
size_t i = 0;
|
|
|
|
for (; i + sizeof(double_mask) < size; i += sizeof(double_mask))
|
|
|
|
{
|
|
|
|
((uint64_t*)copy)[i / sizeof(double_mask)] = ((const uint64_t*)array)[i / sizeof(double_mask)] ^ double_mask;
|
|
|
|
}
|
|
|
|
for (; i + sizeof(mask) < size; i += sizeof(mask))
|
|
|
|
{
|
|
|
|
((uint32_t*)copy)[i / sizeof(mask)] = ((const uint32_t*)array)[i / sizeof(mask)] ^ mask;
|
|
|
|
}
|
|
|
|
for (; i < size; i++)
|
|
|
|
{
|
|
|
|
copy[i] = array[i] ^ ((mask >> (8 * (i % 4))) & 0xff);
|
|
|
|
}
|
|
|
|
result = tf_util_new_uint8_array(context, copy, size);
|
|
|
|
tf_free(copy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-11-03 18:15:46 -04:00
|
|
|
void tf_util_register(JSContext* context)
|
|
|
|
{
|
|
|
|
JSValue global = JS_GetGlobalObject(context);
|
|
|
|
JS_SetPropertyStr(context, global, "utf8Decode", JS_NewCFunction(context, _util_utf8_decode, "utf8Decode", 1));
|
2022-01-26 20:15:54 -05:00
|
|
|
JS_SetPropertyStr(context, global, "utf8Encode", JS_NewCFunction(context, _util_utf8_encode, "utf8Encode", 1));
|
2022-09-28 19:52:44 -04:00
|
|
|
JS_SetPropertyStr(context, global, "base64Decode", JS_NewCFunction(context, _util_base64_decode, "base64Decode", 1));
|
|
|
|
JS_SetPropertyStr(context, global, "base64Encode", JS_NewCFunction(context, _util_base64_encode, "base64Encode", 1));
|
2021-11-03 18:15:46 -04:00
|
|
|
JS_SetPropertyStr(context, global, "print", JS_NewCFunction(context, _util_print, "print", 1));
|
2021-12-27 15:49:07 -05:00
|
|
|
JS_SetPropertyStr(context, global, "setTimeout", JS_NewCFunction(context, _util_setTimeout, "setTimeout", 2));
|
2022-12-31 11:47:10 -05:00
|
|
|
JS_SetPropertyStr(context, global, "parseHttp", JS_NewCFunction(context, _util_parseHttp, "parseHttp", 2));
|
2023-01-28 16:59:36 -05:00
|
|
|
JS_SetPropertyStr(context, global, "sha1Digest", JS_NewCFunction(context, _util_sha1_digest, "sha1Digest", 1));
|
|
|
|
JS_SetPropertyStr(context, global, "maskBytes", JS_NewCFunction(context, _util_mask_bytes, "maskBytes", 2));
|
2021-11-03 18:15:46 -04:00
|
|
|
JS_FreeValue(context, global);
|
|
|
|
}
|
2022-07-09 11:13:35 -04:00
|
|
|
|
|
|
|
int tf_util_get_length(JSContext* context, JSValue value)
|
|
|
|
{
|
|
|
|
JSValue length = JS_GetPropertyStr(context, value, "length");
|
|
|
|
int result = 0;
|
|
|
|
JS_ToInt32(context, &result, length);
|
|
|
|
JS_FreeValue(context, length);
|
|
|
|
return result;
|
|
|
|
}
|
2022-07-11 21:51:15 -04:00
|
|
|
|
|
|
|
int tf_util_insert_index(const void* key, const void* base, size_t count, size_t size, int (*compare)(const void*, const void*))
|
|
|
|
{
|
|
|
|
int lower = 0;
|
|
|
|
int upper = count;
|
|
|
|
while (lower < upper && lower < (int)count)
|
|
|
|
{
|
|
|
|
int guess = (lower + upper) / 2;
|
|
|
|
int result = compare(key, ((char*)base) + size * guess);
|
|
|
|
if (result < 0)
|
|
|
|
{
|
|
|
|
upper = guess;
|
|
|
|
}
|
|
|
|
else if (result > 0)
|
|
|
|
{
|
|
|
|
lower = guess + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return guess;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return lower;
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:15:24 -05:00
|
|
|
size_t tf_base64_encode(const uint8_t* source, size_t source_length, char* out, size_t out_length)
|
|
|
|
{
|
|
|
|
sodium_bin2base64(out, out_length, source, source_length, sodium_base64_VARIANT_ORIGINAL);
|
|
|
|
return sodium_base64_ENCODED_LEN(source_length, sodium_base64_VARIANT_ORIGINAL) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t tf_base64_decode(const char* source, size_t source_length, uint8_t* out, size_t out_length)
|
|
|
|
{
|
|
|
|
size_t actual_length = 0;
|
|
|
|
return sodium_base642bin(out, out_length, source, source_length, NULL, &actual_length, NULL, sodium_base64_VARIANT_ORIGINAL) == 0 ? actual_length : 0;
|
|
|
|
}
|