Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
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)
29static 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
40unsigned 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
63unsigned 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 }
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
118static 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
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
149static 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
186{
187 ux_flow_next_internal(0);
188}
189
190void ux_flow_next(void)
191{
192 ux_flow_next_internal(1);
193}
194
195void 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
234void 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
295void 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
320void 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]
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
357void 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
364unsigned 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
381const 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
400const 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
407unsigned 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)
const ux_flow_step_t * ux_flow_get_current(void)
void ux_flow_prev(void)
unsigned int ux_flow_is_first(void)
void ux_flow_error(unsigned int error)
#define FLOW_BARRIER
void ux_flow_next_no_display(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)
const void * ux_stack_get_step_params(unsigned int stack_slot)
unsigned int ux_flow_is_last(void)
void ux_flow_validate(void)
const void * ux_stack_get_current_step_params(void)
#define FLOW_END_STEP
unsigned int ux_flow_relayout(void)