sync/gen_statemachine.ysl2
author Dirk Zimmermann <dz@pep.security>
Tue, 09 Apr 2019 16:02:46 +0200
branchIOS-1482
changeset 3480 689c15d6bef7
parent 3393 ecdb1635904a
child 3509 a9c0c6f31c56
permissions -rw-r--r--
IOS-1482 Xcode: Change organization name.
     1 // This file is under GNU General Public License 3.0
     2 // see LICENSE.txt
     3 
     4 // generate state machine code
     5 
     6 // Copyleft (c) 2016 - 2019, p≡p foundation
     7 
     8 // Written by Volker Birk
     9 
    10 include yslt.yml2
    11 
    12 tstylesheet {
    13     include standardlib.ysl2
    14     include ./functions.ysl2
    15 
    16     template "/protocol" {
    17         document "generated/{@name}_event.h", "text"
    18         ||
    19         // This file is under GNU General Public License 3.0
    20         // see LICENSE.txt
    21 
    22         #pragma once
    23 
    24         #include "dynamic_api.h"
    25 
    26         #ifdef __cplusplus
    27         extern "C" {
    28         #endif
    29 
    30         typedef struct «@name» «yml:ucase(@name)»;
    31         typedef int «yml:ucase(@name)»_PR;
    32 
    33         typedef struct «@name»_event {
    34             «yml:ucase(@name)»_PR fsm;
    35             int event;
    36             «yml:ucase(@name)» *msg;
    37         } «@name»_event_t;
    38 
    39  
    40         // new_«@name»_event() - allocate a new «@name»_event
    41         //
    42         //  parameters:
    43         //      fsm (in)        finite state machine the event is for
    44         //      event (in)      event or None
    45         //      msg (in)        message to compute event from
    46         //
    47         //  return value:
    48         //      pointer to new event or NULL in case of failure
    49         //
    50         //  caveat:
    51         //      event must be valid for fsm or None
    52         //      in case msg is given event will be calculated out of message
    53 
    54         «@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «yml:ucase(@name)» *msg);
    55 
    56         #define «yml:ucase(@name)»_TIMEOUT_EVENT new_«@name»_event(«@name»_PR_NOTHING, 0, NULL);
    57 
    58     
    59         // free_«@name»_event() - free memory occupied by event
    60         //
    61         //  parameters:
    62         //      ev (in)         event to free
    63 
    64         void free_«@name»_event(«@name»_event_t *ev);
    65 
    66 
    67         #ifdef __cplusplus
    68         }
    69         #endif
    70 
    71         ||
    72 
    73         document "generated/{@name}_event.c", "text"
    74         ||
    75         // This file is under GNU General Public License 3.0
    76         // see LICENSE.txt
    77 
    78         #include "platform.h"
    79 
    80         #include "pEp_internal.h"
    81         #include "«@name»_event.h"
    82         #include "«@name»_func.h"
    83         `` for "fsm" | #include "«@name»_fsm.h"
    84 
    85         «@name»_event_t *new_«@name»_event(«yml:ucase(@name)»_PR fsm, int event, «@name»_t *msg)
    86         {
    87             «@name»_event_t *ev = («@name»_event_t *) calloc(1, sizeof(«@name»_event_t));
    88             assert(ev);
    89             if (!ev)
    90                 return NULL;
    91 
    92             ev->fsm = fsm;
    93             ev->event = event;
    94             ev->msg = msg;
    95 
    96             if (msg) {
    97                 switch (fsm) {
    98                     `` apply "fsm", 3, mode=event
    99                     default:
   100                         // unknown protocol
   101                         free(ev);
   102                         return NULL;
   103                 }
   104             }
   105 
   106             return ev;
   107         }
   108 
   109         void free_«@name»_event(«@name»_event_t *ev)
   110         {
   111             if (ev) {
   112                 free_«@name»_message(ev->msg);
   113                 free(ev);
   114             }
   115         }
   116 
   117         ||
   118 
   119         document "generated/{@name}_impl.h", "text" {
   120             ||
   121             // This file is under GNU General Public License 3.0
   122             // see LICENSE.txt
   123 
   124             #pragma once
   125 
   126             #include "fsm_common.h"
   127             #include "«@name»_event.h"
   128             #include "message_api.h"
   129             #include "../asn.1/«@name».h"
   130             
   131             #define «yml:ucase(@name)»_THRESHOLD «@threshold»
   132             `` for "fsm" | #define «yml:ucase(@name)»_THRESHOLD «@threshold»
   133 
   134             #ifdef __cplusplus
   135             extern "C" {
   136             #endif
   137 
   138             // conditions
   139 
   140             ||
   141             for "func:distinctName(*//condition)"
   142                 | PEP_STATUS «@name»(PEP_SESSION session, bool *result);
   143             ||
   144 
   145             // actions
   146 
   147             ||
   148             for "func:distinctName(*//action)"
   149                 | PEP_STATUS «@name»(PEP_SESSION session);
   150             ||
   151 
   152             // timeout handler
   153             
   154             ||
   155             for "fsm[@threshold > 0]"
   156                 | PEP_STATUS «@name»TimeoutHandler(PEP_SESSION session);
   157             ||
   158 
   159             // send message about an event to communication partners using state
   160 
   161             PEP_STATUS send_«@name»_message(
   162                     PEP_SESSION session, 
   163                     «@name»_PR fsm,
   164                     int message_type
   165                 );
   166 
   167             // receive message and store it in state
   168 
   169             PEP_STATUS recv_«@name»_event(
   170                     PEP_SESSION session,
   171                     «@name»_event_t *ev
   172                 );
   173         
   174             // state machine driver
   175             // if fsm or event set to 0 use fields in src if present
   176 
   177             PEP_STATUS «@name»_driver(
   178                     PEP_SESSION session,
   179                     «@name»_PR fsm,
   180                     int event
   181                 );
   182 
   183             // API being used by the engine internally
   184 
   185             // call this if you need to signal an external event
   186 
   187             PEP_STATUS signal_«@name»_event(
   188                     PEP_SESSION session, 
   189                     «@name»_PR fsm,
   190                     int event
   191                 );
   192             
   193             // call this if you are a transport and are receiving
   194             // a «@name» message
   195 
   196             PEP_STATUS signal_«@name»_message(
   197                     PEP_SESSION session, 
   198                     PEP_rating rating,
   199                     const char *data,
   200                     size_t size,
   201                     const pEp_identity *from,
   202                     const char *signature_fpr
   203                 );
   204 
   205             #ifdef __cplusplus
   206             }
   207             #endif
   208 
   209             ||
   210         }
   211 
   212         document "generated/{@name}_impl.c", "text" {
   213             ||
   214             // This file is under GNU General Public License 3.0
   215             // see LICENSE.txt
   216         
   217             #include "«@name»_impl.h"
   218             #include "pEp_internal.h"
   219             #include "«@name»_event.h"
   220             #include "«yml:lcase(@name)»_codec.h"
   221             #include "baseprotocol.h"
   222             `` for "fsm" | #include "«@name»_fsm.h"
   223 
   224             `` apply "fsm", 0, mode=timeout
   225             PEP_STATUS «@name»_driver(
   226                     PEP_SESSION session,
   227                     «@name»_PR fsm,
   228                     int event
   229                 )
   230             {
   231                 assert(session);
   232                 if (!session)
   233                     return PEP_ILLEGAL_VALUE;
   234 
   235                 switch (fsm) {
   236                     case None:
   237                         if (!event) {
   238                             // timeout occured
   239                         `` for "fsm" |>>>> «../@name»_driver(session, «../@name»_PR_«yml:lcase(@name)», None);
   240                             return PEP_STATUS_OK;
   241                         }
   242                         return PEP_ILLEGAL_VALUE;
   243 
   244                     `` apply "fsm", mode=reset_state_machine;
   245                     default:
   246                         return PEP_ILLEGAL_VALUE;
   247                 }
   248 
   249                 int next_state = None;
   250                 do {
   251                     switch (fsm) {
   252                         `` apply "fsm", 3, mode=driver               
   253                         default:
   254                             return PEP_ILLEGAL_VALUE;
   255                     }
   256                 }  while (next_state);
   257 
   258                 return PEP_STATUS_OK;
   259             }
   260 
   261             PEP_STATUS signal_«@name»_event(
   262                     PEP_SESSION session, 
   263                     «@name»_PR fsm,
   264                     int event
   265                 )
   266             {
   267                 «@name»_t *msg = NULL;
   268                 «@name»_event_t *ev = NULL;
   269 
   270                 assert(session && fsm > 0 && event > None);
   271                 if (!(session && fsm > 0 && event > None))
   272                     return PEP_ILLEGAL_VALUE;
   273 
   274                 PEP_STATUS status = PEP_STATUS_OK;
   275 
   276                 if (!session->inject_«yml:lcase(@name)»_event)
   277                    return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK;
   278 
   279                 if (event < Extra) {
   280                     msg = new_«@name»_message(fsm, event);
   281                     if (!msg) {
   282                         status = PEP_OUT_OF_MEMORY;
   283                         goto the_end;
   284                     }
   285 
   286                     status = update_«@name»_message(session, msg);
   287                     if (status)
   288                         goto the_end;
   289                 }
   290 
   291                 ev = new_«@name»_event(fsm, event, msg);
   292                 if (!ev) {
   293                     status = PEP_OUT_OF_MEMORY;
   294                     goto the_end;
   295                 }
   296 
   297                 int result = session->inject_«yml:lcase(@name)»_event(ev,
   298                         session->«yml:lcase(@name)»_management);
   299                 if (result) {
   300                     status = PEP_STATEMACHINE_ERROR;
   301                     goto the_end;
   302                 }
   303                 return PEP_STATUS_OK;
   304 
   305             the_end:
   306                 free_«@name»_event(ev); // msg gets freed here
   307                 return status;
   308             }
   309 
   310             PEP_STATUS signal_«@name»_message(
   311                     PEP_SESSION session, 
   312                     PEP_rating rating,
   313                     const char *data,
   314                     size_t size,
   315                     const pEp_identity *from,
   316                     const char *signature_fpr
   317                 )
   318             {
   319                 assert(session && data && size);
   320                 if (!(session && data && size))
   321                     return PEP_ILLEGAL_VALUE;
   322 
   323                 if (!session->inject_«yml:lcase(@name)»_event)
   324                    return PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK;
   325 
   326                 PEP_STATUS status = PEP_STATUS_OK;
   327                 «@name»_event_t *ev = NULL;
   328 
   329                 if (from) {
   330                     free_identity(session->«yml:lcase(@name)»_state.common.from);
   331                     session->«yml:lcase(@name)»_state.common.from = identity_dup(from);
   332                     if (!session->«yml:lcase(@name)»_state.common.from) {
   333                         status = PEP_OUT_OF_MEMORY;
   334                         goto the_end;
   335                     }
   336                 }
   337 
   338                 if (signature_fpr) {
   339                     free(session->«yml:lcase(@name)»_state.common.signature_fpr);
   340                     session->«yml:lcase(@name)»_state.common.signature_fpr = strdup(signature_fpr);
   341                     if (!session->«yml:lcase(@name)»_state.common.signature_fpr) {
   342                         status = PEP_OUT_OF_MEMORY;
   343                         goto the_end;
   344                     }
   345                 }
   346 
   347                 «@name»_t *msg = NULL;
   348                 status = decode_«@name»_message(data, size, &msg);
   349                 if (status)
   350                     return status;
   351 
   352                 «@name»_PR fsm = msg->present;
   353                 int event = 0;
   354 
   355                 switch (fsm) {
   356                     `` apply "fsm", 2, mode=signal_message
   357                     default:
   358                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   359                         goto the_end;
   360                 }
   361 
   362                 ev = new_«@name»_event(fsm, event, msg);
   363                 if (!ev) {
   364                     status = PEP_OUT_OF_MEMORY;
   365                     goto the_end;
   366                 }
   367 
   368                 int result = session->inject_«yml:lcase(@name)»_event(ev,
   369                         session->«yml:lcase(@name)»_management);
   370                 if (result) {
   371                     status = PEP_STATEMACHINE_ERROR;
   372                     goto the_end;
   373                 }
   374 
   375                 return PEP_STATUS_OK;
   376 
   377             the_end:
   378                 free_«@name»_event(ev); // msg gets freed here
   379                 return status;
   380             }
   381 
   382             PEP_STATUS send_«@name»_message(
   383                     PEP_SESSION session, 
   384                     «@name»_PR fsm,
   385                     int message_type
   386                 )
   387             {
   388                 PEP_STATUS status = PEP_STATUS_OK;
   389 
   390                 assert(session && fsm > None && message_type > None);
   391                 if (!(session && fsm > None && message_type > None))
   392                     return PEP_ILLEGAL_VALUE;
   393                 
   394                 «@name»_t *msg = new_«@name»_message(fsm, message_type);
   395                 if (!msg)
   396                     return PEP_OUT_OF_MEMORY;
   397 
   398                 char *data = NULL;
   399                 message *m = NULL;
   400                 identity_list *channels = NULL;
   401 
   402                 status = update_«@name»_message(session, msg);
   403                 if (status)
   404                     goto the_end;
   405 
   406                 size_t size = 0;
   407                 status = encode_«@name»_message(msg, &data, &size);
   408                 if (status)
   409                     goto the_end;
   410 
   411                 switch (message_type) {
   412                     // these messages are being broadcasted
   413                     `` for "fsm/message[@type='broadcast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   414                         status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
   415                         if (status)
   416                             goto the_end;
   417 
   418                         if (!(channels && channels->ident)) {
   419                             // status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
   420                             // we don't check for having a channel, because if
   421                             // this is initial setup before having an own
   422                             // identity we're fine
   423                             goto the_end;
   424                         }
   425                         break;
   426 
   427                     // these go anycast; previously used address is sticky (unicast)
   428                     `` for "fsm/message[@type='anycast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   429                         if (!session->«yml:lcase(@name)»_state.common.from `> |`|
   430                             (session->«yml:lcase(@name)»_state.common.from->flags &
   431                             PEP_idf_not_for_«yml:lcase(@name)»)) {
   432 
   433                             // no address available yet, try to find one
   434                             status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
   435                             if (!status)
   436                                 goto the_end;
   437                             break;
   438 
   439                             if (channels && channels->ident) {
   440                                 // only need the first one
   441                                 free_identity_list(channels->next);
   442                                 channels->next = NULL;
   443                             }
   444                             else {
   445                                 status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
   446                                 goto the_end;
   447                             }
   448                         }
   449                         else {
   450                             pEp_identity *channel = identity_dup(session->«yml:lcase(@name)»_state.common.from);
   451                             if (!channel) {
   452                                 status = PEP_OUT_OF_MEMORY;
   453                                 goto the_end;
   454                             }
   455 
   456                             channels = new_identity_list(channel);
   457                             if (!channels) {
   458                                 status = PEP_OUT_OF_MEMORY;
   459                                 goto the_end;
   460                             }
   461                         }
   462                         break;
   463 
   464                     default:
   465                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   466                         goto the_end;
   467                 }
   468 
   469                 for (identity_list *li = channels; li && li->ident ; li = li->next) {
   470                     message *_m = NULL;
   471                     char *_data = NULL;
   472                     
   473                     _data = malloc(size);
   474                     assert(_data);
   475                     if (!_data) {
   476                         status = PEP_OUT_OF_MEMORY;
   477                         goto the_end;
   478                     }
   479                     memcpy(_data, data, size);
   480 
   481                     switch (message_type) {
   482                     `` for "fsm/message[@security='unencrypted']" |>>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   483                             status = base_prepare_message(
   484                                     session,
   485                                     li->ident,
   486                                     li->ident,
   487                                     _data,
   488                                     size,
   489                                     li->ident->fpr,
   490                                     NULL,
   491                                     &_m
   492                                 );
   493                             if (status) {
   494                                 free(_data);
   495                                 goto the_end;
   496                             }
   497                             attach_own_key(session, _m);
   498                             m = _m;
   499                             break;
   500 
   501                     `` for "fsm/message[@security='attach_own_keys']" |>>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   502                             status = base_prepare_message(
   503                                     session,
   504                                     li->ident,
   505                                     li->ident,
   506                                     _data,
   507                                     size,
   508                                     NULL,
   509                                     &session->«yml:lcase(@name)»_state.common.own_keys,
   510                                     &_m
   511                                 );
   512                             if (status) {
   513                                 free(_data);
   514                                 goto the_end;
   515                             }
   516                             status = encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
   517                             if (status) {
   518                                 status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
   519                                 goto the_end;
   520                             }
   521                             free_message(_m);
   522                             break;
   523 
   524                         default:
   525                             status = base_prepare_message(
   526                                     session,
   527                                     li->ident,
   528                                     li->ident,
   529                                     _data,
   530                                     size,
   531                                     NULL,
   532                                     NULL,
   533                                     &_m
   534                                 );
   535                             if (status) {
   536                                 free(_data);
   537                                 goto the_end;
   538                             }
   539                             status = encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
   540                             if (status) {
   541                                 status = PEP_«yml:ucase(@name)»_CANNOT_ENCRYPT;
   542                                 goto the_end;
   543                             }
   544                             free_message(_m);
   545                     }
   546 
   547                     status = session->messageToSend(m);
   548                     m = NULL;
   549                 }
   550 
   551             the_end:
   552                 free_identity_list(channels);
   553                 free_message(m);
   554                 free(data);
   555                 free_«@name»_message(msg);
   556                 return status;
   557             }
   558 
   559             PEP_STATUS recv_«@name»_event(
   560                     PEP_SESSION session,
   561                     «@name»_event_t *ev
   562                 )
   563             {
   564                 assert(session && ev);
   565                 if (!(session && ev))
   566                     return PEP_ILLEGAL_VALUE;
   567 
   568                 PEP_STATUS status = PEP_STATUS_OK;
   569                 «@name»_PR fsm = (int) None;
   570                 int event = None;
   571 
   572                 if (ev->event > None && ev->event < Extra) {
   573                     status = update_«@name»_state(session, ev->msg, &fsm, &event);
   574                     if (status)
   575                         goto the_end;
   576 
   577                     if (ev->fsm) {
   578                         if (ev->fsm != fsm |`> |` ev->event != event) {
   579                             status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   580                             goto the_end;
   581                         }
   582                     }
   583                     else if (ev->event) {
   584                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   585                         goto the_end;
   586                     }
   587                 }
   588                 else {
   589                     fsm = ev->fsm;
   590                     event = ev->event;
   591                 }
   592 
   593                 status = «@name»_driver(session, fsm, event);
   594 
   595             the_end:
   596                 //free_«@name»_event(ev); // FIXME: We don't own this pointer. Are we sure it gets freed externally?
   597                 return status;
   598             }
   599 
   600             ||
   601         }
   602 
   603         apply "fsm", 0, mode=gen;
   604     }
   605 
   606     template "fsm", mode=timeout
   607     ||
   608     static bool _«@name»_timeout(int state)
   609     {
   610         static int last_state = None;
   611         static time_t switch_time = 0;
   612 
   613         if (state > Init) {
   614             if (state == last_state) {
   615                 if (time(NULL) - switch_time > «yml:ucase(@name)»_THRESHOLD) {
   616                     last_state = None;
   617                     switch_time = 0;
   618                     return true;
   619                 }
   620             }
   621             else {
   622                 last_state = state;
   623                 switch_time = time(NULL);
   624             }
   625         }
   626         else {
   627             last_state = None;
   628             switch_time = 0;
   629         }
   630 
   631         return false;
   632     }
   633 
   634     ||
   635 
   636     template "fsm", mode=reset_state_machine
   637     ||
   638         case «../@name»_PR_«yml:lcase(@name)»: {
   639             int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state;
   640             switch (state) {
   641                 `` for "state[@name!='InitState' and @timeout != 'off']" |>>> case «@name»:
   642                     if (_«@name»_timeout(state)) {
   643                         session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = Init;
   644                         event = Init;
   645                         `` if "@threshold > 0" |>>>>> «@name»TimeoutHandler(session);
   646                     }
   647                     break;
   648                 
   649                 default:
   650                     _«@name»_timeout(None);
   651             }
   652             break;
   653         }
   654 
   655     ||
   656 
   657     template "fsm", mode=signal_message
   658     {
   659         ||
   660         case «../@name»_PR_«yml:lcase(@name)»:
   661             switch (msg->choice.«yml:lcase(@name)».payload.present) {
   662         ||
   663         if "message[@security='unencrypted']" {
   664             |>> // these messages require a detached signature
   665             for "message[@security='unencrypted']" {
   666             ||
   667                     case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   668                         if (!signature_fpr) {
   669                             status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
   670                             goto the_end;
   671                         }
   672                         event = «@name»;
   673                         break;
   674 
   675             ||
   676             }
   677         }
   678         if "message[@security='untrusted']" {
   679             |>> // these messages must arrive encrypted
   680             for "message[@security='untrusted']" {
   681             ||
   682                     case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   683                         if (rating < PEP_rating_reliable) {
   684                             status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
   685                             goto the_end;
   686                         }
   687                         event = «@name»;
   688                         break;
   689 
   690             ||
   691             }
   692         }
   693         if "message[@security='trusted']" {
   694             |>> // these messages must come through a trusted channel
   695             for "message[@security='trusted']" {
   696             ||
   697                     case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   698                         if (rating < PEP_rating_trusted) {
   699                             status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
   700                             goto the_end;
   701                         }
   702                         event = «@name»;
   703                         break;
   704 
   705             ||
   706             }
   707         }
   708         ||
   709                 default:
   710                     status = PEP_«yml:ucase(ancestor::protocol/@name)»_ILLEGAL_MESSAGE;
   711                     goto the_end;
   712             }
   713             break;
   714 
   715         ||
   716     }
   717 
   718     template "fsm", mode=event
   719     {
   720     ||
   721     case «../@name»_PR_«yml:lcase(@name)»: {
   722         switch (msg->choice.«yml:lcase(@name)».payload.present) {
   723     ||
   724     for "message"
   725     ||
   726             case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   727                 ev->event = «@name»;
   728                 break;
   729     ||
   730     ||
   731             default:
   732                 // unknown message type
   733                 free(ev);
   734                 return NULL;
   735         }
   736         break;
   737     }
   738 
   739     ||
   740     }
   741 
   742     template "fsm", mode=driver
   743     ||
   744     case «../@name»_PR_«yml:lcase(@name)»: {
   745         int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state;
   746         next_state = fsm_«@name»(session, state, event);
   747         if (next_state > None) {
   748             session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = next_state;
   749             event = Init;
   750         }
   751         else if (next_state < None) {
   752             return PEP_STATEMACHINE_ERROR - next_state;
   753         }
   754         break;
   755     }
   756 
   757     ||
   758 
   759     template "fsm", mode=gen {
   760         document "generated/{@name}_fsm.h", "text" {
   761         ||
   762         // This file is under GNU General Public License 3.0
   763         // see LICENSE.txt
   764 
   765         #pragma once
   766 
   767         #include "«../@name»_impl.h"
   768 
   769         #ifdef __cplusplus
   770         extern "C" {
   771         #endif
   772 
   773         // state machine for «@name»
   774 
   775         // states
   776 
   777         typedef enum _«@name»_state {
   778             «@name»_state_None = None,
   779             «@name»_state_Init = Init,
   780         ||
   781         for "func:distinctName(state[not(@name='InitState')])"
   782             |> «@name»`if "position()!=last()" > , `
   783         ||
   784         } «@name»_state;
   785 
   786         // events
   787 
   788         typedef enum _«@name»_event {
   789             «@name»_event_None = None,
   790             «@name»_event_Init = Init,
   791         ||
   792         for "message" {
   793             const "name", "@name";
   794             |> «$name» = «/protocol/fsm/message[@name=$name]/@id»,
   795         }
   796         |> «@name»_event_Extra = Extra,
   797         for "external" {
   798             if "@id < 128"
   799                 error > external «@name» must have ID >= 128 but it's «@id»
   800             |> «@name» = «@id»,
   801         }
   802         for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" {
   803             if "@name!='Init'"
   804                 |> «@name»`if "position()!=last()" > , `
   805         }
   806         ||
   807         } «@name»_event;
   808 
   809         // state machine
   810 
   811         const char *«@name»_state_name(int state);
   812         const char *«@name»_event_name(int event);
   813 
   814         // the state machine function is returning the next state in case of a
   815         // transition or None for staying
   816 
   817         «@name»_state fsm_«@name»(
   818                 PEP_SESSION session,
   819                 «@name»_state state,
   820                 «@name»_event event
   821             );
   822 
   823         #ifdef __cplusplus
   824         }
   825         #endif
   826 
   827         ||
   828         }
   829 
   830         document "generated/{@name}_fsm.c", "text" {
   831         ||
   832         // This file is under GNU General Public License 3.0
   833         // see LICENSE.txt
   834 
   835         #include "«@name»_fsm.h"
   836         #include <stdlib.h>
   837 
   838         const char *«@name»_state_name(int state)
   839         {
   840             switch (state) {
   841                 case End:
   842                     return "End";
   843                 case None:
   844                     return "None";
   845                 case Init:
   846                     return "InitState";
   847         ||
   848         for "func:distinctName(state[not(@name='InitState')])" {
   849             |>> case «@name»:
   850             |>>> return "«@name»";
   851         }
   852         ||
   853                 default:
   854                     return "unknown state";
   855             }
   856         }
   857 
   858         const char *«@name»_event_name(int event)
   859         {
   860             switch (event) {
   861                 case None:
   862                     return "None";
   863                 case Init:
   864                     return "Init";
   865         ||
   866         for "func:distinctName(state/event[not(@name='Init')])" {
   867             |>> case «@name»:
   868             |>>> return "«@name»";
   869         }
   870         ||
   871                 default:
   872                     return "unknown event";
   873             }
   874         }
   875 
   876 
   877         static char *_str(int n, bool hex)
   878         {
   879             char *buf = calloc(1, 24);
   880             assert(buf);
   881             if (!buf)
   882                 return NULL;
   883 
   884             if (hex)
   885                 snprintf(buf, 24, "%.4x", n);
   886             else
   887                 snprintf(buf, 24, "%d", n);
   888             return buf;
   889         }
   890 
   891         #define «@name»_ERR_LOG(t, d) log_event(session, (t), "«@name»", (d), "error")
   892 
   893         static PEP_STATUS _«@name»_ERR_LOG_int(PEP_SESSION session, char *t, int n, bool hex)
   894         {
   895             char *_buf = _str(n, hex);
   896             if (!_buf)
   897                 return PEP_OUT_OF_MEMORY;
   898             PEP_STATUS status = «@name»_ERR_LOG(t, _buf);
   899             free(_buf);
   900             return status;
   901         }
   902 
   903         #define «@name»_ERR_LOG_INT(t, n) _«@name»_ERR_LOG_int(session, (t), (n), false)
   904         #define «@name»_ERR_LOG_HEX(t, n) _«@name»_ERR_LOG_int(session, (t), (n), true)
   905         #define «@name»_SERVICE_LOG(t, d) SERVICE_LOG(session, (t), "«@name»", (d))
   906 
   907         «@name»_state fsm_«@name»(
   908                 PEP_SESSION session,
   909                 «@name»_state state,
   910                 «@name»_event event
   911             )
   912         {
   913             assert(session);
   914             if (!session)
   915                 return invalid_state;
   916 
   917             if (state == None)
   918                 state = «@name»_state_Init;
   919 
   920             switch (state) {
   921                 `` apply "state", 2, mode=fsm
   922                 default:
   923                     «@name»_ERR_LOG_INT("invalid state", state);
   924                     return invalid_state;
   925             }
   926             
   927             return None;
   928         }
   929 
   930         ||
   931         }
   932     }
   933     
   934     template "state", mode=fsm {
   935         choose {
   936             when "@name='InitState'" | case «../@name»_state_Init:
   937             otherwise | case «@name»:
   938         }
   939         ||
   940             «../@name»_SERVICE_LOG("in state", "«@name»");
   941 
   942             switch (event) {
   943                 case None:
   944                     «../@name»_SERVICE_LOG("received None event", "ignoring");
   945                     break;
   946      
   947         ||
   948         if "not(event[@name='Init'])"
   949         ||
   950                 case Init:
   951                     «../@name»_SERVICE_LOG("received Init but nothing to do", "Init");
   952                     break;
   953 
   954         ||
   955         ||
   956                 `` apply "event", 2, mode=fsm
   957                 default:
   958                     // ignore events not handled here
   959                     «../@name»_SERVICE_LOG("ignoring event", KeySync_event_name(event));
   960                     return invalid_event;
   961             }
   962             break;
   963 
   964         ||
   965     }
   966 
   967     template "event", mode=fsm {
   968         | case «@name»: {
   969         if "condition|action|send" |> PEP_STATUS status;
   970         if "condition" |> bool result = false;
   971         if "condition|action|send" |
   972         ||
   973             «../../@name»_SERVICE_LOG("received event", "«@name»");
   974             `` apply "transition|action|condition|else|send";
   975         ||
   976         if "name(*[last()])!='transition'" {
   977             |
   978             |> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»");
   979             |> break;
   980         }
   981         ||
   982         }
   983         
   984         ||
   985     }
   986 
   987     template "transition" {
   988         const "fsm", "ancestor::fsm";
   989         ||
   990 
   991         «$fsm/@name»_SERVICE_LOG("transition to state", "«@target»");
   992         return «@target»;
   993         ||
   994     }
   995 
   996     template "send" {
   997         const "fsm", "ancestor::fsm";
   998         const "protocol", "ancestor::protocol";
   999         ||
  1000 
  1001         «$fsm/@name»_SERVICE_LOG("send message", "«@name»");
  1002         status = send_«$protocol/@name»_message(session, «$fsm/@id», «$fsm/@name»__payload_PR_«yml:mixedCase(@name)»);
  1003         if (status == PEP_OUT_OF_MEMORY)
  1004             return out_of_memory;
  1005         if (status) {
  1006             «$fsm/@name»_ERR_LOG_HEX("sending «@name» failed", status);
  1007             return cannot_send;
  1008         }
  1009         ||
  1010     }
  1011 
  1012     template "action" {
  1013         const "fsm", "ancestor::fsm";
  1014         ||
  1015 
  1016         «$fsm/@name»_SERVICE_LOG("do action", "«@name»");
  1017         status = «@name»(session);
  1018         if (status == PEP_OUT_OF_MEMORY)
  1019             return out_of_memory;
  1020         if (status) {
  1021             «$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status);
  1022             return invalid_action;
  1023         }
  1024         ||
  1025     }
  1026 
  1027     template "condition" {
  1028         const "fsm", "ancestor::fsm";
  1029         ||
  1030 
  1031         status = «@name»(session, &result);
  1032         if (status == PEP_OUT_OF_MEMORY)
  1033             return out_of_memory;
  1034         if (status) {
  1035             «$fsm/@name»_ERR_LOG_HEX("computing condition «@name» failed", status);
  1036             return invalid_condition;
  1037         }
  1038         if (result) {
  1039             «$fsm/@name»_SERVICE_LOG("condition applies", "«@name»");
  1040         ||
  1041         apply "transition|action|condition|else|send";
  1042         | }
  1043     }
  1044 
  1045     template "else" {
  1046         if "not(name(preceding-sibling::*[last()]) = 'condition')"
  1047             error "else without if";
  1048 
  1049         | else {
  1050         |> «ancestor::fsm/@name»_SERVICE_LOG("condition does not apply", "«preceding-sibling::*[last()]/@name»");
  1051         apply "transition|action|condition|else|send";
  1052         | }
  1053     }
  1054 }