passkey_fw/examples/device/hid_cdc_passkey/src/main.c

996 lines
32 KiB
C

/* SPDX-License-Identifier: MIT
* Copyright 2024 King Kévin <kingkevin@cuvoodoo.info>
*/
#include "stm32f0xx_hal.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "bsp/board_api.h"
#include "tusb.h"
#include "usb_descriptors.h"
#include "keylayouts.h"
#include "WjCryptLib_Sha256.h"
#include "date.h"
// get the length of an array
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
// print debug messages
#define DEBUG (true)
#define BUTTON1_PORT GPIOA
#define BUTTON1_PIN GPIO_PIN_5
#define BUTTON1_STATE_ACTIVE 0
#define BUTTON2_PORT GPIOA
#define BUTTON2_PIN GPIO_PIN_7
#define BUTTON2_STATE_ACTIVE 0
#define BUTTON_DEBOUNCE (50U) // debounce time in ms
#define TIMEOUT_GLOBAL (12U * 60) // maximum time to hold the credentials, in minutes
#define TIMEOUT_REPEAT (3U * 60) // maximum time after last repeat to hold the credentials, in minutes
char manuf_key[32 + 1]; // the key set by the manufacturer to authenticate the device
static struct config_t {
uint8_t layout; // keyboard layout
char user_key[32 + 1]; // the key set by the user to authenticate the device
bool button_swap; // the order of the buttons (which is for username or password)
uint16_t timeout_global; // time to hold the credentials, in minutes
uint16_t timeout_repeat; // time after last repeat to hold the credentials, in minutes
uint8_t crc; // simple XOR CRC to check config validity
} config;
// page to store manufacturer key (can't be corrupted by save config)
#define FLASH_MANUF_ADDR ((uint32_t)0x08000000 + 0x400 * 30)
// last available page of last sector to store config
#define FLASH_CONFIG_ADDR ((uint32_t)0x08000000 + 0x400 * 31)
// the credentials to store and repeat
static char username[64] = {0}; // the credentials username
static char password[64] = {0}; // the credentials password
static bool credentials = false; // if the credentials are valid
static uint32_t entered_when = 0; // when credentials have been entered
static uint32_t pasted_when = 0; // last time the credentials have been pasted
#define KEY_INTERVAL (20U) // time in ms between keypress
static bool key_pressed = false; // if HID keyboard press report is sent
static uint32_t key_when = 0; // when HID keyboard press report is sent
static uint8_t user_paste = 0; // how much of the username is pasted
static uint8_t pass_paste = 0; // how much of the username is pasted
// LED blink period, in ms
#define BLINK_TIME (500U)
// split long string into USB packets
static const char* help_str[] = {
"\r\npress key to enter menu\r\n",
"h help\r\n",
"c enter credentials (ACSII only)\r\n",
"b swap buttons\r\n",
"g set global timeout\r\n",
"r set repeat timeout\r\n",
"k set authentication key\r\n",
"a authenticate device\r\n",
"l set keyboard layout\r\n",
};
static const char* prog_str[] = {
"K set manufacturer key\r\n",
"L lock device\r\n",
"E erase firmware\r\n",
};
enum {
MENU_HOME,
MENU_USER,
MENU_PASS,
MENU_MANUF,
MENU_KEY,
MENU_AUTH,
MENU_LAYOUT,
MENU_TIMEOUT_GLOBAL,
MENU_TIMEOUT_REPEAT,
} menu = MENU_HOME;
void led_blinking_task(void);
void hid_task(void);
void cdc_task(void);
void button_task(void);
static void board_supplement_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = BUTTON1_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(BUTTON1_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = BUTTON2_PIN;
HAL_GPIO_Init(BUTTON2_PORT, &GPIO_InitStruct);
}
uint32_t board_button1_read(void)
{
return BUTTON1_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON1_PORT, BUTTON1_PIN);
}
uint32_t board_button2_read(void)
{
return BUTTON2_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON2_PORT, BUTTON2_PIN);
}
static bool locked(void)
{
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
return (OB_RDP_LEVEL_2 == ob.RDPLevel);
}
static void clear_credentials(void)
{
// clear credentials
credentials = false;
memset(username, 0, sizeof(username));
memset(password, 0, sizeof(password));
pasted_when = 0;
entered_when = 0;
printf("\r\ncredentials cleared\r\n");
}
static void load_config(void)
{
memcpy(manuf_key, (uint8_t*)FLASH_MANUF_ADDR, sizeof(manuf_key));
bool manuf_ok = false;
for (uint8_t i = 0; i < sizeof(manuf_key); i++) {
if (0 == manuf_key[i]) {
manuf_ok = true;
break;
}
}
if (!manuf_ok) {
memset(manuf_key, 0, sizeof(manuf_key));
}
memcpy(&config, (uint8_t*)FLASH_CONFIG_ADDR, sizeof(config));
uint8_t crc = 0x42; // start with non-zero value to have a checksum work with empty config
for (uint16_t i = 0; i < sizeof(config); i++) {
crc ^= *(((uint8_t*)&config) + i);
}
if (0 == crc) {
printf("config loaded\r\n");
} else {
memset(&config, 0, sizeof(config));
printf("config initialized\r\n");
}
if (config.layout >= LENGTH(name_asciimap)) {
config.layout = 0;
}
if (0 == config.timeout_global || config.timeout_global > TIMEOUT_GLOBAL) {
config.timeout_global = TIMEOUT_GLOBAL;
}
if (0 == config.timeout_repeat || config.timeout_repeat > TIMEOUT_REPEAT) {
config.timeout_repeat = TIMEOUT_REPEAT;
}
}
static void save_config(void)
{
// update CRC
config.crc = 0; // clear CRC for correct calcuation
uint8_t crc = 0x42; // start with non-zero value to have a checksum work with empty config
for (uint16_t i = 0; i < sizeof(config); i++) {
crc ^= *(((uint8_t*)&config) + i);
}
config.crc = crc;
// save to flash
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef EraseInitStruct;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = FLASH_CONFIG_ADDR;
EraseInitStruct.NbPages = 1;
uint32_t error;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
printf("earsing page failed\r\n");
goto end;
}
for (uint16_t i = 0; i < sizeof(config); i += 4) {
const uint32_t address = FLASH_CONFIG_ADDR + i;
uint32_t data;
memcpy(&data, (uint8_t*)&config + i, sizeof(data)); // works even if the data it not aligned
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
printf("flash page failed\r\n");
goto end;
}
}
printf("config saved\r\n");
end:
HAL_FLASH_Lock();
}
static void save_manuf(void)
{
// save to flash
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef EraseInitStruct;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = FLASH_MANUF_ADDR;
EraseInitStruct.NbPages = 1;
uint32_t error;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
printf("earsing page failed\r\n");
goto end;
}
for (uint16_t i = 0; i < sizeof(manuf_key); i += 4) {
const uint32_t address = FLASH_MANUF_ADDR + i;
uint32_t data;
memcpy(&data, (uint8_t*)&manuf_key[i], sizeof(data)); // works even if the data it not aligned
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
printf("flash page failed\r\n");
goto end;
}
}
printf("manufacturer key saved\r\n");
end:
HAL_FLASH_Lock();
}
// convert bytes to ASCII hex (does not add end zero)
static void b2h(const uint8_t* b, char* h, uint8_t len)
{
if (!b || !h || !len) {
return;
}
for (uint8_t i = 0; i < len; i++) {
uint8_t nibble = b[i] >> 4;
if (nibble <= 9) {
h[i * 2 + 0] = '0' + nibble;
} else {
h[i * 2 + 0] = 'a' + nibble - 0xa;
}
nibble = b[i] & 0xf;
if (nibble <= 9) {
h[i * 2 + 1] = '0' + nibble;
} else {
h[i * 2 + 1] = 'a' + nibble - 0xa;
}
}
}
// ensure string is sent
void tud_cdc_write_str_flush(const char* str)
{
while (tud_cdc_write_available() < CFG_TUD_CDC_TX_BUFSIZE) {
tud_task();
}
tud_cdc_write_str(str);
tud_cdc_write_flush();
while (tud_cdc_write_available() < CFG_TUD_CDC_TX_BUFSIZE) {
tud_task();
}
}
int main(void)
{
board_init();
board_supplement_init();
// init device stack on configured roothub port
tud_init(BOARD_TUD_RHPORT);
printf("\r\npasskey\r\n");
printf("hardware version: 2\r\n"); // for now we just have version 2
printf("firmware date: %04u-%02u-%02u\r\n", BUILD_YEAR, BUILD_MONTH, BUILD_DAY); // show firmware build date
load_config();
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
if (OB_RDP_LEVEL_2 != ob.RDPLevel) {
printf("device not locked\r\n");
}
if (0 == strlen(manuf_key)) {
printf("manufacturer key not set\r\n");
}
if (board_init_after_tusb) {
board_init_after_tusb();
}
while (true)
{
tud_task(); // tinyusb device task
// check for credential timeout
const uint32_t minutes = board_millis();
if (credentials && ((minutes > entered_when + config.timeout_global * 1000 * 60) || (minutes > entered_when + TIMEOUT_GLOBAL * 1000 * 60))) {
const char str[] = "global timeout reached, clearing cedentials\r\n";
tud_cdc_write_str(str);
tud_cdc_write_flush();
printf(str);
clear_credentials();
}
if (credentials && ((minutes > pasted_when + config.timeout_repeat * 1000 * 60) || (minutes > pasted_when + TIMEOUT_REPEAT * 1000 * 60))) {
const char str[] = "repeat timeout reached, clearing cedentials\r\n";
tud_cdc_write_str(str);
tud_cdc_write_flush();
printf(str);
clear_credentials();
}
// run the usual tasks
led_blinking_task();
button_task();
hid_task();
cdc_task();
}
}
//--------------------------------------------------------------------+
// Device callbacks
//--------------------------------------------------------------------+
// Invoked when device is mounted
void tud_mount_cb(void)
{
clear_credentials();
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
clear_credentials();
}
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en)
{
(void) remote_wakeup_en;
}
// Invoked when usb bus is resumed
void tud_resume_cb(void)
{
}
//--------------------------------------------------------------------+
// USB CDC
//--------------------------------------------------------------------+
void cdc_task(void)
{
static char tmp[64];
// connected() check for DTR bit
// Most but not all terminal client set this when making connection
// if ( tud_cdc_connected() )
{
// connected and there are data available
if (tud_cdc_available()) {
// read data
char buf[CFG_TUD_CDC_RX_BUFSIZE];
const uint32_t count = tud_cdc_read(buf, sizeof(buf));
bool echo = true; // if we echo back input
if (0 == count) {
return;
}
const char* str = NULL; // message to print
static uint16_t i = 0; // generic array index
switch (menu) {
case MENU_HOME:
switch (buf[0]) {
case 'c': // input credentials
i = 0; // reset index
menu = MENU_USER; // go to corresponding menu
str = "\r\nenter credentials to paste\r\nusername: ";
break;
case 'b': // swap buttons
config.button_swap = !config.button_swap;
str = "\r\nbuttons swapped\r\n";
save_config();
break;
case 'g': // set global timeout
i = 0; // reset index
menu = MENU_TIMEOUT_GLOBAL;
snprintf(tmp, sizeof(tmp), "\r\nenter global timeout in minutes [%u]: ", config.timeout_global);
str = tmp;
break;
case 'r': // set repeat timeout
i = 0; // reset index
menu = MENU_TIMEOUT_REPEAT;
snprintf(tmp, sizeof(tmp), "\r\nenter repeat timeout in minutes [%u]: ", config.timeout_repeat);
str = tmp;
break;
case 'k': // set user key
i = 0; // reset index
menu = MENU_KEY;
str = "\r\nenter authentication key (up to 32 char): ";
break;
case 'a': // run authentication
if (0 == strlen(config.user_key) && 0 == strlen(manuf_key)) {
str = "\r\nno key set\r\n";
} else {
i = 0; // reset index
menu = MENU_AUTH;
str = "\r\nenter random string (up to 60 char): ";
}
break;
case 'l': // set keyboard layout
echo = false;
tud_cdc_write(buf, 1); // since echo will be off
tud_cdc_write_str("\n\r");
tud_cdc_write_flush();
for (uint8_t j = 0; j < LENGTH(name_asciimap); j++) {
snprintf(tmp, sizeof(tmp), "%02u %s\r\n", j, name_asciimap[j]);
tud_cdc_write_str_flush(tmp);
}
snprintf(tmp, sizeof(tmp), "set keyboard layout [%s]: ", name_asciimap[config.layout]);
tud_cdc_write_str_flush(tmp);
i = 0; // reset index
menu = MENU_LAYOUT;
break;
// hidden menu
case 'K': // set manufacturer key
if (!locked()) {
i = 0; // reset index
menu = MENU_MANUF;
str = "\r\nenter manufacturer key (up to 32 char): ";
}
break;
case 'L': // lock device
if (!locked()) {
tud_cdc_write_str_flush("\r\nlocking device\r\n");
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.RDPLevel = OB_RDP_LEVEL_2; // level 2, disabling debug, and write protecting option byte
ob.WRPState = OB_WRPSTATE_ENABLE; // write protect pages
ob.WRPPage = 0x7; // all but last sector holding the config
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
//HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
HAL_FLASH_OB_Launch();
}
break;
case 'E': // mass erase to restart STM32 bootloader
if (!locked()) {
tud_cdc_write_str_flush("\r\nerasing firmware\r\n");
HAL_FLASH_Unlock();
FLASH_EraseInitTypeDef EraseInitStruct;
EraseInitStruct.TypeErase = FLASH_TYPEERASE_MASSERASE;
uint32_t error;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &error) != HAL_OK) {
printf("mass erase failed\r\n");
}
HAL_FLASH_Lock();
HAL_FLASH_OB_Launch(); // just to restart
}
break;
case '\r':
case '\n':
break; // nothing to do
case 'h':
default:
tud_cdc_write(buf, 1); // since echo will be off
tud_cdc_write_flush();
echo = false;
for (uint8_t j = 0; j < LENGTH(help_str); j++) {
tud_cdc_write_str_flush(help_str[j]);
}
if (!locked()) {
for (uint8_t j = 0; j < LENGTH(prog_str); j++) {
tud_cdc_write_str_flush(prog_str[j]);
}
}
}
break;
case MENU_USER:
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
username[i] = 0; // end string
str = NULL;
i = 0; // reset index
menu = MENU_PASS; // go to next menu
str = "\r\npassword: ";
} else if (i >= sizeof(username) - 2) {
memset(username, 0, sizeof(username)); // clear username
str = "\r\nlimit reached\r\n";
} else {
username[i++] = buf[j]; // save username
}
}
break;
case MENU_PASS:
echo = false; // keep secret
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
password[i] = 0; // end password
if (strlen(username) && strlen(password)) {
credentials = true;
entered_when = board_millis();
pasted_when = board_millis();
str = "\r\ncredentials saved\r\n";
} else {
credentials = false;
str = "\r\ninvalid credentials\r\n";
}
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(password) - 2) {
memset(password, 0, sizeof(password)); // clear password
str = "\r\nlimit reached\r\n";
} else {
password[i++] = buf[j]; // save password
}
}
break;
case MENU_MANUF:
echo = false; // keep secret
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end key
if (strlen(tmp)) {
memset(manuf_key, 0, sizeof(manuf_key));
memcpy(manuf_key, tmp, i);
save_manuf();
str = "\r\nmanufacturer key saved\r\n";
} else {
str = "\r\ninvalid input\r\n";
}
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(config.user_key) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save password
}
}
break;
case MENU_KEY:
echo = false; // keep secret
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end key
if (strlen(tmp)) {
memset(config.user_key, 0, sizeof(config.user_key));
memcpy(config.user_key, tmp, i);
save_config();
str = "\r\nauthentication key saved\r\n";
} else {
echo = true;
str = "\r\ninvalid input\r\n";
}
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(config.user_key) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save password
}
}
break;
case MENU_AUTH:
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end string
if (strlen(tmp)) {
echo = false; // we need to write now
if (strlen(manuf_key)) {
char uid[4 * 3 * 2 + 1];
b2h((uint8_t*)UID_BASE, uid, 4 * 3);
uid[sizeof(uid) - 1] = 0; // end string
tud_cdc_write_str_flush("\r\ngo to https://passkey.cuvoodoo.info/?auth=");
tud_cdc_write_str_flush(uid);
tud_cdc_write_str_flush(tmp);
uint8_t hash_in[sizeof(manuf_key) + sizeof(tmp)];
memcpy(&hash_in[0], manuf_key, strlen(manuf_key));
memcpy(&hash_in[strlen(manuf_key)], tmp, strlen(tmp));
SHA256_HASH hash_out;
Sha256Calculate(hash_in, strlen(manuf_key) + strlen(tmp), &hash_out);
char hash_str[SHA256_HASH_SIZE * 2 + 1];
b2h(hash_out.bytes, hash_str, SHA256_HASH_SIZE);
hash_str[SHA256_HASH_SIZE * 2] = 0; // end string
tud_cdc_write_str_flush("\r\nexpected response: ");
tud_cdc_write_str_flush(hash_str); // luckily the USB packet len is the same as the string
}
if (strlen(config.user_key)) {
uint8_t hash_in[sizeof(config.user_key) + sizeof(tmp)];
memcpy(&hash_in[0], config.user_key, strlen(config.user_key));
memcpy(&hash_in[strlen(config.user_key)], tmp, strlen(tmp));
SHA256_HASH hash_out;
Sha256Calculate(hash_in, strlen(config.user_key) + strlen(tmp), &hash_out);
char hash_str[SHA256_HASH_SIZE * 2 + 1];
b2h(hash_out.bytes, hash_str, SHA256_HASH_SIZE);
hash_str[SHA256_HASH_SIZE * 2] = 0; // end string
tud_cdc_write_str_flush("\r\necho -n \"<key>");
tud_cdc_write_str_flush(tmp);
tud_cdc_write_str_flush("\" | sha256sum\r\n");
tud_cdc_write_str_flush(hash_str); // luckily the USB packet len is the same as the string
}
str = "\r\n";
} else {
str = "\r\ninvalid input\r\n";
}
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(config.user_key) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save password
}
}
break;
case MENU_LAYOUT:
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end time input
const uint16_t layout = atoi(tmp);
if (0 == strlen(tmp)) {
str = "\r\n"; // leave previous
} else if (layout > LENGTH(map_asciimap)) {
str = "\r\ninvalid entry\r\n";
} else {
config.layout = layout;
save_config();
str = "\r\nlayout set\r\n";
}
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(tmp) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save user entry
}
}
break;
case MENU_TIMEOUT_GLOBAL:
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end time input
const uint16_t time = atoi(tmp);
if (0 == strlen(tmp)) {
str = "\r\n"; // leave previous
} else {
if (0 == time || config.timeout_global > TIMEOUT_GLOBAL) {
config.timeout_global = TIMEOUT_GLOBAL;
}
save_config();
}
snprintf(tmp, sizeof(tmp), "\r\nglobal timeout set to %u minutes\r\n", config.timeout_global);
str = tmp;
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(tmp) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save password
}
}
break;
case MENU_TIMEOUT_REPEAT:
for (uint16_t j = 0; j < count; j++) {
if ('\r' == buf[j] || '\n' == buf[j]) { // end received
tmp[i] = 0; // end time input
const uint16_t time = atoi(tmp);
if (0 == strlen(tmp)) {
str = "\r\n"; // leave previous
} else {
if (0 == time || config.timeout_repeat > TIMEOUT_REPEAT) {
config.timeout_repeat = TIMEOUT_REPEAT;
}
save_config();
}
snprintf(tmp, sizeof(tmp), "\r\nrepeat timeout set to %u minutes\r\n", config.timeout_repeat);
str = tmp;
i = 0; // reset index
menu = MENU_HOME; // go to next menu
} else if (i >= sizeof(tmp) - 2) {
memset(tmp, 0, sizeof(tmp)); // clear password
str = "\r\nlimit reached\r\n";
} else {
tmp[i++] = buf[j]; // save password
}
}
break;
default: // unknown menu
menu = MENU_HOME; // revert to home
}
if (echo) {
tud_cdc_write(buf, count);
}
if (str) {
tud_cdc_write_str(str);
}
if (echo || str) {
tud_cdc_write_flush();
}
}
}
}
// Invoked when cdc when line state changed e.g connected/disconnected
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
(void) itf;
(void) rts;
// TODO set some indicator
if (dtr) {
// Terminal connected
} else {
// Terminal disconnected
}
}
// Invoked when CDC interface received data from host
void tud_cdc_rx_cb(uint8_t itf)
{
(void) itf;
}
//--------------------------------------------------------------------+
// USB HID
//--------------------------------------------------------------------+
static void send_hid_report(uint8_t modifier, uint8_t keycode)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) {
return;
}
uint8_t keycodes[6] = {keycode, 0, 0, 0, 0, 0};
if (keycode) {
key_when = board_millis(); // remember when we sent the key
key_pressed = true; // remember we pressed a key
} else {
key_pressed = false; // key released
}
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, modifier, keycodes);
}
static void send_hid_char(char c)
{
// set default to US
if (config.layout >= LENGTH(map_asciimap)) {
config.layout = 0;
}
if (0 == c) {
send_hid_report(0, 0);
} else if ('\t' == c) {
send_hid_report(0, KEY_TAB);
} else if ('\r' == c || '\n' == c) {
send_hid_report(0, KEY_ENTER);
} else if (c >= ' ' && c <= '~') {
const uint16_t code = map_asciimap[config.layout][c - ' '];
const uint8_t modifier = code >> 8;
const uint8_t keycode = code & 0xff;
send_hid_report(modifier, keycode);
}
}
void hid_task(void)
{
if (key_when) { // pasting started
if (board_millis() < key_when + KEY_INTERVAL) {
return; // wait more before next key press
}
if (!credentials) { // nothing to send
key_when = 0;
user_paste = 0;
pass_paste = 0;
return;
}
if (pass_paste) { // send password
if (pass_paste < strlen(password)) { // send password
send_hid_char(password[pass_paste++]);
} else if (pass_paste == strlen(password)) { // press enter
send_hid_char('\n');
pass_paste++;
} else if (pass_paste == (strlen(password) + 1)) { // finished pasting
user_paste = 0;
pass_paste = 0;
key_when = 0;
}
}
if (user_paste) { // send username
if (user_paste < strlen(username)) { // send username
send_hid_char(username[user_paste++]);
} else if (user_paste == strlen(username)) { // switch to password
send_hid_char('\t');
user_paste++;
} else if (user_paste == (strlen(username) + 1)) { // start sending password
pass_paste = 0;
send_hid_char(password[pass_paste++]);
user_paste++;
}
}
}
}
static void paste_credentials(bool user)
{
if (!credentials || 0 == strlen(username) || 0 == strlen(password)) {
printf("no credentials to paste\r\n");
return;
}
user_paste = 0;
pass_paste = 0;
pasted_when = board_millis();
if (user) {
send_hid_char(username[user_paste++]);
printf("pasting username and password\r\n");
} else {
send_hid_char(password[pass_paste++]);
printf("pasting password\r\n");
}
}
void button_task(void)
{
static uint32_t button1_ms = 0; // when the button has been pressed (in ms)
static bool button1_ok = false; // if the button is pressed (after debounce)
static uint32_t button2_ms = 0; // when the button has been pressed (in ms)
static bool button2_ok = false; // if the button is pressed (after debounce)
// Poll every 10ms
const uint32_t interval_ms = 10;
static uint32_t start_ms = 0;
if ( board_millis() - start_ms < interval_ms) return; // not enough time
start_ms += interval_ms;
const bool button1_pressed = board_button1_read();
const bool button2_pressed = board_button2_read();
// Remote wakeup
if ( tud_suspended() && (button1_pressed || button2_pressed) ) {
// Wake up host if we are in suspend mode
// and REMOTE_WAKEUP feature is enabled by host
tud_remote_wakeup();
return;
}
// ensure button is pressed
if (button1_pressed && !button1_ok) {
if (0 == button1_ms) {
button1_ms = board_millis();
} else if ( board_millis() + BUTTON_DEBOUNCE > button1_ms ) {
button1_ok = true;
}
}
if (button2_pressed && !button2_ok) {
if (0 == button2_ms) {
button2_ms = board_millis();
} else if ( board_millis() + BUTTON_DEBOUNCE > button2_ms ) {
button2_ok = true;
}
}
// check which button is pressed/released
if (button1_pressed && button2_pressed && button1_ok && button2_ok) { // two buttons pressed
if (credentials) {
clear_credentials();
printf("clearing credentials\r\n");
}
} else if (!button1_pressed && button1_ok && !button2_ok) { // button 1 released
//past_credentials(!config.button_swap); // I have no idea why this does not work
if (config.button_swap) {
paste_credentials(false);
} else {
paste_credentials(true);
}
} else if (!button2_pressed && !button1_ok && button2_ok) { // button 2 released
if (config.button_swap) {
paste_credentials(true);
} else {
paste_credentials(false);
}
}
// clear button state
if (!button1_pressed) {
button1_ok = false;
button1_ms = 0;
}
if (!button2_pressed) {
button2_ok = false;
button2_ms = 0;
}
}
// Invoked when sent REPORT successfully to host
// Application can use this to send the next report
// Note: For composite reports, report[0] is report ID
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
{
(void) instance;
(void) report;
(void) len;
if ((report[0] == REPORT_ID_KEYBOARD) && key_pressed) {
//tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL); // release press
send_hid_report(0, 0); // release key
//return;
}
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
// TODO not Implemented
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
(void) instance;
static uint8_t kbd_leds_last = 0; // last state
static uint32_t kbd_leds_when = 0; // when did the state change
static uint8_t kbd_leds_count = 0; // how many times it changed (within a certain period)
if (report_type == HID_REPORT_TYPE_OUTPUT) {
// Set keyboard LED e.g Capslock, Numlock etc...
if (report_id == REPORT_ID_KEYBOARD) {
// bufsize should be (at least) 1
if ( bufsize < 1 ) return;
uint8_t const kbd_leds = buffer[0];
uint8_t const kbd_leds_change = kbd_leds ^ kbd_leds_last;
if (kbd_leds_change & (KEYBOARD_LED_CAPSLOCK | KEYBOARD_LED_NUMLOCK)) { // numlock or capslock pressed
if (board_millis() - kbd_leds_when > 500) { // not fast enough
kbd_leds_count = 0; // reset count
}
kbd_leds_count++;
kbd_leds_when = board_millis();
if (kbd_leds_count > 3 && credentials) {
clear_credentials();
}
}
kbd_leds_last = kbd_leds; // remember for next comparision
}
}
}
//--------------------------------------------------------------------+
// BLINKING TASK
//--------------------------------------------------------------------+
void led_blinking_task(void)
{
static uint32_t start_ms = 0;
static bool led_state = false;
// Blink every interval ms
if ( board_millis() - start_ms < BLINK_TIME) return; // not enough time
start_ms = board_millis();
if (!credentials) {
led_state = !led_state; // toggle
} else {
led_state = true;
}
board_led_write(led_state);
board_uart_write(".", 1); // debug heartbeat
}