#include "lboot.h"
#include "getopt.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include "ltimer.h"
#include "sha.h"

#ifdef ENABLE_USB
    #include "lboot_usb.h"
#else
    #define LBOOT_USB_OPTIONS
    #define LBOOT_USB_BOOT_IFACES
    #define LBOOT_USB_APP_IFACES
#endif
#ifdef ENABLE_MBRTU
    #include "lboot_mbrtu.h"
#else
    #define LBOOT_MBRTU_OPTIONS
    #define LBOOT_MBRTU_BOOT_IFACES
    #define LBOOT_MBRTU_APP_IFACES
#endif
#ifdef ENABLE_TFTP
    #include "lboot_tftp.h"
#else
    #define LBOOT_TFTP_OPTIONS
    #define LBOOT_TFTP_BOOT_IFACES
    #define LBOOT_TFTP_APP_IFACES
#endif
#ifdef ENABLE_HTTP
    #include "lboot_http.h"
#else
    #define LBOOT_HTTP_OPTIONS
    #define LBOOT_HTTP_BOOT_IFACES
    #define LBOOT_HTTP_APP_IFACES
#endif
#ifdef ENABLE_UDP
    #include "lboot_udp.h"
#else
    #define LBOOT_UDP_OPTIONS
    #define LBOOT_UDP_BOOT_IFACES
    #define LBOOT_UDP_APP_IFACES
#endif

#include "lboot_prt.h"



#define LBOOT_SUPPORT_GET_FLAGS(ver)    ((ver >= 0x102))
#define LBOOT_SUPPORT_DONT_START(ver)   ((ver >= 0x102))
#define LBOOT_SUPPORT_RECOVERY(ver)     ((ver >= 0x102))
#define LBOOT_SUPPORT_PROT_DATA(ver)    ((ver >= 0x104))
#define LBOOT_SUPPORT_GET_FEATURES(ver) ((ver >= 0x104))


/* коды опций, для проверки возвращаемого getopt значения */
#define LBOOT_OPT_HELP              'h'
#define LBOOT_OPT_VERBOSE           'v'
#define LBOOT_OPT_RESERVED_COPY     'r'
#define LBOOT_OPT_DEVNAME           'd'
#define LBOOT_OPT_SERIAL            's'
#define LBOOT_OPT_SIGFILE           0x10000
#define LBOOT_OPT_NOSIGN            0x10001
#define LBOOT_OPT_HASHFILE          0x10002
#define LBOOT_OPT_XZ                0x10003
#define LBOOT_OPT_VERSION           0x10004
#define LBOOT_OPT_RECOVERY          0x10005
#define LBOOT_OPT_NOSTART           0x10006
#define LBOOT_OPT_START_ONLY        0x10007
#define LBOOT_OPT_OUT_FORMAT        0x10008
#define LBOOT_OPT_NOERR             0x10009
#define LBOOT_OPT_CON_TIME          0x1000A
#define LBOOT_OPT_PROT_DATA_WR      0x1000B
#define LBOOT_OPT_APP_TO_BOOT       0x1000C
#define LBOOT_OPT_APP_CON_TIME      0x1000D
#define LBOOT_OPT_PRECON_WAIT       0x1000E



/* константы, соотеветствующие строкам интерфейса */
#define LBOOT_INTF_INVALID    0
#define LBOOT_INTF_USB        1
#define LBOOT_INTF_MBRTU      2


/** форматы вывода информации на stdout */
typedef enum {
    LBOOT_OUTFMT_NONE, /** нет вывода на stdout (только stderr) */
    LBOOT_OUTFMT_PLAIN, /** просто текст в читабельном виде */
    LBOOT_OUTFMT_DSV  /** dsv формат типа action:status[:par=val]* для анализа
                        другой программой */
} t_out_fmt;

#define LBOOT_OUTFMT_NONE_STR  "none"
#define LBOOT_OUTFMT_TEXT_STR  "plain"
#define LBOOT_OUTFMT_DSV_STR   "dsv"

/* строки, соответствующие операциям программы */
#define LBOOT_ACTION_APP_TO_BOOT     "app_to_boot"
#define LBOOT_ACTION_CHECK_PARAMS    "check_params"
#define LBOOT_ACTION_PREOPEN_WAIT    "preopen_wait"
#define LBOOT_ACTION_OPEN_DEVICE     "open_device"
#define LBOOT_ACTION_CHECK_INFO      "check_info"
#define LBOOT_ACTION_GET_DEVMODE     "get_devmode"
#define LBOOT_ACTION_CHECK_DEVMODE   "check_devmode"
#define LBOOT_ACTION_GET_BOOTVER     "get_bootver"
#define LBOOT_ACTION_CHECK_BOOTVER   "check_bootver"
#define LBOOT_ACTION_GET_DEVFLAGS    "get_devflags"
#define LBOOT_ACTION_GET_FEATURES    "get_features"
#define LBOOT_ACTION_WRITE_FIRMWARE  "write_firmware"
#define LBOOT_ACTION_WRITE_SIGN      "write_sign"
#define LBOOT_ACTION_RECOVERY        "recovery"
#define LBOOT_ACTION_START_APP       "start_app"
#define LBOOT_ACTION_PROT_DATA_WR    "wr_prot_data"

#define LBOOT_ACTION_STATUS_START    "start"
#define LBOOT_ACTION_STATUS_DONE     "done"
#define LBOOT_ACTION_STATUS_ERROR    "error"
#define LBOOT_ACTION_STATUS_WARNING  "warning"
#define LBOOT_ACTION_STATUS_PROGR    "progress"




/** информация о параметрах программы */
typedef struct {
    t_out_fmt out_fmt; /**< формат вывода */
    int xz;           /**< признак, что файл упакован с помощью xz */
    int nosign;       /**< признак, что используется хеш вместо подписи */
    int reserv;       /**< признак, что записывается резервная, а не основная */
    int recovery;     /**< признак, что нужно с помощью команды восстанвить
                            основуню прошивку из резервной */
    int dont_start;   /**< не нужно загружать приложение после прошивки */
    int start_only;  /**< нужно просто запустить приложение без прошивки */
    int no_err;      /**< не выводить на stderr */
    int app_to_boot; /**< выполнить переход из application в bootloader */
    const t_app_iface *app_iface; /**< интерфейс для перехода из application в bootloader */
    unsigned app_to_boot_tout; /**< таймаут на успешный запрос на переход из
                                           приложения в bootloader */
    unsigned precon_time; /**< время ожидания перед первой попыткой соединения */
    const char *devname; /**< имя устройства, с которым нужно установить связь */
    const char *serial; /**< серийный номер устройства, с которым нужно установить связь */
    const char *filename; /**< имя файла с прошивкой */
    const char *prot_wr_file; /**< имя файла для записи защищенных данных */
    char *sigfile; /**< имя файла с подписью */
    char *hashfile; /**< имя файла с хеш-функцией */
    const t_boot_iface* boot_iface; /**< интерфейс по которому идет работа */
    unsigned con_tout; /** таймаут, в течении которого идет попытка установить связь с устройством */
} t_lboot_params;

/** информация о загружаемом модуле */
typedef struct {
    t_lboot_devinfo info; /**< общая информация */
    uint8_t mode; /** режим работы (application/bootloader) */
    uint32_t flags;   /**< флаги состояния загрузчика */
    uint32_t features;   /**< флаги, указывающие поддерживаемые функции */
    uint16_t boot_ver; /**< версия загрузчика */
} t_dev_info;

typedef struct {
    t_lboot_params par; /**< параметры программы */
    t_dev_info dev;    /**< информация о загружаемом модуле */
    const char* action; /**< текущее действие, выполняемое программой */
    int boot_file;      /**< признак, что нужно загружать файл с прошивкой */
    t_lboot_startwr_info wr_info; /**< информация о  записываемой прошивке */
    uint32_t wr_done_size; /**< количество записанных данных от прошивки */

    FILE *ffirm; /**< файл с прошивкой */
    FILE *fsig;  /**< файл с подписью или хеш-функцией */
    FILE *fprot_wr; /**< Файл с защищенным блоком данных */
    SHA256Context sha256; /**< контекст sha256 для случая автогенерации хеша */
    int free_sig;
} t_lboot_state;


static const char* f_usage_descr = \
"\nUsage: lboot [OPTIONS] boot-iface [firmware-file]\n\n" \
" If firmware-file specified, lboot load firmware image from firmware file \n"
"   with signature to L-Card board.\n"
" Also lboot can be used to show device bootloader info, start application,\n"
"   write user data to board and for send another command to bootloader (see \n"
"   options description)\n"
" lboot automaticaly start application after write firmware or after recovery\n"
"   if `--dont-start` option is not specified. For another use cases, lboot \n"
"   leave board in bootloader mode if `--start-app` is not specified.\n";

static const char* f_options_descr =
"Options:\n" \
"    --app-to-boot=iface    - switch from application to bootloader before\n"
"                             bootloader operations through specified interface\n"
"                             You must specify options with enough information\n"
"                             for this switch.\n"
"                             You can omit app-to-boot iface to use boot interface\n"
"                             for switch.\n"
"    --app-con-time=ms      - time for successfully connect to application\n"
"                             to switch to bootloader mode if `--app-to-boot`\n"
"                             option is prsent\n"
"    --con-time=ms          - connection time in ms. If specified, lboot try to\n"
"                             find bootable device until timeout expiration \n"
"                             rather than stop after first unsuccessfull try\n"
"-d, --devname=name         - write firmware only for device with specified name\n"\
"    --dont-start           - don't start application after write firmware or \n"\
"                             recovery\n"
"-h, --help                 - display this help and exit\n"
"    --hash                 - use SHA-2 hash rather than SHA-2 + RSA signature\n"
"    --hash-file=file       - use hash from specified file.\n"
"                             Usable only with `--hash` option.\n"
"                             If `--hash-file` option is't specified,\n"
"                             lboot automatically generate hash on the fly\n"
"    --output-fmt=fmt       - set format for output messages:\n"
"                               - none - no output on stdout - only errors on\n"
"                                        stderr\n"
"                               - plain - verbose progress text on stdout.\n"
"                                        Equal to `-v` option.\n"
"                               - dsv  - dsv format for parsing from another\n"
"                                        program. Each message has format like:\n"
"                                        action:status[:param=param_value]*\n"
"    --precon-wait-time=ms  - wait specified time in ms before first attempt to\n"
"                             open bootable device.\n"
"    --prot-data-wr=file    - write protected user data block from file to device\n"
"-r, --reserved-copy        - write reserved copy of firmware,\n"
"                             rather than main application\n"
"    --recovery             - recovery main application from reserved copy.\n"
"                             If '-r' flag specified then  firstly write new\n"
"                             reserved-copy, next recovery main application from\n"
"                             new reserved-copy.\n"
"                             Can be used to write both reserved copy and main\n"
"                             application at the same time.\n"
"    --no-stderr            - dont write error info on stderr\n"
"-s, --serial=serial        - write firmware only for device with specified\n"
"                             serial number\n"
"    --sig-file=file        - use signature from specified file, rather than\n"
"                             from default file with name '<firmware-file>.sig'\n"
"    --start-app            - Start application\n"
"-v, --verbose              - print boot progress & board information, rather\n"
"                             than only errors prints.\n"
"    --version              - display version information and exit\n"
"    --xz-compressed        - specified, that file is compressed with xz.\n"
"                             If file has '.xz' extension, lboot automaticly\n"
"                             recognized that this is compressed file.\n"
"                             Use this option for files with others extension.\n";


static const struct option f_long_opt[] = {
    {"help",                no_argument,       0, LBOOT_OPT_HELP},
    /* вывод сообщений о процессе загрузки */
    {"verbose",             no_argument,       0, LBOOT_OPT_VERBOSE},
    /* признак, что записывается резервная копия, а не основная прошивка */
    {"reserved-copy",       no_argument,       0, LBOOT_OPT_RESERVED_COPY},
    /* название прошиваемой платы (если опция не указана - может быть любое)*/
    {"devname",             required_argument, 0, LBOOT_OPT_DEVNAME},
    /* серийный номер устройства (если опция не указана - любой) */
    {"serial",              required_argument, 0, LBOOT_OPT_SERIAL},
    /* указывает файл подписи (если нет => то берется <файл прошивки>.sig)*/
    {"sig-file",            required_argument, 0, LBOOT_OPT_SIGFILE},
    /* признак, что подпись не используется, а просто генерится хеш */
    {"hash",                no_argument,       0, LBOOT_OPT_NOSIGN},
    /* файл с хеш-функцией, если указана опция --hash */
    {"hash-file",           required_argument, 0, LBOOT_OPT_HASHFILE},
    /* явный признак, что файл сжат компрессором XZ
    (если расширение .xz - то по-умолчанию это уже подразумевается) */
    {"xz-compressed",       no_argument,      0, LBOOT_OPT_XZ},
    {"version",             no_argument,      0, LBOOT_OPT_VERSION},
    {"recovery",            no_argument,      0, LBOOT_OPT_RECOVERY},
    {"dont-start",          no_argument,      0, LBOOT_OPT_NOSTART},
    {"start-app",           no_argument,      0, LBOOT_OPT_START_ONLY},
    {"output-fmt",          required_argument,0, LBOOT_OPT_OUT_FORMAT},
    {"no-stderr",           no_argument,      0, LBOOT_OPT_NOERR},
    {"con-time",            required_argument, 0, LBOOT_OPT_CON_TIME},
    {"prot-data-wr",        required_argument, 0, LBOOT_OPT_PROT_DATA_WR},
    {"app-to-boot",         optional_argument, 0, LBOOT_OPT_APP_TO_BOOT},
    {"app-con-time",        required_argument, 0, LBOOT_OPT_APP_CON_TIME},
    {"precon-wait-time",    required_argument, 0, LBOOT_OPT_PRECON_WAIT},
    LBOOT_USB_OPTIONS
    LBOOT_MBRTU_OPTIONS
    LBOOT_TFTP_OPTIONS
    LBOOT_HTTP_OPTIONS
    LBOOT_UDP_OPTIONS
    {0,0,0,0}
};

static const char* f_opt_str = "hvrd:s:";





static const t_boot_iface* f_boot_ifaces[] = {
    LBOOT_USB_BOOT_IFACES
    LBOOT_MBRTU_BOOT_IFACES
    LBOOT_TFTP_BOOT_IFACES
    LBOOT_HTTP_BOOT_IFACES
    LBOOT_UDP_BOOT_IFACES
    NULL
};

static const t_app_iface *f_app_ifaces[] = {
    LBOOT_USB_APP_IFACES
    LBOOT_MBRTU_APP_IFACES
    LBOOT_TFTP_APP_IFACES
    LBOOT_HTTP_APP_IFACES
    LBOOT_UDP_APP_IFACES
    NULL
};

typedef struct {
    t_out_fmt format_code;
    const char* format_str;
} t_output_fmt_descr;

static const t_output_fmt_descr f_output_fmt_descr[]= {
    {LBOOT_OUTFMT_NONE, LBOOT_OUTFMT_NONE_STR},
    {LBOOT_OUTFMT_PLAIN, LBOOT_OUTFMT_TEXT_STR},
    {LBOOT_OUTFMT_DSV, LBOOT_OUTFMT_DSV_STR}
};


#define LBOOT_TMP_STR_MAX 20
/* временные строки сохраняются, использующиеся в DSV для постановки защитных
   символов, добавляются в этот массив, чтобы потом их очистить */
static int f_tmp_str_cnt = 0;
static char* f_tmp_str[LBOOT_TMP_STR_MAX];





#define PRINT_DSV_ACTION(action, status, ...) do { \
    fprintf(stdout, "%s:%s", action, status); \
    fprintf(stdout, __VA_ARGS__); \
    fprintf(stdout, "\n");\
    fflush(stdout);\
    } while(0);





/* расширение для файла подписи по-умолчанию */
static const char* f_sig_ext = ".sig";
/* расширения для упакованного с помощью xz файла */
static const char* f_xz_ext  = ".xz";


/** Строки, соответвующие кодам ошибок программы загрузчика (сами ошибки в lboot.h) */
static const char* f_err_str[] = {
    "Invalid usage",
    "Insufficient arguments",
    "Invalid boot interface",
    "Can't open firmware image file ",
    "Can't open firmware signature file ",
    "Can't open hash file ",
    "Memory allocation error",
    "Allocation resource error",
    "Specified device is't found",
    "Control request failed",
    "Found device has invalid board name - ",
    "Found device has invalid serial - ",
    "Device must be in bootloader mode",
    "Flag `--dont-start` is supported only starting from bootloader version 1.2",
    "Flag `--recovery` is supported only starting from bootloader version 1.2",
    "invalid output format for `--output`-fmt option",
    "Flag `--prot-data-wr` is supported only started from bootloader version 1.4",
    "Can't open file with protection data block",
    "Invalid app-to-boot interface",
    "App-to-boot interface is not specified"
};


static const char* f_err_sub_str[] = {
    "Try 'lboot --help' for more information",
    "Try 'lboot --help' for more information",
    "Try 'lboot --help' for more information"
};

/** Строки, соответствующие кодам ошибок, которые вернуло устройство. Коды
    определены в lboot_prt.h */
static const char* f_deverr_str[] = {
    "signature check failed",
    "cant't write unstable firmware as reserve copy",
    "firmware is for invalid device",
    "incorrect sign for start write firmware",
    "write this type of firmware is disabled",
    "flash write error",
    "invalid reset address in firmware",
    "invalid request data length",
    "invalid command",
    "unpack is't supported",
    "unpack is't complete",
    "firmware integrity check error",
    "invalid write sector",
    "write firmware without signature is disabled",
    "invalid firmware hash",
    "write development firmware is disabled",
    "invalid special command code",
    "invalid special command parameters",
    "invalid special command sign",
    "reserved copy is't present",
    "main application is't present",
    "invalid firmware size",
    "invalid protection block size",
    "write protection block is disabled",
    "protection block write check error",
    "invalid register address",
    "operation aborted by another request",
    "invalid xz archive format",
    "invalid signature format",
    "flash compare operation failed",
    "flash erase error",
    "current firmware is not selected",
    "unsupported firmware type",
};

static const char* f_lcrypt_err_str[] = {
    "Insufficient data in signature packet",
    "Invalid signature packet format",
    "Unsupported cryptographic algorithm",
    "Unsupported hash algorithm"
};


typedef struct {
    const char* action;
    const char* status;
    const char* dsv_fmt;
    const char* plain_fmt;    
} t_action_descr;


static t_action_descr f_actions_descr[] = {
    {0, LBOOT_ACTION_STATUS_WARNING, ":msg=%s", "Warning! %s.\n"},
    {0, LBOOT_ACTION_STATUS_ERROR, ":err_code=%d:err_type=%s:err_str=%s%s",0},
    {LBOOT_ACTION_CHECK_PARAMS, LBOOT_ACTION_STATUS_DONE, 0, "Parameters checked successfully\n"},
    {LBOOT_ACTION_PREOPEN_WAIT, LBOOT_ACTION_STATUS_START, ":time=%d", "Initial wait for %d ms\n"},
    {LBOOT_ACTION_OPEN_DEVICE, LBOOT_ACTION_STATUS_START, 0, "Start open bootable device\n"},
    {LBOOT_ACTION_OPEN_DEVICE, LBOOT_ACTION_STATUS_DONE,
      ":name=%s:serial=%s:softver=%s:board_revision=%s:board_imp=%s",
      "Open device successfully. devinfo:\n   name = %s \n   serial = %s\n"
      "   softver = %s\n   board revision = %s\n   board implementation = %s\n"},
    {LBOOT_ACTION_GET_DEVMODE, LBOOT_ACTION_STATUS_DONE,
     ":mode=%s", "   mode = %s\n"},
    {LBOOT_ACTION_GET_BOOTVER, LBOOT_ACTION_STATUS_DONE,
     ":bootver=%d.%d", "   bootloader version = %d.%d\n"},
    {LBOOT_ACTION_GET_DEVFLAGS, LBOOT_ACTION_STATUS_DONE,
     ":app_present=%s:reserv_present=%s:app_wr_en=%s:reserv_wr_en=%s",
     "   application present         = %s\n"
     "   reserved copy present       = %s\n"
     "   application write enabled   = %s\n"
     "   reserved copy write enabled = %s\n"},
    {LBOOT_ACTION_GET_FEATURES, LBOOT_ACTION_STATUS_DONE,
     "usb=%s::mbrtu=%s:tftp client=%s:tftp server=%s:http server=%s:can_lss=%s:check_rsa=%s:check_hash=%s:prot_data=%s"
     ":prot_data_hw=%s:prot_data_sign=%s:hw_default=%s:hw_recovery=%s:startup_default=%s:xz=%s",
     "bootloader features:\n"
     "   interfaces: usb=%s, mbrtu=%s, tftp client=%s, tftp server=%s, http server=%s, can_lss=%s\n"
     "   firmware check: rsa=%s, hash=%s\n"
     "   protected user data=%s, hard protect=%s, sign protect=%s\n"
     "   spec. modes: hw default=%s, hw recovery=%s, startup default=%s\n"
     "   xz firmware compression = %s\n"},
    {LBOOT_ACTION_WRITE_FIRMWARE, LBOOT_ACTION_STATUS_START,
     ":filename=%s:size=%d", "Start write firmware. file: %s, size: %d Bytes\n"},
    {LBOOT_ACTION_WRITE_FIRMWARE, LBOOT_ACTION_STATUS_PROGR,
     ":cpl_size=%d:full_size=%d", "."},
    {LBOOT_ACTION_WRITE_FIRMWARE, LBOOT_ACTION_STATUS_DONE, 0, "\nFirmware write - done successfully\n"},
    {LBOOT_ACTION_WRITE_SIGN, LBOOT_ACTION_STATUS_START,
     ":type=%s:filename=%s", "Start write %s from file %s\n"},
    {LBOOT_ACTION_WRITE_SIGN, LBOOT_ACTION_STATUS_DONE, 0, "Signature write - done successfully\n"},
    {LBOOT_ACTION_RECOVERY, LBOOT_ACTION_STATUS_START, 0, "Start main application recovery from reserved copy\n"},
    {LBOOT_ACTION_RECOVERY, LBOOT_ACTION_STATUS_DONE, 0, "Application recovery done successfully\n"},
    {LBOOT_ACTION_START_APP, LBOOT_ACTION_STATUS_DONE, 0, "Start application successfully\n"},

    {LBOOT_ACTION_PROT_DATA_WR, LBOOT_ACTION_STATUS_START,
     ":filename=%s:size=%d", "Start write protection data. file: %s, size: %d Bytes\n"},
    {LBOOT_ACTION_PROT_DATA_WR, LBOOT_ACTION_STATUS_PROGR,
     ":cpl_size=%d:full_size=%d", "."},
    {LBOOT_ACTION_PROT_DATA_WR, LBOOT_ACTION_STATUS_DONE, 0, "Protection block write - done successfully\n"},


    {LBOOT_ACTION_APP_TO_BOOT, LBOOT_ACTION_STATUS_START, 0,
     "Start try switch device from application to bootloader\n"},
    {LBOOT_ACTION_APP_TO_BOOT, LBOOT_ACTION_STATUS_DONE, 0, "Switch form application to bootloader done successfully\n"}
};



static const char f_ctl_symb[] = {'=', '%', ':', '\n', '\r', 0};

/** Чтобы избежать возникновения управляющих символов в строке, данная функция
  для формата DSV создает новую строку, где заменяет все управляющие символы (из
  f_ctl_symb) на % за которым идет код символа и возвращает эту строку в качестве
  результата. Созданная строка сохраняется так же в массиве временных строк
  f_tmp_str и должна быть очищена после завершения вывода с помощью f_free_tmp_str().
  Для форматов, отличных от DSV, функция возвращает исходную строку.
  @param[in] fmt  Формат вывода (чтобы определить, нужно ли создавать новую строку)
  @param[in] str  Указатель на исходную строку
  @return         Указатель на строку, пригодную для вывода в указанном формате */
static const char* f_make_tmp_protect_str(t_out_fmt fmt, const char* str) {
    if (fmt == LBOOT_OUTFMT_DSV) {
        const char* p;
        char* newstr=0;
        int rep_cnt = 0;

        if (f_tmp_str_cnt >= LBOOT_TMP_STR_MAX)
            return 0;

        for (p=str; *p!=0; p++) {
            char fnd=0;
            const char* sym;
            for (fnd=0, sym = f_ctl_symb; *sym && !fnd; sym++) {
                if (*p == *sym)
                    fnd = *sym;
            }
            if (fnd)
                rep_cnt++;
        }


        newstr = (char*)malloc(p-str + rep_cnt*2 + 1);
        if (newstr) {
            char* np;

            f_tmp_str[f_tmp_str_cnt++]= newstr;

            for (p=str, np = newstr; *p!=0; p++) {
                char fnd=0;
                const char* sym;
                for (fnd=0, sym = f_ctl_symb; *sym && !fnd; sym++) {
                    if (*p == *sym)
                        fnd = *sym;
                }
                if (fnd) {
                    *np++ = '%';
                    sprintf(np, "%02X", fnd);
                    //*np++=fnd/16 + '0';
                    //*np++=fnd%16 + '0';
                    np += 2;
                }
                else
                    *np++ = *p;
            }

            if (!*p)
                *np=0;
        }
        return newstr;
    } else {
        return str;
    }
}

/** Функция очищает все временные строки из f_tmp_str, созданные
    f_make_tmp_protect_str() */
static void f_free_tmp_str(void) {
    int i;
    for (i=0; i < f_tmp_str_cnt; i++)  {
        free(f_tmp_str[i]);
        f_tmp_str[i]=0;
    }
    f_tmp_str_cnt=0;
}


/***************************************************************************//**
  Функция заключает в себе логику вывода в зависимости от формата (st->par.out_fmt),
  текущего действия (st->action) и статуса (status). В определенных комбинациях
  этих параметров используются другая информация из st для формирования строк.
  @param[in] st        Состояние программы
  @param[in] status    Статус текущего действия (из LBOOT_ACTION_STATUS_...)
  @param[in] msg       Дополнительная строка, используемая для вывода в некоторых
                       комбинациях
  @todo Сейчас сделано несколько криво - большим перебором. рассматреть
    вариант с таблицей или как-то более простым способом....
  *****************************************************************************/
static void print_state(const t_lboot_state* st, const char* status,...) {
    unsigned i;
    int fnd;
    const char* dsv_str = 0;
    const char* str = 0;
    for (i=0, fnd=-1; i< sizeof(f_actions_descr)/sizeof(f_actions_descr[0])
         && (fnd==-1); i++) {
        if ((!f_actions_descr[i].action || !strcmp(f_actions_descr[i].action, st->action))
                && (!f_actions_descr[i].status || !strcmp(f_actions_descr[i].status, status))) {
            fnd = i;
            dsv_str = f_actions_descr[i].dsv_fmt;
            str = f_actions_descr[i].plain_fmt;
        }
    }

    switch (st->par.out_fmt) {
        case LBOOT_OUTFMT_DSV: {
                printf("%s:%s", st->action, status);
                if (dsv_str) {
                    va_list args;
                    va_start(args, status);
                    vprintf(dsv_str, args);
                    va_end(args);
                }
            }
            printf("\n");
            break;
        case LBOOT_OUTFMT_PLAIN:
            if (str) {
                va_list args;
                va_start(args, status);
                vprintf(str, args);
                va_end(args);
            }
            break;
        case LBOOT_OUTFMT_NONE:
            break;
    }
    fflush(stdout);
}


/****************************************************************************//**
    Функция печатает сообщение об ошибке на stderr. (если оно не заерещено)
    Строка выбирается из таблиц f_err_str, f_deverr_str, f_lcrypt_err_str в
    зависимости от  диапазона кода (принадлежит ошибка программе или устройству).
    Сообщение дополняется строкой из msg, если msg!=0. Для f_err_str
    печатается еще дополнительная строка с новой строки из f_err_sub_str, если есть.
    Если коду  ошибки не найдена строка, то идет попытка найти ее в модуле
    интерфейса.

    Кроме того, если текущий формат - dsv, то на stdout печатается информация
    о ошибке в dsv формате.

    @param[in] st указатель на структуру, описываемую состояние загрузчика. из
                       нее получается формат вывода, запрет вывода на stderr,
                       выполняемое действие(стадия работы) программы
    @param[in] err     Код ошибки
    @param[in] first_str Отдельная строка, печатаемая на stderr перед сообщением
                       об ошибке
    @param[in] msg     Дополнительная строка, выводимая за стандартной строкой об
                       ошибке. Может быть 0.
    @return            Возвращаемое значение аналогично err
    ***************************************************************************/
static int f_err_print(const t_lboot_state *st, int err, const char* first_str,
                       const char* msg) {
    const char* err_code_msg = "unknown error code";
    const char* err_type_msg = "utility";
    const char* err_sub_msg = NULL;

    /* коды ошибок, которые вернуло устройство */
    if (err <= LBOOT_ERR_OFFSET) {
        /* отдельно рассматриваем ошибки разбора пакета подписи, так как они
          идут не подряд с остальными ошибками устройства */
        if (err <= LBOOT_ERR_LCRYPT_OFFSET) {
            int err_i = LBOOT_ERR_LCRYPT_OFFSET - err;
            if (err_i < (int)(sizeof(f_lcrypt_err_str)/sizeof(f_lcrypt_err_str[0]))) {
                err_code_msg = f_lcrypt_err_str[err_i];
            } else {
                err_code_msg = "Unknown error";
            }
        } else {
            int err_i = LBOOT_ERR_OFFSET-err;
            if (err_i < (int)(sizeof(f_deverr_str)/sizeof(f_deverr_str[0]))) {
                err_code_msg = f_deverr_str[err_i];
            } else {
                err_code_msg = "Unknown error";
            }
        }
        err_type_msg = "device";
    }
    /* коды ошибок программы */
    else if ((err <= (int32_t)LBOOT_ERR_MIN_ID) &&
            (err > ((int32_t)LBOOT_ERR_MIN_ID -
                    (int32_t)(sizeof(f_err_str)/sizeof(f_err_str[0]))))) {
        err_code_msg = f_err_str[LBOOT_ERR_MIN_ID-err];
        if ((LBOOT_ERR_MIN_ID-err) <
                (int32_t)(sizeof(f_err_sub_str)/sizeof(f_err_sub_str[0]))) {
            err_sub_msg = f_err_sub_str[LBOOT_ERR_MIN_ID-err];
        }
    } else {
        /* смотрим коды ошибок интерфейса */
        int i, fnd = 0;
        for (i = 0; f_boot_ifaces[i] && !fnd; i++) {
            if (f_boot_ifaces[i]->gen.get_err_str) {
                err_code_msg = f_boot_ifaces[i]->gen.get_err_str(err);
                if (err_code_msg) {
                    err_type_msg = "interface";
                    fnd = 1;
                }
            }
        }
        for (i = 0; f_app_ifaces[i] && !fnd; i++) {
            if (f_app_ifaces[i]->gen.get_err_str) {
                err_code_msg = f_app_ifaces[i]->gen.get_err_str(err);
                if (err_code_msg) {
                    err_type_msg = "interface";
                    fnd = 1;
                }
            }
        }

    }

    if (!st->par.no_err) {
        if (first_str)
            fprintf(stderr, "%s\n", first_str);

        fprintf(stderr, "%s error (code = %d): %s", err_type_msg, err, err_code_msg);
        if (msg) {
            fprintf(stderr, "%s", msg);
        }
        fprintf(stderr, ".\n");

        if (err_sub_msg) {
            fprintf(stderr, "%s", err_sub_msg);
            fprintf(stderr, "\n");
        }
    }

    if (st->par.out_fmt == LBOOT_OUTFMT_DSV) {
        if (!msg)
            msg="";

        print_state(st, LBOOT_ACTION_STATUS_ERROR, err,
                    f_make_tmp_protect_str(st->par.out_fmt, err_type_msg),
                    f_make_tmp_protect_str(st->par.out_fmt, err_code_msg),
                    f_make_tmp_protect_str(st->par.out_fmt, msg));
        f_free_tmp_str();
    }
    return err;
}




/*************************************************************
  Вывод сообщения о использовании программы
  ************************************************************/
static void f_print_usage(void) {
    int i;
    fprintf(stdout, "%s", f_usage_descr);
    fprintf(stdout, "\nBoot interfaces:\n");
    for (i=0; f_boot_ifaces[i]; i++) {
        fprintf(stdout, "    %7s - %s\n", f_boot_ifaces[i]->gen.name, f_boot_ifaces[i]->gen.descr);
    }
    fprintf(stdout, "\nApp-to-boot interfaces:\n");
    for (i=0; f_app_ifaces[i]; i++) {
        fprintf(stdout, "    %7s - %s\n", f_app_ifaces[i]->gen.name, f_app_ifaces[i]->gen.descr);
    }


    fprintf(stdout,"\n");
    fprintf(stdout, "%s", f_options_descr);
    for (i=0; f_boot_ifaces[i]; i++) {
        if (f_boot_ifaces[i]->gen.opt_descr) {
            fprintf(stdout, "%s", f_boot_ifaces[i]->gen.opt_descr());
            fprintf(stdout, "\n");
        }
    }
    for (i=0; f_app_ifaces[i]; i++) {
        if (f_app_ifaces[i]->gen.opt_descr) {
            fprintf(stdout, "%s", f_app_ifaces[i]->gen.opt_descr());
            fprintf(stdout, "\n");
        }
    }

}

/*********************************************************************
  Вывод сообщения о версии программы
  ********************************************************************/
static void f_print_version(void) {
    fprintf(stdout, "lboot version: %s\n", LBOOT_VERSION);
}

static const t_app_iface *f_get_app_iface(const char *name) {
    const t_app_iface *ret = NULL;
    int i;
    for (i=0; f_app_ifaces[i] && !ret; i++) {
        if (!strcmp(name, f_app_ifaces[i]->gen.name))
            ret = f_app_ifaces[i];
    }
    return ret;
}



/***************************************************************************//**
  Функция разбирает параметры из argc/argv и заполняет соответствующие поля
  в st->par. При ошибках разбора опций печатает сообщение и возвращает код ошибки
  @param[in,out] st   Состояние загрузчика. Заполняется в соответствии с
                      разобранными параметрами
  @param[in]  argc    Количество параметров
  @param[in]  argv    Массив указателей на строки со значениями параметров
  @param[out] out     Признак, что дальнейшие действия не нужны - можно завершать
                      программу (для опций --version или --help)
  @return             Код ошибки
*******************************************************************************/
static int f_parse_options(t_lboot_state* st, int argc, char **argv, int* out) {
    int err = 0;
    int opt = 0;

    *out = 0;
    st->action = LBOOT_ACTION_CHECK_PARAMS;

    /************************** разбор опций *****************************/
    while ((opt!=-1) && !err && !*out) {
        opt = getopt_long(argc, argv, f_opt_str,
                          f_long_opt, 0);
        switch (opt) {
            case -1:
                break;
            case LBOOT_OPT_HELP:
                f_print_usage();
                *out = 1;
                break;
            case LBOOT_OPT_VERBOSE:
                st->par.out_fmt = LBOOT_OUTFMT_PLAIN;
                break;
            case LBOOT_OPT_RESERVED_COPY:
                st->par.reserv = 1;
                break;
            case LBOOT_OPT_DEVNAME:
                st->par.devname = optarg;
                break;
            case LBOOT_OPT_SERIAL:
                st->par.serial = optarg;
                break;
            case LBOOT_OPT_SIGFILE:
                st->par.sigfile = optarg;
                break;
            case LBOOT_OPT_NOSIGN:
                st->par.nosign = 1;
                break;
            case LBOOT_OPT_HASHFILE:
                st->par.hashfile = optarg;
                break;
            case LBOOT_OPT_XZ:
                st->par.xz = 1;
                break;
            case LBOOT_OPT_RECOVERY:
                st->par.recovery = 1;
                break;
            case LBOOT_OPT_NOSTART:
                st->par.dont_start = 1;
                break;
            case LBOOT_OPT_NOERR:
                st->par.no_err = 1;
                break;
            case LBOOT_OPT_PROT_DATA_WR:
                st->par.prot_wr_file = optarg;
                break;
            case LBOOT_OPT_APP_TO_BOOT:
                st->par.app_to_boot = 1;
                if (optarg) {
                    st->par.app_iface = f_get_app_iface(optarg);
                    if (!st->par.app_iface) {
                        err = LBOOT_ERR_INVALID_APP_IFACE;
                        f_err_print(st, err, 0, 0);
                    }
                }
                break;
            case LBOOT_OPT_APP_CON_TIME:
                st->par.app_to_boot_tout = strtol(optarg, NULL, 10);
                break;
            case LBOOT_OPT_PRECON_WAIT:
                st->par.precon_time = strtol(optarg, NULL, 10);
                break;
            case LBOOT_OPT_OUT_FORMAT: {
                    int fnd = 0;
                    unsigned i;
                    for (i=0; i < sizeof(f_output_fmt_descr)/sizeof(f_output_fmt_descr[0]) && !fnd; i++) {
                        if (!strcmp(f_output_fmt_descr[i].format_str, optarg)) {
                            fnd = 1;
                            st->par.out_fmt = f_output_fmt_descr[i].format_code;
                        }
                    }

                    if (!fnd) {
                        err = LBOOT_ERR_INVALID_OUTPUT_FMT;
                        f_err_print(st, err, 0, 0);
                    }
                }
                break;
            case LBOOT_OPT_START_ONLY:
                st->par.start_only = 1;
                break;
            case LBOOT_OPT_CON_TIME:
                st->par.con_tout = atoi(optarg);
                break;
            case LBOOT_OPT_VERSION:
                f_print_version();
                *out = 1;
                break;
            default: {
                    /* если неподдерживаемя опция, то проверяем - не поддерживает
                      ли данную опцию какой-либо интерфейс */
                    int i, opterr = LBOOT_ERR_INVALID_OPTION;
                    for (i=0; f_boot_ifaces[i] && (opterr == LBOOT_ERR_INVALID_OPTION); i++) {
                        if (f_boot_ifaces[i]->gen.opt_parse)
                            opterr = f_boot_ifaces[i]->gen.opt_parse(opt);
                    }
                    for (i=0; f_app_ifaces[i] && (opterr == LBOOT_ERR_INVALID_OPTION); i++) {
                        if (f_app_ifaces[i]->gen.opt_parse)
                            opterr = f_app_ifaces[i]->gen.opt_parse(opt);
                    }
                    err = opterr;
                    if (err)
                        f_err_print(st, err, 0,0);
                }
                break;
        }
    }

    if (!*out && !err) {

        /* признак, что нужно освободить память под имя файла с подписью -
           для случая, когда имя файла не передано явно и
           программа сама формирует имя во временном буфере */
        st->free_sig = 0;

        /* определяем - нужно ли открывать и записывать прошивку -
           не нужно если не указан файл или если требуется только восстановить
           основную прошивку без записи application*/
        if (((argc-optind) >= 2) && !(st->par.recovery && !st->par.reserv)
                && !st->par.start_only) {
            st->boot_file = 1;
        } else if (((argc-optind) < 1)) {
            err = f_err_print(st, LBOOT_ERR_INSUFFICIENT_ARGUMENTS, 0, 0);
        }


        /* определяем указанный интерфейс для загрузки */
        if (!err) {
            int i;
            for (i=0; f_boot_ifaces[i] && !st->par.boot_iface; i++) {
                if (!strcmp(argv[optind], f_boot_ifaces[i]->gen.name))
                    st->par.boot_iface = f_boot_ifaces[i];
            }
            if (!st->par.boot_iface)
                err = f_err_print(st, LBOOT_ERR_INVALID_INTERFACE, 0,0);
        }

        //сохраняем название файла прошивки
        if (!err && st->boot_file) {
            st->par.filename = argv[optind+1];
        }


        /******* дополнительные проверки на лишине опции и конфликтущие опции ******/
        if (!err) {
            if (st->par.sigfile && st->par.hashfile) {
                //rint_state(st, LBOOT_ACTION_STATUS_WARNING,
               //     "--hash-file and --sig-file are specified! ignore --hash-file");
                st->par.hashfile = NULL;
            }

            if (st->par.sigfile && st->par.nosign) {
                //print_state(st, LBOOT_ACTION_STATUS_WARNING,
               //              "--sig-file and --hash are specified! ignore --hash");
                st->par.nosign = 0;
            }

            if (st->par.hashfile && !st->par.nosign)
                st->par.nosign = 1;

            /** @todo start-app, dont-start и другие несовместимые опции */
        }



        /* открываем требуемые файлы (сразу, чтобы сообщить о ошибке, если их нет */
        if (!err && st->boot_file) {
            st->ffirm = fopen(st->par.filename, "rb");
            if (!st->ffirm)
                err = f_err_print(st, LBOOT_ERR_FIRMWARE_FILE_OPEN, 0,
                                  st->par.filename);
        }

        if (!err && st->boot_file) {
            if (!st->par.nosign) {
                /* если файл подписи не указан явно - полагаем что его имя -
                  <имя_файла>.sig */
                if (!st->par.sigfile) {
                    int len = strlen(st->par.filename) + sizeof(f_sig_ext)+1;
                    st->par.sigfile = malloc(len);
                    if (st->par.sigfile) {
                        strcpy(st->par.sigfile, st->par.filename);
                        strcat(st->par.sigfile, f_sig_ext);
                        st->free_sig = 1;
                    } else {
                        err = f_err_print(st, LBOOT_ERR_MEMORY_ALLOC, 0, 0);
                    }
                }

                if (!err) {
                    st->fsig = fopen(st->par.sigfile, "rb");
                    if (!st->fsig)
                        err = f_err_print(st, LBOOT_ERR_SIGNATURE_FILE_OPEN, 0,
                                          st->par.sigfile);
                }
            } else if (st->par.hashfile) {
                st->fsig = fopen(st->par.hashfile, "rb");
                if (!st->fsig) {
                    err = f_err_print(st, LBOOT_ERR_HASH_FILE_OPEN,
                                      0, st->par.hashfile);
                }
            }
        }

        if (!err && st->par.prot_wr_file) {
            st->fprot_wr = fopen(st->par.prot_wr_file, "rb");
            if (!st->fprot_wr) {
                    err = f_err_print(st, LBOOT_ERR_PROT_DATA_FILE_OPEN, 0,
                                      st->par.prot_wr_file);
            }
        }

        if (!err && st->par.app_to_boot && !st->par.app_iface) {
            st->par.app_iface = st->par.boot_iface->default_app_iface;
            if (!st->par.app_iface) {
                err  = f_err_print(st, LBOOT_ERR_APP_IFACE_NOT_SPECIFIED, 0, 0);
            }
        }

        if (!err)
            print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
    }
    return err;
}

/***************************************************************************//**
    Функция проверяет информацию о устойстве и читает дополнительную информацию:
    - читает режим работы устройства и проверяет что это режим загрузчика
    - читает версию загрузчика и проверяет, что не заданы действия, не поддерживаемые версией
    - читает флаги состояния загрузчика (для версии >=1.2)
    @param[in,out] st  Состояние загрузчика. Изменяется в соответствии с
                       прочитанными данными
    @return            Код ошибки
    ***************************************************************************/
static int f_read_info(t_lboot_state* st) {
    int err = 0;

    st->action = LBOOT_ACTION_CHECK_INFO;
    print_state(st, LBOOT_ACTION_STATUS_START, 0);
    /****  если заданы имя устройства и серийный - проверяем совпадение  ****/
    if (st->par.devname && strcmp(st->par.devname, st->dev.info.devname)) {
        err = LBOOT_ERR_INVALID_DEVICE;
        f_err_print(st, err, 0, st->dev.info.devname);
    }

    if (!err && st->par.serial && strcmp(st->par.serial, st->dev.info.serial)) {
        err = LBOOT_ERR_INVALID_SERIAL;
        f_err_print(st, err, 0, st->dev.info.serial);
    }
    if (!err)
        print_state(st, LBOOT_ACTION_STATUS_DONE, 0);

    /********** получаем режим работы устройства - загрузчик/приложение ***/
    if (!err)  {
        st->action = LBOOT_ACTION_GET_DEVMODE;
        print_state(st, LBOOT_ACTION_STATUS_START, 0);
        err = st->par.boot_iface->get_devmode(&st->dev.mode);
        if (err) {
            f_err_print(st, err, "Can't read device mode!",0);
        } else {
            print_state(st, LBOOT_ACTION_STATUS_DONE,
                (st->dev.mode == LBOOT_DEVMODE_BOOT) ? "boot" : "app");
            st->action = LBOOT_ACTION_CHECK_DEVMODE;

            //режим должен быть обязательно загрузчика
            if (st->dev.mode != LBOOT_DEVMODE_BOOT) {
                err = LBOOT_ERR_INVALID_DEVMODE;
                f_err_print(st, err, 0,0);
            } else {
                print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
            }
        }
    }

    /**************  получаем версию загрузчика  *******************/
    if (!err) {
        st->action = LBOOT_ACTION_GET_BOOTVER;
        print_state(st, LBOOT_ACTION_STATUS_START, 0);
        err = st->par.boot_iface->get_bootldr_ver(&st->dev.boot_ver);
        if (err) {
            f_err_print(st, err, "Can't get bootloader version!", NULL);
        } else {
            print_state(st, LBOOT_ACTION_STATUS_DONE,
                        (st->dev.boot_ver >> 8) &0xF, (st->dev.boot_ver) &0xF);
        }
    }

    /* если версия загрузчика поддерживает, получаем информацию о состоянии устройтсва */
    if (!err && LBOOT_SUPPORT_GET_FLAGS(st->dev.boot_ver)) {
        st->action = LBOOT_ACTION_GET_DEVFLAGS;
        print_state(st, LBOOT_ACTION_STATUS_START, 0);

        err = st->par.boot_iface->get_dev_flags(&st->dev.flags);
        if (err) {
            f_err_print(st, err, "Can't get bootloader state flags!",0);
        } else {
            print_state(st, LBOOT_ACTION_STATUS_DONE,
                        st->dev.flags & LBOOT_STATEFLAGS_APP_VALID ? "yes" : "no",
                        st->dev.flags & LBOOT_STATEFLAGS_RESERV_VALID ? "yes" : "no",
                        st->dev.flags & LBOOT_STATEFLAGS_APP_WR_EN ? "yes" : "no",
                        st->dev.flags & LBOOT_STATEFLAGS_RESERV_WR_EN ? "yes" : "no");
        }
    }


    /* если версия загрузчика поддерживает, получаем информацию о состоянии устройтсва */
    if (!err && LBOOT_SUPPORT_GET_FEATURES(st->dev.boot_ver)) {
        st->action = LBOOT_ACTION_GET_FEATURES;
        print_state(st, LBOOT_ACTION_STATUS_START, 0);

        err = st->par.boot_iface->get_features(&st->dev.features);
        if (err) {
            f_err_print(st, err, "Can't get bootloader features!",0);
        } else {
            print_state(st, LBOOT_ACTION_STATUS_DONE,
                        st->dev.features & LBOOT_FEATURES_INTF_USB ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_INTF_MODBUS_RTU ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_INTF_TFTP_CLIENT ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_INTF_TFTP_SERVER ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_INTF_HTTP_SERVER ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_INTF_CAN_LSS ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_CHECK_RSA ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_CHECK_HASH ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_PROT_DATA ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_PROT_DATA_HW ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_PROT_DATA_SIGN ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_HW_DEFAULT_MODE ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_HW_RECOVERY ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_STARTUP_DEFAULT_MODE ? "yes" : "no",
                        st->dev.features & LBOOT_FEATURES_XZ ? "yes" : "no");
        }
    }

    /* проверяем, что не были запрошены возможности, которые не позволяет
       выполнить загрузчик */
    if (!err) {
        st->action = LBOOT_ACTION_CHECK_BOOTVER;
        print_state(st, LBOOT_ACTION_STATUS_START, 0);
        if (st->par.dont_start && !LBOOT_SUPPORT_DONT_START(st->dev.boot_ver)) {
            f_err_print(st, err = LBOOT_ERR_UNSUP_FLAG_DONT_START, 0,0);
        }

        if (!err && st->par.recovery && !LBOOT_SUPPORT_RECOVERY(st->dev.boot_ver)) {
            f_err_print(st, err = LBOOT_ERR_UNSUP_FLAG_RECOVERY, 0,0);
        }

        if (!err && st->par.prot_wr_file && !LBOOT_SUPPORT_PROT_DATA(st->dev.boot_ver)) {
            f_err_print(st, err = LBOOT_ERR_UNSUP_WR_PROT_DATA, 0,0);
        }

        if (!err)
            print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
    }

    return err;
}



static int f_firm_block_read(void *ctx, uint8_t *buf, uint32_t size, uint32_t *ret_size) {
    t_lboot_state* st = (t_lboot_state *)ctx;
    size_t rd_size = fread(buf, 1, size, st->ffirm);
    if (st->par.nosign && !st->par.hashfile && (rd_size>0))
         SHA256Input(&st->sha256, buf, rd_size);

    if (ret_size != NULL)
        *ret_size = rd_size;
    return 0;
}

static void f_firm_block_done(void *ctx, int err, uint32_t rd_size) {
    t_lboot_state* st = (t_lboot_state *)ctx;
    if (!err) {
        st->wr_done_size +=rd_size;
        st->wr_info.size -=rd_size;
        print_state(st, LBOOT_ACTION_STATUS_PROGR,
                    st->wr_done_size, st->wr_info.size + st->wr_done_size);
    }
}


int lboot_fw_transfer_std_block(void *ctx, t_lboot_firm_block_read block_rd,
                                  t_lboot_firm_block_done block_done, uint32_t fw_size) {
    t_lboot_state* st = (t_lboot_state *)ctx;
    uint8_t* buf;
    int err = LBOOT_ERR_SUCCESS;

    /* выделяем память под записываемый блок */
    buf = malloc(st->par.boot_iface->wr_block_size);
    if (!buf)
        f_err_print(st, err = LBOOT_ERR_MEMORY_ALLOC, 0,0);

    //пишем файл с прошивкой
    while (!err && (fw_size > 0)) {
        uint32_t rd_size;
        err = block_rd(ctx, buf, st->par.boot_iface->wr_block_size, &rd_size);
        if (!err && (rd_size > 0)) {
            err = st->par.boot_iface->fw_block_write(buf, rd_size);
            fw_size -= rd_size;
        }

        block_done(ctx, err, rd_size);
    }

    free(buf);

    return err;
}





/***************************************************************************//**
    Функция передает вначале информацию о прошивке, а затем записывает прошивку
    по блокам
    @param[in,out] st  Состояние загрузчика. Содержит информацию о файле прошивки
    @return            Код ошибки
 ******************************************************************************/
static int f_write_firm(t_lboot_state* st) {

    int err = 0;

    st->action = LBOOT_ACTION_WRITE_FIRMWARE;

    //определяем размер файла
    fseek(st->ffirm, 0, SEEK_END);
    st->wr_info.size = ftell(st->ffirm);
    fseek(st->ffirm, 0, SEEK_SET);

    print_state(st, LBOOT_ACTION_STATUS_START,
                f_make_tmp_protect_str(st->par.out_fmt, st->par.filename),
                st->wr_info.size);

    f_free_tmp_str();

    /******************* заполняем структуру с информацией о прошивке *********/
    st->wr_info.sign  = LBOOT_WRSTART_SIGN;
    st->wr_info.flags = 0;

    /*  устанавливаем флаги в соответствии с опциями */
    if (st->par.reserv)
        st->wr_info.flags |= LBOOT_STARTWR_FLAGS_RESERV_WR;
    if (st->par.nosign)
        st->wr_info.flags |= LBOOT_STARTWR_FLAGS_NOSIGN;
    /* признак упакованных данных - либо явный флаг, либо
      расширение xz */
    if (st->par.xz) {
        st->wr_info.flags |= LBOOT_STARTWR_FLAGS_PACK_XZ;
    } else {
        int len = strlen(st->par.filename);
        int ext_len = strlen(f_xz_ext);
        if ((len > ext_len) && (!strcmp(&st->par.filename[len-ext_len], f_xz_ext))) {
            st->wr_info.flags |= LBOOT_STARTWR_FLAGS_PACK_XZ;
        }
    }

    if (st->par.dont_start || st->par.recovery) {
        st->wr_info.flags |= LBOOT_STARTWR_FLAGS_DONT_START;
    }

    if (!err) {
        /* записываем информацию о прошивке в устройство  */
        err = st->par.boot_iface->start_write(st->wr_info);
        if (err) {
            f_err_print(st, err, "Can't start write firmware!", 0);
        }
    }


    /* если без подписи и не указан файл с хеш-функцией,
      то генерим хеш сами => инициализируем хеш*/
    if (!err && st->par.nosign && !st->par.hashfile)
        SHA256Reset(&st->sha256);

    if (!err) {
        err = st->par.boot_iface->fw_transfer(st, f_firm_block_read, f_firm_block_done, st->wr_info.size);
        if (err)
            f_err_print(st, err, "Can't write firmware", 0);
    }

    if (!err) {
        print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
    }

    return err;
}

/***************************************************************************//**
    Функция передает подпись или хеш прошивки.
    Хеш может передаваться как и файла, так и автосгенерированный
    @param[in,out] st  Состояние загрузчика
    @return            Код ошибки
*******************************************************************************/
static int f_write_sig(t_lboot_state* st) {
    FILE* f_wr = NULL;
    uint8_t hash[SHA256HashSize];
    int err = 0;

    st->action = LBOOT_ACTION_WRITE_SIGN;

    /* определяем, откуда перем данные для записи
      (сгенерированный хеш, из хеш-файла или из файла подписи) */
    if (st->par.nosign) {
        if (!st->par.hashfile) {
            SHA256Result(&st->sha256, hash);
            print_state(st, LBOOT_ACTION_STATUS_START, "hash", "<autogenerated>");
        } else {
            f_wr = st->fsig;
            st->par.sigfile = st->par.hashfile;
            print_state(st, LBOOT_ACTION_STATUS_START, "hash",
                        f_make_tmp_protect_str(st->par.out_fmt, st->par.sigfile));
        }
    } else {
        f_wr = st->fsig;
        print_state(st, LBOOT_ACTION_STATUS_START, "signature",
                    f_make_tmp_protect_str(st->par.out_fmt, st->par.sigfile));
    }

    f_free_tmp_str();

    if (f_wr) {
        uint8_t* buf = 0;
        int out = 0;
        /* выделяем память под записываемый блок */
        buf = malloc(st->par.boot_iface->wr_block_size);
        if (!buf)
            f_err_print(st, err = LBOOT_ERR_MEMORY_ALLOC, 0,0);

        while (!err && !out) {
            int rd_size = fread(buf, 1, st->par.boot_iface->wr_block_size, f_wr);
            if (rd_size >= 0)
                err = st->par.boot_iface->write_sign(buf, rd_size);
            if (err) {
                f_err_print(st, err, "Can't write signature!",0);
            }

            if (!err) {
                if (rd_size < (int)st->par.boot_iface->wr_block_size)
                    out =1;
            }
        }

        if (buf)
            free(buf);
    } else {
        int wr_size = 0, out = 0;

        for (wr_size=0; !out && !err;) {
            uint32_t block =  st->par.boot_iface->wr_block_size;
            if ((wr_size + block) > SHA256HashSize)
                block = SHA256HashSize - wr_size;
            err = st->par.boot_iface->write_sign(&hash[wr_size], block);
            if (err)  {
                f_err_print(st, err, "Can't write signature!",0);
            } else {
                wr_size+=block;
            }
            if (block < st->par.boot_iface->wr_block_size)
                out =1;
        }
    }

    if (!err) {
         print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
    }
    return err;
}

/***************************************************************************//**
    Функция записывает блок данных из файла (из st->par.prot_wr_file)
        в блок защищенных данных загрузчика
    @param[in,out] st  Состояние загрузчика
    @return            Код ошибки
*******************************************************************************/
int f_write_prot(t_lboot_state* st) {
    int err = 0;
    uint32_t wr_size;


    /* определяем размер файла */
    fseek(st->fprot_wr, 0, SEEK_END);
    wr_size = ftell(st->fprot_wr);
    fseek(st->fprot_wr, 0, SEEK_SET);

    /* выводим сообщение о запуске операции */
    st->action = LBOOT_ACTION_PROT_DATA_WR;
    print_state(st, LBOOT_ACTION_STATUS_START,
                f_make_tmp_protect_str(st->par.out_fmt, st->par.prot_wr_file),
                wr_size);

    /* заполняем структуру с параметрами команды и посылаем устройству */
    if (!err) {
        uint8_t wr_params[sizeof(t_lboot_speccmd_info) + sizeof(t_cmd_params_wr_prot_data)];
        t_lboot_speccmd_info *cmd = (t_lboot_speccmd_info*)wr_params;
        t_cmd_params_wr_prot_data *cmd_par = (t_cmd_params_wr_prot_data*)cmd->params;

        memset(wr_params, 0, sizeof(t_lboot_speccmd_info) + sizeof(t_cmd_params_wr_prot_data));
        cmd->sign = LBOOT_SPECCMD_SIGN;
        cmd->cmd_code = LBOOT_CMD_CODE_WRITE_PROT_DATA;
        cmd_par->data_size = wr_size;

        err = st->par.boot_iface->speccmd(cmd, sizeof(t_lboot_speccmd_info) + sizeof(t_cmd_params_wr_prot_data));
        if (err) {
            f_err_print(st, err, "Start write protection data error!",0);
        }
    }

    if (!err) {
        uint8_t* buf = 0;
        /* выделяем память под записываемый блок */
        buf = malloc(st->par.boot_iface->wr_block_size);
        if (!buf)
            f_err_print(st, err = LBOOT_ERR_MEMORY_ALLOC, 0,0);

        //пишем файл с прошивкой
        while (!err && (wr_size > 0)) {
            size_t rd_size = fread(buf, 1, st->par.boot_iface->wr_block_size, st->fprot_wr);

            err = st->par.boot_iface->fw_block_write(buf, rd_size);
            if (!err) {
                wr_size -= rd_size;
            } else {
                f_err_print(st, err, "Can't write protection data block", 0);
            }
        }

        if (!err) {
            print_state(st, LBOOT_ACTION_STATUS_DONE, 0);
        }

        if (buf)
            free(buf);
    }

    return err;
}





int main(int argc, char **argv) {
    t_lboot_state st;
    int err = 0;
    int out = 0;
    int i;


    for (i=0; f_boot_ifaces[i]!=NULL; i++) {
        if (f_boot_ifaces[i]->gen.init)
            f_boot_ifaces[i]->gen.init();
    }
    for (i=0; f_app_ifaces[i]!=NULL; i++) {
        if (f_app_ifaces[i]->gen.init)
            f_app_ifaces[i]->gen.init();
    }

    memset(&st,0,sizeof(st));
    /* разбор опций */
    err = f_parse_options(&st, argc, argv, &out);

    if (!out && !err) {
        int is_opened = 0;
        t_ltimer con_tmr;


        if ((st.par.app_to_boot) && (st.par.app_iface != NULL)) {
            int sw_err;
            st.action = LBOOT_ACTION_APP_TO_BOOT;
            print_state(&st, LBOOT_ACTION_STATUS_START, 0);

            ltimer_set_ms(&con_tmr, st.par.app_to_boot_tout);
            do {
                sw_err = st.par.app_iface->app_to_boot(&out);
            } while (sw_err && !out && !ltimer_expired(&con_tmr));

            if (sw_err) {
                f_err_print(&st, sw_err, "Cannot switch from app to bootloader", NULL);
            } else {
                print_state(&st, LBOOT_ACTION_STATUS_DONE, 0);
            }
        }



        if (st.par.precon_time > 0) {
            st.action = LBOOT_ACTION_PREOPEN_WAIT;
            print_state(&st, LBOOT_ACTION_STATUS_START, st.par.precon_time);
            SLEEP_MS(st.par.precon_time);
            print_state(&st, LBOOT_ACTION_STATUS_DONE);
        }

        /**************  пробуем открыть указанное устройство *********/
        st.action = LBOOT_ACTION_OPEN_DEVICE;
        print_state(&st, LBOOT_ACTION_STATUS_START, 0);
        ltimer_set_ms(&con_tmr, st.par.con_tout);
        do {
            err = st.par.boot_iface->open(st.par.devname, st.par.serial, NULL, &st.dev.info, &out);
        } while (err && !out && !ltimer_expired(&con_tmr));


        if (!err) {
            /* при успехе выводим информацию о устройстве */
            is_opened = 1;
            print_state(&st, LBOOT_ACTION_STATUS_DONE,
                        f_make_tmp_protect_str(st.par.out_fmt, st.dev.info.devname),
                        f_make_tmp_protect_str(st.par.out_fmt, st.dev.info.serial),
                        f_make_tmp_protect_str(st.par.out_fmt, st.dev.info.soft_ver),
                        f_make_tmp_protect_str(st.par.out_fmt, st.dev.info.brd_revision),
                        f_make_tmp_protect_str(st.par.out_fmt, st.dev.info.brd_impl));
            f_free_tmp_str();
        } else {
            f_err_print(&st, err, "Cannot open boot device", NULL);
        }

        /* получаем всю информацию о прошивке */
        if (!err) {
            err = f_read_info(&st);
        }

        if (!err && st.par.prot_wr_file) {
            err = f_write_prot(&st);
        }

        /***********  начинаем запись прошивки ********************/
        if (!err && st.boot_file) {
            err = f_write_firm(&st);
            if (!err)
                err = f_write_sig(&st);
        } /* if (!err && boot_file) - завершение записи прошивки */

        /* передаем команду на восстановление основной прошивки при необходимости */
        if (!err && st.par.recovery) {
            t_lboot_speccmd_info cmd = {LBOOT_SPECCMD_SIGN, LBOOT_CMD_CODE_RESERV_RECOV, 0};
            st.action = LBOOT_ACTION_RECOVERY;
            print_state(&st, LBOOT_ACTION_STATUS_START, 0);
            err = st.par.boot_iface->speccmd(&cmd, sizeof(cmd));
            if (err) {
                f_err_print(&st, err, "Recovery cmd error!", NULL);
            } else {
                print_state(&st, LBOOT_ACTION_STATUS_DONE,0);
            }
        }

        /* запускаем прошивку при --start-app или после recovery (если нет --dont-start) */
        if (!err && ((!st.par.dont_start && (st.boot_file || st.par.recovery))
                     || (st.par.start_only))) {
            st.action = LBOOT_ACTION_START_APP;
            print_state(&st, LBOOT_ACTION_STATUS_START,0);

            if (st.par.start_only || (st.par.recovery && !st.par.dont_start))  {
                t_lboot_speccmd_info cmd = {LBOOT_SPECCMD_SIGN, LBOOT_CMD_CODE_START_APPL, 0};
                err = st.par.boot_iface->speccmd(&cmd, sizeof(cmd));
                if (err) {
                    f_err_print(&st, err, "Start application cmd error!", NULL);
                }                
            }

            if (!err) {
               print_state(&st, LBOOT_ACTION_STATUS_DONE,0);
            }
        }


        if (is_opened) {
            int stoperr = st.par.boot_iface->close();
            if (stoperr)
                f_err_print(&st, stoperr, "Device close error!",0);
            if (!err)
                err = stoperr;
        }


        if (st.free_sig && st.par.sigfile)
            free(st.par.sigfile);
        if (st.ffirm)
            fclose(st.ffirm);
        if (st.fsig)
            fclose(st.fsig);
        if (st.fprot_wr)
            fclose(st.fprot_wr);
    }

    for (i=0; f_boot_ifaces[i]!=NULL; i++) {
        if (f_boot_ifaces[i]->gen.cleanup)
            f_boot_ifaces[i]->gen.cleanup();
    }
    for (i=0; f_app_ifaces[i]!=NULL; i++) {
        if (f_app_ifaces[i]->gen.cleanup)
            f_app_ifaces[i]->gen.cleanup();
    }

    return err;
}
