#include "lboot.h"
#include "lboot_http.h"
#include "getopt.h"
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <curl/curl.h>


#if LIBCURL_VERSION_NUM >= 0x073d00
/* In libcurl 7.61.0, support was added for extracting the time in plain
   microseconds. Older libcurl versions are stuck in using 'double' for this
   information so we complicate this example a bit by supporting either
   approach. */
#define CURL_TIME_IN_US 1
#define CURL_TIMETYPE curl_off_t
#define CURL_TIMEOPT CURLINFO_TOTAL_TIME_T
#else
#define CURL_TIME_IN_US 0
#define CURL_TIMETYPE double
#define CURL_TIMEOPT CURLINFO_TOTAL_TIME
#endif

typedef enum {
    /**< Признак, что успешно принят заголовок и сервер ожидает приема данных */
    LIP_HTTP_STATUS_CONTINUE                   = 100,
    /**< Для доступа к ресурсу нужно изменить проткол через Upgrade */
    LIP_HTTP_STATUS_SWITCHING_PROTOCOL         = 101,

    /** Запрос выполнен успешно */
    LIP_HTTP_STATUS_OK                         = 200,
    /** Запрос успешно выполнен и во время выполнения создан новый ресурс */
    LIP_HTTP_STATUS_CREATED                    = 201,

    LIP_HTTP_STATUS_ACCEPTED                   = 202,
    LIP_HTTP_STATUS_NON_AUTHORITATIVE          = 203,
    LIP_HTTP_STATUS_NO_CONTENT                 = 204,
    LIP_HTTP_STATUS_RESET_CONTENT              = 205,
    LIP_HTTP_STATUS_PARTIAL_CONTENT            = 206,

    LIP_HTTP_STATUS_MULTIPLE_CHOICES           = 300,
    LIP_HTTP_STATUS_MOVED_PERMANENTLY          = 301,
    LIP_HTTP_STATUS_FOUND                      = 302,
    LIP_HTTP_STATUS_SEE_OTHER                  = 303,
    LIP_HTTP_STATUS_NOT_MODIFIED               = 304,
    LIP_HTTP_STATUS_USE_PROXY                  = 305,
    LIP_HTTP_STATUS_TEMPORARY_REDIRECT         = 307,

    LIP_HTTP_STATUS_BAD_REQUEST                = 400,
    LIP_HTTP_STATUS_UNAUTHORIZED               = 401,
    LIP_HTTP_STATUS_PAYMENT_REQUIRED           = 402,
    LIP_HTTP_STATUS_FORBIDDEN                  = 403,
    LIP_HTTP_STATUS_NOT_FOUND                  = 404,
    LIP_HTTP_STATUS_METHOD_NOT_ALLOWED         = 405,
    LIP_HTTP_STATUS_NOT_ACCEPTABLE             = 406,
    LIP_HTTP_STATUS_PROXY_AUTH_REQIRED         = 407,
    LIP_HTTP_STATUS_REQUEST_TIMEOUT            = 408,
    LIP_HTTP_STATUS_CONFLICT                   = 409,
    LIP_HTTP_STATUS_GONE                       = 410,
    LIP_HTTP_STATUS_LENGTH_REQUIRED            = 411,
    LIP_HTTP_STATUS_PRECONDITION_FAILED        = 412,
    LIP_HTTP_STATUS_PAYLOAD_TOO_LARGE          = 413,
    LIP_HTTP_STATUS_URI_TOO_LONG               = 414,
    LIP_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE     = 415,
    LIP_HTTP_STATUS_RANGE_NOT_SATISFIABLE      = 416,
    LIP_HTTP_STATUS_EXPECTATION_FAILED         = 417,
    LIP_HTTP_STATUS_UPGRADE_REQUIRED           = 418,

    LIP_HTTP_STATUS_INTERNAL_SERVER_ERROR      = 500,
    LIP_HTTP_STATUS_NOT_IMPLEMENTED            = 501,
    LIP_HTTP_STATUS_BAD_GATEWAY                = 502,
    LIP_HTTP_STATUS_SERVICE_UNAVAILABLE        = 503,
    LIP_HTTP_STATUS_GATEWAY_TIMEOUT            = 504,
    LIP_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505
} t_lip_http_status_code;



typedef struct  {
    uint8_t *buf;
    unsigned buf_pos;
    unsigned buf_size;
} t_mem_ctx;

typedef struct {
    int err;
    uint32_t rem_size;
    void *lboot_ctx;
    curl_off_t last_progr_bytes;
    curl_off_t last_progr_time;
    t_lboot_firm_block_read block_rd;
    t_lboot_firm_block_done block_done;
} t_fw_ctx;



static int f_init(void);
static int f_cleanup(void);
static int f_parse_option(int opt);
static const char* f_get_err_str(int err);
static const char* f_get_opt_descr(void);
static int f_open(const char* devname, const char* serial, const void* params,
                   t_lboot_devinfo* info, int* out);
static int f_get_devmode(uint8_t* mode);
static int f_get_bootloader_version(uint16_t* ver);
static int f_get_dev_flags(uint32_t* flags);
static int f_get_features(uint32_t* features);
static int f_start_write(t_lboot_startwr_info wr_info);
int f_fw_transfer(void *ctx, t_lboot_firm_block_read block_rd,
                  t_lboot_firm_block_done block_done, uint32_t fw_size);
static int f_write_sign(const uint8_t* sign, uint32_t size);
static int f_speccmd(const t_lboot_speccmd_info* cmd_info, uint32_t size);
static int f_close(void);




static const char *f_addr = "192.168.0.1";
static const char *f_port = "80";
static const char *f_proxy = NULL;
static long f_con_tout = 10000;
static long f_transfer_tout = 5000;
static char f_url[1024];
static char f_http_resp_str[1024];
static int f_nosign;
static int f_wr_size = 0;

static CURL *curl_handle;
static const char *f_curl_err_str;
static int f_http_err;
static char f_dev_err_str[128];
static t_mem_ctx f_err_ctx;
static struct curl_slist *post_hdr_list = NULL;
static struct {
    int err;
    curl_off_t last_dl;
    curl_off_t last_ul;
    curl_off_t last_time;
} f_progr_ctx;




/** Описание опций интерфейса  */
static const char* f_opt_descr =
"http options:\n"\
"    --http-ip-addr=addr        - board ip-addres (for instance \"192.168.0.1\")\n"\
"    --http-port=port           - http server TCP port (default 80)\n"
"    --http-proxy=proxy         - specify http proxy settings \n"
"                                   (for instancse \"http://192.168.12.1:3128\").\n"
"                                   Use empty string to explicit disable proxy usage.\n"
"    --http-con-tout=tout       - connection timeout in ms (default 10000).\n"
"    --http-transfer-tout=tout  - block transfer timeout in ms (default 5000).\n"
"                                   Use 0 to disable transfer timeout check.\n";


typedef enum {
    LBOOT_ERR_HTTP_FIRST            = -300,
    LBOOT_ERR_HTTP_CURL             = -300,
    LBOOT_ERR_HTTP_RESPONSE         = -301,
    LBOOT_ERR_HTTP_RX_INSUF_SIZE    = -302,
    LBOOT_ERR_HTTP_TRANSFER_TOUT    = -303,
} t_lboot_http_errs;

static const char* f_err_str_list[] = {
    "",
    "error http server response",
    "receive insufficient data size",
    "http transfer timeout"
};

static struct {
    t_lip_http_status_code status;
    const char *msg;
} f_http_resp_str_list[] = {
    {LIP_HTTP_STATUS_MULTIPLE_CHOICES, "multiple choices"},
    {LIP_HTTP_STATUS_MOVED_PERMANENTLY, "moved permanently"},
    {LIP_HTTP_STATUS_FOUND, "found"},
    {LIP_HTTP_STATUS_SEE_OTHER, "see other"},
    {LIP_HTTP_STATUS_NOT_MODIFIED, "not modified"},
    {LIP_HTTP_STATUS_USE_PROXY, "use proxy"},
    {LIP_HTTP_STATUS_TEMPORARY_REDIRECT, "temporary redirect"},
    {LIP_HTTP_STATUS_BAD_REQUEST, "bad request"},
    {LIP_HTTP_STATUS_UNAUTHORIZED, "unauthorized"},
    {LIP_HTTP_STATUS_PAYMENT_REQUIRED, "payment reqired"},
    {LIP_HTTP_STATUS_FORBIDDEN, "forbidden"},
    {LIP_HTTP_STATUS_NOT_FOUND, "not found"},
    {LIP_HTTP_STATUS_METHOD_NOT_ALLOWED, "method not allowed"},
    {LIP_HTTP_STATUS_NOT_ACCEPTABLE, "not acceptable"},
    {LIP_HTTP_STATUS_PROXY_AUTH_REQIRED, "proxy authentication required"},
    {LIP_HTTP_STATUS_REQUEST_TIMEOUT, "request timeout"},
    {LIP_HTTP_STATUS_CONFLICT, "confilict"},
    {LIP_HTTP_STATUS_GONE, "gone"},
    {LIP_HTTP_STATUS_LENGTH_REQUIRED, "length required"},
    {LIP_HTTP_STATUS_PRECONDITION_FAILED, "precondition failed"},
    {LIP_HTTP_STATUS_PAYLOAD_TOO_LARGE, "payload too large"},
    {LIP_HTTP_STATUS_URI_TOO_LONG, "uri too large"},
    {LIP_HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, "unsupported media type"},
    {LIP_HTTP_STATUS_RANGE_NOT_SATISFIABLE, "range not satisfaible"},
    {LIP_HTTP_STATUS_EXPECTATION_FAILED, "expectation failed"},
    {LIP_HTTP_STATUS_UPGRADE_REQUIRED, "upgrade required"},
    {LIP_HTTP_STATUS_INTERNAL_SERVER_ERROR, "internal server error"},
    {LIP_HTTP_STATUS_NOT_IMPLEMENTED, "not implemented"},
    {LIP_HTTP_STATUS_BAD_GATEWAY, "bad gateway"},
    {LIP_HTTP_STATUS_SERVICE_UNAVAILABLE, "service unavailable"},
    {LIP_HTTP_STATUS_GATEWAY_TIMEOUT, "gateway timeout"},
    {LIP_HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, "HTTP version not supppered"}
};

const t_boot_iface g_http_intf_info = {
    { "http",
      "HTTP protocol over Ethernet interface",
      f_init,
      f_cleanup,
      f_parse_option,
      f_get_opt_descr,
      f_get_err_str,
    },
    NULL,
    /* так как явно не используем запись по блокам, то устанавливаем размер блока
     * достаточный для однократной записи подписи, команды или защищенных данных,
     * а запись прошивки этот параметр в любом случае не использует */
    64*1024,
    f_open,
    f_get_devmode,
    f_get_bootloader_version,
    f_get_dev_flags,
    f_get_features,
    f_start_write,
    f_fw_transfer,
    /* так как curl сам забирает данные через callback, то нельзя воспользоваться
     * штатной функцией, читающей данные по блокам, а нужная своя реализация.
     * fw_write_block при этом не используется */
    NULL,
    f_write_sign,
    f_speccmd,
    f_close
};


int f_prog_cb(void *clientp,   curl_off_t dltotal,   curl_off_t dlnow,   curl_off_t ultotal,   curl_off_t ulnow) {
    int ret = 0;
    curl_off_t ms_total;
    CURL_TIMETYPE total;

    int updated = 0;

    if (ultotal > 0) {
        curl_off_t delta = ulnow - f_progr_ctx.last_ul;
        if (delta > 0) {
            f_progr_ctx.last_ul = ulnow;
            updated = 1;
        }
    }

    if (dltotal > 0) {
        curl_off_t delta = dlnow - f_progr_ctx.last_dl;
        if (delta > 0) {
            f_progr_ctx.last_dl = dlnow;
            updated = 1;
        }
    }

    curl_easy_getinfo(curl_handle, CURL_TIMEOPT, &total);

#if CURL_TIME_IN_US
    ms_total = total/1000;
#else
    ms_total = (curl_off_t)(total * 1000);
#endif
    /* если передали новый блок, то сохраняем текущее время, как время последнего обновления,
     * иначе - проверяем, что время простоя не превышает заданный интервал */
    if (updated) {
        f_progr_ctx.last_time = ms_total;
    } else {
        /* для таймаута на первый омен данными также включаем и время таймаута на
         * подключение, т.к. время считается все и мы не должны прерывать
         * подключение, если таймаут на трансфер меньше */
        if ((f_transfer_tout > 0) && ((ms_total - f_progr_ctx.last_time) >
                                      f_transfer_tout + ((dlnow + ulnow) == 0 ? f_con_tout : 0))) {
            f_progr_ctx.err = LBOOT_ERR_HTTP_TRANSFER_TOUT;
            ret = -1;
        }
    }

    return ret;
}


static char *f_get_http_resp_str(int code) {
    size_t err_idx;
    int fnd_idx = -1;
    for (err_idx = 0; (err_idx < sizeof(f_http_resp_str_list)/sizeof(f_http_resp_str_list[0])) && (fnd_idx < 0); err_idx++ ) {
        if (f_http_resp_str_list[err_idx].status == code) {
            fnd_idx = err_idx;
        }
    }
    sprintf(f_http_resp_str, "http bad response. status %d: %s", code,
            fnd_idx >= 0 ? f_http_resp_str_list[fnd_idx].msg : "unknown");
    return f_http_resp_str;
}



static size_t f_rx_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
    t_mem_ctx *ctx = (t_mem_ctx *)userdata;
    size_t rx_bytes = size * nmemb;
    size_t rdy_bytes = ctx->buf_size - ctx->buf_pos;
    if (rdy_bytes < rx_bytes)
        rx_bytes = rdy_bytes;
    if (rx_bytes > 0) {
        memcpy(&ctx->buf[ctx->buf_pos], ptr, rx_bytes);
        ctx->buf_pos += rx_bytes;
    }
    return size * nmemb;
}



static size_t f_tx_cb(void *dest, size_t size, size_t nmemb, void *userdata) {
    t_mem_ctx *ctx = (t_mem_ctx *)userdata;
    size_t tx_bytes = size*nmemb;
    size_t rdy_bytes = ctx->buf_size - ctx->buf_pos;
    if (rdy_bytes < tx_bytes)
        tx_bytes = rdy_bytes;
    if (tx_bytes > 0) {
        memcpy(dest, &ctx->buf[ctx->buf_pos], tx_bytes);
        ctx->buf_pos += tx_bytes;
    }
    return tx_bytes;
}


int f_fw_tx_prog_cb(void *clientp,   curl_off_t dltotal,   curl_off_t dlnow,   curl_off_t ultotal,   curl_off_t ulnow) {
    t_fw_ctx *ctx = (t_fw_ctx *)clientp;

    if (ultotal > 0) {
        int delta = (int)(ulnow - ctx->last_progr_bytes);
        if (delta > 0) {
            ctx->last_progr_bytes += delta;
            ctx->block_done(ctx->lboot_ctx, ctx->err, delta);
        }
    }
    return f_prog_cb(clientp, dltotal, dlnow, ultotal, ulnow);
}


static size_t f_fw_tx_cb(void *dest, size_t size, size_t nmemb, void *userdata) {
    t_fw_ctx *ctx = (t_fw_ctx *)userdata;
    uint32_t proc_size = 0;
    if (ctx->err == LBOOT_ERR_SUCCESS) {
        size_t tx_bytes = size*nmemb;
        size_t rdy_bytes = ctx->rem_size;

        if (rdy_bytes < tx_bytes)
            tx_bytes = rdy_bytes;

        /* ограничение размера, сделанное исключительно для блоее мелкой индикации прогресса */
        if (tx_bytes > 2048)
            tx_bytes = 2048;
        if (tx_bytes > 0) {
            ctx->err = ctx->block_rd(ctx->lboot_ctx, dest, tx_bytes, &proc_size);
            if (ctx->err == LBOOT_ERR_SUCCESS) {
                ctx->rem_size -= proc_size;                
            } else {
                proc_size = CURL_READFUNC_ABORT;
            }
        }
    }
    return proc_size;
}



static void f_curl_req_init(const char *filename) {
    curl_easy_reset(curl_handle);


    sprintf(f_url, "http://%s:%s/lboot/%s", f_addr, f_port, filename);
    curl_easy_setopt(curl_handle, CURLOPT_URL, f_url);
    curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT_MS, f_con_tout);
    curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
    if (f_proxy)
        curl_easy_setopt(curl_handle, CURLOPT_PROXY, f_proxy);


    f_progr_ctx.err = LBOOT_ERR_SUCCESS;
    f_progr_ctx.last_dl = 0;
    f_progr_ctx.last_ul = 0;
    f_progr_ctx.last_time = 0;
    curl_easy_setopt(curl_handle, CURLOPT_XFERINFOFUNCTION, f_prog_cb);
    curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L);
}

static void f_curl_req_post_init(const char *filename, unsigned size) {
    f_err_ctx.buf_pos = 0;
    f_curl_req_init(filename);
    curl_easy_setopt(curl_handle, CURLOPT_POST, 1UL);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, NULL);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE , size);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, f_rx_cb);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&f_err_ctx);

    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, post_hdr_list);
}


static int f_curl_req_exec() {
    int err = LBOOT_ERR_SUCCESS;
    f_err_ctx.buf_pos = 0;
    f_http_err = LIP_HTTP_STATUS_OK;

    CURLcode res = curl_easy_perform(curl_handle);
    if ((res == CURLE_ABORTED_BY_CALLBACK) && (f_progr_ctx.err != LBOOT_ERR_SUCCESS)) {
        err = f_progr_ctx.err;
    } else if (res != CURLE_OK) {
        f_curl_err_str = curl_easy_strerror(res);
        err = LBOOT_ERR_HTTP_CURL;
    } else {
        long resp_code;
        curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &resp_code);
        if (resp_code >= 300) {
            int deverr = 0;

            f_http_err = resp_code;

            if (f_err_ctx.buf_pos > 0) {
                f_err_ctx.buf[f_err_ctx.buf_pos] = '\0';
                deverr = strtol(f_err_ctx.buf, NULL, 10);
            }

            if (deverr != 0) {
                err = deverr;
            } else {
                err = LBOOT_ERR_HTTP_RESPONSE;
            }
        }
    }
    return err;
}

/* чтение ресурса с сервера в бфер */
static int f_get_to_buf(const char *srv_filename, uint8_t *buf, unsigned buf_size, unsigned min_size, unsigned *rx_size) {
    int err = LBOOT_ERR_SUCCESS;
    t_mem_ctx ctx;
    ctx.buf = buf;
    ctx.buf_size = buf_size;
    ctx.buf_pos = 0;

    f_curl_req_init(srv_filename);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, f_rx_cb);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&ctx);

    err  = f_curl_req_exec();
    if (err == LBOOT_ERR_SUCCESS) {
        if (ctx.buf_pos < min_size)
            err = LBOOT_ERR_HTTP_RX_INSUF_SIZE;
        if (rx_size)
            *rx_size = ctx.buf_pos;
    }
    return err;
}

/* передача ресурса из буфера на сервер */
static int f_post_from_buf(const char *srv_filename, const uint8_t *buf, unsigned buf_size) {
    t_mem_ctx ctx;
    ctx.buf = (uint8_t*)buf;
    ctx.buf_size = buf_size;
    ctx.buf_pos = 0;

    f_curl_req_post_init(srv_filename, buf_size);
    curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, f_tx_cb);
    curl_easy_setopt(curl_handle, CURLOPT_READDATA, (void *)&ctx);

    return f_curl_req_exec();
}

static int f_init(void) {
    curl_global_init(CURL_GLOBAL_ALL);
    curl_handle = curl_easy_init();
    post_hdr_list = curl_slist_append(post_hdr_list, "Content-Type: application/octet-stream");


    f_err_ctx.buf = (uint8_t*)f_dev_err_str;
    f_err_ctx.buf_pos = 0;
    /* запас в 1 символ для добавления нулевого симвала - признака конца строки */
    f_err_ctx.buf_size = sizeof(f_dev_err_str) - 1;


    return 0;
}

static int f_cleanup(void) {
    curl_easy_cleanup(curl_handle);
    curl_slist_free_all(post_hdr_list);
    curl_global_cleanup();
    return 0;
}

static int f_close(void) {
    return 0;
}


static int f_parse_option(int opt) {
    int err = 0;
    switch (opt) {
        case LBOOT_OPT_HTTP_IP_ADDR:
            f_addr = optarg;
            break;
        case LBOOT_OPT_HTTP_PORT:
            f_port = optarg;
            break;
        case LBOOT_OPT_HTTP_PROXY:
            f_proxy = optarg;
            break;
        case LBOOT_OPT_HTTP_CON_TOUT:
            f_con_tout = strtol(optarg, NULL, 10);
            break;
        case LBOOT_OPT_HTTP_TRANSF_TOUT:
            f_transfer_tout = strtol(optarg, NULL, 10);
            break;
        default:
            err = LBOOT_ERR_INVALID_OPTION;
            break;
    }
    return err;
}
static const char* f_get_err_str(int err) {
    return ((err > LBOOT_ERR_HTTP_FIRST) ||
            (err <  (LBOOT_ERR_HTTP_FIRST -
                     (int32_t)(sizeof(f_err_str_list)/sizeof(f_err_str_list[0]))+1))) ? NULL :
            err == LBOOT_ERR_HTTP_CURL ? f_curl_err_str :
            err == LBOOT_ERR_HTTP_RESPONSE ? f_get_http_resp_str(f_http_err) :
                                            f_err_str_list[LBOOT_ERR_HTTP_FIRST - err];
}
static const char* f_get_opt_descr(void) {
    return f_opt_descr;
}
static int f_open(const char* devname, const char* serial, const void* params,
                  t_lboot_devinfo* info, int* out) {
    unsigned size = sizeof(*info);
    return f_get_to_buf("devinfo", (uint8_t*)info, size, size, NULL);
}

static int f_get_devmode(uint8_t* mode) {
    unsigned size = sizeof(*mode);
    return f_get_to_buf("devmode", mode, size, size, NULL);
}
static int f_get_bootloader_version(uint16_t* ver) {
    unsigned size = sizeof(*ver);
    return f_get_to_buf("bootver", (uint8_t*)ver, size, size, NULL);
}
static int f_get_dev_flags(uint32_t* flags) {
    unsigned size = sizeof(*flags);
    return f_get_to_buf("devflags", (uint8_t*)flags, size, size, NULL);
}
static int f_get_features(uint32_t* features) {
    unsigned size = sizeof(*features);
    return f_get_to_buf("features", (uint8_t*)features, size, size, NULL);
}
static int f_start_write(t_lboot_startwr_info wr_info) {
    int err = f_post_from_buf("startwr", (uint8_t*)&wr_info, sizeof(wr_info));
    if (!err) {
        f_wr_size = wr_info.size;
        f_nosign = wr_info.flags & LBOOT_STARTWR_FLAGS_NOSIGN;
    }
    return err;
}

int f_fw_transfer(void *lboot_ctx, t_lboot_firm_block_read block_rd,
                  t_lboot_firm_block_done block_done, uint32_t fw_size) {
    t_fw_ctx ctx;

    int err;

    ctx.err = LBOOT_ERR_SUCCESS;
    ctx.lboot_ctx = lboot_ctx;
    ctx.block_rd = block_rd;
    ctx.block_done = block_done;
    ctx.rem_size = fw_size;
    ctx.last_progr_bytes = 0;
    ctx.last_progr_time = 0;


    f_curl_req_post_init("firmware.preconf", fw_size);
    curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, f_fw_tx_cb);
    curl_easy_setopt(curl_handle, CURLOPT_READDATA, (void *)&ctx);

    curl_easy_setopt(curl_handle, CURLOPT_XFERINFOFUNCTION, f_fw_tx_prog_cb);
    curl_easy_setopt(curl_handle, CURLOPT_XFERINFODATA, &ctx);

    err = f_curl_req_exec();
    return (ctx.err == LBOOT_ERR_SUCCESS ? err : ctx.err);
}


static int f_write_sign(const uint8_t* sign, uint32_t size) {
    return f_post_from_buf(f_nosign ? "hash" : "sig", sign, size);
}
static int f_speccmd(const t_lboot_speccmd_info* cmd_info, uint32_t size) {
    return f_post_from_buf("cmd", (uint8_t*)cmd_info, size);
}












