pebble / auth / src / totp.c

/** \file
 * Google Two Factor authentication watch face.
 */
#include "pebble_os.h"
#include "pebble_app.h"
#include "pebble_fonts.h"
#include "sha1.h"

#define UUID {0x24, 0x15, 0x2D, 0xAE, 0xDD, 0x49, 0x47, 0x47, 0x96, 0xD5, 0x78, 0xB1, 0xDD, 0xBD, 0x1B, 0xB2}

PBL_APP_INFO(
	UUID,
	"2wo Factor",
	"hudson",
	2, 0, // Version
	INVALID_RESOURCE,
	APP_INFO_WATCH_FACE
);


static char time_buffer[32];
static char otp_buffer[32];
static Window window;
static TextLayer time_layer;
static TextLayer otp_layer;
static Layer bargraph_layer;
static uint32_t remaining;
static unsigned old_half;

static void
bargraph_layer_update(
	Layer * const me,
	GContext * ctx
)
{
	// Draw a white rectangle across the entire bottom
	const unsigned w = me->bounds.size.w;
	const unsigned h = me->bounds.size.h;
	graphics_context_set_fill_color(ctx, GColorWhite);
	graphics_fill_rect(ctx, GRect(0, 0, w, h), 0, GCornersAll);

	graphics_context_set_fill_color(ctx, GColorBlack);
	graphics_fill_rect(ctx, GRect(0, 0, (w * remaining) / 30, h), 4, GCornersRight);
}

#define GMT_OFFSET 5

static uint32_t unix_epoch_time(
	const PblTm * const curr_time
)
{
  long now = 0;
  now += (curr_time->tm_year-70)*31536000;
  now += ((curr_time->tm_year-69)/4)*86400;
  now -= ((curr_time->tm_year-1)/100)*86400;
  now += ((curr_time->tm_year+299)/400)*86400;
  now += curr_time->tm_yday*86400;
  now += curr_time->tm_hour*3600;
  now += curr_time->tm_min*60;
  now += curr_time->tm_sec;
  return now;
}

static void
handle_tick(
	AppContextRef ctx,
	PebbleTickEvent * const event
)
{
	(void) ctx;
	const PblTm * const ptm = event->tick_time;

	// Only recompute and update if we have a new time step
	remaining = 30 - (ptm->tm_sec % 30);
	layer_mark_dirty(&bargraph_layer);

	unsigned new_half = ptm->tm_sec > 30;
	if (new_half == old_half)
		return;
	old_half = new_half;

	string_format_time(
		time_buffer,
		sizeof(time_buffer),
		"%H:%M",
		event->tick_time
	);
	text_layer_set_text(&time_layer, time_buffer);

	// Convert the time into a unix epoch time based on our local time.
	// this should be done in a library
	const uint32_t now = unix_epoch_time(ptm) + 3600 * GMT_OFFSET;
	uint32_t auth = oauth_calc(now);

	otp_buffer[6] = '\0';
	otp_buffer[5] = '0' + auth % 10; auth /= 10;
	otp_buffer[4] = '0' + auth % 10; auth /= 10;
	otp_buffer[3] = '0' + auth % 10; auth /= 10;
	otp_buffer[2] = '0' + auth % 10; auth /= 10;
	otp_buffer[1] = '0' + auth % 10; auth /= 10;
	otp_buffer[0] = '0' + auth % 10; auth /= 10;
	text_layer_set_text(&otp_layer, otp_buffer);
}


static void
handle_init(
	AppContextRef ctx
)
{
	(void) ctx;

	// Store an invalid value in the old_half so that we will
	// be forced to recompute the current hash
	old_half = 999;

	window_init(&window, "RFC 6238");
	window_stack_push(&window, true);

	text_layer_init(&time_layer, GRect(22,10,144-24,50));
	time_buffer[0] = '\0';
	text_layer_set_text(&time_layer, time_buffer);
	text_layer_set_font(&time_layer, fonts_get_system_font(FONT_KEY_GOTHAM_42_BOLD));
	layer_add_child(&window.layer, &time_layer.layer);

	text_layer_init(&otp_layer, GRect(13,70,144-13,40));
	text_layer_set_font(&otp_layer, fonts_get_system_font(FONT_KEY_GOTHAM_30_BLACK));
	otp_buffer[0] = '\0';
	text_layer_set_text(&otp_layer, otp_buffer);
	layer_add_child(&window.layer, &otp_layer.layer);

	// Bargraph layer is across the bottom
	const unsigned bargraph_height = 20;
	layer_init(&bargraph_layer, GRect(0,168-bargraph_height-15,144,bargraph_height));
	bargraph_layer.update_proc = bargraph_layer_update;

	layer_add_child(&window.layer, &bargraph_layer);
}


void
pbl_main(
	void * const params
)
{
	PebbleAppHandlers handlers = {
		.init_handler	= &handle_init,
		.tick_info	= {
			.tick_handler = &handle_tick,
			.tick_units = SECOND_UNIT,
		},
	};

	app_event_loop(params, &handlers);
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.