// 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 <stdlib.h>
|
|
|
|
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";
|
|
| }
|
|
}
|
|
}
|
|
|