p≡p engine fork for my own dirty testing of stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1016 lines
32 KiB

// 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
typedef struct «@name» «yml:ucase(@name)»;
typedef int «yml:ucase(@name)»_PR;
typedef struct «@name»_event {
«yml:ucase(@name)»_PR fsm;
int event;
«yml:ucase(@name)» *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
«@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «yml:ucase(@name)» *msg);
#define «yml:ucase(@name)»_TIMEOUT_EVENT new_«@name»_event(«@name»_PR_NOTHING, 0, NULL);
// free_«@name»_event() - free memory occupied by event
//
// parameters:
// ev (in) event to free
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 "platform.h"
#include "pEp_internal.h"
#include "«@name»_event.h"
#include "«@name»_func.h"
`` for "fsm" | #include "«@name»_fsm.h"
«@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «@name»_t *msg)
{
«@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;
}
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 "«@name»_event.h"
#include "message_api.h"
#include "../asn.1/«@name».h"
#define «yml:ucase(@name)»_THRESHOLD «@threshold»
`` for "fsm" | #define «yml:ucase(@name)»_THRESHOLD «@threshold»
#ifdef __cplusplus
extern "C" {
#endif
// conditions
||
for "func:distinctName(*//condition)"
| PEP_STATUS «@name»(PEP_SESSION session, bool *result);
||
// actions
||
for "func:distinctName(*//action)"
| PEP_STATUS «@name»(PEP_SESSION session);
||
// timeout handler
||
for "fsm[@threshold > 0]"
| PEP_STATUS «@name»TimeoutHandler(PEP_SESSION session);
||
// 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
);
// API being used by the engine internally
// call this if you need to signal an external event
PEP_STATUS signal_«@name»_event(
PEP_SESSION session,
«@name»_PR fsm,
int event
);
// call this if you are a transport and are receiving
// a «@name» message
PEP_STATUS signal_«@name»_message(
PEP_SESSION session,
PEP_rating rating,
const char *data,
size_t size,
const char *fpr
);
#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 "«yml:lcase(@name)»_codec.h"
#include "baseprotocol.h"
`` for "fsm" | #include "«@name»_fsm.h"
`` apply "fsm", 0, mode=timeout
PEP_STATUS «@name»_driver(
PEP_SESSION session,
«@name»_PR fsm,
int event
)
{
assert(session);
if (!session)
return PEP_ILLEGAL_VALUE;
switch (fsm) {
case None:
if (!event) {
// timeout occured
`` for "fsm" |>>>> «../@name»_driver(session, «../@name»_PR_«yml:lcase(@name)», None);
return PEP_STATUS_OK;
}
return PEP_ILLEGAL_VALUE;
`` apply "fsm", mode=reset_state_machine;
default:
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 signal_«@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)
return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK;
if (event < Extra) {
msg = new_«@name»_message(fsm, event);
if (!msg) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
status = update_«@name»_message(session, msg);
if (status)
goto the_end;
}
ev = new_«@name»_event(fsm, event, msg);
if (!ev) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
int result = session->inject_«yml:lcase(@name)»_event(ev,
session->«yml:lcase(@name)»_management);
if (result) {
status = PEP_STATEMACHINE_ERROR;
goto the_end;
}
return PEP_STATUS_OK;
the_end:
free_«@name»_event(ev);
free_«@name»_message(msg);
return status;
}
PEP_STATUS signal_«@name»_message(
PEP_SESSION session,
PEP_rating rating,
const char *data,
size_t size,
const char *fpr
)
{
assert(session && data && size);
if (!(session && data && size))
return PEP_ILLEGAL_VALUE;
if (!session->inject_«yml:lcase(@name)»_event)
return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK;
«@name»_t *msg = NULL;
PEP_STATUS status = decode_«@name»_message(data, size, &msg);
if (status)
return status;
«@name»_event_t *ev = NULL;
«@name»_PR fsm = msg->present;
int event = 0;
switch (fsm) {
`` apply "fsm", 2, mode=signal_message
default:
status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
if (fpr) {
if (session->«yml:lcase(@name)»_state.common.from->fpr)
free(session->«yml:lcase(@name)»_state.common.from->fpr);
session->«yml:lcase(@name)»_state.common.from->fpr = strdup(fpr);
assert(session->«yml:lcase(@name)»_state.common.from->fpr);
if (!session->«yml:lcase(@name)»_state.common.from->fpr) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
}
ev = new_«@name»_event(fsm, event, msg);
if (!ev) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
int result = session->inject_«yml:lcase(@name)»_event(ev,
session->«yml:lcase(@name)»_management);
if (result) {
status = PEP_STATEMACHINE_ERROR;
goto the_end;
}
return PEP_STATUS_OK;
the_end:
free_«@name»_event(ev);
free_«@name»_message(msg);
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(fsm, message_type);
if (!msg)
return PEP_OUT_OF_MEMORY;
char *data = NULL;
message *m = NULL;
identity_list *channels = NULL;
status = update_«@name»_message(session, msg);
if (status)
goto the_end;
size_t size = 0;
status = encode_«@name»_message(msg, &data, &size);
if (status)
goto the_end;
switch (message_type) {
// these messages are being broadcasted
`` for "fsm/message[@type='broadcast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
if (status)
goto the_end;
if (!(channels && channels->ident)) {
// status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
// we don't check for having a channel, because if
// this is initial setup before having an own
// identity we're fine
goto the_end;
}
break;
// these go anycast; previously used address is sticky (unicast)
`` for "fsm/message[@type='anycast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
if (!session->«yml:lcase(@name)»_state.common.from `> |`|
(session->«yml:lcase(@name)»_state.common.from->flags &
PEP_idf_not_for_«yml:lcase(@name)»)) {
// no address available yet, try to find one
status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
if (!status)
goto the_end;
break;
if (channels && channels->ident) {
// only need the first one
free_identity_list(channels->next);
channels->next = NULL;
}
else {
status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
goto the_end;
}
}
else {
pEp_identity *channel = identity_dup(session->«yml:lcase(@name)»_state.common.from);
if (!channel) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
channels = new_identity_list(channel);
if (!channels) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
}
default:
status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
for (identity_list *li = channels; li && li->ident ; li = li->next) {
message *_m = NULL;
char *_data = NULL;
_data = malloc(size);
assert(_data);
if (!_data) {
status = PEP_OUT_OF_MEMORY;
goto the_end;
}
memcpy(_data, data, size);
switch (message_type) {
`` for "fsm/message[@security='unencrypted']" |>>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
status = base_prepare_message(
session,
li->ident,
li->ident,
_data,
size,
li->ident->fpr,
&_m
);
if (status) {
free(_data);
goto the_end;
}
attach_own_key(session, _m);
m = _m;
break;
default:
status = base_prepare_message(
session,
li->ident,
li->ident,
_data,
size,
NULL,
&_m
);
if (status) {
free(_data);
goto the_end;
}
status = encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
if (status) {
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
goto the_end;
}
free_message(_m);
}
status = session->messageToSend(m);
m = NULL;
}
the_end:
free_identity_list(channels);
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;
«@name»_PR fsm = (int) None;
int event = None;
if (ev->event > None && ev->event < Extra) {
status = update_«@name»_state(session, ev->msg, &fsm, &event);
if (status)
goto the_end;
if (ev->fsm) {
if (ev->fsm != fsm |`> |` ev->event != event) {
status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
}
else if (ev->event) {
status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
}
else {
fsm = ev->fsm;
event = ev->event;
}
status = «@name»_driver(session, fsm, event);
the_end:
free_«@name»_event(ev);
return status;
}
||
}
apply "fsm", 0, mode=gen;
}
template "fsm", mode=timeout
||
static bool _«@name»_timeout(int state)
{
static int last_state = None;
static time_t switch_time = 0;
if (state > Init) {
if (state == last_state) {
if (time(NULL) - switch_time > «yml:ucase(@name)»_THRESHOLD) {
last_state = None;
switch_time = 0;
return true;
}
}
else {
last_state = state;
switch_time = time(NULL);
}
}
else {
last_state = None;
switch_time = 0;
}
return false;
}
||
template "fsm", mode=reset_state_machine
||
case «../@name»_PR_«yml:lcase(@name)»: {
int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state;
switch (state) {
`` for "state[@name!='InitState' and @timeout != 'off']" |>>> case «@name»:
if (_«@name»_timeout(state)) {
session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = Init;
event = Init;
`` if "@threshold > 0" |>>>>> «@name»TimeoutHandler(session);
}
break;
default:
_«@name»_timeout(None);
}
break;
}
||
template "fsm", mode=signal_message
{
||
case «../@name»_PR_«yml:lcase(@name)»:
event = msg->choice.«yml:lcase(@name)».payload.present;
switch (event) {
||
if "message[@security='unencrypted']" {
| // these messages require a detached signature
for "message[@security='unencrypted']"
|>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
||
if (!fpr) {
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
break;
||
}
if "message[@security='untrusted']"
||
// these messages must arrive encrypted
`` for "message[@security='untrusted']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
if (fpr || rating < PEP_rating_reliable) {
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
break;
||
if "message[@security='trusted']"
||
// these messages must come through a trusted channel
`` for "message[@security='trusted']" |>> case «ancestor::fsm/@name»__payload_PR_«yml:mixedCase(@name)»:
if (fpr || rating < PEP_rating_trusted) {
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
break;
default:
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
goto the_end;
}
break;
||
}
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 - next_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);
const char *«@name»_event_name(int event);
// 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";
}
}
const char *«@name»_event_name(int event)
{
switch (event) {
case None:
return "None";
case Init:
return "Init";
||
for "func:distinctName(state/event[not(@name='Init')])" {
|>> case «@name»:
|>>> return "«@name»";
}
||
default:
return "unknown event";
}
}
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:
// ignore events not handled here
«../@name»_SERVICE_LOG("ignoring event", KeySync_event_name(event));
break;
}
break;
||
}
template "event", mode=fsm {
| case «@name»: {
if "condition|action|send" |> PEP_STATUS status;
if "condition" |> bool result = false;
if "condition|action|send" |
||
«../../@name»_SERVICE_LOG("received event", "«@name»");
`` apply "transition|action|condition|else|send";
||
if "name(*[last()])!='transition'" {
|
|> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»");
|> break;
}
||
}
||
}
template "transition" {
const "fsm", "ancestor::fsm";
||
«$fsm/@name»_SERVICE_LOG("transition to state", "«@target»");
return «@target»;
||
}
template "send" {
const "fsm", "ancestor::fsm";
const "protocol", "ancestor::protocol";
||
«$fsm/@name»_SERVICE_LOG("send message", "«@name»");
status = send_«$protocol/@name»_message(session, «$fsm/@id», «$fsm/@name»__payload_PR_«yml:mixedCase(@name)»);
if (status == PEP_OUT_OF_MEMORY)
return out_of_memory;
if (status) {
«$fsm/@name»_ERR_LOG_HEX("sending «@name» failed", status);
return cannot_send;
}
||
}
template "action" {
const "fsm", "ancestor::fsm";
||
«$fsm/@name»_SERVICE_LOG("do action", "«@name»");
status = «@name»(session);
if (status == PEP_OUT_OF_MEMORY)
return out_of_memory;
if (status) {
«$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status);
return invalid_action;
}
||
}
template "condition" {
const "fsm", "ancestor::fsm";
||
status = «@name»(session, &result);
if (status == PEP_OUT_OF_MEMORY)
return out_of_memory;
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|else|send";
| }
}
template "else" {
if "not(name(preceding-sibling::*[last()]) = 'condition')"
error "else without if";
| else {
|> «ancestor::fsm/@name»_SERVICE_LOG("condition does not apply", "«preceding-sibling::*[last()]/@name»");
apply "transition|action|condition|else|send";
| }
}
}