DÍA 129 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

La Máquina de Estados de Interfaz (UI State Machine)

En sistemas embebidos, no puedes usar funciones bloqueantes como scanf() o gets() que detienen el universo hasta que el usuario teclea algo. El usuario podría irse a tomar un café a mitad del PIN, y si tu sistema se queda esperando, el tanque de gas podría desbordarse. La entrada de datos debe ser dinámica y construida sobre una máquina de estados controlada por un temporizador (Timeout).

Nuestra lógica necesita estados claros: VENTA_NORMAL, SOLICITANDO_PIN, EVALUANDO y MENU_SUPERVISOR. Si el usuario está en SOLICITANDO_PIN y pasan 10 segundos sin teclear nada, la máquina aborta la operación y regresa al estado seguro automáticamente.

Validación en Tiempo Constante (Constant-Time Compare)

Un ingeniero novato valida un PIN de 4 dígitos así: compara el primer dígito; si falla, retorna false inmediatamente. Si acierta, pasa al segundo dígito.
El problema: Un atacante con un osciloscopio o un temporizador de red puede medir exactamente cuánto tarda el procesador en responder "Error". Si la respuesta tarda unos nanosegundos más de lo normal, el atacante sabe que adivinó el primer número correctamente. Así, en lugar de 10,000 intentos, descifra el PIN en menos de 40 intentos. A esto se le llama Timing Attack.

En ingeniería Bare-Metal segura, validamos usando Operaciones Bitwise XOR (^) y OR (|). Evaluamos los 4 dígitos SIEMPRE, sin importar si el primero falló, garantizando que la función tarde exactamente la misma cantidad de ciclos de reloj (Tiempo Constante) independientemente de si el PIN es correcto o incorrecto.

MÁQUINA DE ESTADOS: HMI DE ACCESO METROLÓGICO MODO VENTA (Despacho Normal) Tecla 'A' ESPERANDO PIN LCD: "***_" Dígitos 0-9 (Max 4) VALIDANDO... XOR Bitwise Check Tecla 'D' MODO SUPERVISOR Cambiar Precios / Factor K PIN OK PIN ERROR o Timeout (>10 seg)

⚙️ Ejercicios Prácticos

Ejercicio 1: Integraremos la lectura del teclado por interrupción (Día 128) con una máquina de estados para capturar el PIN. Implementaremos la función de enmascaramiento con asteriscos * en la LCD y crearemos la función criptográfica validar_pin_seguro() usando aritmética de bits.


/**
 * @file hmi_security.c
 * @brief Máquina de Estados para ingreso y validación segura de PIN
 */

#include "stm32f401xe.h"
#include <stdbool.h>

// Dependencias de módulos previos
extern volatile uint32_t milisegundos_globales; // SysTick
extern void lcd_escribir_async(const char* string); // LCD Asíncrona (Día 126)
extern void lcd_set_cursor(uint8_t fila, uint8_t col);

// PIN Maestro de Supervisor (Usualmente se guarda cifrado en la EEPROM, 
// para este ejercicio lo definimos estático en la memoria Flash).
const char PIN_SUPERVISOR[4] = {'7', '3', '9', '2'};

// --- VARIABLES DE ESTADO DE LA UI ---
typedef enum {
    UI_MODO_VENTA,
    UI_INGRESANDO_PIN,
    UI_MODO_SUPERVISOR
} EstadoUI_t;

EstadoUI_t ui_estado = UI_MODO_VENTA;

// Búfer para almacenar los dígitos ingresados
char pin_buffer[4] = {0, 0, 0, 0};
uint8_t pin_indice = 0;
uint32_t pin_timeout = 0;

/**
 * @brief Validación de PIN en "Tiempo Constante" (Constant-Time).
 *        Previene ataques de canal lateral temporal. Toma siempre 
 *        la misma cantidad de ciclos de CPU acertar o fallar.
 */
bool validar_pin_seguro(void) {
    uint8_t acumulador_diferencias = 0;
    
    // Evaluamos los 4 dígitos SIEMPRE mediante XOR (^).
    // Si los caracteres son idénticos, 'XOR' produce 0. 
    // Si hay diferencia, 'XOR' produce un número mayor a 0.
    // Usamos OR (|) para acumular cualquier error sin salir del bucle.
    for (int i = 0; i < 4; i++) {
        acumulador_diferencias |= (pin_buffer[i] ^ PIN_SUPERVISOR[i]);
    }
    
    // Si el acumulador se mantuvo en 0, el PIN es exactamente igual
    return (acumulador_diferencias == 0);
}

/**
 * @brief Máquina de estados que procesa los caracteres que llegan del teclado
 */
void procesar_tecla_hmi(char tecla_presionada) {
    
    switch(ui_estado) {
        
        case UI_MODO_VENTA:
            // La tecla 'A' invoca el menú de administrador
            if (tecla_presionada == 'A') {
                ui_estado = UI_INGRESANDO_PIN;
                pin_indice = 0; // Limpiar búfer
                for(int i=0; i<4; i++) pin_buffer[i] = 0;
                
                lcd_set_cursor(0, 0);
                lcd_escribir_async("Modo Supervisor     ");
                lcd_set_cursor(1, 0);
                lcd_escribir_async("Ingrese PIN: _      ");
                
                // Iniciamos la cuenta regresiva del Timeout (10 segundos)
                pin_timeout = milisegundos_globales;
            }
            break;
            
        case UI_INGRESANDO_PIN:
            // Refrescar timeout si presiona cualquier tecla válida
            pin_timeout = milisegundos_globales;
            
            // Si presiona un número y hay espacio en el búfer
            if (tecla_presionada >= '0' && tecla_presionada <= '9') {
                if (pin_indice < 4) {
                    pin_buffer[pin_indice] = tecla_presionada;
                    pin_indice++;
                    
                    // Enmascaramos la entrada por seguridad visual
                    char mascara_visual[] = "Ingrese PIN:     ";
                    for(int i=0; i<pin_indice; i++) {
                        mascara_visual[13 + i] = '*'; // Reemplazar espacio con *
                    }
                    lcd_set_cursor(1, 0);
                    lcd_escribir_async(mascara_visual);
                }
            }
            // La tecla 'D' (Done / Enter) intenta validar
            else if (tecla_presionada == 'D') {
                if (pin_indice == 4) {
                    if (validar_pin_seguro()) {
                        ui_estado = UI_MODO_SUPERVISOR;
                        lcd_set_cursor(0, 0);
                        lcd_escribir_async("ACCESO CONCEDIDO    ");
                        lcd_set_cursor(1, 0);
                        lcd_escribir_async("1. Precio 2. FactorK");
                    } else {
                        // Error de PIN
                        ui_estado = UI_MODO_VENTA;
                        lcd_set_cursor(0, 0);
                        lcd_escribir_async("ERROR DE PIN        ");
                        lcd_set_cursor(1, 0);
                        lcd_escribir_async("Acceso Denegado     ");
                        // (Requeriría un retraso visual antes de restaurar la pantalla normal)
                    }
                }
            }
            // Tecla '*' funciona como Borrar/Cancelar
            else if (tecla_presionada == '*') {
                ui_estado = UI_MODO_VENTA;
                lcd_set_cursor(0, 0);
                lcd_escribir_async("Cancelado.          ");
            }
            break;
            
        case UI_MODO_SUPERVISOR:
            // (Lógica para modificar EEPROM, cambiar precios, etc.)
            // Tecla '*' sale del menú
            if (tecla_presionada == '*') {
                ui_estado = UI_MODO_VENTA;
                lcd_set_cursor(0, 0);
                lcd_escribir_async("Saliendo...         ");
            }
            break;
    }
}

/**
 * @brief Función auxiliar para evitar que la máquina se quede bloqueada
 *        si el usuario abandona el teclado a la mitad de un PIN.
 */
void vigilar_timeout_hmi(void) {
    if (ui_estado == UI_INGRESANDO_PIN) {
        // Si pasan más de 10,000 ms (10 seg) sin interactuar
        if ((milisegundos_globales - pin_timeout) > 10000) {
            ui_estado = UI_MODO_VENTA;
            lcd_set_cursor(0, 0);
            lcd_escribir_async("Timeout Acceso.     ");
            lcd_set_cursor(1, 0);
            lcd_escribir_async("                    ");
        }
    }
}

int main(void) {
    
    // (Inicializaciones previas de LCD, Teclado EXTI, Relojes...)
    
    while(1) {
        
        // El Súper Bucle maneja la red y la venta de gas
        
        // Si el EXTI capturó una pulsación y pasó el Debounce (Día 128)
        char nueva_tecla = escanear_teclado_async();
        if (nueva_tecla != 0) {
            procesar_tecla_hmi(nueva_tecla);
        }
        
        // Vigilar si el usuario abandonó la consola
        vigilar_timeout_hmi();
        
        // Rutina del LCD No Bloqueante (Día 126)
        // lcd_procesar_tareas();
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Sellado Metrológico e Integridad de la Facturación.

En el rubro del gas y combustibles, si un empleado descubre cómo entrar al menú de configuración para alterar el Factor K del caudalímetro (la variable que decide cuántos pulsos son 1 litro), puede modificar la máquina para entregar 900 mililitros pero cobrar por 1 Litro entero, robando sistemáticamente a los clientes.

La NOM-005 exige mecanismos de Inviolabilidad de Ajustes. Al implementar este escudo con la Máquina de Estados del PIN, has asegurado la barrera de software del despachador. Gracias a la validación de Tiempo Constante y a la arquitectura sin bloqueos, ni siquiera un auditor técnico con hardware especializado podría vulnerar el PIN inyectando comandos maliciosos o cronometrando las respuestas de tu CPU. Tu Interfaz Humano-Máquina ahora es un puente blindado hacia los parámetros críticos en la memoria EEPROM.

📝 Resumen del Día