sync/gen_statemachine.ysl2
author Volker Birk <vb@pep.foundation>
Wed, 22 Aug 2018 15:28:03 +0200
branchsync
changeset 2869 e06e7024a1cd
parent 2868 d812e7a24b52
child 2870 9c0567977ed2
permissions -rw-r--r--
...
     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 inject_«@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 inject_«@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 broadcasted
   351                 `` for "fsm/message[@type='broadcast']" |>> case «@name»:
   352                     status = _own_identities_retrieve(session, &channels, PEP_idf_not_for_sync);
   353                     if (!status)
   354                         goto the_end;
   355                     break;
   356 
   357                 // all other go unicast
   358                 default: {
   359                     if (!session->«yml:lcase(@name)»_state.common.from `> |`|
   360                         (session->«yml:lcase(@name)»_state.common.from->flags &
   361                         PEP_idf_not_for_sync))
   362                     {
   363                         status = PEP_SYNC_NO_CHANNEL;
   364                         goto the_end;
   365                     }
   366 
   367                     pEp_identity *channel = identity_dup(session->«yml:lcase(@name)»_state.common.from);
   368                     if (!channel) {
   369                         status = PEP_OUT_OF_MEMORY;
   370                         goto the_end;
   371                     }
   372 
   373                     channels = new_identity_list(channel);
   374                     if (!channels) {
   375                         status = PEP_OUT_OF_MEMORY;
   376                         goto the_end;
   377                     }
   378                 }
   379             }
   380 
   381             for (identity_list *li = channels; li && li->ident ; li = li->next) {
   382                 status = base_prepare_message(
   383                         li->ident,
   384                         li->ident,
   385                         data,
   386                         size,
   387                         &m
   388                     );
   389                 if (status)
   390                     goto the_end;
   391                 
   392                 status = session->messageToSend(session->«yml:lcase(@name)»_obj, m);
   393                 free_message(m);
   394                 m = NULL;
   395             }
   396 
   397         the_end:
   398             free_identity_list(channels);
   399             free_message(m);
   400             free(data);
   401             free_«@name»_message(msg);
   402             return status;
   403         }
   404 
   405         PEP_STATUS recv_«@name»_event(
   406                 PEP_SESSION session,
   407                 «@name»_event_t *ev
   408             )
   409         {
   410             assert(session && ev);
   411             if (!(session && ev))
   412                 return PEP_ILLEGAL_VALUE;
   413 
   414             PEP_STATUS status = PEP_STATUS_OK;
   415 
   416             if (ev->event < Extra) {
   417                 «@name»_PR fsm = (int) None;
   418                 int event = None;
   419 
   420                 status = update_«@name»_state(session, ev->msg, &fsm, &event);
   421                 if (status)
   422                     goto error;
   423 
   424                 if (ev->fsm) {
   425                     if (ev->fsm != fsm |`> |` ev->event != event) {
   426                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   427                         goto error;
   428                     }
   429                 }
   430                 else {
   431                     if (ev->event) {
   432                         status = PEP_«yml:ucase(@name)»_ILLEGAL_MESSAGE;
   433                         goto error;
   434                     }
   435                     ev->fsm = fsm;
   436                     ev->event = event;
   437                 }
   438             }
   439 
   440             free_«@name»_message(ev->msg);
   441             free(ev);
   442             status = «@name»_driver(session, ev->fsm, ev->event);
   443             return status;
   444 
   445         error:
   446             free_«@name»_message(ev->msg);
   447             free(ev);
   448             return status;
   449         }
   450 
   451         ||
   452 
   453         apply "fsm", 0, mode=gen;
   454     }
   455 
   456     template "fsm", mode=event
   457     {
   458     ||
   459     case «../@name»_PR_«yml:lcase(@name)»: {
   460         switch (msg->choice.«yml:lcase(@name)».payload.present) {
   461     ||
   462     for "message"
   463     ||
   464             case «../@name»__payload_PR_«yml:mixedCase(@name)»:
   465                 ev->event = «@name»;
   466                 break;
   467     ||
   468     ||
   469             default:
   470                 // unknown message type
   471                 free(ev);
   472                 return NULL;
   473         }
   474         break;
   475     }
   476 
   477     ||
   478     }
   479 
   480     template "fsm", mode=driver
   481     ||
   482     case «../@name»_PR_«yml:lcase(@name)»: {
   483         int state = session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state;
   484         next_state = fsm_«@name»(session, state, event);
   485         if (next_state > None) {
   486             session->«yml:lcase(../@name)»_state.«yml:lcase(@name)».state = next_state;
   487             event = Init;
   488         }
   489         else if (next_state < None) {
   490             return PEP_STATEMACHINE_ERROR - state;
   491         }
   492         break;
   493     }
   494 
   495     ||
   496 
   497     template "fsm", mode=gen {
   498         document "generated/{@name}_fsm.h", "text" {
   499         ||
   500         // This file is under GNU General Public License 3.0
   501         // see LICENSE.txt
   502 
   503         #pragma once
   504 
   505         #include "«../@name»_impl.h"
   506 
   507         #ifdef __cplusplus
   508         extern "C" {
   509         #endif
   510 
   511         // state machine for «@name»
   512 
   513         // states
   514 
   515         typedef enum _«@name»_state {
   516             «@name»_state_None = None,
   517             «@name»_state_Init = Init,
   518         ||
   519         for "func:distinctName(state[not(@name='InitState')])"
   520             |> «@name»`if "position()!=last()" > , `
   521         ||
   522         } «@name»_state;
   523 
   524         // events
   525 
   526         typedef enum _«@name»_event {
   527             «@name»_event_None = None,
   528             «@name»_event_Init = Init,
   529         ||
   530         for "func:distinctName(state/event[not(not(../../message/@name=@name))])" {
   531             const "name", "@name";
   532             |> «$name» = «/protocol/fsm/message[@name=$name]/@id»,
   533         }
   534         for "func:distinctName(state/event[not(not(../../external/@name=@name))])" {
   535             const "name", "@name";
   536             |> «$name» = «/protocol/fsm/external[@name=$name]/@id»,
   537         }
   538         |> «@name»_event_Extra = Extra,
   539         for "func:distinctName(state/event[not(../../message/@name=@name or ../../external/@name=@name)])" {
   540             if "@name!='Init'"
   541                 |> «@name»`if "position()!=last()" > , `
   542         }
   543         ||
   544         } «@name»_event;
   545 
   546         // state machine
   547 
   548         const char *«@name»_state_name(int state);
   549 
   550         // the state machine function is returning the next state in case of a
   551         // transition or None for staying
   552 
   553         «@name»_state fsm_«@name»(
   554                 PEP_SESSION session,
   555                 «@name»_state state,
   556                 «@name»_event event
   557             );
   558 
   559         #ifdef __cplusplus
   560         }
   561         #endif
   562 
   563         ||
   564         }
   565 
   566         document "generated/{@name}_fsm.c", "text" {
   567         ||
   568         // This file is under GNU General Public License 3.0
   569         // see LICENSE.txt
   570 
   571         #include "«@name»_fsm.h"
   572         #include <stdlib.h>
   573 
   574         const char *«@name»_state_name(int state)
   575         {
   576             switch (state) {
   577                 case End:
   578                     return "End";
   579                 case None:
   580                     return "None";
   581                 case Init:
   582                     return "InitState";
   583         ||
   584         for "func:distinctName(state[not(@name='InitState')])" {
   585             |>> case «@name»:
   586             |>>> return "«@name»";
   587         }
   588         ||
   589                 default:
   590                     return "unknown state";
   591             }
   592         }
   593 
   594         static char *_str(int n, bool hex)
   595         {
   596             char *buf = calloc(1, 24);
   597             assert(buf);
   598             if (!buf)
   599                 return NULL;
   600 
   601             if (hex)
   602                 snprintf(buf, 24, "%.4x", n);
   603             else
   604                 snprintf(buf, 24, "%d", n);
   605             return buf;
   606         }
   607 
   608         #define «@name»_ERR_LOG(t, d) log_event(session, (t), "«@name»", (d), "error")
   609 
   610         static PEP_STATUS _«@name»_ERR_LOG_int(PEP_SESSION session, char *t, int n, bool hex)
   611         {
   612             char *_buf = _str(n, hex);
   613             if (!_buf)
   614                 return PEP_OUT_OF_MEMORY;
   615             PEP_STATUS status = «@name»_ERR_LOG(t, _buf);
   616             free(_buf);
   617             return status;
   618         }
   619 
   620         #define «@name»_ERR_LOG_INT(t, n) _«@name»_ERR_LOG_int(session, (t), (n), false)
   621         #define «@name»_ERR_LOG_HEX(t, n) _«@name»_ERR_LOG_int(session, (t), (n), true)
   622 
   623         #ifndef SERVICE_LOG
   624         // SERVICE LOG is meant to check session->service_log in runtime config;
   625         // for older engines log more than needed
   626         #define SERVICE_LOG(session, t, n, d) log_event((session), (t), (n), (d), "service")
   627         #endif 
   628 
   629         #define «@name»_SERVICE_LOG(t, d) SERVICE_LOG(session, (t), "«@name»", (d))
   630 
   631         «@name»_state fsm_«@name»(
   632                 PEP_SESSION session,
   633                 «@name»_state state,
   634                 «@name»_event event
   635             )
   636         {
   637             assert(session);
   638             if (!session)
   639                 return invalid_state;
   640 
   641             switch (state) {
   642                 case None:
   643                     return «@name»_state_Init;
   644                 
   645                 `` apply "state", 2, mode=fsm
   646                 default:
   647                     «@name»_ERR_LOG_INT("invalid state", state);
   648                     return invalid_state;
   649             }
   650             
   651             return None;
   652         }
   653 
   654         ||
   655         }
   656     }
   657     
   658     template "state", mode=fsm {
   659         choose {
   660             when "@name='InitState'" | case «../@name»_state_Init:
   661             otherwise | case «@name»:
   662         }
   663         ||
   664             «../@name»_SERVICE_LOG("in state", "«@name»");
   665 
   666             switch (event) {
   667                 case None:
   668                     «../@name»_SERVICE_LOG("received None event", "ignoring");
   669                     break;
   670      
   671         ||
   672         if "not(event[@name='Init'])"
   673         ||
   674                 case Init:
   675                     // nothing to do
   676                     break;
   677 
   678         ||
   679         ||
   680                 `` apply "event", 2, mode=fsm
   681                 default:
   682                     «../@name»_ERR_LOG_INT("invalid event", event);
   683                     return invalid_event;
   684             }
   685             break;
   686 
   687         ||
   688     }
   689 
   690     template "event", mode=fsm {
   691         | case «@name»: {
   692         if "condition|action|send" |> PEP_STATUS status;
   693         if "condition" |> bool result = false;
   694         if "condition|action|send" |
   695         ||
   696             «../../@name»_SERVICE_LOG("received event", "«@name»");
   697             `` apply "transition|action|condition|send";
   698         ||
   699         if "name(*[last()])!='transition'" {
   700             |
   701             |> «../../@name»_SERVICE_LOG("remaining in state", "«../@name»");
   702             |> break;
   703         }
   704         ||
   705         }
   706         
   707         ||
   708     }
   709 
   710     template "transition" {
   711         const "fsm", "ancestor::fsm";
   712         ||
   713 
   714         «$fsm/@name»_SERVICE_LOG("transition to state", "«@target»");
   715         return «@target»;
   716         ||
   717     }
   718 
   719     template "send" {
   720         const "fsm", "ancestor::fsm";
   721         const "protocol", "ancestor::protocol";
   722         ||
   723 
   724         «$fsm/@name»_SERVICE_LOG("send message", "«@name»");
   725         status = send_«$protocol/@name»_message(session, «$fsm/@id», «$fsm/@name»__payload_PR_«yml:mixedCase(@name)»);
   726         ||
   727     }
   728 
   729     template "action" {
   730         const "fsm", "ancestor::fsm";
   731         ||
   732 
   733         «$fsm/@name»_SERVICE_LOG("do action", "«@name»");
   734         status = «@name»(session);
   735         if (status) {
   736             «$fsm/@name»_ERR_LOG_HEX("executing action «@name»() failed", status);
   737             return invalid_action;
   738         }
   739         ||
   740     }
   741 
   742     template "condition" {
   743         const "fsm", "ancestor::fsm";
   744         ||
   745 
   746         status = «@name»(session, &result);
   747         if (status) {
   748             «$fsm/@name»_ERR_LOG_HEX("computing condition «@name» failed", status);
   749             return invalid_condition;
   750         }
   751         if (result) {
   752             «$fsm/@name»_SERVICE_LOG("condition applies", "«@name»");
   753         ||
   754         apply "transition|action|condition|send";
   755         | }
   756     }
   757 }
   758