sync/gen_statemachine.ysl2
author Volker Birk <vb@pep.foundation>
Thu, 23 Aug 2018 10:24:49 +0200
branchsync
changeset 2880 fa5b054aa4b6
parent 2879 1b90ffed2d89
child 2881 26b451252f73
permissions -rw-r--r--
trigger KeyGen
     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 - 2018, 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         #include "«@name».h"
    31 
    32 
    33         typedef struct «@name»_event {
    34             «@name»_PR fsm;
    35             int event;
    36             «@name»_t *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         DYNAMIC_API «@name»_event_t *new_«@name»_event(«@name»_PR fsm, int event, «@name»_t *msg);
    55 
    56 
    57         // free_«@name»_event() - free memory occupied by event
    58         //
    59         //  parameters:
    60         //      ev (in)         event to free
    61 
    62         DYNAMIC_API void free_«@name»_event(«@name»_event_t *ev);
    63 
    64 
    65         #ifdef __cplusplus
    66         }
    67         #endif
    68 
    69         ||
    70 
    71         document "generated/{@name}_event.c", "text"
    72         ||
    73         // This file is under GNU General Public License 3.0
    74         // see LICENSE.txt
    75 
    76         #include "pEp_internal.h"
    77         #include "«@name»_event.h"
    78         #include "«@name»_func.h"
    79         `` for "fsm" | #include "«@name»_fsm.h"
    80 
    81         DYNAMIC_API «@name»_event_t *new_«@name»_event(«@name»_PR fsm, int event, «@name»_t *msg)
    82         {
    83             assert(fsm > 0 && (event >= 0 |`> |` msg));
    84             if (!(fsm > 0 && (event >= 0 |`> |` msg)))
    85                 return NULL;
    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         DYNAMIC_API 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 "message_api.h"
   128             #include "«@name»_event.h"
   129             
   130             #ifdef __cplusplus
   131             extern "C" {
   132             #endif
   133 
   134             // conditions
   135 
   136             ||
   137             for "func:distinctName(*//condition)"
   138                 | PEP_STATUS «@name»(PEP_SESSION session, bool *result);
   139             ||
   140 
   141             // actions
   142 
   143             ||
   144             for "func:distinctName(*//action)"
   145                 | PEP_STATUS «@name»(PEP_SESSION session);
   146             ||
   147 
   148             // notify state machine from event
   149             // use state to generate «@name» message if necessary
   150 
   151             PEP_STATUS «@name»_notify(
   152                     PEP_SESSION session, 
   153                     «@name»_PR fsm,
   154                     int message_type
   155                 );
   156 
   157             // send message about an event to communication partners using state
   158 
   159             PEP_STATUS send_«@name»_message(
   160                     PEP_SESSION session, 
   161                     «@name»_PR fsm,
   162                     int message_type
   163                 );
   164 
   165             // receive message and store it in state
   166 
   167             PEP_STATUS recv_«@name»_event(
   168                     PEP_SESSION session,
   169                     «@name»_event_t *ev
   170                 );
   171         
   172             // state machine driver
   173             // if fsm or event set to 0 use fields in src if present
   174 
   175             PEP_STATUS «@name»_driver(
   176                     PEP_SESSION session,
   177                     «@name»_PR fsm,
   178                     int event
   179                 );
   180 
   181             PEP_STATUS signal_«@name»_event(
   182                     PEP_SESSION session, 
   183                     «@name»_PR fsm,
   184                     int event
   185                 );
   186 
   187 
   188             #ifdef __cplusplus
   189             }
   190             #endif
   191 
   192             ||
   193         }
   194 
   195         document "generated/{@name}_impl.c", "text" {
   196             ||
   197             // This file is under GNU General Public License 3.0
   198             // see LICENSE.txt
   199         
   200             #include "«@name»_impl.h"
   201             #include "pEp_internal.h"
   202             #include "«@name»_event.h"
   203             #include "«@name»_codec.h"
   204             #include "baseprotocol.h"
   205             `` for "fsm" | #include "«@name»_fsm.h"
   206 
   207             PEP_STATUS «@name»_driver(
   208                     PEP_SESSION session,
   209                     «@name»_PR fsm,
   210                     int event
   211                 )
   212             {
   213                 assert(session && fsm);
   214                 if (!(session && fsm))
   215                     return PEP_ILLEGAL_VALUE;
   216 
   217                 int next_state = None;
   218                 do {
   219                     switch (fsm) {
   220                         `` apply "fsm", 3, mode=driver               
   221                         default:
   222                             return PEP_ILLEGAL_VALUE;
   223                     }
   224                 }  while (next_state);
   225 
   226                 return PEP_STATUS_OK;
   227             }
   228 
   229             PEP_STATUS signal_«@name»_event(
   230                     PEP_SESSION session, 
   231                     «@name»_PR fsm,
   232                     int event
   233                 )
   234             {
   235                 «@name»_t *msg = NULL;
   236                 «@name»_event_t *ev = NULL;
   237 
   238                 assert(session && fsm > 0 && event > None);
   239                 if (!(session && fsm > 0 && event > None))
   240                     return PEP_ILLEGAL_VALUE;
   241 
   242                 PEP_STATUS status = PEP_STATUS_OK;
   243 
   244                 if (!session->inject_«yml:lcase(@name)»_event) {
   245                    status = PEP_«yml:ucase(@name)»_NO_INJECT_CALLBACK;
   246                    goto error;
   247                 }
   248 
   249                 if (event < Extra) {
   250                     msg = new_«@name»_message(fsm, event);
   251                     if (!msg) {
   252                         status = PEP_OUT_OF_MEMORY;
   253                         goto error;
   254                     }
   255 
   256                     status = update_«@name»_message(session, msg);
   257                     if (status)
   258                         goto error;
   259                 }
   260 
   261                 ev = («@name»_event_t *) calloc(1, sizeof(«@name»_event_t));
   262                 assert(ev);
   263                 if (!ev) {
   264                     status = PEP_OUT_OF_MEMORY;
   265                     goto error;
   266                 }
   267                 
   268                 ev->fsm = fsm;
   269                 ev->event = event;
   270                 ev->msg = msg;
   271 
   272                 int result = session->inject_«yml:lcase(@name)»_event(ev,
   273                         session->«yml:lcase(@name)»_management);
   274                 if (result) {
   275                     status = PEP_STATEMACHINE_ERROR;
   276                     goto error;
   277                 }
   278 
   279                 goto the_end;
   280 
   281             error:
   282                 free(ev);
   283                 free_«@name»_message(msg);
   284 
   285             the_end:
   286                 return status;
   287             }
   288 
   289             PEP_STATUS «@name»_notify(
   290                     PEP_SESSION session, 
   291                     «@name»_PR fsm,
   292                     int message_type
   293                 )
   294             {
   295                 assert(session && fsm > 0 && message_type > 1 && message_type < Extra);
   296                 if (!(session && fsm > 0 && message_type > 1 && message_type < Extra))
   297                     return PEP_ILLEGAL_VALUE;
   298 
   299                 PEP_STATUS status = PEP_STATUS_OK;
   300 
   301                 «@name»_t *msg = new_«@name»_message(fsm, message_type);
   302                 if (!msg) {
   303                     status = PEP_OUT_OF_MEMORY;
   304                     goto error;
   305                 }
   306 
   307                 status = update_«@name»_message(session, msg);
   308                 if (status)
   309                     goto error;
   310 
   311                 goto the_end;
   312 
   313             error:
   314                 free_«@name»_message(msg);
   315 
   316             the_end:
   317                 return status;
   318             }
   319 
   320             PEP_STATUS send_«@name»_message(
   321                     PEP_SESSION session, 
   322                     «@name»_PR fsm,
   323                     int message_type
   324                 )
   325             {
   326                 PEP_STATUS status = PEP_STATUS_OK;
   327 
   328                 assert(session && fsm > None && message_type > None);
   329                 if (!(session && fsm > None && message_type > None))
   330                     return PEP_ILLEGAL_VALUE;
   331                 
   332                 «@name»_t *msg = new_«@name»_message(fsm, message_type);
   333                 if (!msg)
   334                     return PEP_OUT_OF_MEMORY;
   335 
   336                 char *data = NULL;
   337                 message *m = NULL;
   338                 identity_list *channels = NULL;
   339 
   340                 status = update_«@name»_message(session, msg);
   341                 if (status)
   342                     goto the_end;
   343 
   344                 size_t size = 0;
   345                 status = encode_«@name»_message(msg, &data, &size);
   346                 if (status)
   347                     goto the_end;
   348 
   349                 switch (message_type) {
   350                     // these messages are being broadcasted
   351                     `` for "fsm/message[@type='broadcast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   352                         status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
   353                         if (status)
   354                             goto the_end;
   355 
   356                         if (!(channels && channels->ident)) {
   357                             status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
   358                             goto the_end;
   359                         }
   360                         break;
   361 
   362                     // these go anycast; previously used address is sticky (unicast)
   363                     `` for "fsm/message[@type='anycast']" |>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   364                         if (!session->«yml:lcase(@name)»_state.common.from `> |`|
   365                             (session->«yml:lcase(@name)»_state.common.from->flags &
   366                             PEP_idf_not_for_«yml:lcase(@name)»)) {
   367 
   368                             // no address available yet, try to find one
   369                             status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_«yml:lcase(@name)»);
   370                             if (!status)
   371                                 goto the_end;
   372                             break;
   373 
   374                             if (channels && channels->ident) {
   375                                 // only need the first one
   376                                 free_identity_list(channels->next);
   377                                 channels->next = NULL;
   378                             }
   379                             else {
   380                                 status = PEP_«yml:ucase(@name)»_NO_CHANNEL;
   381                                 goto the_end;
   382                             }
   383                         }
   384                         else {
   385                             pEp_identity *channel = identity_dup(session->«yml:lcase(@name)»_state.common.from);
   386                             if (!channel) {
   387                                 status = PEP_OUT_OF_MEMORY;
   388                                 goto the_end;
   389                             }
   390 
   391                             channels = new_identity_list(channel);
   392                             if (!channels) {
   393                                 status = PEP_OUT_OF_MEMORY;
   394                                 goto the_end;
   395                             }
   396                         }
   397 
   398                     default:
   399                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   400                         goto the_end;
   401                 }
   402 
   403                 for (identity_list *li = channels; li && li->ident ; li = li->next) {
   404                     message *_m = NULL;
   405 
   406                     status = base_prepare_message(
   407                             li->ident,
   408                             li->ident,
   409                             data,
   410                             size,
   411                             &_m
   412                         );
   413                     if (status)
   414                         goto the_end;
   415                     data = NULL;
   416 
   417                     switch (message_type) {
   418                     `` for "fsm/message[@security='unencrypted']" |>>> case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   419                             m = _m;
   420                             break;
   421 
   422                         default:
   423                             status = encrypt_message(session, _m, NULL, &m, PEP_enc_PEP, 0);
   424                             if (status) {
   425                                 status = PEP_SYNC_CANNOT_ENCRYPT;
   426                                 goto the_end;
   427                             }
   428                             free_message(_m);
   429                     }
   430 
   431                     status = session->messageToSend(session->«yml:lcase(@name)»_obj, m);
   432                     m = NULL;
   433                 }
   434 
   435             the_end:
   436                 free_identity_list(channels);
   437                 free_message(m);
   438                 free(data);
   439                 free_«@name»_message(msg);
   440                 return status;
   441             }
   442 
   443             PEP_STATUS recv_«@name»_event(
   444                     PEP_SESSION session,
   445                     «@name»_event_t *ev
   446                 )
   447             {
   448                 assert(session && ev);
   449                 if (!(session && ev))
   450                     return PEP_ILLEGAL_VALUE;
   451 
   452                 PEP_STATUS status = PEP_STATUS_OK;
   453 
   454                 if (ev->event < Extra) {
   455                     «@name»_PR fsm = (int) None;
   456                     int event = None;
   457 
   458                     status = update_«@name»_state(session, ev->msg, &fsm, &event);
   459                     if (status)
   460                         goto error;
   461 
   462                     if (ev->fsm) {
   463                         if (ev->fsm != fsm |`> |` ev->event != event) {
   464                             status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   465                             goto error;
   466                         }
   467                     }
   468                     else {
   469                         if (ev->event) {
   470                             status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   471                             goto error;
   472                         }
   473                         ev->fsm = fsm;
   474                         ev->event = event;
   475                     }
   476                 }
   477 
   478                 free_«@name»_message(ev->msg);
   479                 free(ev);
   480                 status = «@name»_driver(session, ev->fsm, ev->event);
   481                 return status;
   482 
   483             error:
   484                 free_«@name»_message(ev->msg);
   485                 free(ev);
   486                 return status;
   487             }
   488 
   489             ||
   490         }
   491 
   492         apply "fsm", 0, mode=gen;
   493     }
   494 
   495     template "fsm", mode=event
   496     {
   497     ||
   498     case «../@name»_PR_«yml:lcase(@name)»: {
   499         switch (msg->choice.«yml:lcase(@name)».payload.present) {
   500     ||
   501     for "message"
   502     ||
   503             case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   504                 ev->event = «@name»;
   505                 break;
   506     ||
   507     ||
   508             default:
   509                 // unknown message type
   510                 free(ev);
   511                 return NULL;
   512         }
   513         break;
   514     }
   515 
   516     ||
   517     }
   518 
   519     template "fsm", mode=driver
   520     ||
   521     case «../@name»_PR_«yml:lcase(@name)»: {
   522         int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state;
   523         next_state = fsm_«@name»(session, state, event);
   524         if (next_state > None) {
   525             session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = next_state;
   526             event = Init;
   527         }
   528         else if (next_state < None) {
   529             return PEP_STATEMACHINE_ERROR - state;
   530         }
   531         break;
   532     }
   533 
   534     ||
   535 
   536     template "fsm", mode=gen {
   537         document "generated/{@name}_fsm.h", "text" {
   538         ||
   539         // This file is under GNU General Public License 3.0
   540         // see LICENSE.txt
   541 
   542         #pragma once
   543 
   544         #include "«../@name»_impl.h"
   545 
   546         #ifdef __cplusplus
   547         extern "C" {
   548         #endif
   549 
   550         // state machine for «@name»
   551 
   552         // states
   553 
   554         typedef enum _«@name»_state {
   555             «@name»_state_None = None,
   556             «@name»_state_Init = Init,
   557         ||
   558         for "func:distinctName(state[not(@name='InitState')])"
   559             |> «@name»`if "position()!=last()" > , `
   560         ||
   561         } «@name»_state;
   562 
   563         // events
   564 
   565         typedef enum _«@name»_event {
   566             «@name»_event_None = None,
   567             «@name»_event_Init = Init,
   568         ||
   569         for "func:distinctName(state/event[not(not(../../message/@name=@name))])" {
   570             const "name", "@name";
   571             |> «$name» = «/protocol/fsm/message[@name=$name]/@id»,
   572         }
   573         for "func:distinctName(state/event[not(not(../../external/@name=@name))])" {
   574             const "name", "@name";
   575             |> «$name» = «/protocol/fsm/external[@name=$name]/@id»,
   576         }
   577         |> «@name»_event_Extra = Extra,
   578         for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" {
   579             if "@name!='Init'"
   580                 |> «@name»`if "position()!=last()" > , `
   581         }
   582         ||
   583         } «@name»_event;
   584 
   585         // state machine
   586 
   587         const char *«@name»_state_name(int state);
   588 
   589         // the state machine function is returning the next state in case of a
   590         // transition or None for staying
   591 
   592         «@name»_state fsm_«@name»(
   593                 PEP_SESSION session,
   594                 «@name»_state state,
   595                 «@name»_event event
   596             );
   597 
   598         #ifdef __cplusplus
   599         }
   600         #endif
   601 
   602         ||
   603         }
   604 
   605         document "generated/{@name}_fsm.c", "text" {
   606         ||
   607         // This file is under GNU General Public License 3.0
   608         // see LICENSE.txt
   609 
   610         #include "«@name»_fsm.h"
   611         #include <stdlib.h>
   612 
   613         const char *«@name»_state_name(int state)
   614         {
   615             switch (state) {
   616                 case End:
   617                     return "End";
   618                 case None:
   619                     return "None";
   620                 case Init:
   621                     return "InitState";
   622         ||
   623         for "func:distinctName(state[not(@name='InitState')])" {
   624             |>> case «@name»:
   625             |>>> return "«@name»";
   626         }
   627         ||
   628                 default:
   629                     return "unknown state";
   630             }
   631         }
   632 
   633         static char *_str(int n, bool hex)
   634         {
   635             char *buf = calloc(1, 24);
   636             assert(buf);
   637             if (!buf)
   638                 return NULL;
   639 
   640             if (hex)
   641                 snprintf(buf, 24, "%.4x", n);
   642             else
   643                 snprintf(buf, 24, "%d", n);
   644             return buf;
   645         }
   646 
   647         #define «@name»_ERR_LOG(t, d) log_event(session, (t), "«@name»", (d), "error")
   648 
   649         static PEP_STATUS _«@name»_ERR_LOG_int(PEP_SESSION session, char *t, int n, bool hex)
   650         {
   651             char *_buf = _str(n, hex);
   652             if (!_buf)
   653                 return PEP_OUT_OF_MEMORY;
   654             PEP_STATUS status = «@name»_ERR_LOG(t, _buf);
   655             free(_buf);
   656             return status;
   657         }
   658 
   659         #define «@name»_ERR_LOG_INT(t, n) _«@name»_ERR_LOG_int(session, (t), (n), false)
   660         #define «@name»_ERR_LOG_HEX(t, n) _«@name»_ERR_LOG_int(session, (t), (n), true)
   661 
   662         #ifndef SERVICE_LOG
   663         // SERVICE LOG is meant to check session->service_log in runtime config;
   664         // for older engines log more than needed
   665         #define SERVICE_LOG(session, t, n, d) log_event((session), (t), (n), (d), "service")
   666         #endif 
   667 
   668         #define «@name»_SERVICE_LOG(t, d) SERVICE_LOG(session, (t), "«@name»", (d))
   669 
   670         «@name»_state fsm_«@name»(
   671                 PEP_SESSION session,
   672                 «@name»_state state,
   673                 «@name»_event event
   674             )
   675         {
   676             assert(session);
   677             if (!session)
   678                 return invalid_state;
   679 
   680             switch (state) {
   681                 case None:
   682                     return «@name»_state_Init;
   683                 
   684                 `` apply "state", 2, mode=fsm
   685                 default:
   686                     «@name»_ERR_LOG_INT("invalid state", state);
   687                     return invalid_state;
   688             }
   689             
   690             return None;
   691         }
   692 
   693         ||
   694         }
   695     }
   696     
   697     template "state", mode=fsm {
   698         choose {
   699             when "@name='InitState'" | case «../@name»_state_Init:
   700             otherwise | case «@name»:
   701         }
   702         ||
   703             «../@name»_SERVICE_LOG("in state", "«@name»");
   704 
   705             switch (event) {
   706                 case None:
   707                     «../@name»_SERVICE_LOG("received None event", "ignoring");
   708                     break;
   709      
   710         ||
   711         if "not(event[@name='Init'])"
   712         ||
   713                 case Init:
   714                     // nothing to do
   715                     break;
   716 
   717         ||
   718         ||
   719                 `` apply "event", 2, mode=fsm
   720                 default:
   721                     «../@name»_ERR_LOG_INT("invalid event", event);
   722                     return invalid_event;
   723             }
   724             break;
   725 
   726         ||
   727     }
   728 
   729     template "event", mode=fsm {
   730         | case «@name»: {
   731         if "condition|action|send" |> PEP_STATUS status;
   732         if "condition" |> bool result = false;
   733         if "condition|action|send" |
   734         ||
   735             «../../@name»_SERVICE_LOG("received event", "«@name»");
   736             `` apply "transition|action|condition|send";
   737         ||
   738         if "name(*[last()])!='transition'" {
   739             |
   740             |> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»");
   741             |> break;
   742         }
   743         ||
   744         }
   745         
   746         ||
   747     }
   748 
   749     template "transition" {
   750         const "fsm", "ancestor::fsm";
   751         ||
   752 
   753         «$fsm/@name»_SERVICE_LOG("transition to state", "«@target»");
   754         return «@target»;
   755         ||
   756     }
   757 
   758     template "send" {
   759         const "fsm", "ancestor::fsm";
   760         const "protocol", "ancestor::protocol";
   761         ||
   762 
   763         «$fsm/@name»_SERVICE_LOG("send message", "«@name»");
   764         status = send_«$protocol/@name»_message(session, «$fsm/@id», «$fsm/@name»__payload_PR_«yml:mixedCase(@name)»);
   765         ||
   766     }
   767 
   768     template "action" {
   769         const "fsm", "ancestor::fsm";
   770         ||
   771 
   772         «$fsm/@name»_SERVICE_LOG("do action", "«@name»");
   773         status = «@name»(session);
   774         if (status) {
   775             «$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status);
   776             return invalid_action;
   777         }
   778         ||
   779     }
   780 
   781     template "condition" {
   782         const "fsm", "ancestor::fsm";
   783         ||
   784 
   785         status = «@name»(session, &result);
   786         if (status) {
   787             «$fsm/@name»_ERR_LOG_HEX("computing condition «@name» failed", status);
   788             return invalid_condition;
   789         }
   790         if (result) {
   791             «$fsm/@name»_SERVICE_LOG("condition applies", "«@name»");
   792         ||
   793         apply "transition|action|condition|send";
   794         | }
   795     }
   796 }
   797