// This file is under GNU General Public License 3.0 // see LICENSE.txt // generate state machine code // Copyleft (c) 2016 - 2018, p≡p foundation // Written by Volker Birk include yslt.yml2 tstylesheet { include standardlib.ysl2 include ./functions.ysl2 template "/protocol" { document "generated/{@name}_event.h", "text" || // This file is under GNU General Public License 3.0 // see LICENSE.txt #pragma once #include "dynamic_api.h" #ifdef __cplusplus extern "C" { #endif #include "«@name».h" typedef struct «@name»_event { «@name»_PR fsm; int event; «@name»_t *msg; } «@name»_event_t; // new_«@name»_event() - allocate a new «@name»_event // // parameters: // fsm (in) finite state machine the event is for // event (in) event or None // msg (in) message to compute event from // // return value: // pointer to new event or NULL in case of failure // // caveat: // event must be valid for fsm or None // in case msg is given event will be calculated out of message DYNAMIC_API «@name»_event_t *new_«@name»_event(«@name»_PR fsm, int event, «@name»_t *msg); // free_«@name»_event() - free memory occupied by event // // parameters: // ev (in) event to free DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev); #ifdef __cplusplus } #endif || document "generated/{@name}_event.c", "text" || // This file is under GNU General Public License 3.0 // see LICENSE.txt #include "pEp_internal.h" #include "«@name»_event.h" #include "«@name»_func.h" `` for "fsm" | #include "«@name»_fsm.h" DYNAMIC_API «@name»_event_t *new_«@name»_event(«@name»_PR fsm, int event, «@name»_t *msg) { assert(fsm > 0 && (event >= 0 |`> |` msg)); if (!(fsm > 0 && (event >= 0 |`> |` msg))) return NULL; «@name»_event_t *ev = («@name»_event_t *) calloc(1, sizeof(«@name»_event_t)); assert(ev); if (!ev) return NULL; ev->fsm = fsm; ev->event = event; ev->msg = msg; if (msg) { switch (fsm) { `` apply "fsm", 3, mode=event default: // unknown protocol free(ev); return NULL; } } return ev; } DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev) { if (ev) { free_«@name»_message(ev->msg); free(ev); } } || document "generated/{@name}_impl.h", "text" { || // This file is under GNU General Public License 3.0 // see LICENSE.txt #pragma once #include "fsm_common.h" #include "message_api.h" #include "«@name»_event.h" #ifdef __cplusplus extern "C" { #endif // conditions || for "func:distinctName(*//condition)" | PEP_STATUS «@name»(PEP_SESSION session, bool *result); || // actions || const "name", "@name"; for "func:distinctName(*//action[not(starts-with(@name, 'send'))])" | PEP_STATUS «@name»(PEP_SESSION session); || // notify state machine from event // use state to generate «@name» message if necessary PEP_STATUS «@name»_notify( PEP_SESSION session, «@name»_PR fsm, int message_type ); // send message about an event to communication partners using state PEP_STATUS send_«@name»_message( PEP_SESSION session, «@name»_PR fsm, int message_type ); // receive message and store it in state PEP_STATUS recv_«@name»_event( PEP_SESSION session, «@name»_event_t *ev ); // state machine driver // if fsm or event set to 0 use fields in src if present PEP_STATUS «@name»_driver( PEP_SESSION session, «@name»_PR fsm, int event ); PEP_STATUS inject_«@name»_event( PEP_SESSION session, «@name»_PR fsm, int event ); #ifdef __cplusplus } #endif || } document "generated/{@name}_impl.c", "text" || // This file is under GNU General Public License 3.0 // see LICENSE.txt #include "«@name»_impl.h" #include "pEp_internal.h" #include "«@name»_event.h" #include "«@name»_codec.h" #include "baseprotocol.h" `` for "fsm" | #include "«@name»_fsm.h" PEP_STATUS «@name»_driver( PEP_SESSION session, «@name»_PR fsm, int event ) { assert(session && fsm); if (!(session && fsm)) return PEP_ILLEGAL_VALUE; int next_state = None; do { switch (fsm) { `` apply "fsm", 3, mode=driver default: return PEP_ILLEGAL_VALUE; } } while (next_state); return PEP_STATUS_OK; } PEP_STATUS inject_«@name»_event( PEP_SESSION session, «@name»_PR fsm, int event ) { «@name»_t *msg = NULL; «@name»_event_t *ev = NULL; assert(session && fsm > 0 && event > None); if (!(session && fsm > 0 && event > None)) return PEP_ILLEGAL_VALUE; PEP_STATUS status = PEP_STATUS_OK; if (!session->inject_«yml:lcase(@name)»_event) { status = PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK; goto error; } if (event < Extra) { msg = new_«@name»_message(fsm, event); if (!msg) { status = PEP_OUT_OF_MEMORY; goto error; } status = update_«@name»_message(session, fsm, event, msg); if (status) goto error; } ev = («@name»_event_t *) calloc(1, sizeof(«@name»_event_t)); assert(ev); if (!ev) { status = PEP_OUT_OF_MEMORY; goto error; } ev->fsm = fsm; ev->event = event; ev->msg = msg; int result = session->inject_«yml:lcase(@name)»_event(ev, session->«yml:lcase(@name)»_management); if (result) { status = PEP_STATEMACHINE_ERROR; goto error; } goto the_end; error: free(ev); free_«@name»_message(msg); the_end: return status; } PEP_STATUS «@name»_notify( PEP_SESSION session, «@name»_PR fsm, int message_type ) { assert(session && fsm > 0 && message_type > 1 && message_type < Extra); if (!(session && fsm > 0 && message_type > 1 && message_type < Extra)) return PEP_ILLEGAL_VALUE; PEP_STATUS status = PEP_STATUS_OK; «@name»_t *msg = new_«@name»_message(fsm, message_type); if (!msg) { status = PEP_OUT_OF_MEMORY; goto error; } status = update_«@name»_message(session, fsm, message_type, msg); if (status) goto error; goto the_end; error: free_«@name»_message(msg); the_end: return status; } PEP_STATUS send_«@name»_message( PEP_SESSION session, «@name»_PR fsm, int message_type ) { PEP_STATUS status = PEP_STATUS_OK; assert(session && fsm > None && message_type > None); if (!(session && fsm > None && message_type > None)) return PEP_ILLEGAL_VALUE; «@name»_t *msg = new_«@name»_message(None, None); if (!msg) return PEP_OUT_OF_MEMORY; char *data = NULL; message *m = NULL; status = update_«@name»_message(session, fsm, message_type, msg); if (status) goto the_end; size_t size = 0; status = encode_«@name»_message(msg, &data, &size); if (status) goto the_end; status = base_prepare_message( session->«yml:lcase(@name)»_state.common.from, session->«yml:lcase(@name)»_state.common.from, data, size, &m ); if (status) goto the_end; status = session->messageToSend(session->«yml:lcase(@name)»_obj, m); the_end: free_message(m); free(data); free_«@name»_message(msg); return status; } PEP_STATUS recv_«@name»_event( PEP_SESSION session, «@name»_event_t *ev ) { assert(session && ev); if (!(session && ev)) return PEP_ILLEGAL_VALUE; PEP_STATUS status = PEP_STATUS_OK; if (ev->event < Extra) { «@name»_PR fsm = (int) None; int event = None; status = update_«@name»_state(session, ev->msg, &fsm, &event); if (status) goto error; if (ev->fsm) { if (ev->fsm != fsm |`> |` ev->event != event) { status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; goto error; } } else { if (ev->event) { status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE; goto error; } ev->fsm = fsm; ev->event = event; } } free_«@name»_message(ev->msg); free(ev); status = «@name»_driver(session, ev->fsm, ev->event); return status; error: free_«@name»_message(ev->msg); free(ev); return status; } || apply "fsm", 0, mode=gen; } template "fsm", mode=event { || case «../@name»_PR_«yml:lcase(@name)»: { switch (msg->choice.«yml:lcase(@name)».payload.present) { || for "message" || case «../@name»__payload_PR_«yml:mixedCase(@name)»: ev->event = «@name»; break; || || default: // unknown message type free(ev); return NULL; } break; } || } template "fsm", mode=driver || case «../@name»_PR_«yml:lcase(@name)»: { int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state; next_state = fsm_«@name»(session, state, event); if (next_state > None) { session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = next_state; event = Init; } else if (next_state < None) { return PEP_STATEMACHINE_ERROR - state; } break; } || template "fsm", mode=gen { document "generated/{@name}_fsm.h", "text" { || // This file is under GNU General Public License 3.0 // see LICENSE.txt #pragma once #include "«../@name»_impl.h" #ifdef __cplusplus extern "C" { #endif // state machine for «@name» // states typedef enum _«@name»_state { «@name»_state_None = None, «@name»_state_Init = Init, || for "func:distinctName(state[not(@name='InitState')])" |> «@name»`if "position()!=last()" > , ` || } «@name»_state; // events typedef enum _«@name»_event { «@name»_event_None = None, «@name»_event_Init = Init, || for "func:distinctName(state/event[not(not(../../message/@name=@name))])" { const "name", "@name"; |> «$name» = «/protocol/fsm/message[@name=$name]/@id», } for "func:distinctName(state/event[not(not(../../external/@name=@name))])" { const "name", "@name"; |> «$name» = «/protocol/fsm/external[@name=$name]/@id», } |> «@name»_event_Extra = Extra, for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" { if "@name!='Init'" |> «@name»`if "position()!=last()" > , ` } || } «@name»_event; // state machine const char *«@name»_state_name(int state); // the state machine function is returning the next state in case of a // transition or None for staying «@name»_state fsm_«@name»( PEP_SESSION session, «@name»_state state, «@name»_event event ); #ifdef __cplusplus } #endif || } document "generated/{@name}_fsm.c", "text" { || // This file is under GNU General Public License 3.0 // see LICENSE.txt #include "«@name»_fsm.h" #include const char *«@name»_state_name(int state) { switch (state) { case End: return "End"; case None: return "None"; case Init: return "InitState"; || for "func:distinctName(state[not(@name='InitState')])" { |>> case «@name»: |>>> return "«@name»"; } || default: return "unknown state"; } } static char *_str(int n, bool hex) { char *buf = calloc(1, 24); assert(buf); if (!buf) return NULL; if (hex) snprintf(buf, 24, "%.4x", n); else snprintf(buf, 24, "%d", n); return buf; } #define «@name»_ERR_LOG(t, d) log_event(session, (t), "«@name»", (d), "error") static PEP_STATUS _«@name»_ERR_LOG_int(PEP_SESSION session, char *t, int n, bool hex) { char *_buf = _str(n, hex); if (!_buf) return PEP_OUT_OF_MEMORY; PEP_STATUS status = «@name»_ERR_LOG(t, _buf); free(_buf); return status; } #define «@name»_ERR_LOG_INT(t, n) _«@name»_ERR_LOG_int(session, (t), (n), false) #define «@name»_ERR_LOG_HEX(t, n) _«@name»_ERR_LOG_int(session, (t), (n), true) #ifndef SERVICE_LOG // SERVICE LOG is meant to check session->service_log in runtime config; // for older engines log more than needed #define SERVICE_LOG(session, t, n, d) log_event((session), (t), (n), (d), "service") #endif #define «@name»_SERVICE_LOG(t, d) SERVICE_LOG(session, (t), "«@name»", (d)) «@name»_state fsm_«@name»( PEP_SESSION session, «@name»_state state, «@name»_event event ) { assert(session); if (!session) return invalid_state; switch (state) { case None: return «@name»_state_Init; `` apply "state", 2, mode=fsm default: «@name»_ERR_LOG_INT("invalid state", state); return invalid_state; } return None; } || } } template "state", mode=fsm { choose { when "@name='InitState'" | case «../@name»_state_Init: otherwise | case «@name»: } || «../@name»_SERVICE_LOG("in state", "«@name»"); switch (event) { case None: «../@name»_SERVICE_LOG("received None event", "ignoring"); break; || if "not(event[@name='Init'])" || case Init: // nothing to do break; || || `` apply "event", 2, mode=fsm default: «../@name»_ERR_LOG_INT("invalid event", event); return invalid_event; } break; || } template "event", mode=fsm { | case «@name»: { if "condition|action" |> PEP_STATUS status; if "condition" |> bool result = false; if "condition|action" | || «../../@name»_SERVICE_LOG("received event", "«@name»"); `` apply "transition|action|condition" with "protocol", "../../..", with "fsm", "../.." || if "name(*[last()])!='transition'" { | |> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»"); |> break; } || } || } template "transition" { param "fsm"; || «$fsm/@name»_SERVICE_LOG("transition to state", "«@target»"); return «@target»; || } template "action" { param "protocol"; param "fsm"; choose { when "starts-with(@name, 'send')" { const "name", "substring(@name, 5)"; || «$fsm/@name»_SERVICE_LOG("send message", "«$name»"); status = send_«$protocol/@name»_message(session, «$fsm/@id», «$name»); || } otherwise || «$fsm/@name»_SERVICE_LOG("do action", "«@name»"); status = «@name»(session); || } || if (status) { «$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status); return invalid_action; } || } template "condition" { param "protocol"; param "fsm"; || status = «@name»(session, &result); if (status) { «$fsm/@name»_ERR_LOG_HEX("computing condition «@name» failed", status); return invalid_condition; } if (result) { «$fsm/@name»_SERVICE_LOG("condition applies", "«@name»"); || apply "transition|action|condition" with "protocol", "$protocol", with "fsm", "$fsm"; | } } }