#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
    #define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <WinSock2.h>
typedef int socklen_t;
typedef unsigned long in_addr_t;
#else
    #define closesocket(sock) close(sock)

    #include <fcntl.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <netinet/tcp.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>

    typedef int SOCKET;
    #define INVALID_SOCKET -1
    #define SOCKET_ERROR -1
#endif

#include <errno.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "lboot.h"
#include "lboot_tftp.h"

#define TFTP_DEFAULT_TIMEOUT 1000
#define TFTP_RETRANS_CNT     5

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);
static int f_write(const uint8_t* buf, uint32_t 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);


#define LBOOT_DEFAULT_TFTP_WR_BLOCK_SIZE 1428

#define LIP_TFTP_DEFAULT_BLOCK_SIZE  512


//опкоды пакетов TFTP
#define LIP_TFTP_OPCODE_RRQ   1
#define LIP_TFTP_OPCODE_WRQ   2
#define LIP_TFTP_OPCODE_DATA  3
#define LIP_TFTP_OPCODE_ACK   4
#define LIP_TFTP_OPCODE_ERR   5
#define LIP_TFTP_OPCODE_OACK  6


#define LIP_TFTP_OPTS_BLKSIZE     "blksize"
#define LIP_TFTP_OPTS_FILE_SIZE   "tsize"

enum en_TFTP_ECode {
    TFTP_ECODE_MISC = 0,
    TFTP_ECODE_FILE_NOT_FOUND,
    TFTP_ECODE_ACCESS_VIOLATION,
    TFTP_ECODE_DISK_FULL,
    TFTP_ECODE_ILLEGAL_OPERATION,
    TFTP_ECODE_UNKNOWN_TID,
    TFTP_ECODE_FILE_EXISTS,
    TFTP_ECODE_NO_SUCH_USER
};


/** Описание опций интерфейса  */
static const char* f_opt_descr =
"tftp options:\n"\
"    --tftp-ip-addr=addr     - board IP-addres (for instance \"192.168.0.1\")\n"\
"    --tftp-port=port        - TFTP server UDP port (default 69)\n"\
"    --tftp-block-size=size  - TFTP data packet preffered block size\n";

/** Коды ошибок для интерфейса  */
typedef enum {
    LBOOT_ERR_TFTP_FIRST         = -200,
    /** Ошибка создания сокета */
    LBOOT_ERR_TFTP_SOCK_CREATE   = -200,
    /** Ошибка передачи данных */
    LBOOT_ERR_TFTP_SEND          = -201,
    /** Ошибка передачи данных */
    LBOOT_ERR_TFTP_RECV          = -202,
    /** Неверный формат принятого пакета */
    LBOOT_ERR_TFTP_RECV_PKT_FMT  = -203,
    /** Ошибка передачи данных */
    LBOOT_ERR_TFTP_TIMEOUT       = -204,
    /** Неверный формат адреса */
    LBOOT_ERR_TFTP_IP_ADDR       = -205,
    /** Неверный номер порта */
    LBOOT_ERR_TFTP_UDP_PORT      = -206,
    LBOOT_ERR_TFTP_BLOCK_SIZE    = -207,
    /** Принят пакет ERR по TFTP */
    LBOOT_ERR_TFTP_ERR_PACKET    = -208,
    LBOOT_ERR_TFTP_RX_OVERFLOW   = -209,
    LBOOT_ERR_TFTP_RX_INSUF_SIZE = -210,
    LBOOT_ERR_TFTP_PACKET_SKIP   = -211,
    LBOOT_ERR_TFTP_WSA_INIT      = -212,
    LBOOT_ERR_TFTP_CON_RESET     = -213
} t_lboot_tftp_errs;

static const char* f_err_str[] = {
    "can't create socket",
    "can't send tftp packet",
    "receive tftp packet error",
    "invalid received packet format",
    "tftp retransmition timeout",
    "invalid ip addr value",
    "invalid udp port value",
    "invalid tftp block size parameter",
    "tftp err packet received",
    "tftp receive file overflow",
    "receive insufficient size",
    "invalid block number",
    "WSA init errror",
    "connection reset by another side (port unreachable?)"
};

/** параметры интерфейса и его интерфейсные функции */
t_boot_iface g_tftp_intf_info = {
    { "tftp",
      "TFTP protocol over Ethernet interface",
      f_init,
      f_cleanup,
      f_parse_option,
      f_get_opt_descr,
      f_get_err_str,
    },
    NULL,
    LBOOT_DEFAULT_TFTP_WR_BLOCK_SIZE,
    f_open,
    f_get_devmode,
    f_get_bootloader_version,
    f_get_dev_flags,
    f_get_features,
    f_start_write,
    lboot_fw_transfer_std_block,
    f_write,
    f_write_sign,
    f_speccmd,
    f_close
};




typedef struct {
    SOCKET sock;
} t_tftp_state;


static struct sockaddr_in f_addr;
static uint16_t f_last_err;
static char f_last_err_str[512];
static int f_nosign;





static uint8_t f_buf[2500];
static const char f_mode[] = "octet";

static int f_send_req(SOCKET s, struct sockaddr_in* addr, const char* filename, int wr, int size) {
    int err = 0;
    int put_pos = 0;
    int op = wr ? LIP_TFTP_OPCODE_WRQ : LIP_TFTP_OPCODE_RRQ;
    int trans;

    f_buf[put_pos++] = (op>>8)&0xFF;
    f_buf[put_pos++] = op&0xFF;
    strcpy((char*)&f_buf[put_pos], filename);
    put_pos += strlen(filename) + 1;
    strcpy((char*)&f_buf[put_pos], f_mode);
    put_pos += sizeof(f_mode);

    if (g_tftp_intf_info.wr_block_size != LIP_TFTP_DEFAULT_BLOCK_SIZE) {
        strcpy((char*)&f_buf[put_pos], LIP_TFTP_OPTS_BLKSIZE);
        put_pos += sizeof(LIP_TFTP_OPTS_BLKSIZE);
        sprintf((char*)&f_buf[put_pos], "%d", g_tftp_intf_info.wr_block_size);
        put_pos += strlen((char*)&f_buf[put_pos])+1;
    }

    strcpy((char*)&f_buf[put_pos], LIP_TFTP_OPTS_FILE_SIZE);
    put_pos += sizeof(LIP_TFTP_OPTS_FILE_SIZE);
    sprintf((char*)&f_buf[put_pos], "%d", size);
    put_pos += strlen((char*)&f_buf[put_pos])+1;

    trans = sendto(s, f_buf, put_pos, 0, (struct sockaddr*)addr, sizeof(struct sockaddr_in));
    if (trans != put_pos) {
        err = LBOOT_ERR_TFTP_SEND;
    }
    return err;
}

static int f_send_ack(SOCKET s, struct sockaddr_in* addr, int block) {
    int put_pos = 0, trans;
    f_buf[put_pos++] = (LIP_TFTP_OPCODE_ACK>>8)&0xFF;
    f_buf[put_pos++] = LIP_TFTP_OPCODE_ACK&0xFF;
    f_buf[put_pos++] = (block>>8)&0xFF;
    f_buf[put_pos++] = block&0xFF;
    trans = sendto(s, f_buf, put_pos, 0, (struct sockaddr*)addr, sizeof(struct sockaddr_in));
    return trans!=put_pos ? LBOOT_ERR_TFTP_SEND : 0;
}

static int f_send_err(SOCKET s, struct sockaddr_in* addr, int err, const char* str) {
    int put_pos = 0, trans;
    f_buf[put_pos++] = (LIP_TFTP_OPCODE_ERR>8)&0xFF;
    f_buf[put_pos++] = LIP_TFTP_OPCODE_ERR&0xFF;
    f_buf[put_pos++] = (err>>8)&0xFF;
    f_buf[put_pos++] = err&0xFF;
    strcpy((char*)&f_buf[put_pos], str);
    put_pos+=strlen(str)+1;
    trans = sendto(s, f_buf, put_pos, 0, (struct sockaddr*)addr, sizeof(struct sockaddr_in));
    return trans!=put_pos ? LBOOT_ERR_TFTP_SEND : 0;
}



static int f_recv_packet(SOCKET s, int retr_cnt, int* size, struct sockaddr_in* addr) {
    int err = 0;
    fd_set rset, eset;
    struct timeval timeout = { ((retr_cnt+1) * TFTP_DEFAULT_TIMEOUT)/1000,
                (((retr_cnt+1) * TFTP_DEFAULT_TIMEOUT)%1000)*1000 };
    struct sockaddr_in rx_addr;
    socklen_t addr_size = sizeof(rx_addr);
    int sel_res = 0;

    /* Receive data */
    FD_ZERO(&rset);
    FD_SET(s, &rset);
    FD_ZERO(&eset);
    FD_SET(s, &eset);
    sel_res = select((int)s+1, &rset, NULL, &eset, &timeout);
    if ((sel_res < 0) || (FD_ISSET(s, &eset))) {
        err = LBOOT_ERR_TFTP_RECV;
    } else if (!FD_ISSET(s, &rset)) {
        err = LBOOT_ERR_TFTP_TIMEOUT;
    } else {
        int data_len = recvfrom(s, f_buf, sizeof(f_buf), 0, (struct sockaddr*) &rx_addr, &addr_size);

        if (SOCKET_ERROR == data_len) {
            err = LBOOT_ERR_TFTP_RECV;
#ifdef _WIN32            
            if (WSAGetLastError()==WSAECONNRESET)
                err = LBOOT_ERR_TFTP_CON_RESET;
#endif
        } else if ((addr->sin_port != 0) && (rx_addr.sin_port != addr->sin_port)
                 && (rx_addr.sin_addr.s_addr != addr->sin_addr.s_addr)) {
            err = LBOOT_ERR_TFTP_PACKET_SKIP;
            f_send_err(s, &rx_addr, TFTP_ECODE_UNKNOWN_TID, "invalid tid");
        }

        if (!err) {
            if (data_len < 2) {
                err = LBOOT_ERR_TFTP_RECV_PKT_FMT;
            } else {
                *size = data_len;
                if (addr->sin_port == 0)
                    *addr = rx_addr;
            }
        }
    }
    return err;
}

static int f_proc_oak(uint8_t* buf, int size, uint32_t* blk_size) {
    int err = 0;
    uint32_t res_blk_size = LIP_TFTP_DEFAULT_BLOCK_SIZE;
    while (size > 0) {
        char* opt = (char*)buf;

        int len = strnlen(opt, size)+1;
        buf += len;
        size -= len;
        if (size) {
            char* val = (char*)buf;
            len = strnlen(val, size)+1;
            buf += len;
            size -= len;

            if (!strcmp(LIP_TFTP_OPTS_BLKSIZE, opt)) {
                unsigned blk = atoi(val);
                if ((blk >= 8) && (blk <= g_tftp_intf_info.wr_block_size)) {
                    res_blk_size = blk;
                } else {
                    err = LBOOT_ERR_TFTP_RECV_PKT_FMT;
                }
            } else if (!strcmp(LIP_TFTP_OPTS_FILE_SIZE, opt)) {

            } else {
                err = LBOOT_ERR_TFTP_RECV_PKT_FMT;
            }
        }
    }

    if (!err) {
        *blk_size = res_blk_size;
    }
    return err;
}

static int f_process_err(uint8_t* buf, int size) {
    if (size < 2)
        return LBOOT_ERR_TFTP_RECV_PKT_FMT;

    f_last_err = ((unsigned)buf[0]<<8) | buf[1];
    strncpy(f_last_err_str, (char*)&buf[2], size-2);
    return LBOOT_ERR_TFTP_ERR_PACKET;
}

static int f_process_ack(uint8_t* buf, int size, int block) {
    int ack;
    if (size < 2)
        return LBOOT_ERR_TFTP_RECV_PKT_FMT;

    ack = ((unsigned)buf[0]<<8) | buf[1];
    if (ack != block)
        return LBOOT_ERR_TFTP_PACKET_SKIP;

    return 0;
}


static int f_process_data(uint8_t* buf, int size, int block, uint8_t* out_buf, int* out_size) {
    int rcv_block = ((unsigned)buf[0] << 8) | buf[1];
    int max_size = *out_size;
    int err = 0;
    buf += 2;
    size -= 2;
    if (rcv_block != (block+1))
        return LBOOT_ERR_TFTP_PACKET_SKIP;

    if (size > max_size) {
        size = max_size;
        err = LBOOT_ERR_TFTP_RX_OVERFLOW;
    } else {
        memcpy(out_buf, buf, size);
        *out_size = size;
    }
    return err;
}

static int f_tftp_read(struct sockaddr_in* addr, const char* filename, uint8_t* buf, uint32_t* rx_size) {
    SOCKET s;
    int err = 0;
    struct sockaddr_in rx_addr;
    uint32_t max_size = *rx_size;

    memset(&rx_addr, 0, sizeof(rx_addr));
    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s != INVALID_SOCKET) {
        int trans, rq_done = 0, f_done = 0, rx_block = 0;
        int retr_cnt=0;
        *rx_size=0;

        while (!rq_done && !err) {
            err = f_send_req(s, addr, filename, 0, 0);

            if (!err) {
                err = f_recv_packet(s, retr_cnt, &trans, &rx_addr);
                if ((err == LBOOT_ERR_TFTP_TIMEOUT) && (++retr_cnt < TFTP_RETRANS_CNT)) {
                    err = 0;
                    retr_cnt++;
                } else if (!err) {
                    unsigned op = ((unsigned)f_buf[0] <<8) | f_buf[1];
                    if (op == LIP_TFTP_OPCODE_OACK) {
                        err = f_proc_oak(&f_buf[2], trans-2, &g_tftp_intf_info.wr_block_size);
                        if (!err) {
                            rq_done = 1;
                        }
                    } else if (op == LIP_TFTP_OPCODE_ERR) {
                        err = f_process_err(&f_buf[2], trans-2);
                    } else if (op == LIP_TFTP_OPCODE_DATA) {
                        int data_size = max_size-*rx_size;
                        g_tftp_intf_info.wr_block_size = LIP_TFTP_DEFAULT_BLOCK_SIZE;
                        err = f_process_data(&f_buf[2], trans-2, rx_block, buf, &data_size);
                        if (!err) {
                            buf += data_size;
                            *rx_size += *rx_size+ data_size;
                            rq_done = 1;
                            rx_block++;
                            if (data_size < (int)g_tftp_intf_info.wr_block_size) {
                                f_send_ack(s, &rx_addr, rx_block);
                                f_done = 1;
                            }
                        } else {
                            err = 0;
                        }
                    }
                }
            }
        }

        while (!f_done && !err) {
            int skip = 1;
             err = f_send_ack(s, &rx_addr, rx_block);

             while (skip && !err) {
                 skip = 0;
                 if (!err)
                    err = f_recv_packet(s, retr_cnt, &trans, &rx_addr);
                 if (err==LBOOT_ERR_TFTP_PACKET_SKIP) {
                     skip=1;
                 } else if (!err) {
                     unsigned op = ((unsigned)f_buf[0] <<8) | f_buf[1];
                     if (op == LIP_TFTP_OPCODE_ERR) {
                         err = f_process_err(&f_buf[2], trans-2);
                     } else if (op == LIP_TFTP_OPCODE_DATA) {
                         int data_size = max_size-*rx_size;
                         err = f_process_data(&f_buf[2], trans-2, rx_block, buf, &data_size);
                         if (!err) {
                             buf+=data_size;
                             *rx_size = *rx_size+ data_size;
                             rx_block++;
                             if (data_size < (int)g_tftp_intf_info.wr_block_size) {
                                 f_done = 1;
                                 f_send_ack(s, &rx_addr, rx_block);
                             }
                         } else {
                             err = 0;
                         }
                     } else {
                         skip = 1;
                     }
                 }
             }

             if ((err == LBOOT_ERR_TFTP_TIMEOUT) && (++retr_cnt < TFTP_RETRANS_CNT)) {
                 err = 0;
                 retr_cnt++;
             }
        }

        closesocket(s);
    }
    return err;
}

static int f_tftp_wr_req(struct sockaddr_in* addr, const char* filename, uint32_t size,
                         SOCKET* sock, struct sockaddr_in* con_addr) {
    SOCKET s;
    int err = 0;
    struct sockaddr_in rx_addr;
    int retr_cnt = 0;

    memset(&rx_addr, 0, sizeof(rx_addr));
    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s != INVALID_SOCKET) {
        int trans, rq_done = 0;

        while (!rq_done && !err) {
            err = f_send_req(s, addr, filename, 1, size);

            if (!err) {
                err = f_recv_packet(s, retr_cnt, &trans, &rx_addr);
                if ((err==LBOOT_ERR_TFTP_TIMEOUT) && (++retr_cnt < TFTP_RETRANS_CNT)) {
                    err = 0;
                    retr_cnt++;
                } else if (!err) {
                    unsigned op = ((unsigned)f_buf[0] <<8) | f_buf[1];
                    if (op == LIP_TFTP_OPCODE_OACK) {
                        err = f_proc_oak(&f_buf[2], trans-2, &g_tftp_intf_info.wr_block_size);
                        if (!err) {
                            rq_done = 1;
                        }
                    } else if (op == LIP_TFTP_OPCODE_ERR) {
                        err = f_process_err(&f_buf[2], trans-2);
                    } else if (op == LIP_TFTP_OPCODE_ACK) {
                        err = f_process_ack(&f_buf[2], trans-2,0);
                        g_tftp_intf_info.wr_block_size = LIP_TFTP_DEFAULT_BLOCK_SIZE;
                    }
                }
            }
        }

        if (err) {
            closesocket(s);
        } else {
            *sock = s;
            *con_addr = rx_addr;
        }
    }
    return err;
}

static int f_tftp_wr_block(SOCKET s, struct sockaddr_in* addr, int block,
                           const uint8_t* data, int size) {
    int err = 0, done = 0, retr_cnt =0;

    while (!err && !done) {
        int put_pos = 0, trans;
        int skip = 1;
        f_buf[put_pos++] = (LIP_TFTP_OPCODE_DATA>>8)&0xFF;
        f_buf[put_pos++] = LIP_TFTP_OPCODE_DATA&0xFF;
        f_buf[put_pos++] = (block>>8)&0xFF;
        f_buf[put_pos++] = block&0xFF;
        memcpy(&f_buf[put_pos], data, size);
        put_pos+=size;

        trans = sendto(s, f_buf, put_pos, 0, (struct sockaddr*)addr, sizeof(struct sockaddr_in));
        err =  trans!=put_pos ? LBOOT_ERR_TFTP_SEND : 0;

        while (!err && skip) {
            skip = 0;
            err = f_recv_packet(s, retr_cnt, &trans, addr);

            if (!err) {
                unsigned op = ((unsigned)f_buf[0] <<8) | f_buf[1];
                if (op == LIP_TFTP_OPCODE_ERR) {
                    err = f_process_err(&f_buf[2], trans-2);
                } else if (op==LIP_TFTP_OPCODE_ACK) {
                    err = f_process_ack(&f_buf[2], trans-2,block);

                    if (!err)
                        done =1;
                } else {
                    skip=1;
                }
            }

            if (err == LBOOT_ERR_TFTP_PACKET_SKIP) {
                err = 0;
                skip = 1;
            }
        }

        if ((err == LBOOT_ERR_TFTP_TIMEOUT) && (++retr_cnt < TFTP_RETRANS_CNT)) {
            err = 0;
            retr_cnt++;
        }
    }
    return err;
}

static int f_tftp_write(struct sockaddr_in* addr, const char* filename,
                        const uint8_t* buf, uint32_t size) {
    struct sockaddr_in con_addr;
    SOCKET con_sock;
    int err = f_tftp_wr_req(addr, filename, size, &con_sock, &con_addr);
    if (!err) {
        uint32_t block, pos;
        for (block = 1, pos = 0; !err && (pos < size); block++) {
            uint32_t block_size = size - pos;
            if (block_size > g_tftp_intf_info.wr_block_size)
                block_size = g_tftp_intf_info.wr_block_size;
            err = f_tftp_wr_block(con_sock, &con_addr, block, &buf[pos], block_size);
            if (!err) {
                pos += block_size;
            }
        }

        if (!err) {
            if (!(size % g_tftp_intf_info.wr_block_size))
                err = f_tftp_wr_block(con_sock, &con_addr, block, 0, 0);
        }
        closesocket(con_sock);
    }
    return err;
}


static const char* f_get_err_str(int err) {
    return ((err > LBOOT_ERR_TFTP_FIRST) ||
            (err <  (LBOOT_ERR_TFTP_FIRST -
                     (int32_t)(sizeof(f_err_str)/sizeof(f_err_str[0]))+1))) ? NULL :
            err==LBOOT_ERR_TFTP_ERR_PACKET ? f_last_err_str :
                                              f_err_str[LBOOT_ERR_TFTP_FIRST - err];
}

static int f_init(void) {
    int err = 0;
#ifdef _WIN32
    WORD wVersionRequested = MAKEWORD(2,2);
    WSADATA wsaData;

    if (WSAStartup(wVersionRequested, &wsaData))
        err = LBOOT_ERR_TFTP_WSA_INIT;

    if (wsaData.wVersion!=wVersionRequested) {
        WSACleanup();
        err = LBOOT_ERR_TFTP_WSA_INIT;
    }
#endif
    if (!err) {
        memset(&f_addr, 0, sizeof(f_addr));
        f_addr.sin_family = AF_INET;
        f_addr.sin_port = htons(69);
        f_addr.sin_addr.s_addr = htonl((192UL << 24) | (168UL << 16) | (0UL << 8) | (1));
    }
    return err;
}

static int f_cleanup(void) {
#ifdef _WIN32
    WSACleanup();
#endif
    return 0;
}

static int f_parse_option(int opt) {
    int err = 0;

    switch (opt) {
        case LBOOT_OPT_TFTP_IP_ADDR: {
                in_addr_t addr = inet_addr(optarg);
                if (addr==INADDR_NONE) {
                    err = LBOOT_ERR_TFTP_IP_ADDR;
                } else {
                    f_addr.sin_addr.s_addr = addr;
                }
            }
            break;
        case LBOOT_OPT_TFTP_PORT: {
                int port = atoi(optarg);
                if ((port<=0) || (port>65535)) {
                    err = LBOOT_ERR_TFTP_UDP_PORT;
                } else {
                    f_addr.sin_port = htons(port);
                }
            }
            break;
        case LBOOT_OPT_TFTP_BLOCK_SIZE: {
                int block = atoi(optarg);
                if ((block>=8) &&  (block < 65535)) {
                    g_tftp_intf_info.wr_block_size = block;
                } else {
                    err = LBOOT_ERR_TFTP_BLOCK_SIZE;
                }
            }
            break;
        default:
            err = LBOOT_ERR_INVALID_OPTION;
            break;
    }
    return 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) {
    int err = 0;
    uint32_t size = sizeof(*info);
    err = f_tftp_read(&f_addr, "devinfo", (uint8_t*)info, &size);
    if (!err && (size < sizeof(*info))) {
        err = LBOOT_ERR_TFTP_RX_INSUF_SIZE;
    }

    if (err)
        *out = 1;
    return err;
}

static int f_get_devmode(uint8_t* mode) {
    int err = 0;
    uint32_t size = sizeof(*mode);
    err = f_tftp_read(&f_addr, "devmode", mode, &size);
    if (!err && (size < sizeof(*mode))) {
        err = LBOOT_ERR_TFTP_RX_INSUF_SIZE;
    }
    return err;
}



static int f_get_bootloader_version(uint16_t* ver) {
    int err = 0;
    uint32_t size = sizeof(*ver);
    err = f_tftp_read(&f_addr, "bootver", (uint8_t*)ver, &size);
    if (!err && (size < sizeof(*ver))) {
        err = LBOOT_ERR_TFTP_RX_INSUF_SIZE;
    }
    return err;
}

static int f_get_dev_flags(uint32_t* flags) {
    int err = 0;
    uint32_t size = sizeof(*flags);
    err = f_tftp_read(&f_addr, "devflags", (uint8_t*)flags, &size);
    if (!err && (size < sizeof(*flags))) {
        err = LBOOT_ERR_TFTP_RX_INSUF_SIZE;
    }
    return err;
}

static int f_get_features(uint32_t* features) {
    int err = 0;
    uint32_t size = sizeof(*features);
    err = f_tftp_read(&f_addr, "features", (uint8_t*)features, &size);
    if (!err && (size < sizeof(*features))) {
        err = LBOOT_ERR_TFTP_RX_INSUF_SIZE;
    }
    return err;
}


static int f_wr_size = 0;
static int f_wr_block = 0;

static struct sockaddr_in f_con_addr;
static SOCKET f_con_s;

static int f_start_write(t_lboot_startwr_info wr_info) {
    int err = f_tftp_write(&f_addr, "startwr", (uint8_t*)&wr_info, sizeof(wr_info));
    if (!err) {
        f_wr_block = 0;
        f_wr_size = wr_info.size;
        f_nosign = wr_info.flags & LBOOT_STARTWR_FLAGS_NOSIGN;
    }
    return err;
}


static int f_write(const uint8_t* buf, uint32_t size) {
    int err = 0;
    if (!f_wr_block) {
        err = f_tftp_wr_req(&f_addr, "firmware.preconf", f_wr_size, &f_con_s, &f_con_addr);
        if (!err)
            f_wr_block = 1;
    }

    if (!err) {
        err = f_tftp_wr_block(f_con_s, &f_con_addr, f_wr_block, buf, size);
        if (!err) {
            f_wr_size-=size;
            f_wr_block++;

            if (f_wr_size==0) {
                if (!(size%g_tftp_intf_info.wr_block_size))
                    err = f_tftp_wr_block(f_con_s, &f_con_addr, f_wr_block, buf, 0);
                closesocket(f_con_s);
                f_wr_block = 0;
            }
        } else {
            closesocket(f_con_s);
        }
    }
    return err;
}



static int f_write_sign(const uint8_t* sign, uint32_t size) {
    int err = 0;
    if (!f_wr_block) {
        err = f_tftp_wr_req(&f_addr, f_nosign ? "hash" : "sig", f_wr_size, &f_con_s, &f_con_addr);
        if (!err)
            f_wr_block = 1;
    }

    if (!err) {
        err = f_tftp_wr_block(f_con_s, &f_con_addr, f_wr_block, sign, size);
        if (!err) {
            f_wr_block++;
            if(size < g_tftp_intf_info.wr_block_size) {
                closesocket(f_con_s);
                f_wr_block = 0;
            }
        } else {
            closesocket(f_con_s);
        }
    }
    return err;
}


static int f_speccmd(const t_lboot_speccmd_info* cmd_info, uint32_t size) {
    return f_tftp_write(&f_addr, "cmd", (uint8_t*)cmd_info, size);
}


static int f_close(void) {
    return 0;
}

