feat: release Dever 0.1.5 with direct workers and long transactions
This commit is contained in:
1 parent
89629c2197
commit
a900d7e8db
153 files changed
+3676
-3708
No files matched your search
@@ -0,0 +1,310 @@
|
||||
#include "dever_ffi.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_BYTES (16U * 1024U * 1024U)
|
||||
#define MAX_TOKENS 65536U
|
||||
#define MAX_DEPTH 64U
|
||||
|
||||
typedef struct {
|
||||
size_t start, end, after, count;
|
||||
char kind;
|
||||
} token;
|
||||
|
||||
typedef struct {
|
||||
const char *data;
|
||||
size_t length, cursor, used;
|
||||
token *tokens;
|
||||
} document;
|
||||
|
||||
static void whitespace(document *doc) {
|
||||
while (doc->cursor < doc->length && strchr(" \t\r\n", doc->data[doc->cursor]) != NULL)
|
||||
doc->cursor++;
|
||||
}
|
||||
|
||||
static int string_end(document *doc) {
|
||||
if (doc->data[doc->cursor++] != '"') return 0;
|
||||
while (doc->cursor < doc->length) {
|
||||
unsigned char byte = (unsigned char)doc->data[doc->cursor++];
|
||||
if (byte == '"') return 1;
|
||||
if (byte < 32) return 0;
|
||||
if (byte == '\\') {
|
||||
if (doc->cursor == doc->length) return 0;
|
||||
byte = (unsigned char)doc->data[doc->cursor++];
|
||||
if (byte == 'u') {
|
||||
for (unsigned digit = 0; digit < 4; digit++)
|
||||
if (doc->cursor == doc->length || !isxdigit((unsigned char)doc->data[doc->cursor++])) return 0;
|
||||
} else if (strchr("\"\\/bfnrt", byte) == NULL) return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int number_end(document *doc) {
|
||||
if (doc->data[doc->cursor] == '-') doc->cursor++;
|
||||
if (doc->cursor == doc->length) return 0;
|
||||
if (doc->data[doc->cursor] == '0') doc->cursor++;
|
||||
else {
|
||||
if (doc->data[doc->cursor] < '1' || doc->data[doc->cursor] > '9') return 0;
|
||||
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
|
||||
}
|
||||
if (doc->cursor < doc->length && doc->data[doc->cursor] == '.') {
|
||||
size_t start = ++doc->cursor;
|
||||
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
|
||||
if (doc->cursor == start) return 0;
|
||||
}
|
||||
if (doc->cursor < doc->length && strchr("eE", doc->data[doc->cursor]) != NULL) {
|
||||
doc->cursor++;
|
||||
if (doc->cursor < doc->length && strchr("+-", doc->data[doc->cursor]) != NULL) doc->cursor++;
|
||||
size_t start = doc->cursor;
|
||||
while (doc->cursor < doc->length && isdigit((unsigned char)doc->data[doc->cursor])) doc->cursor++;
|
||||
if (doc->cursor == start) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int value(document *doc, unsigned depth) {
|
||||
whitespace(doc);
|
||||
if (depth > MAX_DEPTH || doc->used == MAX_TOKENS || doc->cursor == doc->length) return 0;
|
||||
token *item = &doc->tokens[doc->used++];
|
||||
item->start = doc->cursor;
|
||||
item->kind = doc->data[doc->cursor];
|
||||
if (item->kind == '"') {
|
||||
if (!string_end(doc)) return 0;
|
||||
} else if (item->kind == '{' || item->kind == '[') {
|
||||
char end = item->kind == '{' ? '}' : ']';
|
||||
doc->cursor++;
|
||||
whitespace(doc);
|
||||
if (doc->cursor == doc->length) return 0;
|
||||
if (doc->data[doc->cursor] != end) {
|
||||
for (;;) {
|
||||
whitespace(doc);
|
||||
if (item->kind == '{') {
|
||||
if (doc->cursor == doc->length || doc->data[doc->cursor] != '"' || !value(doc, depth + 1)) return 0;
|
||||
whitespace(doc);
|
||||
if (doc->cursor == doc->length || doc->data[doc->cursor++] != ':') return 0;
|
||||
}
|
||||
if (!value(doc, depth + 1)) return 0;
|
||||
item->count++;
|
||||
whitespace(doc);
|
||||
if (doc->cursor == doc->length) return 0;
|
||||
if (doc->data[doc->cursor] == end) break;
|
||||
if (doc->data[doc->cursor++] != ',') return 0;
|
||||
}
|
||||
}
|
||||
doc->cursor++;
|
||||
} else {
|
||||
const char *literal = item->kind == 't' ? "true" : item->kind == 'f' ? "false" : item->kind == 'n' ? "null" : NULL;
|
||||
if (literal != NULL) {
|
||||
size_t size = strlen(literal);
|
||||
if (size > doc->length - doc->cursor || memcmp(doc->data + doc->cursor, literal, size) != 0) return 0;
|
||||
doc->cursor += size;
|
||||
} else if (!number_end(doc)) return 0;
|
||||
}
|
||||
item->end = doc->cursor;
|
||||
item->after = doc->used;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int parse(document *doc, const char *data, size_t size) {
|
||||
*doc = (document){.data = data, .length = size};
|
||||
if (size == 0 || size > MAX_BYTES || memchr(data, 0, size) != NULL) return 0;
|
||||
doc->tokens = calloc(MAX_TOKENS, sizeof(token));
|
||||
if (doc->tokens == NULL) return 0;
|
||||
if (value(doc, 0)) {
|
||||
whitespace(doc);
|
||||
if (doc->cursor == size) return 1;
|
||||
}
|
||||
free(doc->tokens);
|
||||
doc->tokens = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dever_ffi_slice slice(const document *doc, size_t index) {
|
||||
const token *item = &doc->tokens[index];
|
||||
return (dever_ffi_slice){doc->data + item->start, item->end - item->start};
|
||||
}
|
||||
|
||||
static int text_is(const document *doc, size_t index, const char *text) {
|
||||
if (index >= doc->used || doc->tokens[index].kind != '"') return 0;
|
||||
dever_ffi_slice part = slice(doc, index);
|
||||
return part.size == strlen(text) + 2 && memcmp(part.data + 1, text, (size_t)part.size - 2) == 0;
|
||||
}
|
||||
|
||||
static size_t field(const document *doc, const char *name) {
|
||||
size_t found = MAX_TOKENS;
|
||||
if (doc->tokens[0].kind != '{') return found;
|
||||
for (size_t index = 1; index < doc->tokens[0].after;) {
|
||||
size_t entry = doc->tokens[index].after;
|
||||
if (text_is(doc, index, name)) {
|
||||
if (found != MAX_TOKENS) return MAX_TOKENS;
|
||||
found = entry;
|
||||
}
|
||||
index = doc->tokens[entry].after;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
static int same(const document *left, size_t left_index, const document *right, size_t right_index) {
|
||||
if (left_index >= left->used || right_index >= right->used) return 0;
|
||||
const token *left_item = &left->tokens[left_index], *right_item = &right->tokens[right_index];
|
||||
if (left_item->kind != right_item->kind || left_item->count != right_item->count) return 0;
|
||||
if (left_item->kind == '[') {
|
||||
for (size_t offset = 1; offset <= left_item->count; offset++)
|
||||
if (!same(left, left_index + offset, right, right_index + offset)) return 0;
|
||||
return 1;
|
||||
}
|
||||
dever_ffi_slice left_slice = slice(left, left_index), right_slice = slice(right, right_index);
|
||||
return left_slice.size == right_slice.size && memcmp(left_slice.data, right_slice.data, (size_t)left_slice.size) == 0;
|
||||
}
|
||||
|
||||
static int append(dever_ffi_output *output, dever_ffi_slice input) {
|
||||
if (input.size > output->capacity - output->size) return 0;
|
||||
memcpy(output->data + output->size, input.data, (size_t)input.size);
|
||||
output->size += input.size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int literal(dever_ffi_output *output, const char *text) {
|
||||
return append(output, (dever_ffi_slice){text, strlen(text)});
|
||||
}
|
||||
|
||||
static int send(dever_ffi_slice data) {
|
||||
if (data.size == 0 || data.size > MAX_BYTES) return 0;
|
||||
unsigned char size[4] = {(unsigned char)(data.size >> 24), (unsigned char)(data.size >> 16), (unsigned char)(data.size >> 8), (unsigned char)data.size};
|
||||
return fwrite(size, 1, 4, stdout) == 4 && fwrite(data.data, 1, (size_t)data.size, stdout) == data.size && fflush(stdout) == 0;
|
||||
}
|
||||
|
||||
static char *receive(size_t *length) {
|
||||
unsigned char size[4];
|
||||
if (fread(size, 1, 4, stdin) != 4) return NULL;
|
||||
*length = ((uint32_t)size[0] << 24) | ((uint32_t)size[1] << 16) | ((uint32_t)size[2] << 8) | size[3];
|
||||
if (*length == 0 || *length > MAX_BYTES) return NULL;
|
||||
char *data = malloc(*length);
|
||||
if (data == NULL) return NULL;
|
||||
if (fread(data, 1, *length, stdin) != *length) { free(data); return NULL; }
|
||||
return data;
|
||||
}
|
||||
|
||||
static int identifier(const document *doc, size_t index, uint64_t *result) {
|
||||
if (index >= doc->used) return 0;
|
||||
dever_ffi_slice input = slice(doc, index);
|
||||
uint64_t number = 0;
|
||||
for (uint64_t offset = 0; offset < input.size; offset++) {
|
||||
unsigned digit = (unsigned char)input.data[offset] - '0';
|
||||
if (digit > 9 || number > ((uint64_t)INT64_MAX - digit) / 10) return 0;
|
||||
number = number * 10 + digit;
|
||||
}
|
||||
*result = number;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int serve(const dever_ffi_handler *handler, const document *manifest, int *opened) {
|
||||
size_t length = 0;
|
||||
char *input = receive(&length);
|
||||
document hello = {0};
|
||||
char *buffer = malloc(MAX_BYTES), *returned = malloc(MAX_BYTES);
|
||||
int okay = input != NULL && buffer != NULL && returned != NULL && parse(&hello, input, length);
|
||||
const char *fields[] = {"port", "schema", "adapter", "capabilities", "operations"};
|
||||
if (okay) okay = hello.tokens[0].count == 8 && text_is(&hello, field(&hello, "kind"), "hello") && text_is(&hello, field(&hello, "version"), "dever-component-1");
|
||||
for (size_t index = 0; okay && index < 5; index++) okay = same(&hello, field(&hello, fields[index]), manifest, field(manifest, fields[index]));
|
||||
size_t setting = okay ? field(&hello, "setting") : MAX_TOKENS;
|
||||
if (okay) okay = setting < hello.used;
|
||||
if (okay && handler->open != NULL) okay = handler->open(handler->context, slice(&hello, setting)) == DEVER_FFI_OK;
|
||||
dever_ffi_output output = {buffer, MAX_BYTES, 0};
|
||||
if (okay) {
|
||||
*opened = 1;
|
||||
okay = literal(&output, "{\"kind\":\"ready\",\"version\":\"dever-component-1\"");
|
||||
for (size_t index = 0; okay && index < 5; index++)
|
||||
okay = literal(&output, ",\"") && literal(&output, fields[index]) && literal(&output, "\":") && append(&output, slice(manifest, field(manifest, fields[index])));
|
||||
okay = okay && literal(&output, "}") && send((dever_ffi_slice){output.data, output.size});
|
||||
}
|
||||
free(hello.tokens);
|
||||
free(input);
|
||||
uint64_t next_id = 1;
|
||||
int cancelled = 0, stopped = 0;
|
||||
while (okay && !stopped) {
|
||||
input = receive(&length);
|
||||
document message = {0};
|
||||
okay = input != NULL && parse(&message, input, length);
|
||||
if (!okay) { free(input); break; }
|
||||
size_t kind = field(&message, "kind"), id_index = field(&message, "id");
|
||||
uint64_t request_id = 0;
|
||||
output.size = 0;
|
||||
if (text_is(&message, kind, "shutdown")) {
|
||||
okay = message.tokens[0].count == 1 && send((dever_ffi_slice){"{\"kind\":\"shutdown\"}", 19});
|
||||
stopped = 1;
|
||||
} else if (text_is(&message, kind, "health")) {
|
||||
okay = message.tokens[0].count == 2 && identifier(&message, id_index, &request_id) && request_id == 0 && send((dever_ffi_slice){input, length});
|
||||
} else if (text_is(&message, kind, "cancel")) {
|
||||
okay = message.tokens[0].count == 2 && identifier(&message, id_index, &request_id) && request_id > 0 && request_id == next_id - 1 && !cancelled;
|
||||
cancelled = 1;
|
||||
} else if (text_is(&message, kind, "call")) {
|
||||
size_t operation = field(&message, "operation"), payload = field(&message, "payload");
|
||||
okay = message.tokens[0].count == 4 && identifier(&message, id_index, &request_id) && request_id == next_id && payload < message.used && operation < message.used && message.tokens[operation].kind == '"';
|
||||
size_t operations = field(manifest, "operations");
|
||||
int found = 0;
|
||||
for (size_t index = operations + 1; okay && index < manifest->tokens[operations].after; index = manifest->tokens[index].after)
|
||||
if (same(&message, operation, manifest, index)) found = 1;
|
||||
okay = okay && found;
|
||||
if (okay) {
|
||||
next_id++;
|
||||
cancelled = 0;
|
||||
dever_ffi_slice operation_name = slice(&message, operation);
|
||||
operation_name.data++;
|
||||
operation_name.size -= 2;
|
||||
dever_ffi_output result = {returned, MAX_BYTES - 256, 0};
|
||||
int status = handler->invoke(handler->context, operation_name, slice(&message, payload), &result);
|
||||
document result_doc = {0};
|
||||
okay = result.data == returned && result.capacity == MAX_BYTES - 256 && result.size <= result.capacity && parse(&result_doc, returned, (size_t)result.size);
|
||||
if (okay && status == DEVER_FFI_OK) {
|
||||
okay = literal(&output, "{\"kind\":\"result\",\"id\":") && append(&output, slice(&message, id_index)) && literal(&output, ",\"payload\":") && append(&output, (dever_ffi_slice){returned, result.size});
|
||||
} else if (okay && status == DEVER_FFI_ERROR) {
|
||||
size_t error = field(&result_doc, "error"), error_payload = field(&result_doc, "payload");
|
||||
okay = result_doc.tokens[0].count == 2 && error < result_doc.used && result_doc.tokens[error].kind == '"' && error_payload < result_doc.used;
|
||||
okay = okay && literal(&output, "{\"kind\":\"error\",\"id\":") && append(&output, slice(&message, id_index)) && literal(&output, ",\"error\":") && append(&output, slice(&result_doc, error)) && literal(&output, ",\"payload\":") && append(&output, slice(&result_doc, error_payload));
|
||||
} else okay = 0;
|
||||
okay = okay && literal(&output, "}") && send((dever_ffi_slice){output.data, output.size});
|
||||
free(result_doc.tokens);
|
||||
}
|
||||
} else okay = 0;
|
||||
free(message.tokens);
|
||||
free(input);
|
||||
}
|
||||
free(buffer);
|
||||
free(returned);
|
||||
return okay && stopped;
|
||||
}
|
||||
|
||||
int dever_ffi_serve(const dever_ffi_handler *handler) {
|
||||
if (handler == NULL || handler->abi != DEVER_FFI_ABI_V1 || handler->invoke == NULL) return 1;
|
||||
FILE *file = fopen("contract.json", "rb");
|
||||
if (file == NULL) return 1;
|
||||
char *data = malloc(MAX_BYTES + 1);
|
||||
if (data == NULL) { fclose(file); return 1; }
|
||||
size_t size = fread(data, 1, MAX_BYTES + 1, file);
|
||||
int okay = !ferror(file) && size <= MAX_BYTES;
|
||||
fclose(file);
|
||||
document manifest = {0};
|
||||
okay = okay && parse(&manifest, data, size);
|
||||
if (okay) {
|
||||
size_t operations = field(&manifest, "operations");
|
||||
okay = operations < manifest.used && manifest.tokens[operations].kind == '[' && manifest.tokens[operations].count > 0;
|
||||
const char *names[] = {"port", "schema", "adapter"};
|
||||
for (size_t index = 0; okay && index < 3; index++) {
|
||||
size_t entry = field(&manifest, names[index]);
|
||||
okay = entry < manifest.used && manifest.tokens[entry].kind == '"';
|
||||
}
|
||||
size_t capabilities = field(&manifest, "capabilities");
|
||||
okay = okay && capabilities < manifest.used && manifest.tokens[capabilities].kind == '[';
|
||||
}
|
||||
int opened = 0;
|
||||
okay = okay && serve(handler, &manifest, &opened);
|
||||
if (opened && handler->close != NULL) handler->close(handler->context);
|
||||
free(manifest.tokens);
|
||||
free(data);
|
||||
if (!okay) fputs("native FFI Worker failed\n", stderr);
|
||||
return okay ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef DEVER_FFI_H
|
||||
#define DEVER_FFI_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEVER_FFI_ABI_V1 1
|
||||
#define DEVER_FFI_OK 0
|
||||
#define DEVER_FFI_ERROR 1
|
||||
#define DEVER_FFI_FAULT 2
|
||||
|
||||
typedef struct {
|
||||
const char *data;
|
||||
uint64_t size;
|
||||
} dever_ffi_slice;
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
uint64_t capacity;
|
||||
uint64_t size;
|
||||
} dever_ffi_output;
|
||||
|
||||
typedef struct {
|
||||
uint32_t abi;
|
||||
void *context;
|
||||
int (*open)(void *context, dever_ffi_slice setting);
|
||||
int (*invoke)(void *context, dever_ffi_slice operation, dever_ffi_slice payload, dever_ffi_output *output);
|
||||
void (*close)(void *context);
|
||||
} dever_ffi_handler;
|
||||
|
||||
int dever_ffi_serve(const dever_ffi_handler *handler);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user