2014-10-23 16:09:41 +00:00
/*
2017-11-05 16:46:34 +00:00
* This file is part of the TREZOR project , https : //trezor.io/
2014-10-23 16:09:41 +00:00
*
* Copyright ( C ) 2014 Pavol Rusnak < stick @ satoshilabs . com >
*
* This library is free software : you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <libopencm3/usb/usbd.h>
# include <libopencm3/stm32/flash.h>
# include <string.h>
# include "buttons.h"
# include "bootloader.h"
# include "oled.h"
# include "rng.h"
# include "usb.h"
# include "layout.h"
# include "util.h"
# include "signatures.h"
# include "sha2.h"
2017-07-04 17:34:25 +00:00
# include "ecdsa.h"
# include "secp256k1.h"
2018-01-18 14:21:48 +00:00
# include "memzero.h"
2019-01-27 10:58:30 +00:00
# include "memory.h"
2014-10-23 16:09:41 +00:00
2018-08-27 15:22:04 +00:00
# include "usb21_standard.h"
# include "webusb.h"
# include "winusb.h"
2019-01-27 10:58:30 +00:00
# include "usb_desc.h"
# include "usb_send.h"
# include "usb_erase.h"
2014-10-23 16:09:41 +00:00
enum {
STATE_READY ,
STATE_OPEN ,
STATE_FLASHSTART ,
STATE_FLASHING ,
STATE_CHECK ,
STATE_END ,
} ;
static uint32_t flash_pos = 0 , flash_len = 0 ;
2019-01-27 10:58:30 +00:00
static uint32_t chunk_idx = 0 ;
2014-10-23 16:09:41 +00:00
static char flash_state = STATE_READY ;
2019-01-27 10:58:30 +00:00
static uint32_t FW_HEADER [ FLASH_FWHEADER_LEN / sizeof ( uint32_t ) ] ;
static uint32_t FW_CHUNK [ FW_CHUNK_SIZE / sizeof ( uint32_t ) ] ;
2014-10-23 16:09:41 +00:00
2019-01-27 10:58:30 +00:00
static void check_and_write_chunk ( void )
2014-10-23 16:09:41 +00:00
{
2019-01-27 10:58:30 +00:00
uint32_t offset = ( chunk_idx = = 0 ) ? FLASH_FWHEADER_LEN : 0 ;
uint32_t chunk_pos = flash_pos % FW_CHUNK_SIZE ;
if ( chunk_pos = = 0 ) {
chunk_pos = FW_CHUNK_SIZE ;
}
uint8_t hash [ 32 ] ;
SHA256_CTX ctx ;
sha256_Init ( & ctx ) ;
sha256_Update ( & ctx , ( const uint8_t * ) FW_CHUNK + offset , chunk_pos - offset ) ;
if ( chunk_pos < 64 * 1024 ) {
// pad with FF
for ( uint32_t i = chunk_pos ; i < 64 * 1024 ; i + = 4 ) {
sha256_Update ( & ctx , ( const uint8_t * ) " \xFF \xFF \xFF \xFF " , 4 ) ;
}
}
sha256_Final ( & ctx , hash ) ;
2014-10-23 16:09:41 +00:00
2019-01-27 10:58:30 +00:00
const image_header * hdr = ( const image_header * ) FW_HEADER ;
// invalid chunk sent
if ( 0 ! = memcmp ( hash , hdr - > hashes + chunk_idx * 32 , 32 ) ) {
// erase storage
erase_storage ( ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Error installing " , " firmware. " ) ;
2019-01-27 10:58:30 +00:00
return ;
}
2017-06-29 16:11:19 +00:00
2019-01-27 10:58:30 +00:00
flash_wait_for_last_operation ( ) ;
flash_clear_status_flags ( ) ;
2017-06-29 16:11:19 +00:00
flash_unlock ( ) ;
2019-01-27 10:58:30 +00:00
for ( uint32_t i = offset / sizeof ( uint32_t ) ; i < chunk_pos / sizeof ( uint32_t ) ; i + + ) {
flash_program_word ( FLASH_FWHEADER_START + chunk_idx * FW_CHUNK_SIZE + i * sizeof ( uint32_t ) , FW_CHUNK [ i ] ) ;
2017-06-29 16:11:19 +00:00
}
2019-01-27 10:58:30 +00:00
flash_wait_for_last_operation ( ) ;
2017-06-29 16:11:19 +00:00
flash_lock ( ) ;
2019-01-27 10:58:30 +00:00
// all done
if ( flash_len = = flash_pos ) {
// check remaining chunks if any
for ( uint32_t i = chunk_idx + 1 ; i < 16 ; i + + ) {
// hash should be empty if the chunk is unused
2019-02-21 14:18:00 +00:00
if ( ! mem_is_empty ( hdr - > hashes + 32 * i , 32 ) ) {
2019-01-27 10:58:30 +00:00
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Error installing " , " firmware. " ) ;
2019-01-27 10:58:30 +00:00
return ;
}
}
}
memzero ( FW_CHUNK , sizeof ( FW_CHUNK ) ) ;
chunk_idx + + ;
2017-06-29 16:11:19 +00:00
}
2018-08-27 15:22:04 +00:00
static void rx_callback ( usbd_device * dev , uint8_t ep )
2014-10-23 16:09:41 +00:00
{
( void ) ep ;
2019-01-27 10:58:30 +00:00
static uint16_t msg_id = 0xFFFF ;
2014-10-23 16:09:41 +00:00
static uint8_t buf [ 64 ] __attribute__ ( ( aligned ( 4 ) ) ) ;
2019-01-27 10:58:30 +00:00
static uint32_t w ;
2014-10-23 16:09:41 +00:00
static int wi ;
2019-01-27 10:58:30 +00:00
static int old_was_signed ;
2014-10-23 16:09:41 +00:00
2016-05-23 17:42:25 +00:00
if ( usbd_ep_read_packet ( dev , ENDPOINT_ADDRESS_OUT , buf , 64 ) ! = 64 ) return ;
2014-10-23 16:09:41 +00:00
if ( flash_state = = STATE_END ) {
return ;
}
if ( flash_state = = STATE_READY | | flash_state = = STATE_OPEN | | flash_state = = STATE_FLASHSTART | | flash_state = = STATE_CHECK ) {
if ( buf [ 0 ] ! = ' ? ' | | buf [ 1 ] ! = ' # ' | | buf [ 2 ] ! = ' # ' ) { // invalid start - discard
return ;
}
// struct.unpack(">HL") => msg, size
msg_id = ( buf [ 3 ] < < 8 ) + buf [ 4 ] ;
}
if ( flash_state = = STATE_READY | | flash_state = = STATE_OPEN ) {
if ( msg_id = = 0x0000 ) { // Initialize message (id 0)
send_msg_features ( dev ) ;
flash_state = STATE_OPEN ;
return ;
}
2018-03-07 13:16:24 +00:00
if ( msg_id = = 0x0037 ) { // GetFeatures message (id 55)
send_msg_features ( dev ) ;
return ;
}
2014-10-23 16:09:41 +00:00
if ( msg_id = = 0x0001 ) { // Ping message (id 1)
send_msg_success ( dev ) ;
return ;
2017-06-29 15:31:23 +00:00
}
2018-03-15 02:15:08 +00:00
if ( msg_id = = 0x0005 ) { // WipeDevice message (id 5)
layoutDialog ( & bmp_icon_question , " Cancel " , " Confirm " , NULL , " Do you really want to " , " wipe the device? " , NULL , " All data will be lost. " , NULL , NULL ) ;
2019-01-27 10:58:30 +00:00
bool but = get_button_response ( ) ;
if ( but ) {
erase_storage_code_progress ( ) ;
2018-03-15 02:15:08 +00:00
flash_state = STATE_END ;
layoutDialog ( & bmp_icon_ok , NULL , NULL , NULL , " Device " , " successfully wiped. " , NULL , " You may now " , " unplug your TREZOR. " , NULL ) ;
send_msg_success ( dev ) ;
} else {
flash_state = STATE_END ;
layoutDialog ( & bmp_icon_warning , NULL , NULL , NULL , " Device wipe " , " aborted. " , NULL , " You may now " , " unplug your TREZOR. " , NULL ) ;
send_msg_failure ( dev ) ;
}
return ;
}
2014-10-23 16:09:41 +00:00
}
if ( flash_state = = STATE_OPEN ) {
if ( msg_id = = 0x0006 ) { // FirmwareErase message (id 6)
2019-01-27 10:58:30 +00:00
bool proceed = false ;
if ( firmware_present_new ( ) ) {
2017-04-16 17:28:25 +00:00
layoutDialog ( & bmp_icon_question , " Abort " , " Continue " , NULL , " Install new " , " firmware? " , NULL , " Never do this without " , " your recovery card! " , NULL ) ;
2019-01-27 10:58:30 +00:00
proceed = get_button_response ( ) ;
} else {
proceed = true ;
2017-04-16 17:28:25 +00:00
}
2019-01-27 10:58:30 +00:00
if ( proceed ) {
// check whether the current firmware is signed (old or new method)
if ( firmware_present_new ( ) ) {
const image_header * hdr = ( const image_header * ) FLASH_PTR ( FLASH_FWHEADER_START ) ;
old_was_signed = ( SIG_OK = = signatures_new_ok ( hdr , NULL ) ) & & ( SIG_OK = = check_firmware_hashes ( hdr ) ) ;
} else if ( firmware_present_old ( ) ) {
old_was_signed = signatures_old_ok ( ) ;
2017-12-15 14:49:02 +00:00
} else {
2019-01-27 10:58:30 +00:00
old_was_signed = SIG_FAIL ;
2014-10-23 16:09:41 +00:00
}
2019-01-27 10:58:30 +00:00
erase_code_progress ( ) ;
2014-10-23 16:09:41 +00:00
send_msg_success ( dev ) ;
flash_state = STATE_FLASHSTART ;
2019-01-27 10:58:30 +00:00
} else {
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
layoutDialog ( & bmp_icon_warning , NULL , NULL , NULL , " Firmware installation " , " aborted. " , NULL , " You may now " , " unplug your TREZOR. " , NULL ) ;
2014-10-23 16:09:41 +00:00
}
return ;
}
return ;
}
if ( flash_state = = STATE_FLASHSTART ) {
if ( msg_id = = 0x0007 ) { // FirmwareUpload message (id 7)
if ( buf [ 9 ] ! = 0x0a ) { // invalid contents
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Error installing " , " firmware. " ) ;
2014-10-23 16:09:41 +00:00
return ;
}
// read payload length
2019-02-10 12:17:25 +00:00
const uint8_t * p = buf + 10 ;
2014-10-23 16:09:41 +00:00
flash_len = readprotobufint ( & p ) ;
2019-01-27 10:58:30 +00:00
if ( flash_len < = FLASH_FWHEADER_LEN ) { // firmware is too small
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Firmware is too small. " , NULL ) ;
2019-01-27 10:58:30 +00:00
return ;
}
if ( flash_len > FLASH_FWHEADER_LEN + FLASH_APP_LEN ) { // firmware is too big
2014-10-23 16:09:41 +00:00
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Firmware is too big. " , NULL ) ;
2014-10-23 16:09:41 +00:00
return ;
}
2017-08-20 12:43:21 +00:00
// check firmware magic
2019-01-27 10:58:30 +00:00
if ( memcmp ( p , & FIRMWARE_MAGIC_NEW , 4 ) ! = 0 ) {
2017-08-20 12:43:21 +00:00
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Wrong firmware header. " , NULL ) ;
2017-08-20 12:43:21 +00:00
return ;
}
2019-01-27 10:58:30 +00:00
memzero ( FW_HEADER , sizeof ( FW_HEADER ) ) ;
memzero ( FW_CHUNK , sizeof ( FW_CHUNK ) ) ;
2014-10-23 16:09:41 +00:00
flash_state = STATE_FLASHING ;
2019-01-27 10:58:30 +00:00
flash_pos = 0 ;
chunk_idx = 0 ;
w = 0 ;
2014-10-23 16:09:41 +00:00
while ( p < buf + 64 ) {
2019-01-27 10:58:30 +00:00
w = ( w > > 8 ) | ( * p < < 24 ) ; // assign byte to first byte of uint32_t w
2014-10-23 16:09:41 +00:00
wi + + ;
if ( wi = = 4 ) {
2019-01-27 10:58:30 +00:00
FW_HEADER [ flash_pos / 4 ] = w ;
2014-10-23 16:09:41 +00:00
flash_pos + = 4 ;
wi = 0 ;
}
p + + ;
}
return ;
}
return ;
}
if ( flash_state = = STATE_FLASHING ) {
if ( buf [ 0 ] ! = ' ? ' ) { // invalid contents
send_msg_failure ( dev ) ;
flash_state = STATE_END ;
2019-02-21 14:18:00 +00:00
show_halt ( " Error installing " , " firmware. " ) ;
2014-10-23 16:09:41 +00:00
return ;
}
2019-01-27 10:58:30 +00:00
static uint8_t flash_anim = 0 ;
2016-05-24 22:41:13 +00:00
if ( flash_anim % 32 = = 4 ) {
2014-12-21 17:58:56 +00:00
layoutProgress ( " INSTALLING ... Please wait " , 1000 * flash_pos / flash_len ) ;
}
2014-10-23 16:09:41 +00:00
flash_anim + + ;
2019-01-27 10:58:30 +00:00
const uint8_t * p = buf + 1 ;
2014-10-23 16:09:41 +00:00
while ( p < buf + 64 & & flash_pos < flash_len ) {
2019-01-27 10:58:30 +00:00
w = ( w > > 8 ) | ( * p < < 24 ) ; // assign byte to first byte of uint32_t w
2014-10-23 16:09:41 +00:00
wi + + ;
if ( wi = = 4 ) {
2019-01-27 10:58:30 +00:00
if ( flash_pos < FLASH_FWHEADER_LEN ) {
FW_HEADER [ flash_pos / 4 ] = w ;
2014-10-23 16:09:41 +00:00
} else {
2019-01-27 10:58:30 +00:00
FW_CHUNK [ ( flash_pos % FW_CHUNK_SIZE ) / 4 ] = w ;
2014-10-23 16:09:41 +00:00
}
flash_pos + = 4 ;
wi = 0 ;
2019-01-27 10:58:30 +00:00
// finished the whole chunk
if ( flash_pos % FW_CHUNK_SIZE = = 0 ) {
check_and_write_chunk ( ) ;
}
2014-10-23 16:09:41 +00:00
}
p + + ;
}
// flashing done
if ( flash_pos = = flash_len ) {
2019-01-27 10:58:30 +00:00
// flush remaining data in the last chunk
if ( flash_pos % FW_CHUNK_SIZE > 0 ) {
check_and_write_chunk ( ) ;
}
2014-10-23 16:09:41 +00:00
flash_state = STATE_CHECK ;
2019-01-27 10:58:30 +00:00
const image_header * hdr = ( const image_header * ) FW_HEADER ;
if ( SIG_OK ! = signatures_new_ok ( hdr , NULL ) ) {
2017-07-25 15:24:40 +00:00
send_msg_buttonrequest_firmwarecheck ( dev ) ;
return ;
}
} else {
return ;
2014-10-23 16:09:41 +00:00
}
}
if ( flash_state = = STATE_CHECK ) {
2019-01-27 10:58:30 +00:00
// use the firmware header from RAM
const image_header * hdr = ( const image_header * ) FW_HEADER ;
bool hash_check_ok ;
// show fingerprint of unsigned firmware
if ( SIG_OK ! = signatures_new_ok ( hdr , NULL ) ) {
2017-07-25 15:24:40 +00:00
if ( msg_id ! = 0x001B ) { // ButtonAck message (id 27)
return ;
}
uint8_t hash [ 32 ] ;
2019-01-27 10:58:30 +00:00
compute_firmware_fingerprint ( hdr , hash ) ;
layoutFirmwareFingerprint ( hash ) ;
hash_check_ok = get_button_response ( ) ;
} else {
hash_check_ok = true ;
2017-06-30 14:52:00 +00:00
}
2014-12-21 17:58:56 +00:00
layoutProgress ( " INSTALLING ... Please wait " , 1000 ) ;
2017-12-15 14:49:02 +00:00
// wipe storage if:
2019-01-27 10:58:30 +00:00
// 1) old firmware was unsigned or not present
// 2) signatures are not OK
// 3) hashes are not OK
if ( SIG_OK ! = old_was_signed | | SIG_OK ! = signatures_new_ok ( hdr , NULL ) | | SIG_OK ! = check_firmware_hashes ( hdr ) ) {
// erase storage
erase_storage ( ) ;
// check erasure
uint8_t hash [ 32 ] ;
sha256_Raw ( FLASH_PTR ( FLASH_STORAGE_START ) , FLASH_STORAGE_LEN , hash ) ;
if ( memcmp ( hash , " \x2d \x86 \x4c \x0b \x78 \x9a \x43 \x21 \x4e \xee \x85 \x24 \xd3 \x18 \x20 \x75 \x12 \x5e \x5c \xa2 \xcd \x52 \x7f \x35 \x82 \xec \x87 \xff \xd9 \x40 \x76 \xbc " , 32 ) ! = 0 ) {
send_msg_failure ( dev ) ;
2019-02-21 14:18:00 +00:00
show_halt ( " Error installing " , " firmware. " ) ;
2019-01-27 10:58:30 +00:00
return ;
}
2017-08-20 12:43:21 +00:00
}
2019-01-27 10:58:30 +00:00
flash_wait_for_last_operation ( ) ;
flash_clear_status_flags ( ) ;
flash_unlock ( ) ;
// write firmware header only when hash was confirmed
2017-08-20 12:43:21 +00:00
if ( hash_check_ok ) {
2019-01-27 10:58:30 +00:00
for ( size_t i = 0 ; i < FLASH_FWHEADER_LEN / sizeof ( uint32_t ) ; i + + ) {
flash_program_word ( FLASH_FWHEADER_START + i * sizeof ( uint32_t ) , FW_HEADER [ i ] ) ;
}
2014-10-23 16:09:41 +00:00
} else {
2019-01-27 10:58:30 +00:00
for ( size_t i = 0 ; i < FLASH_FWHEADER_LEN / sizeof ( uint32_t ) ; i + + ) {
flash_program_word ( FLASH_FWHEADER_START + i * sizeof ( uint32_t ) , 0 ) ;
}
2014-10-23 16:09:41 +00:00
}
2019-01-27 10:58:30 +00:00
flash_wait_for_last_operation ( ) ;
flash_lock ( ) ;
2017-08-20 12:43:21 +00:00
2014-10-23 16:09:41 +00:00
flash_state = STATE_END ;
if ( hash_check_ok ) {
2016-06-08 15:55:25 +00:00
layoutDialog ( & bmp_icon_ok , NULL , NULL , NULL , " New firmware " , " successfully installed. " , NULL , " You may now " , " unplug your TREZOR. " , NULL ) ;
2014-10-23 16:09:41 +00:00
send_msg_success ( dev ) ;
2019-01-27 10:58:30 +00:00
shutdown ( ) ;
2014-10-23 16:09:41 +00:00
} else {
2016-06-08 15:55:25 +00:00
layoutDialog ( & bmp_icon_warning , NULL , NULL , NULL , " Firmware installation " , " aborted. " , NULL , " You need to repeat " , " the procedure with " , " the correct firmware. " ) ;
2014-10-23 16:09:41 +00:00
send_msg_failure ( dev ) ;
2019-01-27 10:58:30 +00:00
shutdown ( ) ;
2014-10-23 16:09:41 +00:00
}
return ;
}
}
2018-08-27 15:22:04 +00:00
static void set_config ( usbd_device * dev , uint16_t wValue )
2014-10-23 16:09:41 +00:00
{
( void ) wValue ;
2018-08-27 15:22:04 +00:00
usbd_ep_setup ( dev , ENDPOINT_ADDRESS_IN , USB_ENDPOINT_ATTR_INTERRUPT , 64 , 0 ) ;
usbd_ep_setup ( dev , ENDPOINT_ADDRESS_OUT , USB_ENDPOINT_ATTR_INTERRUPT , 64 , rx_callback ) ;
2014-10-23 16:09:41 +00:00
}
static usbd_device * usbd_dev ;
2018-08-27 15:22:04 +00:00
static uint8_t usbd_control_buffer [ 256 ] __attribute__ ( ( aligned ( 2 ) ) ) ;
static const struct usb_device_capability_descriptor * capabilities [ ] = {
( const struct usb_device_capability_descriptor * ) & webusb_platform_capability_descriptor ,
} ;
static const struct usb_bos_descriptor bos_descriptor = {
. bLength = USB_DT_BOS_SIZE ,
. bDescriptorType = USB_DT_BOS ,
. bNumDeviceCaps = sizeof ( capabilities ) / sizeof ( capabilities [ 0 ] ) ,
. capabilities = capabilities
} ;
2019-01-27 10:58:30 +00:00
static void usbInit ( void )
2018-08-27 15:22:04 +00:00
{
usbd_dev = usbd_init ( & otgfs_usb_driver , & dev_descr , & config , usb_strings , sizeof ( usb_strings ) / sizeof ( const char * ) , usbd_control_buffer , sizeof ( usbd_control_buffer ) ) ;
usbd_register_set_config_callback ( usbd_dev , set_config ) ;
usb21_setup ( usbd_dev , & bos_descriptor ) ;
2019-01-31 21:18:08 +00:00
webusb_setup ( usbd_dev , " trezor.io/start " ) ;
2018-08-27 15:22:04 +00:00
winusb_setup ( usbd_dev , USB_INTERFACE_INDEX_MAIN ) ;
}
2014-10-23 16:09:41 +00:00
2019-01-27 10:58:30 +00:00
static void checkButtons ( void )
2016-10-06 13:02:44 +00:00
{
2016-12-06 15:09:09 +00:00
static bool btn_left = false , btn_right = false , btn_final = false ;
if ( btn_final ) {
return ;
}
2016-10-06 13:02:44 +00:00
uint16_t state = gpio_port_read ( BTN_PORT ) ;
if ( ( state & ( BTN_PIN_YES | BTN_PIN_NO ) ) ! = ( BTN_PIN_YES | BTN_PIN_NO ) ) {
if ( ( state & BTN_PIN_NO ) ! = BTN_PIN_NO ) {
2016-12-06 15:09:09 +00:00
btn_left = true ;
2016-10-06 13:02:44 +00:00
}
if ( ( state & BTN_PIN_YES ) ! = BTN_PIN_YES ) {
2016-12-06 15:09:09 +00:00
btn_right = true ;
2016-10-06 13:02:44 +00:00
}
2016-12-06 15:09:09 +00:00
}
if ( btn_left ) {
oledBox ( 0 , 0 , 3 , 3 , true ) ;
}
if ( btn_right ) {
oledBox ( OLED_WIDTH - 4 , 0 , OLED_WIDTH - 1 , 3 , true ) ;
}
if ( btn_left | | btn_right ) {
2016-10-06 13:02:44 +00:00
oledRefresh ( ) ;
}
2016-12-06 15:09:09 +00:00
if ( btn_left & & btn_right ) {
btn_final = true ;
}
2016-10-06 13:02:44 +00:00
}
2019-01-27 10:58:30 +00:00
void usbLoop ( void )
2014-10-23 16:09:41 +00:00
{
2019-01-27 10:58:30 +00:00
bool firmware_present = firmware_present_new ( ) ;
2018-08-27 15:22:04 +00:00
usbInit ( ) ;
2014-10-23 16:09:41 +00:00
for ( ; ; ) {
usbd_poll ( usbd_dev ) ;
2019-01-27 10:58:30 +00:00
if ( ! firmware_present & & ( flash_state = = STATE_READY | | flash_state = = STATE_OPEN ) ) {
2016-10-06 13:02:44 +00:00
checkButtons ( ) ;
}
2014-10-23 16:09:41 +00:00
}
}