xtotpSettings.c (21260B)
1 /** 2 * @file xtotpSettings.c 3 * @author Adrian STEINER (steia19@bfh.ch) 4 * @brief Settings state machine for secret registration and additional settings 5 * @version 0.1 6 * @date 27-02-2025 7 * 8 * @copyright (C) 2025 Adrian STEINER 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License 20 * along with this program. If not, see <https: //www.gnu.org/licenses/>. 21 * 22 */ 23 24 #include "xtotpSettings.h" 25 26 // #include "version.h" 27 28 #include <stdio.h> 29 #include <string.h> 30 31 #include "base32_converter.h" 32 #include "base8_converter.h" 33 34 #include "frontend.h" 35 #include "inputInterface.h" 36 #include "logger.h" 37 #include "tinytime.h" 38 #include "xtotpConfig.h" 39 #include "xtotpStateMachine.h" 40 #include "xtotpUtil.h" 41 42 /** 43 * @brief This function sets for the additional settings the next state with the 44 * given input. 45 * 46 * This function needs to be called if no special settings is enabled (like 47 * updating a value). 48 * @note To be free to use this function, call it in the state as you like 49 * (rising/falling edge/ high). 50 * 51 * @param appData The user data handler 52 * @param inputButton The current input button 53 */ 54 void checkBaseAdditionalInput(xtotp_Data *appData, inputButtonPos inputButton); 55 56 /* Additional state callback functions */ 57 void additionalClockEntry(xtotp_Data *appData, 58 inputLevelState inputState, 59 inputButtonPos inputButton); 60 void additionalClock(xtotp_Data *appData, 61 inputLevelState inputState, 62 inputButtonPos inputButton); 63 64 void additionalBatteryStateEntry(xtotp_Data *appData, 65 inputLevelState inputState, 66 inputButtonPos inputButton); 67 void additionalBatteryState(xtotp_Data *appData, 68 inputLevelState inputState, 69 inputButtonPos inputButton); 70 71 void additionalLastSyncTimeEntry(xtotp_Data *appData, 72 inputLevelState inputState, 73 inputButtonPos inputButton); 74 void additionalLastSyncTime(xtotp_Data *appData, 75 inputLevelState inputState, 76 inputButtonPos inputButton); 77 78 void additionalDifferenceSyncEntry(xtotp_Data *appData, 79 inputLevelState inputState, 80 inputButtonPos inputButton); 81 void additionalDifferenceSync(xtotp_Data *appData, 82 inputLevelState inputState, 83 inputButtonPos inputButton); 84 85 void additionalTimeoutTimeEntry(xtotp_Data *appData, 86 inputLevelState inputState, 87 inputButtonPos inputButton); 88 void additionalTimeoutTime(xtotp_Data *appData, 89 inputLevelState inputState, 90 inputButtonPos inputButton); 91 92 void additionalScreenModeEntry(xtotp_Data *appData, 93 inputLevelState inputState, 94 inputButtonPos inputButton); 95 void additionalScreenMode(xtotp_Data *appData, 96 inputLevelState inputState, 97 inputButtonPos inputButton); 98 99 void additionalDeviceIDEntry(xtotp_Data *appData, 100 inputLevelState inputState, 101 inputButtonPos inputButton); 102 void additionalDeviceID(xtotp_Data *appData, 103 inputLevelState inputState, 104 inputButtonPos inputButton); 105 106 void additionalFirmwareEntry(xtotp_Data *appData, 107 inputLevelState inputState, 108 inputButtonPos inputButton); 109 void additionalFirmware(xtotp_Data *appData, 110 inputLevelState inputState, 111 inputButtonPos inputButton); 112 113 void additionalUnusedEntry(xtotp_Data *appData, 114 inputLevelState inputState, 115 inputButtonPos inputButton); 116 void additionalUnused(xtotp_Data *appData, 117 inputLevelState inputState, 118 inputButtonPos inputButton); 119 120 stateHandler additionalSettingsState[ADDITIONAL_MAX_STATES] = { 121 [ADDITIONAL_CLOCK] = {.entryAction = additionalClockEntry, 122 .runAction = additionalClock}, 123 [ADDITIONAL_BATTERY_STATE] = {.entryAction = additionalBatteryStateEntry, 124 .runAction = additionalBatteryState}, 125 [ADDITIONAL_LAST_SYNC_TIME] = {.entryAction = additionalLastSyncTimeEntry, 126 .runAction = additionalLastSyncTime}, 127 [ADDITIONAL_DIFFERENCE_SYNC] = {.entryAction = 128 additionalDifferenceSyncEntry, 129 .runAction = additionalDifferenceSync}, 130 [ADDITIONAL_TIMEOUT_TIME] = {.entryAction = additionalTimeoutTimeEntry, 131 .runAction = additionalTimeoutTime}, 132 [ADDITIONAL_SCREEN_MODE] = {.entryAction = additionalScreenModeEntry, 133 .runAction = additionalScreenMode}, 134 [ADDITIONAL_DEVICE_ID] = {.entryAction = additionalDeviceIDEntry, 135 .runAction = additionalDeviceID}, 136 [ADDITIONAL_UNUSED_7] = {.entryAction = additionalUnusedEntry, 137 .runAction = additionalUnused}, 138 [ADDITIONAL_UNSUED_8] = {.entryAction = additionalUnusedEntry, 139 .runAction = additionalUnused}, 140 [ADDITIONAL_FIRMWARE] = {.entryAction = additionalFirmwareEntry, 141 .runAction = additionalFirmware}}; 142 143 void settings_processAdditional(xtotp_Data *appData, 144 inputLevelState inputState, 145 inputButtonPos inputButton) 146 { 147 if (NULL == appData) { 148 return; 149 } 150 xtotp_AdditionalSettingStates oldState = 151 appData->currentStates.additionalSettings; 152 if (appData->currentStates.additionalSettings >= ADDITIONAL_MAX_STATES) { 153 appData->currentStates.additionalSettings = ADDITIONAL_CLOCK; 154 } else { 155 additionalSettingsState[appData->currentStates.additionalSettings] 156 .runAction(appData, inputState, inputButton); 157 } 158 if (oldState != appData->currentStates.additionalSettings) { 159 additionalSettingsState[appData->currentStates.additionalSettings] 160 .entryAction(appData, inputState, inputButton); 161 } 162 } 163 164 void checkBaseAdditionalInput(xtotp_Data *appData, inputButtonPos inputButton) 165 { 166 switch (inputButton) { 167 case INPUT_0: 168 appData->currentStates.additionalSettings = ADDITIONAL_CLOCK; 169 return; 170 case INPUT_1: 171 appData->currentStates.additionalSettings = ADDITIONAL_BATTERY_STATE; 172 return; 173 case INPUT_2: 174 appData->currentStates.additionalSettings = ADDITIONAL_LAST_SYNC_TIME; 175 return; 176 case INPUT_3: 177 appData->currentStates.additionalSettings = ADDITIONAL_DIFFERENCE_SYNC; 178 return; 179 case INPUT_4: 180 appData->currentStates.additionalSettings = ADDITIONAL_TIMEOUT_TIME; 181 return; 182 case INPUT_5: 183 appData->currentStates.additionalSettings = ADDITIONAL_SCREEN_MODE; 184 return; 185 case INPUT_6: 186 appData->currentStates.additionalSettings = ADDITIONAL_DEVICE_ID; 187 return; 188 case INPUT_9: 189 appData->currentStates.additionalSettings = ADDITIONAL_FIRMWARE; 190 return; 191 case INPUT_ENTER: 192 appData->currentStates.base = XTOTP_DISPATCHER; 193 return; 194 case INPUT_DEL: 195 appData->currentStates.base = XTOTP_DISPATCHER; 196 return; 197 default: 198 // Do nothing 199 return; 200 } 201 } 202 203 void additionalClockEntry(xtotp_Data *appData, 204 inputLevelState inputState, 205 inputButtonPos inputButton) 206 { 207 (void)inputState; 208 (void)inputButton; 209 appData->processData.lastNeededTime = 0; 210 frontend_createBaseWindow(appData, "Time & Date", NULL); 211 additionalClock(appData, INPUT_STATE_LOW, INPUT_NONE); 212 return; 213 } 214 215 void additionalClock(xtotp_Data *appData, 216 inputLevelState inputState, 217 inputButtonPos inputButton) 218 { 219 if (appData->secFlag || 0 == appData->processData.lastNeededTime) { 220 appData->iClock.powerOn(); 221 uint64_t currentTime = appData->iClock.getUnixTime(); 222 appData->iClock.powerOff(); 223 if (currentTime != appData->processData.lastNeededTime) { 224 appData->processData.lastNeededTime = currentTime; 225 tinyTimeType tad; 226 tiny_getTimeType(&tad, appData->processData.lastNeededTime); 227 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE1, 228 "%.2u:%.2u:%.2u", tad.hour, tad.min, tad.sec); 229 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 230 "%.2u.%.2u.%.4u", tad.monthDay, tad.month, 231 tad.year); 232 appData->iDisplay.show(); 233 } 234 } 235 if (inputState == INPUT_STATE_RISING_EDGE) { 236 checkBaseAdditionalInput(appData, inputButton); 237 } 238 } 239 240 void additionalBatteryStateEntry(xtotp_Data *appData, 241 inputLevelState inputState, 242 inputButtonPos inputButton) 243 { 244 (void)inputState; 245 (void)inputButton; 246 batMeas_measure(&appData->iBatMeas, appData->iDevice.getTick(), APP_SEC_BASE); 247 frontend_createBaseWindow(appData, "Battery", NULL); 248 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, "%u mV", 249 batMeas_getBatteryVoltageState(&appData->iBatMeas)); 250 appData->iDisplay.show(); 251 return; 252 } 253 void additionalBatteryState(xtotp_Data *appData, 254 inputLevelState inputState, 255 inputButtonPos inputButton) 256 { 257 if (!batMeas_measure(&appData->iBatMeas, appData->iDevice.getTick(), 258 APP_SEC_BASE)) { 259 frontend_writeStatusIcons(appData); 260 frontend_updateDatafield( 261 appData, FRONTEND_DATAFIELD_CENTRED, "%u mV", 262 batMeas_getBatteryVoltageState(&appData->iBatMeas)); 263 appData->iDisplay.show(); 264 } 265 if (inputState == INPUT_STATE_RISING_EDGE) { 266 checkBaseAdditionalInput(appData, inputButton); 267 } 268 } 269 270 void additionalLastSyncTimeEntry(xtotp_Data *appData, 271 inputLevelState inputState, 272 inputButtonPos inputButton) 273 { 274 (void)inputState; 275 (void)inputButton; 276 frontend_createBaseWindow(appData, "Last Sync", NULL); 277 appData->processData.updateDisplayData = true; 278 additionalLastSyncTime(appData, INPUT_STATE_LOW, INPUT_NONE); 279 } 280 281 void additionalLastSyncTime(xtotp_Data *appData, 282 inputLevelState inputState, 283 inputButtonPos inputButton) 284 { 285 if (appData->processData.updateDisplayData) { 286 tinyTimeType tad; 287 tiny_getTimeType(&tad, appData->iSync.lastSyncTime); 288 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE1, 289 "%.2u:%.2u:%.2u %.2u.%.2u.%.4u", tad.hour, tad.min, 290 tad.sec, tad.monthDay, tad.month, tad.year); 291 tiny_getTimeType(&tad, appData->iSync.expectedSyncTime); 292 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 293 "%.2u:%.2u:%.2u %.2u.%.2u.%.4u", tad.hour, tad.min, 294 tad.sec, tad.monthDay, tad.month, tad.year); 295 appData->iDisplay.show(); 296 } 297 // Check window update 298 if (INPUT_STATE_RISING_EDGE == inputState) { 299 if (INPUT_DEL == inputButton && !appData->iSync.isSynchronizing) { 300 sync_enable(&appData->iSync); 301 frontend_writeStatusIcons(appData); 302 appData->iDisplay.show(); 303 return; 304 } 305 checkBaseAdditionalInput(appData, inputButton); 306 } 307 } 308 309 void additionalDifferenceSyncEntry(xtotp_Data *appData, 310 inputLevelState inputState, 311 inputButtonPos inputButton) 312 { 313 (void)inputState; 314 (void)inputButton; 315 frontend_createBaseWindow(appData, "Sync Info", NULL); 316 appData->processData.updateDisplayData = true; 317 additionalDifferenceSync(appData, INPUT_STATE_LOW, inputButton); 318 return; 319 } 320 void additionalDifferenceSync(xtotp_Data *appData, 321 inputLevelState inputState, 322 inputButtonPos inputButton) 323 { 324 // Update at startup/ new data 325 if (appData->processData.updateDisplayData) { 326 frontend_writeStatusIcons(appData); 327 uint64_t syncHour = 0; 328 uint64_t syncMin = 0; 329 uint64_t syncSec = tiny_convertSeconds( 330 MS_TO_SECOND(appData->iSync.syncDiff), NULL, &syncHour, &syncMin); 331 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE1, 332 "Dif:%7s%.2u:%.2u:%.2u", " ", (uint32_t)syncHour, 333 (uint32_t)syncMin, (uint32_t)syncSec); 334 if (appData->iSync.syncDuration >= XTOTP_SYNC_MAX_SYNC_TIME) { 335 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 336 "Dur: Sync error"); 337 } else { 338 syncSec = tiny_convertSeconds(MS_TO_SECOND(appData->iSync.syncDuration), 339 NULL, &syncHour, &syncMin); 340 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 341 "Dur:%7s%.2u:%.2u:%.2u", " ", (uint32_t)syncHour, 342 (uint32_t)syncMin, (uint32_t)syncSec); 343 } 344 appData->iDisplay.show(); 345 } 346 // Sync time 347 else if (appData->iSync.isSynchronizing && appData->secFlag) { 348 uint64_t syncHour = 0; 349 uint64_t syncMin = 0; 350 uint64_t syncSec = tiny_convertSeconds( 351 MS_TO_SECOND(appData->iSync.syncDuration), NULL, &syncHour, &syncMin); 352 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 353 "Dur:%7s%.2u:%.2u:%.2u", " ", (uint32_t)syncHour, 354 (uint32_t)syncMin, (uint32_t)syncSec); 355 appData->iDisplay.show(); 356 } 357 // Check window update 358 if (INPUT_STATE_RISING_EDGE == inputState) { 359 if (INPUT_DEL == inputButton && !appData->iSync.isSynchronizing) { 360 sync_enable(&appData->iSync); 361 frontend_writeStatusIcons(appData); 362 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, 363 "Dur:%7s%.2u:%.2u:%.2u", " ", 0, 0, 0); 364 appData->iDisplay.show(); 365 return; 366 } 367 checkBaseAdditionalInput(appData, inputButton); 368 } 369 } 370 371 void additionalTimeoutTimeEntry(xtotp_Data *appData, 372 inputLevelState inputState, 373 inputButtonPos inputButton) 374 { 375 (void)inputState; 376 (void)inputButton; 377 frontend_createBaseWindow(appData, "Sleep Timeout", NULL); 378 if (appData->timeOutTime != 0) { 379 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, "%3u s", 380 MS_TO_SECOND(appData->timeOutTime)); 381 } else { 382 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, "Disabled"); 383 } 384 appData->iDisplay.show(); 385 return; 386 } 387 bool setNewTime = false; 388 uint64_t newTimeOut = 0; 389 390 void additionalTimeoutTime(xtotp_Data *appData, 391 inputLevelState inputState, 392 inputButtonPos inputButton) 393 { 394 395 if (inputState == XTOTP_REACTION_EDGE) { 396 if (INPUT_DEL == inputButton && !setNewTime) { 397 newTimeOut = 0; 398 setNewTime = true; 399 } 400 if (setNewTime) { 401 if (input_readNumber(&newTimeOut, XTOTP_MAX_TIMEOUT_TIME, inputButton) || 402 (0 == newTimeOut && INPUT_ENTER == inputButton)) { 403 appData->timeOutTime = (uint32_t)SECOND_TO_MS(newTimeOut); 404 if (0 != newTimeOut) { 405 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 406 " %3u s", 407 MS_TO_SECOND(appData->timeOutTime)); 408 } else { 409 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 410 "Disabled"); 411 } 412 appData->iDisplay.show(); 413 setNewTime = false; 414 newTimeOut = 0; 415 } else { 416 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 417 "New: %3u s", (uint32_t)newTimeOut); 418 appData->iDisplay.show(); 419 } 420 } else { 421 checkBaseAdditionalInput(appData, inputButton); 422 } 423 } 424 } 425 426 void additionalScreenModeEntry(xtotp_Data *appData, 427 inputLevelState inputState, 428 inputButtonPos inputButton) 429 { 430 (void)inputState; 431 (void)inputButton; 432 frontend_createBaseWindow(appData, "Theme", 433 (appData->iDisplay.theme == PBM_BLACK) ? "Light" 434 : "Dark"); 435 appData->iDisplay.show(); 436 return; 437 } 438 void additionalScreenMode(xtotp_Data *appData, 439 inputLevelState inputState, 440 inputButtonPos inputButton) 441 { 442 if (inputState == INPUT_STATE_RISING_EDGE) { 443 if (inputButton == INPUT_DEL) { 444 appData->iDisplay.theme = 445 (appData->iDisplay.theme == PBM_BLACK) ? PBM_WHITE : PBM_BLACK; 446 additionalScreenModeEntry(appData, inputState, inputButton); 447 } else { 448 checkBaseAdditionalInput(appData, inputButton); 449 } 450 } 451 } 452 453 static bool setNewDeviceID = false; 454 455 void additionalDeviceIDEntry(xtotp_Data *appData, 456 inputLevelState inputState, 457 inputButtonPos inputButton) 458 { 459 (void)appData; 460 (void)inputState; 461 setNewDeviceID = false; 462 frontend_createBaseWindow(appData, 463 "Device ID:", appData->cryptoHandler.deviceID); 464 input_readBase8(inputButton, NULL, NULL, (XTOTP_DEVICE_ID_SIZE - 1), true); 465 appData->iDisplay.show(); 466 return; 467 } 468 469 void additionalDeviceID(xtotp_Data *appData, 470 inputLevelState inputState, 471 inputButtonPos inputButton) 472 { 473 if (INPUT_STATE_RISING_EDGE == inputState) { 474 if (INPUT_DEL == inputButton && !setNewDeviceID) { 475 setNewDeviceID = true; 476 } 477 if (setNewDeviceID) { 478 uint8_t *inputString = NULL; 479 uint8_t inputLength = 0; 480 if (input_readBase8(inputButton, &inputString, &inputLength, 481 ((XTOTP_DEVICE_ID_SIZE * 8 + 2) / 3), false)) { 482 LOGGER_DEBUG("Got input string: %s with length: %u", inputString, 483 inputLength); 484 uint8_t numBuf[(XTOTP_DEVICE_ID_SIZE * 8 / 3)]; 485 base8_stringToNum(numBuf, (const char *)inputString); 486 uint32_t decodedLength = 0; 487 if (base8_decodeNum((uint8_t *)appData->cryptoHandler.deviceID, 488 &decodedLength, (XTOTP_DEVICE_ID_SIZE - 1), numBuf, 489 inputLength) == BASEX_OK) { 490 appData->cryptoHandler.deviceID[decodedLength] = '\0'; 491 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 492 appData->cryptoHandler.deviceID); 493 input_readBase8(inputButton, NULL, NULL, (XTOTP_DEVICE_ID_SIZE - 1), 494 true); 495 setNewDeviceID = false; 496 } else { 497 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 498 "ERR: PARSE"); 499 } 500 appData->iDisplay.show(); 501 } else { 502 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_CENTRED, 503 (const char *)inputString); 504 appData->iDisplay.show(); 505 } 506 } else { 507 checkBaseAdditionalInput(appData, inputButton); 508 } 509 } 510 } 511 512 void additionalFirmwareEntry(xtotp_Data *appData, 513 inputLevelState inputState, 514 inputButtonPos inputButton) 515 { 516 (void)inputState; 517 (void)inputButton; 518 frontend_createBaseWindow(appData, "Firmware", NULL); 519 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE1, "VER: %14s", 520 FIRMWARE_VERSION); 521 frontend_updateDatafield(appData, FRONTEND_DATAFIELD_LINE2, "DAT: %14s", 522 FIRMWARE_DATE); 523 appData->iDisplay.show(); 524 } 525 526 void additionalFirmware(xtotp_Data *appData, 527 inputLevelState inputState, 528 inputButtonPos inputButton) 529 { 530 if (inputState == INPUT_STATE_RISING_EDGE) { 531 checkBaseAdditionalInput(appData, inputButton); 532 } 533 } 534 535 void additionalUnusedEntry(xtotp_Data *appData, 536 inputLevelState inputState, 537 inputButtonPos inputButton) 538 { 539 (void)inputState; 540 (void)inputButton; 541 appData->currentStates.additionalSettings = ADDITIONAL_CLOCK; 542 } 543 void additionalUnused(xtotp_Data *appData, 544 inputLevelState inputState, 545 inputButtonPos inputButton) 546 { 547 (void)inputState; 548 (void)inputButton; 549 appData->currentStates.additionalSettings = ADDITIONAL_CLOCK; 550 }