Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / assets / jsplease.svg
Author[]Andy Green <andy@warmcat.com> 2026-05-17 09:18 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-05-17 09:27 UTC
Tree7dc141099274b607c3340ffc7a79310f21f2b092   Raw Patch
 
sai-virt: phase3
sai-virt: phase3
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dfc305..fe0a485 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,7 @@ if (requirements) add_subdirectory(src/web) if (NOT MSVC AND NOT WIN32 AND NOT (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")) add_subdirectory(src/power) + add_subdirectory(src/virt) endif() endif() if (SAI_BUILDER) diff --git a/src/virt/CMakeLists.txt b/src/virt/CMakeLists.txt new file mode 100644 index 0000000..a74deea --- /dev/null +++ b/src/virt/CMakeLists.txt @@ -0,0 +1,53 @@ +set(SUB "sai-virt") +set(CPACK_DEBIAN_VIRT_PACKAGE_NAME "sai-virt") + +set(SRCS + v-sai.c + v-ws-server.c + ../common/c-utils.c + ../common/struct-metadata.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) + +if (requirements) + add_executable(${SUB} ${SRCS}) +add_dependencies(${SUB} sai_git_hash) + if (APPLE) + set_property(TARGET ${SUB} PROPERTY MACOSX_RPATH YES) + endif() + + if (SAI_LWS_INC_PATH) + target_include_directories(${SUB} PRIVATE ${SAI_LWS_INC_PATH}) + endif() + + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + target_link_libraries(${SUB} websockets ${SAI_LWS_LIB_PATH}) + + message("LWS_OPENSSL_LIBRARIES ${SUB} '${LWS_OPENSSL_LIBRARIES}'") + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries(${SUB} ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries(${SUB} ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries(${SUB} ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-virt" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT virt) + +endif(requirements) +include(CPack) diff --git a/src/virt/v-private.h b/src/virt/v-private.h new file mode 100644 index 0000000..ff914e8 --- /dev/null +++ b/src/virt/v-private.h @@ -0,0 +1,54 @@ +/* + * Sai virt definitions src/virt/v-private.h + * + * Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + */ + +#ifndef SAI_VIRT_V_PRIVATE_H +#define SAI_VIRT_V_PRIVATE_H + +#include "../common/include/private.h" +#include <pthread.h> + +struct saiv_server; + +/* + * Represents the virt process state + */ +struct sai_virt { + lws_dll2_owner_t sai_server_owner; /* servers we connect to */ + struct lws_context *context; + struct lws_vhost *vhost; + + const char *bind; /* listen socket binding */ + const char *perms; /* user:group */ + const char *port; /* port we listen on */ + + char hostname[64]; +}; + +typedef struct saiv_server { + lws_dll2_t list; + struct lws_ss_handle *ss; + const char *url; + const char *name; +} saiv_server_t; + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; + struct lws_buflist *bl_tx; +} saiv_server_link_t; + +extern struct sai_virt virt; +extern const lws_ss_info_t ssi_saiv_server_link_t; + +int saiv_config(struct sai_virt *virt, const char *d); + +#endif diff --git a/src/virt/v-sai.c b/src/virt/v-sai.c new file mode 100644 index 0000000..18c65b5 --- /dev/null +++ b/src/virt/v-sai.c @@ -0,0 +1,116 @@ +/* + * sai-virt + * + * Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + */ + +#include <libwebsockets.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include "v-private.h" + +struct sai_virt virt; +static int interrupted; + +static const char * const default_ss_policy = + "{" + "\"retry\": [" + "{\"default\": {" + "\"backoff\": [1000, 2000, 3000, 5000, 10000]," + "\"conceal\": 99999," + "\"jitterpc\": 20," + "\"svalidping\": 15," + "\"svalidhup\": 30" + "}}" + "]," + "\"s\": [" + "{\"sai_power_client\": {" + "\"endpoint\": \"${url}\"," + "\"protocol\": \"ws\"," + "\"ws_subprotocol\": \"com-warmcat-sai-builder\"," + "\"http_url\": \"\"," + "\"retry\": \"default\"," + "\"metadata\": [" + "{\"url\": \"\"}" + "]" + "}}" + "]}" +; + +static void sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + const char *p; + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + lws_set_log_level(logs, NULL); + + lwsl_user("Sai Virt - Copyright (C) 2019-2026 Andy Green <andy@warmcat.com>\n"); + + if (gethostname(virt.hostname, sizeof(virt.hostname) - 1)) + lws_strncpy(virt.hostname, "unknown", sizeof(virt.hostname)); + + memset(&info, 0, sizeof info); + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_VALIDATE_UTF8; + + signal(SIGINT, sigint_handler); + + info.pss_policies_json = default_ss_policy; + + virt.context = lws_create_context(&info); + if (!virt.context) { + lwsl_err("lws init failed\n"); + return 1; + } + + virt.vhost = lws_create_vhost(virt.context, &info); + if (!virt.vhost) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* We create the server link manually for testing skeleton */ + saiv_server_t *srv = malloc(sizeof(*srv)); + if (srv) { + memset(srv, 0, sizeof(*srv)); + srv->url = "warmcat.com"; /* example */ + if (lws_ss_create(virt.context, 0, &ssi_saiv_server_link_t, + srv, &srv->ss, NULL, NULL)) { + lwsl_err("%s: failed to create ss\n", __func__); + free(srv); + } else { + lws_dll2_add_tail(&srv->list, &virt.sai_server_owner); + } + } + + while (!lws_service(virt.context, 0) && !interrupted) + ; + + lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, virt.sai_server_owner.head) { + saiv_server_t *s = lws_container_of(d, saiv_server_t, list); + lws_ss_destroy(&s->ss); + lws_dll2_remove(d); + free(s); + } lws_end_foreach_dll_safe(d, d1); + + lws_context_destroy(virt.context); + + return 0; +} diff --git a/src/virt/v-ws-server.c b/src/virt/v-ws-server.c new file mode 100644 index 0000000..afc7276 --- /dev/null +++ b/src/virt/v-ws-server.c @@ -0,0 +1,128 @@ +/* + * sai-virt - src/virt/v-ws-server.c + * + * Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + */ + +#include <libwebsockets.h> +#include <string.h> + +#include "v-private.h" + +/* + * When we connect, we masquerade as a builder/pcon and send our platforms + */ +static lws_ss_state_return_t +saiv_server_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, + size_t *len, int *flags) +{ + saiv_server_link_t *g = (saiv_server_link_t *)userobj; + + return sai_ss_tx_from_buflist_helper(g->ss, &g->bl_tx, buf, len, flags); +} + +static lws_ss_state_return_t +saiv_server_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + struct lejp_ctx ctx; + lws_struct_args_t a; + int m; + + /* + * We receive JSON from sai-server, usually com.warmcat.sai.power.pending_tasks + */ + memset(&a, 0, sizeof(a)); + a.map_st[0] = lsm_schema_pending_tasks; + a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_schema_pending_tasks); + a.ac_block_size = 512; + + lws_struct_json_init_parse(&ctx, NULL, &a); + m = lejp_parse(&ctx, (uint8_t *)buf, (int)len); + if (m < 0) { + lwsl_err("%s: JSON decode failed '%s'\n", __func__, lejp_error_to_string(m)); + return LWSSSSRET_OK; + } + + if (!a.dest) { + lwsac_free(&a.ac); + return LWSSSSRET_OK; + } + + if (a.top_schema_index == 0) { + sai_platform_pending_tasks_t *pt = (sai_platform_pending_tasks_t *)a.dest; + lwsl_notice("%s: Pending tasks for pcons: %s\n", __func__, pt->pcons); + + lws_start_foreach_dll(struct lws_dll2 *, p, pt->tasks.head) { + sai_platform_pending_task_t *t = lws_container_of(p, sai_platform_pending_task_t, list); + lwsl_notice(" - %s: %u pending\n", t->plat, t->pending); + } lws_end_foreach_dll(p); + } + + lwsac_free(&a.ac); + return LWSSSSRET_OK; +} + +static lws_ss_state_return_t +saiv_server_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + saiv_server_link_t *g = (saiv_server_link_t *)userobj; + sai_builder_registration_t r; + struct lwsac *ac = NULL; + + switch (state) { + case LWSSSCS_CREATING: + lwsl_notice("%s: CREATING\n", __func__); + /* We'd set metadata url here from the config, skipped for skeleton */ + break; + + case LWSSSCS_CONNECTED: + lwsl_notice("%s: Connected to sai-server\n", __func__); + + memset(&r, 0, sizeof(r)); + lws_strncpy(r.builder_name, virt.hostname, sizeof(r.builder_name)); + lws_strncpy(r.power_controller_name, virt.hostname, sizeof(r.power_controller_name)); + + /* We can spawn mac-m1, windows-10, etc. (Mocked for now) */ + const char *plats[] = {"windows-x86_64", "mac-m1"}; + for (size_t i = 0; i < LWS_ARRAY_SIZE(plats); i++) { + sai_builder_platform_t *bp = lwsac_use_zero(&ac, sizeof(*bp), 512); + if (bp) { + lws_strncpy(bp->name, plats[i], sizeof(bp->name)); + lws_dll2_add_tail(&bp->list, &r.platforms_owner); + } + } + + sai_ss_serialize_queue_helper(g->ss, &g->bl_tx, + lsm_schema_builder_registration, + LWS_ARRAY_SIZE(lsm_schema_builder_registration), + &r); + lwsac_free(&ac); + break; + + case LWSSSCS_DISCONNECTED: + lwsl_notice("%s: Disconnected\n", __func__); + lws_buflist_destroy_all_segments(&g->bl_tx); + break; + + default: + break; + } + + return LWSSSSRET_OK; +} + +const lws_ss_info_t ssi_saiv_server_link_t = { + .handle_offset = offsetof(saiv_server_link_t, ss), + .opaque_user_data_offset = offsetof(saiv_server_link_t, opaque_data), + .rx = saiv_server_rx, + .tx = saiv_server_tx, + .state = saiv_server_state, + .user_alloc = sizeof(saiv_server_link_t), + .streamtype = "sai_power_client" +};
Page fetched 0s ago, creation time: 10ms (vhost etag hits: 0%, cache hits: 0%)