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.
1826 lines
68 KiB
1826 lines
68 KiB
// This file is under GNU General Public License 3.0
|
|
// see LICENSE.txt
|
|
|
|
// generate state machine code
|
|
|
|
// Copyleft (c) 2016-2020, p≡p foundation
|
|
|
|
// Written by Volker Birk
|
|
|
|
include yslt.yml2
|
|
|
|
tstylesheet {
|
|
include standardlib.ysl2
|
|
include ./functions.ysl2
|
|
|
|
template "/protocol" {
|
|
if "count(fsm/state)>0" document "generated/{@name}_event.h", "text"
|
|
||
|
|
/**
|
|
* @file «@name»_event.h
|
|
* @brief Structures and functions for «@name» events.
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license GNU General Public License 3.0 - see LICENSE.txt
|
|
*/
|
|
|
|
#ifndef «yml:ucase(@name)»_EVENT_H
|
|
#define «yml:ucase(@name)»_EVENT_H
|
|
|
|
#include "pEpEngine.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* «@name» is also defined as «yml:ucase(@name)» for ASN.1 reasons and
|
|
* forward declaration to avoid the need for header inclusion everywhere.
|
|
* @see struct «@name» in «@name».h
|
|
*/
|
|
typedef struct «@name» «yml:ucase(@name)»;
|
|
/** forward declaration - @see enum «@name»_PR in «@name».h */
|
|
typedef int «yml:ucase(@name)»_PR;
|
|
|
|
/**
|
|
* Data for an event related to a message
|
|
*/
|
|
typedef struct «@name»_event {
|
|
// state machine data
|
|
«yml:ucase(@name)»_PR fsm; /*!< state machine type associated with this event */
|
|
int event; /*!< type of event */
|
|
«yml:ucase(@name)» *msg; /*!< the «@name» messaged caused by (or causing???) this event */
|
|
|
|
// transport data
|
|
pEp_identity *from; /*!< identity of the message sender */
|
|
char *sender_fpr; /*!< fpr of key used by the message sender */
|
|
|
|
identity_list *own_identities; /*!< List of our own identities */
|
|
} «@name»_event_t;
|
|
|
|
/**
|
|
* <!-- new_«@name»_event() -->
|
|
*
|
|
* @brief allocate a new «@name»_event
|
|
*
|
|
* @param[in] fsm finite state machine the event is for
|
|
* @param[in] event event or None
|
|
* @param[in] msg message to compute event from
|
|
*
|
|
* @retval pointer to new event
|
|
* @retval NULL in case of failure
|
|
*
|
|
*/
|
|
DYNAMIC_API «@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() -->
|
|
*
|
|
* @brief free memory occupied by event
|
|
*
|
|
* @param[in] ev event to free
|
|
*
|
|
*/
|
|
DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev);
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
||
|
|
|
|
if "count(fsm/state)>0" document "generated/{@name}_event.c", "text"
|
|
||
|
|
|
|
/**
|
|
* @file «@name»_event.c
|
|
* @brief Allocation, fsm drivers, and handling for «@name» events.
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license 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"
|
|
|
|
DYNAMIC_API «@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;
|
|
}
|
|
|
|
DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev)
|
|
{
|
|
if (ev) {
|
|
free_identity_list(ev->own_identities);
|
|
free_«@name»_message(ev->msg);
|
|
free_identity(ev->from);
|
|
free(ev->sender_fpr);
|
|
free(ev);
|
|
}
|
|
}
|
|
|
|
||
|
|
|
|
if "count(fsm/state)>0" document "generated/{@name}_impl.h", "text" {
|
|
||
|
|
/**
|
|
* @file «@name»_impl.h
|
|
* @brief «@name» protocol implementation declarations
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license GNU General Public License 3.0 - see LICENSE.txt
|
|
*/
|
|
|
|
#ifndef «yml:ucase(@name)»_IMPL_H
|
|
#define «yml:ucase(@name)»_IMPL_H
|
|
|
|
#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[count(state)>0]" | #define «yml:ucase(@name)»_THRESHOLD «@threshold»
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// conditions
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
||
|
|
for "func:distinctName(*//condition)" {
|
|
| /**
|
|
| * @brief Evaluate condition: Is «@name» true?
|
|
| * @param[in] session the session
|
|
| * @param[out] result true if «@name», else false
|
|
| * @retval status
|
|
| */
|
|
| PEP_STATUS «@name»(PEP_SESSION session, bool * result);
|
|
}
|
|
||
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// actions
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
||
|
|
for "func:distinctName(*//action)" {
|
|
| /**
|
|
| * @brief Action: Do «@name»
|
|
| * @param[in] session the session
|
|
| * @retval status
|
|
| */
|
|
| PEP_STATUS «@name»(PEP_SESSION session);
|
|
}
|
|
||
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// timeout handlers
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
||
|
|
for "fsm[@threshold > 0]" {
|
|
| /**
|
|
| * <!-- «@name»TimeoutHandler() -->
|
|
| *
|
|
| * @brief Handle timeouts for «@name» state machine
|
|
| *
|
|
| * @param[in] session the session
|
|
| * @retval status
|
|
| */
|
|
| PEP_STATUS «@name»TimeoutHandler(PEP_SESSION session);
|
|
}
|
|
||
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// «@name» state machine driver, message sending, and event receipt
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* <!-- send_«@name»_message() -->
|
|
*
|
|
* @brief send message about «@name» event to communication partners using state
|
|
*
|
|
* @param[in] session the session
|
|
* @param[in] fsm the finite state machine from which to get state
|
|
* @param[in] message_type type of message to send
|
|
*
|
|
* @retval status of message send
|
|
*
|
|
*/
|
|
PEP_STATUS send_«@name»_message(
|
|
PEP_SESSION session,
|
|
«@name»_PR fsm,
|
|
int message_type
|
|
);
|
|
|
|
/**
|
|
* <!-- recv_«@name»_event() -->
|
|
*
|
|
* @brief receive «@name» message and store it in state
|
|
*
|
|
* @param[in] session the session
|
|
* @param[in] ev the event to process
|
|
*
|
|
* @retval status of event processing/storage
|
|
*
|
|
*/
|
|
PEP_STATUS recv_«@name»_event(
|
|
PEP_SESSION session,
|
|
«@name»_event_t *ev
|
|
);
|
|
|
|
/**
|
|
* <!-- «@name»_driver() -->
|
|
*
|
|
* @brief «@name» state machine driver
|
|
*
|
|
* @param[in] session the session
|
|
* @param[in] fsm finite state machine to drive
|
|
* @param[in] ev the event to process???
|
|
*
|
|
* @retval ???
|
|
*
|
|
* @note 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 used by the engine internally //
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* <!-- signal_«@name»_event() -->
|
|
*
|
|
* @brief Internal engine API: call this if you need to signal an external «@name» event
|
|
*
|
|
* @param[in] session the session
|
|
* @param[in] fsm finite state machine
|
|
* @param[in] event event type
|
|
* @param[out] own_identities list of own identities
|
|
*
|
|
* @retval status
|
|
*
|
|
* @ownership the ownership of own_identities goes to the callee
|
|
*
|
|
*/
|
|
PEP_STATUS signal_«@name»_event(
|
|
PEP_SESSION session,
|
|
«@name»_PR fsm,
|
|
int event,
|
|
identity_list *own_identities
|
|
);
|
|
|
|
/**
|
|
* <!-- signal_«@name»_message() -->
|
|
*
|
|
* @brief Internal engine API: to be called by transports receiving a «@name» message
|
|
*
|
|
* @param[in] session the session
|
|
* @param[in] rating rating of the decrypted «@name» message
|
|
* @param[out] data payload of «@name» message
|
|
* @param[out] from identity of the message sender
|
|
* @param[out] fpr fingerprint of the sender key used to sign the message
|
|
*
|
|
* @retval status
|
|
*
|
|
*/
|
|
PEP_STATUS signal_«@name»_message(
|
|
PEP_SESSION session,
|
|
PEP_rating rating,
|
|
const char *data,
|
|
size_t size,
|
|
const pEp_identity *from,
|
|
const char *sender_fpr
|
|
);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
||
|
|
}
|
|
|
|
if "count(fsm/state)>0" document "generated/{@name}_impl.c", "text" {
|
|
||
|
|
/**
|
|
* @file «@name»_impl.c
|
|
* @brief «@name» protocol implementation: driver, event handling, message signalling/sending/receipt, etc.
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license 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"
|
|
#include "security_checks.h"
|
|
`` for "fsm[count(state)>0]" | #include "«@name»_fsm.h"
|
|
|
|
`` apply "fsm[count(state)>0]", 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[count(state)>0]" |>>>> «../@name»_driver(session, «../@name»_PR_«yml:lcase(@name)», None);
|
|
return PEP_STATUS_OK;
|
|
}
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
`` apply "fsm[count(state)>0]", mode=reset_state_machine;
|
|
default:
|
|
return PEP_ILLEGAL_VALUE;
|
|
}
|
|
|
|
int next_state = None;
|
|
do {
|
|
switch (fsm) {
|
|
`` apply "fsm[count(state)>0]", 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,
|
|
identity_list *own_identities
|
|
)
|
|
{
|
|
«@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) {
|
|
// FIXME: there should be a mapping between event and message type
|
|
// with current implementation they've got an offset of 1
|
|
msg = new_«@name»_message(fsm, event - 1);
|
|
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;
|
|
}
|
|
|
|
status = set_all_userids_to_own(session, own_identities);
|
|
if (status != PEP_STATUS_OK)
|
|
goto the_end;
|
|
|
|
ev->own_identities = own_identities;
|
|
|
|
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); // msg gets freed here
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS signal_«@name»_message(
|
|
PEP_SESSION session,
|
|
PEP_rating rating,
|
|
const char *data,
|
|
size_t size,
|
|
const pEp_identity *from,
|
|
const char *sender_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;
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
«@name»_event_t *ev = NULL;
|
|
|
|
«@name»_t *msg = NULL;
|
|
status = decode_«@name»_message(data, size, &msg);
|
|
if (status)
|
|
return status;
|
|
|
|
«@name»_PR fsm = msg->present;
|
|
int event = 0;
|
|
bool is_own_key = false;
|
|
|
|
switch (fsm) {
|
|
`` apply "fsm[count(state)>0]", 2, mode=signal_message
|
|
default:
|
|
status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
|
|
ev = new_«@name»_event(fsm, event, msg);
|
|
if (!ev) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
|
|
// add transport data
|
|
|
|
if (from) {
|
|
ev->from = identity_dup(from);
|
|
if (!ev->from) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
}
|
|
|
|
if (sender_fpr) {
|
|
ev->sender_fpr = strdup(sender_fpr);
|
|
assert(ev->sender_fpr);
|
|
if (!ev->sender_fpr) {
|
|
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); // msg gets freed here
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS send_«@name»_message(
|
|
PEP_SESSION session,
|
|
«@name»_PR fsm,
|
|
int message_type
|
|
)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
assert(session && (int) fsm > None && message_type > None);
|
|
if (!(session && (int) fsm > None && message_type > None))
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
time_t now = time(NULL);
|
|
switch (fsm) {
|
|
||
|
|
apply "fsm[count(state)>0]", 2, mode=send;
|
|
||
|
|
}
|
|
|
|
«@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;
|
|
char *key_data = NULL;
|
|
size_t key_data_size = 0;
|
|
stringlist_t *extra = NULL;
|
|
bool transaction;
|
|
|
|
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;
|
|
|
|
// we never use this
|
|
if (session->«yml:lcase(@name)»_state.comm_partner.identity
|
|
&& session->«yml:lcase(@name)»_state.comm_partner.identity->fpr) {
|
|
free(session->«yml:lcase(@name)»_state.comm_partner.identity->fpr);
|
|
session->«yml:lcase(@name)»_state.comm_partner.identity->fpr = NULL;
|
|
}
|
|
|
|
// if we have this we always use this
|
|
if (session->«yml:lcase(@name)»_state.comm_partner.sender_fpr) {
|
|
free(session->«yml:lcase(@name)»_state.transport.sender_fpr);
|
|
session->«yml:lcase(@name)»_state.transport.sender_fpr =
|
|
strdup(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr);
|
|
assert(session->«yml:lcase(@name)»_state.transport.sender_fpr);
|
|
if (!session->«yml:lcase(@name)»_state.transport.sender_fpr) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
}
|
|
|
|
switch (fsm) {
|
|
||
|
|
apply "fsm[count(state)>0]", 2, mode=send2;
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
|
|
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' and ../@name!='KeySync']" | #error unencrypted only allowed with KeySync
|
|
`` for "fsm/message[@security='unencrypted' and ../@name='KeySync']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
status = try_base_prepare_message(
|
|
session,
|
|
li->ident,
|
|
li->ident,
|
|
BASE_SYNC,
|
|
_data,
|
|
size,
|
|
li->ident->fpr,
|
|
&_m
|
|
);
|
|
if (status) {
|
|
free(_data);
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
continue;
|
|
}
|
|
attach_own_key(session, _m);
|
|
decorate_message(session, _m, PEP_rating_undefined, NULL, true, true);
|
|
m = _m;
|
|
break;
|
|
|
|
`` for "fsm/message[@security='untrusted']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
// add fpr of key of comm partner
|
|
|
|
if (!session->«yml:lcase(@name)»_state.transport.sender_fpr) {
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
|
|
extra = new_stringlist(session->«yml:lcase(@name)»_state.transport.sender_fpr);
|
|
if (!extra) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
|
|
status = base_prepare_message(
|
|
session,
|
|
li->ident,
|
|
li->ident,
|
|
BASE_SYNC,
|
|
_data,
|
|
size,
|
|
NULL,
|
|
&_m
|
|
);
|
|
if (status) {
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
free(_data);
|
|
continue;
|
|
}
|
|
|
|
status = try_encrypt_message(session, _m, extra, &m, PEP_enc_PEP, 0);
|
|
if (status) {
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
add_opt_field(m, "pEp-auto-consume", "yes");
|
|
m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation");
|
|
free_message(_m);
|
|
break;
|
|
|
|
// attach own keys for new member
|
|
`` for "fsm/message[@security='attach_own_keys_for_new_member']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
// check if we had a former negotiation
|
|
|
|
transaction = false;
|
|
for (int i=0; i < session->«yml:lcase(@name)»_state.own.negotiation.size; i++) {
|
|
if (session->«yml:lcase(@name)»_state.own.negotiation.buf[i]) {
|
|
transaction = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if it is a former negotiation check if the key
|
|
// is fully trusted and the sender key of this
|
|
// transaction; if so add the sender key to extra
|
|
// keys allowing this new partner to read the
|
|
// secret keys
|
|
|
|
if (transaction) {
|
|
if (!(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr &&
|
|
session->«yml:lcase(@name)»_state.transport.from &&
|
|
session->«yml:lcase(@name)»_state.transport.from->user_id))
|
|
{
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
|
|
// test if this is a green channel
|
|
|
|
pEp_identity *ident = new_identity(NULL,
|
|
session->«yml:lcase(@name)»_state.comm_partner.sender_fpr,
|
|
session->«yml:lcase(@name)»_state.transport.from->user_id,
|
|
NULL
|
|
);
|
|
if (!ident) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
status = get_trust(session, ident);
|
|
if (status) {
|
|
free_identity(ident);
|
|
goto the_end;
|
|
}
|
|
assert(ident->comm_type == PEP_ct_pEp); // we don't deliver otherwise
|
|
if (ident->comm_type != PEP_ct_pEp) {
|
|
free_identity(ident);
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
free_identity(ident);
|
|
|
|
// test if we accepted this as own key already
|
|
|
|
bool is_own_key = false;
|
|
status = own_key_is_listed(session,
|
|
session->«yml:lcase(@name)»_state.comm_partner.sender_fpr,
|
|
&is_own_key);
|
|
assert(!status);
|
|
if (status)
|
|
goto the_end;
|
|
if (!is_own_key) {
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
|
|
// if so add key of comm partner to extra keys
|
|
|
|
extra = new_stringlist(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr);
|
|
if (!extra) {
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
}
|
|
|
|
status = base_prepare_message(
|
|
session,
|
|
li->ident,
|
|
li->ident,
|
|
BASE_SYNC,
|
|
_data,
|
|
size,
|
|
NULL,
|
|
&_m
|
|
);
|
|
if (status) {
|
|
free(_data);
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
continue;
|
|
}
|
|
|
|
// export secret keys into memory
|
|
|
|
key_data = strdup("");
|
|
assert(key_data);
|
|
if (!key_data) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data_size = 1; // N.B. If null termination makes us happy for debugging, fine, but
|
|
// if we include this in the size, libetpan will null terminate and
|
|
// go bananas. We can't have a NUL in the mime text.
|
|
|
|
for (stringlist_t *sl = session->«yml:lcase(@name)»_state.own.keys;
|
|
sl && sl->value ; sl = sl->next)
|
|
{
|
|
char *_key_data = NULL;
|
|
size_t _size = 0;
|
|
status = export_secret_key(session, sl->value, &_key_data, &_size);
|
|
if (status && status != PEP_KEY_NOT_FOUND) {
|
|
free(_data);
|
|
free_message(_m);
|
|
goto the_end;
|
|
}
|
|
|
|
if (status != PEP_KEY_NOT_FOUND) {
|
|
assert(_key_data && _size);
|
|
char *n = realloc(key_data, key_data_size + _size);
|
|
if (!n) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = n;
|
|
key_data_size += _size;
|
|
strlcat(key_data, _key_data, key_data_size);
|
|
free(_key_data);
|
|
_key_data = NULL;
|
|
}
|
|
status = export_key(session, sl->value, &_key_data, &_size);
|
|
if (status && status != PEP_KEY_NOT_FOUND) {
|
|
free(_data);
|
|
free_message(_m);
|
|
goto the_end;
|
|
}
|
|
|
|
if (status != PEP_KEY_NOT_FOUND) {
|
|
assert(_key_data && _size);
|
|
char *n = realloc(key_data, key_data_size + _size);
|
|
if (!n) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = n;
|
|
key_data_size += _size;
|
|
strlcat(key_data, _key_data, key_data_size);
|
|
free(_key_data);
|
|
_key_data = NULL;
|
|
}
|
|
}
|
|
|
|
// add secret key data as attachment
|
|
|
|
// N.B. The -1 makes sure we do NOT add a NUL into the mime stream!
|
|
bloblist_t *bl = bloblist_add(_m->attachments, key_data, key_data_size - 1,
|
|
"application/octet-stream", "file://own.key");
|
|
if (!bl) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = NULL;
|
|
|
|
status = try_encrypt_message(session, _m, extra, &m, PEP_enc_PEP, 0);
|
|
if (status) {
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
add_opt_field(m, "pEp-auto-consume", "yes");
|
|
m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation");
|
|
free_message(_m);
|
|
break;
|
|
|
|
// attach own keys for group
|
|
`` for "fsm/message[@security='attach_own_keys_for_group']" |>>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
status = base_prepare_message(
|
|
session,
|
|
li->ident,
|
|
li->ident,
|
|
BASE_SYNC,
|
|
_data,
|
|
size,
|
|
NULL,
|
|
&_m
|
|
);
|
|
if (status) {
|
|
free(_data);
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
continue;
|
|
}
|
|
|
|
// export secret keys into memory
|
|
|
|
key_data = strdup("");
|
|
assert(key_data);
|
|
if (!key_data) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data_size = 1; // N.B. If null termination makes us happy for debugging, fine, but
|
|
// if we include this in the size, libetpan will null terminate and
|
|
// go bananas. We can't have a NUL in the mime text.
|
|
|
|
for (stringlist_t *sl = session->«yml:lcase(@name)»_state.own.keys;
|
|
sl && sl->value ; sl = sl->next)
|
|
{
|
|
char *_key_data = NULL;
|
|
size_t _size = 0;
|
|
status = export_secret_key(session, sl->value, &_key_data, &_size);
|
|
if (status && status != PEP_KEY_NOT_FOUND) {
|
|
free(_data);
|
|
free_message(_m);
|
|
goto the_end;
|
|
}
|
|
|
|
if (status != PEP_KEY_NOT_FOUND) {
|
|
assert(_key_data && _size);
|
|
char *n = realloc(key_data, key_data_size + _size);
|
|
if (!n) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = n;
|
|
key_data_size += _size;
|
|
strlcat(key_data, _key_data, key_data_size);
|
|
free(_key_data);
|
|
_key_data = NULL;
|
|
}
|
|
status = export_key(session, sl->value, &_key_data, &_size);
|
|
if (status && status != PEP_KEY_NOT_FOUND) {
|
|
free(_data);
|
|
free_message(_m);
|
|
goto the_end;
|
|
}
|
|
|
|
if (status != PEP_KEY_NOT_FOUND) {
|
|
assert(_key_data && _size);
|
|
char *n = realloc(key_data, key_data_size + _size);
|
|
if (!n) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = n;
|
|
key_data_size += _size;
|
|
strlcat(key_data, _key_data, key_data_size);
|
|
free(_key_data);
|
|
_key_data = NULL;
|
|
}
|
|
}
|
|
|
|
// add secret key data as attachment
|
|
|
|
// N.B. The -1 makes sure we do NOT add a NUL into the mime stream!
|
|
bl = bloblist_add(_m->attachments, key_data, key_data_size - 1,
|
|
"application/octet-stream", "file://own.key");
|
|
if (!bl) {
|
|
free(_data);
|
|
free_message(_m);
|
|
status = PEP_OUT_OF_MEMORY;
|
|
goto the_end;
|
|
}
|
|
key_data = NULL;
|
|
|
|
// we do not support extra keys here and will only encrypt to ourselves
|
|
status = try_encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
|
|
if (status) {
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
continue;
|
|
}
|
|
add_opt_field(m, "pEp-auto-consume", "yes");
|
|
m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation");
|
|
free_message(_m);
|
|
break;
|
|
|
|
default: // security=trusted only
|
|
status = base_prepare_message(
|
|
session,
|
|
li->ident,
|
|
li->ident,
|
|
BASE_SYNC,
|
|
_data,
|
|
size,
|
|
NULL,
|
|
&_m
|
|
);
|
|
if (status) {
|
|
free(_data);
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
continue;
|
|
}
|
|
|
|
status = try_encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
|
|
if (status) {
|
|
status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
|
|
if (status == PEP_OUT_OF_MEMORY)
|
|
goto the_end;
|
|
continue;
|
|
}
|
|
add_opt_field(m, "pEp-auto-consume", "yes");
|
|
m->in_reply_to = stringlist_add(m->in_reply_to, "pEp-auto-consume@pEp.foundation");
|
|
free_message(_m);
|
|
}
|
|
|
|
status = session->messageToSend(m);
|
|
m = NULL;
|
|
}
|
|
||
|
|
if "fsm/message[@ratelimit>0]" {
|
|
||
|
|
|
|
switch (fsm) {
|
|
||
|
|
for "fsm[message/@ratelimit>0]" {
|
|
||
|
|
case Sync_PR_«yml:lcase(@name)»:
|
|
switch (message_type) {
|
|
||
|
|
for "message[@ratelimit>0]"
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
session->«yml:lcase(../../@name)»_state.own.last_«../@name»_«@name» = now;
|
|
break;
|
|
||
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
||
|
|
}
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
||
|
|
}
|
|
||
|
|
|
|
the_end:
|
|
free_stringlist(extra);
|
|
free_identity_list(channels);
|
|
free_message(m);
|
|
free(data);
|
|
free(key_data);
|
|
free_«@name»_message(msg);
|
|
if (status)
|
|
SERVICE_ERROR_LOG(session, "send_«@name»_message()", status);
|
|
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;
|
|
}
|
|
|
|
// update transport data
|
|
|
|
if (ev->from) {
|
|
free_identity(session->«yml:lcase(@name)»_state.transport.from);
|
|
session->«yml:lcase(@name)»_state.transport.from = ev->from;
|
|
ev->from = NULL;
|
|
}
|
|
|
|
if (ev->sender_fpr) {
|
|
free(session->«yml:lcase(@name)»_state.transport.sender_fpr);
|
|
session->«yml:lcase(@name)»_state.transport.sender_fpr = ev->sender_fpr;
|
|
|
|
/* Removed for temp ENGINE-647 fix. Will be reenabled once better sync debugging is in.
|
|
// Check against saved comm_partner sender_fpr state, if there is one yet
|
|
if (session->«yml:lcase(@name)»_state.comm_partner.sender_fpr) {
|
|
// 1. Does it match sender_fpr?
|
|
if (strcasecmp(session->«yml:lcase(@name)»_state.comm_partner.sender_fpr, ev->sender_fpr) != 0) {
|
|
// 2. If not, is it a group key?
|
|
bool is_own_key = false;
|
|
status = own_key_is_listed(session, ev->sender_fpr, &is_own_key);
|
|
if (status)
|
|
goto the_end;
|
|
if (!is_own_key) {
|
|
status = PEP_ILLEGAL_VALUE;
|
|
goto the_end;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
ev->sender_fpr = NULL;
|
|
}
|
|
|
|
// update own identities
|
|
|
|
if (ev->own_identities && ev->own_identities->ident) {
|
|
free_identity_list(session->«yml:lcase(@name)»_state.own.identities);
|
|
session->«yml:lcase(@name)»_state.own.identities = ev->own_identities;
|
|
ev->own_identities = NULL;
|
|
}
|
|
|
|
|
|
status = «@name»_driver(session, fsm, event);
|
|
|
|
the_end:
|
|
//free_«@name»_event(ev); // FIXME: We don't own this pointer. Are we sure it gets freed externally?
|
|
return status;
|
|
}
|
|
|
|
||
|
|
}
|
|
|
|
apply "fsm", 0, mode=gen;
|
|
}
|
|
|
|
template "fsm", mode=send2 {
|
|
||
|
|
case Sync_PR_«yml:lcase(@name)»: {
|
|
switch (message_type) {
|
|
||
|
|
if "message[@type='broadcast']"
|
|
||
|
|
// these messages are being broadcasted
|
|
`` for "message[@type='broadcast']" |>> case «../@name»_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 "message[@type='anycast' and @security!='ignore']" |>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
// if we have a comm_partner fixed send it there
|
|
if (session->«yml:lcase(../@name)»_state.comm_partner.identity) {
|
|
pEp_identity *channel = identity_dup(session->«yml:lcase(../@name)»_state.comm_partner.identity);
|
|
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;
|
|
}
|
|
}
|
|
// if we can reply just do
|
|
else if (session->«yml:lcase(../@name)»_state.transport.from) {
|
|
pEp_identity *channel = identity_dup(session->«yml:lcase(../@name)»_state.transport.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;
|
|
}
|
|
}
|
|
// real anycast, send it to the first matching
|
|
else {
|
|
status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(../@name)»);
|
|
if (status)
|
|
goto the_end;
|
|
if (!channels)
|
|
goto the_end;
|
|
|
|
if (channels->next) {
|
|
free_identity_list(channels->next);
|
|
channels->next = NULL;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
status = PEP_«yml:ucase(../@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
}
|
|
break;
|
|
|
|
||
|
|
}
|
|
|
|
template "fsm[message/@security='ignore' or message/@ratelimit>0]", mode=send {
|
|
||
|
|
case Sync_PR_«yml:lcase(@name)»: {
|
|
||
|
|
if "message[@security='ignore']" {
|
|
||
|
|
// ignore
|
|
switch (message_type) {
|
|
||
|
|
for "message[@security='ignore']" {
|
|
|>> case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
|>>> return PEP_STATUS_OK;
|
|
}
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
||
|
|
}
|
|
if "message[@ratelimit>0]" {
|
|
||
|
|
|
|
// test if a message with a rate limit was just sent; in case drop
|
|
switch (message_type) {
|
|
||
|
|
for "message[@ratelimit>0]"
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
if (now < session->«yml:lcase(../../@name)»_state.own.last_«../@name»_«@name» + «@ratelimit»)
|
|
return PEP_STATUS_OK;
|
|
break;
|
|
|
|
||
|
|
if "message[@ratelimit>0]"
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
||
|
|
||
|
|
default:
|
|
break;
|
|
}
|
|
||
|
|
}
|
|
}
|
|
|
|
template "fsm", mode=timeout
|
|
||
|
|
/**
|
|
* <!-- _«@name»_timeout() -->
|
|
*
|
|
* @internal
|
|
*
|
|
* @brief Determine if «@name» state machine has timed out
|
|
* (by hanging in the same state too long,
|
|
* exceeding «yml:ucase(@name)»_THRESHOLD)
|
|
*
|
|
* @param[in] state current state
|
|
*
|
|
* @retval true if wait has exceeded «yml:ucase(@name)»_THRESHOLD
|
|
* false otherwise
|
|
*/
|
|
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)»:
|
|
switch (msg->choice.«yml:lcase(@name)».present) {
|
|
||
|
|
for "message[@security='unencrypted']" {
|
|
if "position()=1" |>> // these messages require a detached signature
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
if (!sender_fpr) {
|
|
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
event = «@name»;
|
|
break;
|
|
|
|
||
|
|
}
|
|
for "message[@security='untrusted']" {
|
|
if "position()=1" |>> // these messages must arrive encrypted
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
if (rating < PEP_rating_reliable) {
|
|
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
event = «@name»;
|
|
break;
|
|
|
|
||
|
|
}
|
|
for "message[@security!='unencrypted' and @security!='untrusted' and @security!='ignore']" {
|
|
if "position()=1" |>> // these messages must come through a trusted channel
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
if (rating < PEP_rating_trusted) {
|
|
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
status = own_key_is_listed(session, sender_fpr, &is_own_key);
|
|
if (status)
|
|
goto the_end;
|
|
if (!is_own_key) {
|
|
status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
|
|
goto the_end;
|
|
}
|
|
event = «@name»;
|
|
break;
|
|
|
|
||
|
|
}
|
|
for "message[@security='ignore']"
|
|
||
|
|
case «../@name»_PR_«yml:mixedCase(@name)»:
|
|
free_«../../@name»_message(msg);
|
|
return PEP_STATUS_OK;
|
|
|
|
||
|
|
||
|
|
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)».present) {
|
|
||
|
|
for "message"
|
|
||
|
|
case «../@name»_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 {
|
|
if "count(state)>0" document "generated/{@name}_fsm.h", "text" {
|
|
||
|
|
/**
|
|
* @file «@name»_fsm.h
|
|
* @brief Finite state machine states, definitions, and structs for the «@name» protocol.
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license GNU General Public License 3.0 - see LICENSE.txt
|
|
*/
|
|
|
|
#ifndef «yml:ucase(@name)»_FSM_H
|
|
#define «yml:ucase(@name)»_FSM_H
|
|
|
|
#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_Timeout = None,
|
|
«@name»_event_Init = Init,
|
|
||
|
|
for "message" {
|
|
const "name", "@name";
|
|
|> «$name» = «/protocol/fsm/message[@name=$name]/@id»,
|
|
}
|
|
|> «@name»_event_Extra = Extra,
|
|
for "external" {
|
|
if "@id < 128"
|
|
error > external «@name» must have ID >= 128 but it's «@id»
|
|
|> «@name» = «@id»,
|
|
}
|
|
for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" {
|
|
if "@name!='Init'"
|
|
|> «@name»`if "position()!=last()" > , `
|
|
}
|
|
||
|
|
} «@name»_event;
|
|
|
|
|
|
// state machine
|
|
|
|
#ifndef NDEBUG
|
|
/**
|
|
* <!-- «@name»_state_name() -->
|
|
*
|
|
* @brief Convert «@name» state to string
|
|
*
|
|
* @param[in] state state to convert to string
|
|
*
|
|
* @retval string representation of state name
|
|
* NULL if invalid state
|
|
*/
|
|
const char *«@name»_state_name(int state);
|
|
/**
|
|
* <!-- «@name»_event_name() -->
|
|
*
|
|
* @brief Convert «@name» event to string
|
|
*
|
|
* @param[in] event event to convert to string
|
|
*
|
|
* @retval string representation of event name
|
|
* NULL if invalid event
|
|
*/
|
|
const char *«@name»_event_name(int event);
|
|
#endif
|
|
|
|
/**
|
|
* <!-- fsm_«@name» -->
|
|
*
|
|
* @brief Given the current state of a «@name» state machine, determine if
|
|
* a received «@name» event will cause a state transition
|
|
* and, if so, return the next state.
|
|
*
|
|
* @param[in] session session state machine is associated with
|
|
* @param[in] state current state
|
|
* @param[in] event incoming event to evaluate
|
|
*
|
|
* @retval next_state if event causes a state transition
|
|
* @retval None if state machine should remain in the same state
|
|
*
|
|
*/
|
|
«@name»_state fsm_«@name»(
|
|
PEP_SESSION session,
|
|
«@name»_state state,
|
|
«@name»_event event
|
|
);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
||
|
|
}
|
|
|
|
if "count(state)>0" document "generated/{@name}_fsm.c", "text" {
|
|
||
|
|
/**
|
|
* @file «@name»_fsm.c
|
|
* @brief Finite state machine implementation for the «@name» protocol.
|
|
* @generated from ../sync/gen_statemachine.ysl2
|
|
*
|
|
* @license GNU General Public License 3.0 - see LICENSE.txt
|
|
*/
|
|
|
|
#include "«@name»_fsm.h"
|
|
#include <stdlib.h>
|
|
|
|
#ifdef NDEBUG
|
|
static
|
|
#endif
|
|
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:
|
|
assert(0);
|
|
return "unknown state";
|
|
}
|
|
}
|
|
|
|
#ifdef NDEBUG
|
|
static
|
|
#endif
|
|
const char *«@name»_event_name(int event)
|
|
{
|
|
switch (event) {
|
|
case None:
|
|
return "Timeout";
|
|
case Init:
|
|
return "Init";
|
|
||
|
|
for "func:distinctName(state/event[not(@name='Init')]|message)" {
|
|
|>> case «@name»:
|
|
|>>> return "«@name»";
|
|
}
|
|
||
|
|
default:
|
|
assert(0);
|
|
return "unknown event";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <!-- _str() -->
|
|
*
|
|
* @internal
|
|
*
|
|
* @brief Convert an integer to a string representation
|
|
*
|
|
* @param[in] n integer to convert
|
|
* @param[in] hex true if it should be in hex representation,
|
|
* false for decimal
|
|
*
|
|
* @retval string representation of input
|
|
* NULL if out of memory or input invalid
|
|
*/
|
|
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")
|
|
|
|
/**
|
|
* <!-- _«@name»_ERR_LOG_int() -->
|
|
*
|
|
* @internal
|
|
*
|
|
* @brief Write a «@name» error to log with integer input as description
|
|
*
|
|
* @param[in] session relevant session
|
|
* @param[in] t C string event name
|
|
* @param[in] n integer to log (e.g. error status)
|
|
* @param[in] hex true if integer should be logged in hex format,
|
|
* false if decimal
|
|
*
|
|
* @retval PEP_OUT_OF_MEMORY if out of memory
|
|
* @retval PEP_ILLEGAL_VALUE if input error during logging
|
|
* @retval PEP_STATUS_OK otherwise
|
|
*/
|
|
__attribute__((__unused__))
|
|
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)
|
|
#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;
|
|
|
|
if ((int) state == None)
|
|
state = «@name»_state_Init;
|
|
|
|
switch (state) {
|
|
`` apply "state", 2, mode=fsm
|
|
default:
|
|
«@name»_ERR_LOG("invalid state", «@name»_state_name(state));
|
|
assert(0);
|
|
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:
|
|
// received Timeout event, ignoring
|
|
break;
|
|
|
|
||
|
|
if "not(event[@name='Init'])"
|
|
||
|
|
case Init:
|
|
«../@name»_SERVICE_LOG("received Init but nothing to do", "Init");
|
|
break;
|
|
|
|
||
|
|
||
|
|
`` apply "event", 2, mode=fsm
|
|
default:
|
|
// ignore events not handled here
|
|
«../@name»_SERVICE_LOG("ignoring event", «../@name»_event_name(event));
|
|
return invalid_event;
|
|
}
|
|
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|debug";
|
|
||
|
|
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»_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 "debug"
|
|
| KeySync_SERVICE_LOG("«.»", "«ancestor::protocol/@name»");
|
|
|
|
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);
|
|
assert(0);
|
|
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);
|
|
assert(0);
|
|
return invalid_condition;
|
|
}
|
|
if (result) {
|
|
«$fsm/@name»_SERVICE_LOG("condition applies", "«@name»");
|
|
||
|
|
apply "transition|action|condition|else|send|debug";
|
|
| }
|
|
}
|
|
|
|
template "else" {
|
|
if "not(name(preceding-sibling::*[1]) = '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|debug";
|
|
| }
|
|
}
|
|
}
|
|
|