1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "utils_state_machine.h"
17 #include "utils_log.h"
18 #include "utils_mem.h"
19 
20 #include <stddef.h>
21 
22 typedef struct {
23     ListNode link;
24     uint32_t event;
25     const void *para;
26 } PendingEvent;
27 
InitStateMachine(StateMachine * machine,uint32_t machineId,uint32_t initState)28 void InitStateMachine(StateMachine *machine, uint32_t machineId, uint32_t initState)
29 {
30     if (machine == NULL) {
31         return;
32     }
33     machine->machineId = machineId;
34     machine->isScheduling = false;
35     machine->currState = initState;
36     InitListHead(&machine->pendingEventList);
37     InitRecursiveMutex(&machine->mutex);
38 }
39 
GetScheduleStateNode(const StateNode * nodes,uint32_t nodeCnt,uint32_t state,uint32_t event)40 static const StateNode *GetScheduleStateNode(const StateNode *nodes, uint32_t nodeCnt, uint32_t state, uint32_t event)
41 {
42     for (uint32_t i = 0; i < nodeCnt; i++) {
43         const StateNode *node = nodes + i;
44         if ((node->state == state) && (node->event == event)) {
45             return node;
46         }
47     }
48     return NULL;
49 }
50 
PushPendingEvent(StateMachine * machine,uint32_t event,const void * para)51 static inline void PushPendingEvent(StateMachine *machine, uint32_t event, const void *para)
52 {
53     PendingEvent *pending = MALLOC(sizeof(PendingEvent));
54     if (pending == NULL) {
55         return;
56     }
57     pending->event = event;
58     pending->para = para;
59     AddListNodeBefore(&pending->link, &machine->pendingEventList);
60 }
61 
PopPendingEvent(StateMachine * machine,uint32_t * event,const void ** para)62 static inline bool PopPendingEvent(StateMachine *machine, uint32_t *event, const void **para)
63 {
64     ListHead *head = &machine->pendingEventList;
65     if (IsEmptyList(head)) {
66         return false;
67     }
68     PendingEvent *pending = LIST_ENTRY(head->next, PendingEvent, link);
69     RemoveListNode(&pending->link);
70     *event = pending->event;
71     *para = pending->para;
72     FREE(pending);
73     return true;
74 }
75 
ScheduleMachine(const StateNode * nodes,uint32_t nodeCnt,StateMachine * machine,uint32_t event,const void * para)76 void ScheduleMachine(const StateNode *nodes, uint32_t nodeCnt, StateMachine *machine, uint32_t event, const void *para)
77 {
78     // EventPara could be null, need not to check
79     if ((nodes == NULL) || (nodeCnt == 0) || (machine == NULL)) {
80         SECURITY_LOG_ERROR("invlid params, nodes or context is null");
81         return;
82     }
83     LockMutex(&machine->mutex);
84 
85     if (machine->isScheduling) {
86         PushPendingEvent(machine, event, para);
87         UnlockMutex(&machine->mutex);
88         return;
89     }
90 
91     uint32_t state = machine->currState;
92     const StateNode *node = GetScheduleStateNode(nodes, nodeCnt, state, event);
93     if (node != NULL) {
94         bool result = true;
95         if (node->process != NULL) {
96             machine->isScheduling = true;
97             result = node->process(machine, event, para);
98             machine->isScheduling = false;
99         }
100         machine->currState = (result == true) ? node->nextStateT : node->nextStateF;
101     }
102     SECURITY_LOG_INFO("Statemachine(%{public}x) schedule state(%{public}u) + event(%{public}u) -> newState(%{public}u)",
103         machine->machineId, state, event, machine->currState);
104     uint32_t nextEvent = 0;
105     const void *nextPara = NULL;
106     bool isPending = PopPendingEvent(machine, &nextEvent, &nextPara);
107     UnlockMutex(&machine->mutex);
108     if (isPending) {
109         return ScheduleMachine(nodes, nodeCnt, machine, nextEvent, nextPara);
110     }
111 }
112