/** dfu-programmer
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
  * This program 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 General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  */

//___ I N C L U D E S ________________________________________________________
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <stdbool.h>

#include "dfu-device.h"
#include "config.h"
#include "arguments.h"
#include "dfu.h"
#include "stm32.h"
#include "util.h"


//___ M A C R O S   ( P R I V A T E ) ________________________________________
#define STM32_DEBUG_THRESHOLD   50
#define STM32_TRACE_THRESHOLD   55

#define DEBUG(...)  dfu_debug( __FILE__, __FUNCTION__, __LINE__, STM32_DEBUG_THRESHOLD, __VA_ARGS__ )
#define TRACE(...)  dfu_debug( __FILE__, __FUNCTION__, __LINE__, STM32_TRACE_THRESHOLD, __VA_ARGS__ )

#define STM32_MAX_TRANSFER_SIZE     0x0800  /* 2048 */
#define STM32_MIN_SECTOR_BOUND      0x4000  /* 16 kb */
#define STM32_OTP_BYTES_SIZE        528     /* number of OTP bytes (0x210) */
#define STM32_OPTION_BYTES_SIZE     16      /* number option bytes */

#define SET_ADDR_PTR            0x21
#define ERASE_CMD               0x41
#define READ_UNPROTECT          0x92
#define GET_CMD                 0x00

//___ T Y P E D E F S   ( P R I V A T E ) ____________________________________

//___ P R O T O T Y P E S   ( P R I V A T E ) ________________________________
static inline int32_t stm32_get_status( dfu_device_t *device );
  /* run dfu_get_status to get the current status
   * return 0 on status OK, -1 on status req fail, -2 on bad status
   */

static int32_t stm32_set_address_ptr( dfu_device_t *device, uint32_t address );
  /* @brief set the address pointer to a certain address
   * @param the address to set
   * @return 0 on success, negative on failure
   */

static int32_t stm32_write_block( dfu_device_t *device,
                                  size_t xfer_len,
                                  uint8_t *buffer );
  /* flash the contents of memory into a block of memory.  it is assumed that
   * the appropriate page has already been selected.  start and end are the
   * start and end addresses of the flash data.  returns 0 on success,
   * positive dfu error code if one is obtained, or negative if communication
   * with device fails.
   */

static int32_t stm32_read_block( dfu_device_t *device,
                                   size_t xfer_len,
                                   uint8_t *buffer );
  /* read a block of memory, assumes address pointer is already set
   */

static inline void print_progress( intel_buffer_info_t *info,
                    uint32_t *progress );
  /* calculate how many progress indicator steps to print and print them
   * update progress value
   */

static int32_t stm32_erase( dfu_device_t *device, uint8_t *command,
                            uint8_t command_length, bool quiet );
  /* erase, erase page, and read unprotect all share this functionality
   * although with different commands
   */


//___ V A R I A B L E S ______________________________________________________
extern int debug;       /* defined in libdfu.c */

/* FIXME : these should be read from usb device descriptor because they are
 * device specific */
static const uint32_t stm32_sector_addresses[] = {
  0x08000000,   /* sector  0,  16 kb */
  0x08004000,   /* sector  1,  16 kb */
  0x08008000,   /* sector  2,  16 kb */
  0x0800C000,   /* sector  3,  16 kb */
  0x08010000,   /* sector  4,  64 kb */
  0x08020000,   /* sector  5, 128 kb */
  0x08040000,   /* sector  6, 128 kb */
  0x08060000,   /* sector  7, 128 kb */
  0x08080000,   /* sector  8, 128 kb */
  0x080A0000,   /* sector  9, 128 kb */
  0x080C0000,   /* sector 10, 128 kb */
  0x080E0000,   /* sector 11, 128 kb */
  0x1FFF0000,   /* system memory, 30 kb */
  0x1FFF7800,   /* OTP area, 528 bytes */
  0x1FFFC000,   /* Option bytes, 16 bytes */
};

//___ F U N C T I O N S   ( P R I V A T E ) __________________________________
static inline int32_t stm32_get_status( dfu_device_t *device ) {
  dfu_status_t status;
  if( 0 == dfu_get_status(device, &status) ) {
    if( status.bStatus == DFU_STATUS_OK ) {
      DEBUG( "Status OK\n" );
    } else {
      DEBUG( "Status %s not OK, use DFU_CLRSTATUS\n",
          dfu_status_to_string(status.bStatus) );
      dfu_clear_status( device );
      return -2;
    }
  } else {
    DEBUG( "DFU_GETSTATUS request failed\n" );
    return -1;
  }

  return 0;
}

static int32_t stm32_set_address_ptr( dfu_device_t *device, uint32_t address ) {
  TRACE( "%s( 0x%X )\n", __FUNCTION__, address );
  const uint8_t length = 5;
  int32_t status;

  uint8_t command[] = {
    (uint8_t) SET_ADDR_PTR,
    (uint8_t) address & 0xFF,           /* address LSB */
    (uint8_t) (address>>8) & 0xFF,
    (uint8_t) (address>>16) & 0xFF,
    (uint8_t) (address>>24) & 0xFF      /* address MSB */
  };

  /* check dfu status for okay to send */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d getting status on start\n", status);
    return -1;
  }

  dfu_set_transaction_num( device, 0 );     /* set wValue to zero */
  if( length != dfu_download(device, length, command) ) {
    DEBUG( "dfu_download failed\n" );
    return -2;
  }

  /* call dfu get status to trigger command */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d triggering %s\n", status, __FUNCTION__);
    return -3;
  }

  /* check command success */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d: %s unsuccessful\n", status, __FUNCTION__);
    return -4;
  }

  return 0;
}

static int32_t stm32_write_block( dfu_device_t *device,
                                  size_t xfer_len,
                                  uint8_t *buffer ) {
  TRACE( "%s( %p, %u, %p )\n", __FUNCTION__, device, xfer_len, buffer );
  int32_t status;

  /* check input args */
  if( (NULL == device) || (NULL == buffer) ) {
    DEBUG( "ERROR: Invalid arguments, device/buffer pointer is NULL.\n" );
    return -1;
  } else if ( xfer_len > STM32_MAX_TRANSFER_SIZE ) {
    DEBUG( "ERROR: 0x%X byte message > MAX TRANSFER SIZE (0x%X).\n",
        xfer_len, STM32_MAX_TRANSFER_SIZE );
    return -1;
  } else if ( xfer_len < 1 ) {
    DEBUG( "ERROR: xfer_len is %u\n", xfer_len );
    return -1;
  }

  if( xfer_len != dfu_download(device, xfer_len, buffer) ) {
    DEBUG( "dfu_download failed\n" );
    return -3;
  }

  /* call dfu get status to trigger command */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d triggering %s\n", status, __FUNCTION__);
    return -3;
  }

  /* check that the command was successfully executed */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d: %s unsuccessful\n", status, __FUNCTION__);
    return -4;
  }

  return 0;
}

static int32_t stm32_read_block( dfu_device_t *device,
                                   size_t xfer_len,
                                   uint8_t *buffer ) {
  TRACE( "%s( %p, %u, %p )\n", __FUNCTION__, device, xfer_len, buffer );
  int32_t result;

  if( buffer == NULL ) {
    DEBUG("ERROR: buffer ptr is NULL\n");
    return -1;
  } else if( xfer_len > STM32_MAX_TRANSFER_SIZE ) {
    /* this could cause a read problem */
    DEBUG("ERROR: transfer size %d exceeds max %d.\n",
        xfer_len, STM32_MAX_TRANSFER_SIZE );
    return -1;
  }

  /* check status before read */
  if( (result = stm32_get_status(device)) ) {
    DEBUG("Status Error %d before read\n", result );
    return -2;
  }

  result = dfu_upload( device, xfer_len, buffer );
  if( result < 0) {
    dfu_status_t status;

    DEBUG( "ERROR: dfu_upload result: %d\n", result );
    if( 0 == dfu_get_status(device, &status) ) {
      DEBUG( "Error Status %s, state %s\n",
          dfu_status_to_string(status.bStatus),
          dfu_state_to_string(status.bState) );
      if( status.bStatus == DFU_STATUS_ERROR_VENDOR ) {
        /* status = dfuERROR and state = errVENDOR */
        DEBUG("Device is read protected\n");
        return STM32_READ_PROT_ERROR;
      }
    } else {
      DEBUG("DFU GET_STATUS fail\n");
    }
    dfu_clear_status( device );

    return result;
  }

  return 0;
}

static inline void print_progress( intel_buffer_info_t *info,
                                     uint32_t *progress ) {
  if ( !(debug > STM32_DEBUG_THRESHOLD) ) {
    while ( ((info->block_end - info->data_start + 1) * 32) > *progress ) {
      fprintf( stderr, ">" );
      *progress += info->data_end - info->data_start + 1;
    }
  }
}

static int32_t stm32_erase( dfu_device_t *device, uint8_t *command,
                            uint8_t command_length, bool quiet ) {
  int32_t status;
  dfu_set_transaction_num( device, 0 );     /* set wValue to zero */
  if( command_length != dfu_download(device, command_length, command) ) {
    if( !quiet ) fprintf( stderr, "ERROR\n" );
    DEBUG( "dfu_download failed\n" );
    return UNSPECIFIED_ERROR;
  }

  /* call dfu get status to trigger command */
  if( (status = stm32_get_status(device)) ) {
    if( !quiet ) fprintf( stderr, "ERROR\n" );
    DEBUG("Error %d triggering %s\n", status, __FUNCTION__);
    return UNSPECIFIED_ERROR;
  }

  /* check status again for erase status, this can take a while */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d: %s unsuccessful\n", status, __FUNCTION__);
    if( !quiet ) fprintf( stderr, "ERROR\n" );
    return UNSPECIFIED_ERROR;
  } else {
    if( !quiet ) fprintf( stderr, "DONE\n" );
  }

  return SUCCESS;
}

//___ F U N C T I O N S ______________________________________________________
int32_t stm32_erase_flash( dfu_device_t *device, bool quiet ) {
  TRACE( "%s( %p, %s )\n", __FUNCTION__, device, quiet ? "true" : "false" );
  uint8_t command[] = { ERASE_CMD };
  uint8_t length = 1;

  if( !quiet ) {
    fprintf( stderr, "Erasing flash...  " );
    DEBUG("\n");
  }

  return stm32_erase( device, command, length, quiet );
}

int32_t stm32_page_erase( dfu_device_t *device, uint32_t address,
                           bool quiet ) {
  TRACE( "%s( %p, 0x%X, %s )\n", __FUNCTION__, device, address,
      quiet ? "true" : "false" );
  uint8_t length = 5;

  uint8_t command[5] = {
    ERASE_CMD,
    (uint8_t) (0xff & address),             //  page LSB
    (uint8_t) (0xff & ((address)>>8)),
    (uint8_t) (0xff & ((address)>>16)),
    (uint8_t) (0xff & ((address)>>24))      // page MSB
  };

  return stm32_erase( device, command, length, quiet );
}

int32_t stm32_start_app( dfu_device_t *device, bool quiet ) {
  TRACE( "%s( %p )\n", __FUNCTION__, device );
  int32_t status;

  /* set address pointer (jump target) to start address */
  if( (status = stm32_set_address_ptr( device, STM32_FLASH_OFFSET )) ) {
    DEBUG("Error setting address pointer\n");
    return UNSPECIFIED_ERROR;
  }

  /* check dfu status for ok to send */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d getting status on start\n", status);
    return UNSPECIFIED_ERROR;
  }

  if( !quiet ) fprintf( stderr, "Launching program...  \n" );
  dfu_set_transaction_num( device, 0 );     /* set wValue to zero */
  if( 0 != dfu_download(device, 0, NULL) ) {
    if( !quiet ) fprintf( stderr, "ERROR\n" );
    DEBUG( "dfu_download failed\n" );
    return UNSPECIFIED_ERROR;
  }

  /* call dfu get status to trigger command */
  if( (status = stm32_get_status(device)) ) {
    DEBUG("Error %d triggering %s\n", status, __FUNCTION__);
    return UNSPECIFIED_ERROR;
  }

  return SUCCESS;
}

int32_t stm32_read_flash( dfu_device_t *device, intel_buffer_in_t *buin,
    const uint8_t mem_segment, const bool quiet ) {
  TRACE( "%s( %p, %p, %u, %s )\n", __FUNCTION__, device, buin,
      mem_segment, ((true == quiet) ? "true" : "false"));

  uint8_t  reset_address_flag;  // reset address offset required
  uint32_t address_offset;  // keep record of sent progress as bytes * 32
  uint16_t  xfer_size = 0;      // the size of a transfer
  uint8_t mem_section = 0;       // tracks the current memory page
  uint32_t progress = 0;      // used to indicate progress
  int32_t status;
  int32_t retval = UNSPECIFIED_ERROR;   // the return value for this function

  if( (NULL == buin) || (NULL == device) ) {
    DEBUG( "invalid arguments.\n" );
    if( !quiet )
      fprintf( stderr, "Program Error, use debug for more info.\n" );
    return ARGUMENT_ERROR;
  }

  if( !quiet ) {
    if( debug <= STM32_DEBUG_THRESHOLD ) {
      /* NOTE: From here on we should go to finally on error */
      fprintf( stderr, "[================================] " );
    }
    fprintf( stderr, "Reading 0x%X bytes...\n",
        buin->info.data_end - buin->info.data_start + 1 );
    if( debug <= STM32_DEBUG_THRESHOLD ) {
      /* NOTE: From here on we should go to finally on error */
      fprintf( stderr, "[" );
    }
  }

  /* read the data */
  buin->info.block_start = buin->info.data_start;
  reset_address_flag = 0;
  address_offset = buin->info.block_start;

  while( buin->info.block_start <= buin->info.data_end ) {
    if( reset_address_flag ) {
      address_offset = buin->info.block_start;
      if( (status = stm32_set_address_ptr(device,
              STM32_FLASH_OFFSET + address_offset)) ) {
        DEBUG("Error setting address 0x%X\n", address_offset);
        retval = UNSPECIFIED_ERROR;
        goto finally;
      }
      dfu_set_transaction_num( device, 2 ); /* sets block offset 0 */
      reset_address_flag = 0;
    }

    // find end value for the current transfer
    buin->info.block_end = buin->info.block_start + STM32_MAX_TRANSFER_SIZE - 1;
    mem_section = buin->info.block_start / STM32_MIN_SECTOR_BOUND;
    if( buin->info.block_end / STM32_MIN_SECTOR_BOUND > mem_section ) {
      buin->info.block_end = STM32_MIN_SECTOR_BOUND * mem_section - 1;
    }
    if( buin->info.block_end > buin->info.data_end ) {
      buin->info.block_end = buin->info.data_end;
    }
    xfer_size = buin->info.block_end - buin->info.block_start + 1;
    if( xfer_size != STM32_MAX_TRANSFER_SIZE ) {
      DEBUG("xfer_size change, need addr reset\n");
      reset_address_flag = 1;
    }

    if( (status = stm32_read_block( device, xfer_size,
            &buin->data[buin->info.block_start] )) ) {
      DEBUG( "Error reading block 0x%X to 0x%X: err %d.\n",
          buin->info.block_start, buin->info.block_end, status );
      retval = ( status == -10 ) ? DEVICE_ACCESS_ERROR : FLASH_READ_ERROR;
      /* read protect error code in read_block is -10 */
      goto finally;
    }

    buin->info.block_start = buin->info.block_end + 1;
    if( reset_address_flag == 0 && (buin->info.block_start !=
        (STM32_MAX_TRANSFER_SIZE * (dfu_get_transaction_num( device ) - 2))
        + address_offset) ) {
      DEBUG("block start & address mismatch, reset req\n");
      reset_address_flag = 1;
    }

    if( !quiet ) print_progress( &buin->info, &progress );
  }
  retval = SUCCESS;

finally:
  if ( !quiet ) {
    if( SUCCESS == retval ) {
      if ( debug <= STM32_DEBUG_THRESHOLD ) {
        fprintf( stderr, "] " );
      }
      fprintf( stderr, "SUCCESS\n" );
    } else {
      if ( debug <= STM32_DEBUG_THRESHOLD ) {
        fprintf( stderr, " X  ");
      }
      fprintf( stderr, "ERROR\n" );
      if( retval==DEVICE_ACCESS_ERROR )
        fprintf( stderr,
            "Memory access error, use debug for more info.\n" );
      else if( retval==FLASH_READ_ERROR )
        fprintf( stderr,
            "Memory read error, use debug for more info.\n" );
    }
  }
  return retval;
}

int32_t stm32_write_flash( dfu_device_t *device, intel_buffer_out_t *bout,
    const bool eeprom, const bool force, const bool quiet ) {
  TRACE( "%s( %p, %p, %s, %s )\n", __FUNCTION__, device, bout,
          ((true == eeprom) ? "true" : "false"),
          ((true == quiet) ? "true" : "false") );

  uint32_t i;
  uint32_t progress = 0;    // keep record of sent progress as bytes * 32
  uint32_t address_offset;  // keep record of sent progress as bytes * 32
  uint8_t  reset_address_flag;  // reset address offset required
  uint16_t  xfer_size = 0;      // the size of a transfer
  uint8_t mem_section = 0;   // tracks the current memory page
  int32_t retval = UNSPECIFIED_ERROR;   // the return value for this function
  uint8_t buffer[STM32_MAX_TRANSFER_SIZE];     // buffer holding out data
  int32_t status;

  /* check arguments */
  if( (NULL == device) || (NULL == bout) ) {
    DEBUG( "ERROR: Invalid arguments, device/buffer pointer is NULL.\n" );
    if( !quiet )
      fprintf( stderr, "Program Error, use debug for more info.\n" );
    return ARGUMENT_ERROR;
  } else if( bout->info.valid_start > bout->info.valid_end ) {
    DEBUG( "ERROR: No valid target memory, end 0x%X before start 0x%X.\n",
        bout->info.valid_end, bout->info.valid_start );
    if( !quiet )
      fprintf( stderr, "Program Error, use debug for more info.\n" );
    return BUFFER_INIT_ERROR;
  }

  /* for each page with data, fill unassigned values on the page with 0xFF
   * bout->data[0] always aligns with a flash page boundary irrespective
   * of where valid_start is located */
  if( 0 != intel_flash_prep_buffer( bout ) ) {
    if( !quiet )
      fprintf( stderr, "Program Error, use debug for more info.\n" );
    return BUFFER_INIT_ERROR;
  }

  /* determine the limits of where actual data resides in the buffer */
  bout->info.data_start = UINT32_MAX;
  for( i = 0; i < bout->info.total_size; i++ ) {
    if( bout->data[i] <= UINT8_MAX ) {
      bout->info.data_end = i;
      if( bout->info.data_start == UINT32_MAX )
        bout->info.data_start = i;
    }
  }

  /* debug info about data limits */
  DEBUG("Flash available from 0x%X to 0x%X, 0x%X bytes.\n",
      bout->info.valid_start, bout->info.valid_end,
      bout->info.valid_end - bout->info.valid_start + 1); // bytes inclusive so +1
  DEBUG("Data start @ 0x%X; %uB p 0x%X + 0x%X offset.\n",
      bout->info.data_start, bout->info.page_size,
      bout->info.data_start / bout->info.page_size,
      bout->info.data_start % bout->info.page_size);
  DEBUG("Data end @ 0x%X; %uB p 0x%X + 0x%X offset.\n",
      bout->info.data_end, bout->info.page_size,
      bout->info.data_end / bout->info.page_size,
      bout->info.data_end % bout->info.page_size);
  DEBUG("Totals: 0x%X bytes, %u %uB pages.\n",
      bout->info.data_end - bout->info.data_start + 1,
      bout->info.data_end / bout->info.page_size \
        - bout->info.data_start/bout->info.page_size + 1,
      bout->info.page_size );

  /* more error checking */
  if( (bout->info.data_start < bout->info.valid_start) ||
      (bout->info.data_end > bout->info.valid_end) ) {
    DEBUG( "ERROR: Data exists outside of the valid target flash region.\n" );
    if( !quiet )
      fprintf( stderr, "Hex file error, use debug for more info.\n" );
    return BUFFER_INIT_ERROR;
  } else if( bout->info.data_start == UINT32_MAX ) {
    DEBUG( "ERROR: No valid data to flash.\n" );
    if( !quiet )
      fprintf( stderr, "Hex file error, use debug for more info.\n" );
    return BUFFER_INIT_ERROR;
  }

  if( !quiet ) {
    if( debug <= STM32_DEBUG_THRESHOLD ) {
      /* NOTE: from here on we should run finally block */
      fprintf( stderr, "[================================] " );
    }
    fprintf( stderr, "Programming 0x%X bytes...\n",
        bout->info.data_end - bout->info.data_start + 1 );
    if( debug <= STM32_DEBUG_THRESHOLD ) {
      /* NOTE: from here on we need to run finally block */
      fprintf( stderr, "[" );
    }
  }

  /* program the data */
  bout->info.block_start = bout->info.data_start;
  reset_address_flag = 1;

  while( bout->info.block_start <= bout->info.data_end ) {
    if( reset_address_flag ) {
      address_offset = bout->info.block_start;
      if( (status = stm32_set_address_ptr(device,
              STM32_FLASH_OFFSET + address_offset)) ) {
        DEBUG("Error setting address 0x%X\n", address_offset);
        retval = DEVICE_ACCESS_ERROR;
        goto finally;
      }
      dfu_set_transaction_num( device, 2 ); /* sets block offset 0 */
      reset_address_flag = 0;
    }

    /* find end address (info.block_end) for data section to write */
    mem_section = bout->info.block_start / STM32_MIN_SECTOR_BOUND;
    for( i = 0, bout->info.block_end = bout->info.block_start;
         bout->info.block_end <= bout->info.data_end;
         bout->info.block_end++, i++ ) {
      xfer_size = bout->info.block_end - bout->info.block_start + 1;
      // check if the current value is valid
      if( bout->data[bout->info.block_end] > UINT8_MAX ) break;
      // check if the current data packet is too big
      if( xfer_size > STM32_MAX_TRANSFER_SIZE ) break;
      // check if the current data value is outside of the memory sector
      if( bout->info.block_end / STM32_MIN_SECTOR_BOUND - mem_section ) break;

      buffer[i] = (uint8_t) bout->data[bout->info.block_end];
    }
    bout->info.block_end--; // bout->info.block_end was one step beyond the last data value to flash
    xfer_size = bout->info.block_end - bout->info.block_start + 1;
    if( xfer_size != STM32_MAX_TRANSFER_SIZE ) {
      DEBUG("xfer_size %u not max %u, need addr reset\n",
          xfer_size, STM32_MAX_TRANSFER_SIZE);
      reset_address_flag = 1;
    }

    /* write the data */
    DEBUG("Program data block: 0x%X to 0x%X, 0x%X bytes.\n",
        bout->info.block_start, bout->info.block_end, xfer_size);

    if( (status = stm32_write_block( device, xfer_size, buffer )) ) {
      DEBUG( "Error flashing the block: err %d.\n", status );
      retval = FLASH_WRITE_ERROR;
      goto finally;
    }

    // increment bout->info.block_start to the next valid address
    for( bout->info.block_start = bout->info.block_end + 1;
         bout->info.block_start <= bout->info.data_end;
         bout->info.block_start++ ) {
      if( (bout->data[bout->info.block_start] <= UINT8_MAX) ) break;
    } // bout->info.block_start is now on the first valid data for the next segment

    if( reset_address_flag == 0 && (bout->info.block_start !=
        (STM32_MAX_TRANSFER_SIZE * (dfu_get_transaction_num( device ) - 2))
        + address_offset) ) {
      DEBUG("block start does not match addr, reset req\n");
      reset_address_flag = 1;
    }

    // display progress in 32 increments (if not hidden)
    if ( !quiet ) print_progress( &bout->info, &progress );
  }
  retval = SUCCESS;

finally:
  if ( !quiet ) {
    if( SUCCESS == retval ) {
      if ( debug <= STM32_DEBUG_THRESHOLD ) {
        fprintf( stderr, "] " );
      }
      fprintf( stderr, "SUCCESS\n" );
    } else {
      if ( debug <= STM32_DEBUG_THRESHOLD ) {
        fprintf( stderr, " X  ");
      }
      fprintf( stderr, "ERROR\n" );
      if( retval==DEVICE_ACCESS_ERROR )
        fprintf( stderr,
            "Memory access error, use debug for more info.\n" );
      else if( retval==FLASH_WRITE_ERROR )
        fprintf( stderr,
            "Memory write error, use debug for more info.\n" );
    }
  }

  return retval;
}

int32_t stm32_get_commands( dfu_device_t *device ) {
  TRACE("%s( %p )\n", __FUNCTION__, device);
  int32_t result;
  uint8_t i;
  const size_t xfer_len = 80;
  uint8_t buffer[xfer_len];

  /* check status before read */
  if( (result = stm32_get_status(device)) ) {
    DEBUG("Status Error %d before read\n", result );
    return UNSPECIFIED_ERROR;
  }

  dfu_set_transaction_num( device, 0 );
  result = dfu_upload( device, xfer_len, buffer );
  if( result < 0) {
    dfu_status_t status;

    DEBUG( "dfu_upload result: %d\n", result );
    result = UNSPECIFIED_ERROR;
    if( 0 == dfu_get_status(device, &status) ) {
      if( status.bStatus == DFU_STATUS_OK ) {
        DEBUG("DFU Status OK, state %d\n", status.bState);
      } else if( status.bStatus == DFU_STATUS_ERROR_VENDOR ) {
        DEBUG("Device is read protected\n");
        /* status = dfuERROR and state = errVENDOR */
        result = DEVICE_ACCESS_ERROR;
      } else {
        DEBUG("Unknown error status %d / state %d\n",
            status.bStatus, status.bState );
      }
    } else {
      DEBUG("DFU GET_STATUS fail\n");
    }
    dfu_clear_status( device );

    return result;
  }

  fprintf( stdout, "There are %d commands:\n", result );
  for( i = 0; i < result; i++ ) {
    fprintf( stdout, "  0x%02X\n", buffer[i] );
  }

  return SUCCESS;
}

int32_t stm32_get_configuration( dfu_device_t *device ) {
  TRACE("%s( %p )\n", __FUNCTION__, device);
  int32_t status;
  uint8_t i;
  uint8_t buffer[STM32_OPTION_BYTES_SIZE];

  if( (status = stm32_set_address_ptr(device,
          stm32_sector_addresses[mem_st_option_bytes])) ) {
    DEBUG("Error (%d) setting address 0x%X\n",
        status, stm32_sector_addresses[mem_st_option_bytes]);
    return UNSPECIFIED_ERROR;
  }

  if( (status = stm32_read_block(device, STM32_OPTION_BYTES_SIZE, buffer)) ) {
    DEBUG("Error (%d) reading option buffer block\n", status );
    return FLASH_READ_ERROR;
  }

  fprintf( stdout, "There are %d option bytes:\n", STM32_OPTION_BYTES_SIZE );
  fprintf( stdout, "0x%02X", buffer[0] );
  for( i = 1; i < STM32_OPTION_BYTES_SIZE; i++ ) {
    fprintf( stdout, ", 0x%02X", buffer[i] );
  }
  fprintf( stdout, "\n" );

  return SUCCESS;
}

int32_t stm32_read_unprotect( dfu_device_t *device, bool quiet ) {
  TRACE( "%s( %p, %s )\n", __FUNCTION__, device, quiet ? "true" : "false" );
  uint8_t command[] = { READ_UNPROTECT };
  uint8_t length = 1;

  if( !quiet ) {
    fprintf( stderr, "Read Unprotect, Erasing flash...  " );
    DEBUG("\n");
  }

  return stm32_erase( device, command, length, quiet );
}

// cSpell:ignore shiftwidth
// vim: shiftwidth=2
