Embedded SDK
Embedded SDK
ux_flow_engine.c
Go to the documentation of this file.
1 
2 /*******************************************************************************
3  * Ledger Nano S - Secure firmware
4  * (c) 2022 Ledger
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  ********************************************************************************/
18 
19 #include "ux.h"
20 #include "os_helpers.h"
21 #include "os_pic.h"
22 #include "ux_flow_engine.h"
23 
24 #include <string.h>
25 
26 #ifdef HAVE_UX_FLOW
27 
28 // check if flow is valid (has valid length and in correct stack slot)
29 static unsigned int ux_flow_check_valid(void)
30 {
31  unsigned int top_stack_slot = G_ux.stack_count - 1;
32 
33  if (G_ux.stack_count > UX_STACK_SLOT_COUNT || G_ux.flow_stack[top_stack_slot].length == 0) {
34  return 0;
35  }
36  return 1;
37 }
38 
39 // to hide the left tick or not
40 unsigned int ux_flow_is_first(void)
41 {
42  // no previous ?
43  unsigned int top_stack_slot = G_ux.stack_count - 1;
44  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
45  || (G_ux.flow_stack[top_stack_slot].index == 0
46  && G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].length - 1]
47  != FLOW_LOOP)) {
48  return 1;
49  }
50 
51  // previous is a flow barrier ?
52  if (G_ux.flow_stack[top_stack_slot].length > 0
53  && G_ux.flow_stack[top_stack_slot].index < G_ux.flow_stack[top_stack_slot].length
54  && G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index - 1]
55  == FLOW_BARRIER) {
56  return 1;
57  }
58 
59  // not the first, for sure
60  return 0;
61 }
62 
63 unsigned int ux_flow_is_last(void)
64 {
65  // last ?
66  unsigned int top_stack_slot = G_ux.stack_count - 1;
67 
68  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
69  || G_ux.flow_stack[top_stack_slot].length == 0
70  || G_ux.flow_stack[top_stack_slot].index >= G_ux.flow_stack[top_stack_slot].length - 1) {
71  return 1;
72  }
73 
74  // followed by a flow barrier ?
75  if (G_ux.flow_stack[top_stack_slot].length > 0
76  && G_ux.flow_stack[top_stack_slot].index < G_ux.flow_stack[top_stack_slot].length - 2
77  && G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index + 1]
78  == FLOW_BARRIER) {
79  return 1;
80  }
81 
82  // is not last
83  return 0;
84 }
85 
87 {
88  unsigned int top_stack_slot = G_ux.stack_count - 1;
89 
90  if (G_ux.stack_count) {
91  if (G_ux.flow_stack[top_stack_slot].index > G_ux.flow_stack[top_stack_slot].prev_index) {
93  }
94  else if (G_ux.flow_stack[top_stack_slot].index
95  < G_ux.flow_stack[top_stack_slot].prev_index) {
97  }
98  }
99  return FLOW_DIRECTION_START;
100 }
101 
103 {
104  unsigned int top_stack_slot = G_ux.stack_count - 1;
105 
106  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
107  || G_ux.flow_stack[top_stack_slot].index >= G_ux.flow_stack[top_stack_slot].length
108  || G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index]
109  == FLOW_BARRIER
110  || G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index] == FLOW_LOOP
111  || G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index]
112  == FLOW_END_STEP) {
113  return NULL;
114  }
115  return STEPPIC(G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index]);
116 }
117 
118 static void ux_flow_engine_init_step(unsigned int stack_slot)
119 {
120  // invalid ux_flow_length ??? (previous check shall have exited earlier)
121  if (G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index] == FLOW_END_STEP) {
122  return;
123  }
124  // this shall not have occurred due to previous checks
125  if (G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index] == FLOW_BARRIER) {
126  return;
127  }
128  // this shall not have occurred due to previous checks
129  if (G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index] == FLOW_LOOP) {
130  return;
131  }
132  // if init function is set, call it
133  if (STEPPIC(G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index])->init) {
134  INITPIC(STEPPIC(G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index])->init)
135  (stack_slot);
136  }
137  else {
138  // if init method is not set, jump to referenced flow and step
139  ux_flow_init(
140  stack_slot,
141  STEPSPIC(STEPPIC(G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index])
142  ->validate_flow),
143  (const ux_flow_step_t *) PIC(
144  STEPPIC(G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index])
145  ->params));
146  }
147 }
148 
149 static void ux_flow_next_internal(unsigned int display_step)
150 {
151  unsigned int top_stack_slot = G_ux.stack_count - 1;
152 
153  // last reached already (need validation, not next)
154  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
155  || G_ux.flow_stack[top_stack_slot].length <= 1
156  || G_ux.flow_stack[top_stack_slot].index >= G_ux.flow_stack[top_stack_slot].length - 1) {
157  return;
158  }
159 
160  // followed by a flow barrier ? => need validation instead of next
161  if (G_ux.flow_stack[top_stack_slot].index <= G_ux.flow_stack[top_stack_slot].length - 2) {
162  if (G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index + 1]
163  == FLOW_BARRIER) {
164  return;
165  }
166 
167  // followed by a flow barrier ? => need validation instead of next
168  if (G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index + 1]
169  == FLOW_LOOP) {
170  // display first step, fake direction as forward
171  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index = 0;
172  ux_flow_engine_init_step(top_stack_slot);
173  return;
174  }
175  }
176 
177  // advance flow pointer and display it (skip META STEPS)
178  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index;
179  G_ux.flow_stack[top_stack_slot].index++;
180  if (display_step) {
181  ux_flow_engine_init_step(top_stack_slot);
182  }
183 }
184 
185 void ux_flow_next_no_display(void)
186 {
187  ux_flow_next_internal(0);
188 }
189 
190 void ux_flow_next(void)
191 {
192  ux_flow_next_internal(1);
193 }
194 
195 void ux_flow_prev(void)
196 {
197  unsigned int top_stack_slot = G_ux.stack_count - 1;
198 
199  // first reached already
200  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
201  || G_ux.flow_stack[top_stack_slot].length <= 1
202  || (G_ux.flow_stack[top_stack_slot].index == 0
203  && G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].length - 1]
204  != FLOW_LOOP)) {
205  return;
206  }
207 
208  // loop in flow (before checking barrier as there is no prestep when looping)
209  if (G_ux.flow_stack[top_stack_slot].index == 0
210  && G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].length - 1]
211  == FLOW_LOOP) {
212  // display last step (shall skip BARRIER if any, but a flow finishing with a BARRIER is
213  // cryptic)
214  G_ux.flow_stack[top_stack_slot].index = G_ux.flow_stack[top_stack_slot].length - 2;
215  // fact direction as backward
216  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index + 1;
217  ux_flow_engine_init_step(top_stack_slot);
218  return;
219  }
220 
221  // previous item is a flow barrier ?
222  if (G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index - 1]
223  == FLOW_BARRIER) {
224  return;
225  }
226 
227  // advance flow pointer and display it (skip META STEPS)
228  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index;
229  G_ux.flow_stack[top_stack_slot].index--;
230 
231  ux_flow_engine_init_step(top_stack_slot);
232 }
233 
234 void ux_flow_validate(void)
235 {
236  unsigned int top_stack_slot = G_ux.stack_count - 1;
237 
238  // no flow ?
239  if (!ux_flow_check_valid() || G_ux.flow_stack[top_stack_slot].steps == NULL
240  || G_ux.flow_stack[top_stack_slot].length == 0
241  || G_ux.flow_stack[top_stack_slot].index >= G_ux.flow_stack[top_stack_slot].length) {
242  return;
243  }
244 
245  // no validation flow ?
246  if (STEPPIC(G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index])
247  ->validate_flow
248  != NULL) {
249  // execute validation flow
250  ux_flow_init(top_stack_slot,
251  STEPSPIC(STEPPIC(G_ux.flow_stack[top_stack_slot]
252  .steps[G_ux.flow_stack[top_stack_slot].index])
253  ->validate_flow),
254  NULL);
255  }
256  else {
257  // if next is a barrier, then proceed to the item after the barrier
258  // if NOT followed by a barrier, then validation is only performed through
259  // a validate_flow specified in the step, else ignored
260  if (G_ux.flow_stack[top_stack_slot].length > 0
261  && G_ux.flow_stack[top_stack_slot].index
262  <= G_ux.flow_stack[top_stack_slot].length - 2) {
263  if (G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index + 1]
264  == FLOW_BARRIER) {
265  // take into account multi barrier at once, kthx poor code review
266  while (G_ux.flow_stack[top_stack_slot].length > 0
267  && G_ux.flow_stack[top_stack_slot].index
268  <= G_ux.flow_stack[top_stack_slot].length - 2
269  && G_ux.flow_stack[top_stack_slot]
270  .steps[G_ux.flow_stack[top_stack_slot].index + 1]
271  == FLOW_BARRIER) {
272  G_ux.flow_stack[top_stack_slot].index++;
273  }
274  // skip to next step
275  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index;
276  G_ux.flow_stack[top_stack_slot].index++;
277 
278  // execute reached step
279  ux_flow_engine_init_step(top_stack_slot);
280  }
281  // reached the last step, but step if FLOW_LOOP
282  else if (G_ux.flow_stack[top_stack_slot]
283  .steps[G_ux.flow_stack[top_stack_slot].index + 1]
284  == FLOW_LOOP) {
285  // we go the forward direction
286  G_ux.flow_stack[top_stack_slot].prev_index = G_ux.flow_stack[top_stack_slot].index
287  = 0;
288  // execute reached step
289  ux_flow_engine_init_step(top_stack_slot);
290  }
291  }
292  }
293 }
294 
295 void ux_flow_error(unsigned int error)
296 {
297  UNUSED(error);
298 
299  unsigned int top_stack_slot = G_ux.stack_count - 1;
300 
301  if (G_ux.flow_stack[top_stack_slot].steps == NULL || G_ux.flow_stack[top_stack_slot].length == 0
302  || G_ux.flow_stack[top_stack_slot].index >= G_ux.flow_stack[top_stack_slot].length) {
303  return;
304  }
305 
306  if (STEPPIC(G_ux.flow_stack[top_stack_slot].steps[G_ux.flow_stack[top_stack_slot].index])
307  ->error_flow
308  != NULL) {
309  ux_flow_init(top_stack_slot,
310  STEPSPIC(STEPPIC(G_ux.flow_stack[top_stack_slot]
311  .steps[G_ux.flow_stack[top_stack_slot].index])
312  ->error_flow),
313  NULL);
314  }
315 }
316 
320 void ux_flow_init(unsigned int stack_slot,
321  const ux_flow_step_t *const *steps,
322  const ux_flow_step_t *const start_step)
323 {
324  if (stack_slot >= UX_STACK_SLOT_COUNT) {
325  return;
326  }
327 
328  G_ux.flow_stack[stack_slot].length = G_ux.flow_stack[stack_slot].prev_index
329  = G_ux.flow_stack[stack_slot].index = 0;
330  G_ux.flow_stack[stack_slot].steps = NULL;
331 
332  // reset paging to avoid troubles if first step is a paginated step
333  memset(&G_ux.layout_paging, 0, sizeof(G_ux.layout_paging));
334 
335  if (steps) {
336  G_ux.flow_stack[stack_slot].steps = STEPSPIC(steps);
337  while (G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].length]
338  != FLOW_END_STEP) {
339  G_ux.flow_stack[stack_slot].length++;
340  }
341  if (start_step != NULL) {
342  const ux_flow_step_t *const start_step2 = STEPPIC(start_step);
343  while (G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index]
344  != FLOW_END_STEP
345  && STEPPIC(G_ux.flow_stack[stack_slot].steps[G_ux.flow_stack[stack_slot].index])
346  != start_step2) {
347  G_ux.flow_stack[stack_slot].prev_index = G_ux.flow_stack[stack_slot].index;
348  G_ux.flow_stack[stack_slot].index++;
349  }
350  }
351 
352  // init step
353  ux_flow_engine_init_step(stack_slot);
354  }
355 }
356 
357 void ux_flow_uninit(unsigned int stack_slot)
358 {
359  if (stack_slot < UX_STACK_SLOT_COUNT) {
360  memset(&G_ux.flow_stack[stack_slot], 0, sizeof(G_ux.flow_stack[stack_slot]));
361  }
362 }
363 
364 unsigned int ux_flow_button_callback(unsigned int button_mask, unsigned int button_mask_counter)
365 {
366  UNUSED(button_mask_counter);
367  switch (button_mask) {
369  ux_flow_prev();
370  break;
372  ux_flow_next();
373  break;
376  break;
377  }
378  return 0;
379 }
380 
381 const void *ux_stack_get_step_params(unsigned int stack_slot)
382 {
383  if (stack_slot >= UX_STACK_SLOT_COUNT) {
384  return NULL;
385  }
386 
387  if (G_ux.flow_stack[stack_slot].length == 0) {
388  return NULL;
389  }
390 
391  if (G_ux.flow_stack[stack_slot].index >= G_ux.flow_stack[stack_slot].length) {
392  return NULL;
393  }
394 
395  return PIC(
396  STEPPIC(STEPSPIC(G_ux.flow_stack[stack_slot].steps)[G_ux.flow_stack[stack_slot].index])
397  ->params);
398 }
399 
400 const void *ux_stack_get_current_step_params(void)
401 {
402  unsigned int top_stack_slot = G_ux.stack_count - 1;
403 
404  return ux_stack_get_step_params(top_stack_slot);
405 }
406 
407 unsigned int ux_flow_relayout(void)
408 {
409  // if a flow is defined and valid
410  if (ux_flow_get_current() != NULL) {
411  unsigned int top_stack_slot = G_ux.stack_count - 1;
412 
413  ux_flow_engine_init_step(top_stack_slot);
414  return 1;
415  }
416  return 0;
417 }
418 
419 #endif // HAVE_UX_FLOW
#define UX_STACK_SLOT_COUNT
Definition: ux_bagl.h:166
#define BUTTON_RIGHT
Definition: ux_bagl.h:145
#define BUTTON_EVT_RELEASED
Definition: ux_bagl.h:148
#define G_ux
Definition: ux_bagl.h:345
#define BUTTON_LEFT
Definition: ux_bagl.h:144
void ux_flow_init(unsigned int stack_slot, const ux_flow_step_t *const *steps, const ux_flow_step_t *const start_step)
ux_flow_direction_t
@ FLOW_DIRECTION_BACKWARD
@ FLOW_DIRECTION_FORWARD
@ FLOW_DIRECTION_START
#define FLOW_LOOP
ux_flow_direction_t ux_flow_direction(void)
void ux_flow_next(void)
void ux_flow_prev(void)
unsigned int ux_flow_is_first(void)
void ux_flow_error(unsigned int error)
const void * ux_stack_get_step_params(unsigned int stack_slot)
#define FLOW_BARRIER
void ux_flow_next_no_display(void)
const ux_flow_step_t * ux_flow_get_current(void)
const void * ux_stack_get_current_step_params(void)
#define STEPPIC(x)
void ux_flow_uninit(unsigned int stack_slot)
#define INITPIC(x)
unsigned int ux_flow_button_callback(unsigned int button_mask, unsigned int button_mask_counter)
#define STEPSPIC(x)
unsigned int ux_flow_is_last(void)
void ux_flow_validate(void)
#define FLOW_END_STEP
unsigned int ux_flow_relayout(void)