#include "sim_protocol.h" #include #include #include typedef struct { uint32_t state; uint32_t last_seq; uint32_t tick; } protocol_state; static protocol_state state; uint32_t sim_protocol_checkpoint_size(void) { return sizeof(state); } int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity) { if (output == NULL || capacity != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT; memcpy(output, &state, sizeof(state)); return SIM_PROTOCOL_OK; } int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length) { if (input == NULL || length != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT; const protocol_state *saved = (const protocol_state *)input; if (saved->state > SIM_PROTOCOL_DESTROYED) return SIM_PROTOCOL_ERR_ARGUMENT; memcpy(&state, saved, sizeof(state)); return SIM_PROTOCOL_OK; } static int has_text(const char *value) { return value != NULL && value[0] != '\0' && strlen(value) <= 128; } static int is_kind(const char *kind, const char *expected) { return kind != NULL && strcmp(kind, expected) == 0; } static int is_side_effect(const char *kind) { static const char *const commands[] = { "SESSION_CREATE", "LOAD_PROJECT", "START", "STOP", "RESET", "LOAD_WORKSPACE", "SIM_STEP", "COMMAND", "PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT", "PROGRAM_STEP", "PROGRAM_SEEK", "MDI_EXECUTE", "JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE", "SET_JOG_SCALE", "SET_MAX_VELOCITY", "SET_SPINDLE_OVERRIDE", "SPINDLE_COMMAND", "SPINDLE_BRAKE", "SET_SPINDLE_OVERRIDE", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE", "PROBE_CONFIG", "HAL_COMMAND", "HAL_SET", "PLC_START", "PLC_STOP", "PROJECT_SAVE", "IMPORT", "EXPORT", "CHECKPOINT", "RECOVER", "SUBSCRIBE", "UNSUBSCRIBE", "CANCEL", NULL, }; size_t index; if (!has_text(kind)) return 0; for (index = 0; commands[index] != NULL; ++index) { if (is_kind(kind, commands[index])) return 1; } return 0; } static int state_accepts_kind(uint32_t current, const char *kind) { if (current == SIM_PROTOCOL_NEW) return is_kind(kind, "HELLO"); if (current == SIM_PROTOCOL_HELLO) return is_kind(kind, "SESSION_ATTACH") || is_kind(kind, "CAPABILITIES"); if (current == SIM_PROTOCOL_ATTACHED) return is_kind(kind, "SESSION_READY"); /* Task pause does not disconnect the command channel. The task gate decides whether a valid frame may execute, and records refusals at that sequence so Resume/Abort cannot be stranded behind a gap. */ if (current == SIM_PROTOCOL_READY || current == SIM_PROTOCOL_PAUSED) return 1; return is_kind(kind, "RESET"); } void sim_protocol_reset(void) { memset(&state, 0, sizeof(state)); } int32_t sim_protocol_accept(uint32_t schema_version, uint32_t seq, const char *kind, const char *operation_id, uint32_t simulation_tick) { if (schema_version != 1U) return SIM_PROTOCOL_ERR_SCHEMA; if (!has_text(kind)) return SIM_PROTOCOL_ERR_KIND; if (state.state == SIM_PROTOCOL_DESTROYED && !is_kind(kind, "RESET")) return SIM_PROTOCOL_ERR_STATE; if (seq == 0U || seq != state.last_seq + 1U) return SIM_PROTOCOL_ERR_SEQUENCE; if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK; if (!state_accepts_kind(state.state, kind)) return SIM_PROTOCOL_ERR_STATE; if (is_side_effect(kind) && !has_text(operation_id)) return SIM_PROTOCOL_ERR_OPERATION_ID; state.last_seq = seq; state.tick = simulation_tick; if (is_kind(kind, "HELLO")) state.state = SIM_PROTOCOL_HELLO; /* SESSION_READY is the worker response; the next client command is valid immediately. */ else if (is_kind(kind, "SESSION_ATTACH")) state.state = SIM_PROTOCOL_READY; else if (is_kind(kind, "SESSION_READY")) state.state = SIM_PROTOCOL_READY; else if (is_kind(kind, "PROGRAM_PAUSE")) state.state = SIM_PROTOCOL_PAUSED; else if (is_kind(kind, "PROGRAM_RESUME") || is_kind(kind, "RESUME")) state.state = SIM_PROTOCOL_READY; else if (is_kind(kind, "PROGRAM_ABORT") || is_kind(kind, "ESTOP") || is_kind(kind, "POWER")) state.state = SIM_PROTOCOL_READY; else if (is_kind(kind, "DESTROY")) state.state = SIM_PROTOCOL_DESTROYED; else if (is_kind(kind, "RESET")) { /* RESET is a machine/task reset once attached. Keep the transport sequence alive so a Web client can continue issuing commands. A RESET received after DESTROY remains the protocol reinitialization edge used by the session lifecycle. */ if (state.state == SIM_PROTOCOL_DESTROYED) sim_protocol_reset(); else state.state = SIM_PROTOCOL_READY; } return SIM_PROTOCOL_OK; } int32_t sim_protocol_pause(void) { if (state.state != SIM_PROTOCOL_READY) return SIM_PROTOCOL_ERR_STATE; state.state = SIM_PROTOCOL_PAUSED; return SIM_PROTOCOL_OK; } int32_t sim_protocol_resume(void) { if (state.state != SIM_PROTOCOL_PAUSED) return SIM_PROTOCOL_ERR_STATE; state.state = SIM_PROTOCOL_READY; return SIM_PROTOCOL_OK; } int32_t sim_protocol_state_value(void) { return (int32_t)state.state; } uint32_t sim_protocol_last_seq(void) { return state.last_seq; } uint32_t sim_protocol_tick(void) { return state.tick; } int32_t sim_protocol_set_tick(uint32_t simulation_tick) { if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK; state.tick = simulation_tick; return SIM_PROTOCOL_OK; }