/*
 * userport_wic64.c - Userport WiC64 wifi interface emulation.
 *
 * Written by
 *  groepaz <groepaz@gmx.net>
 *  fixed & complemented by pottendo <pottendo@gmx.net>
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

/* #define DEBUG_WIC64 */

/* - WiC64 (C64/C128)

   PET     VIC20  C64/C128   |  I/O
   --------------------------------
   C                 (PB0)   |  I/O   databits from/to C64
   D                 (PB1)   |  I/O
   E                 (PB2)   |  I/O
   F                 (PB3)   |  I/O
   H                 (PB4)   |  I/O
   J                 (PB5)   |  I/O
   K                 (PB6)   |  I/O
   L                 (PB7)   |  I/O
   READ(1) PA6(2)  8 (PC2)   |  O     C64 triggers PC2 IRQ whenever data is read or write
   CB2     CB2     M (PA2)   |  O     Low=device sends data High=C64 sends data (powerup=high)
   CA1     CB1     B (FLAG2) |  I     device asserts high->low transition when databyte sent to c64 is ready (triggers irq)

   (1) tape #1 read
   (2) also connected to tape "sense" (PLAY, F.FWD or REW pressed)

   enable the device and start https://wic64.net/web/__downloads/wic-launcher25.prg

   for more info see https://wic64.net/web/
*/

#include "vice.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "alarm.h"
#include "cmdline.h"
#include "maincpu.h"
#include "resources.h"
#include "joyport.h"
#include "joystick.h"
#include "snapshot.h"
#include "userport.h"
#include "userport_wic64.h"
#include "machine.h"
#include "uiapi.h"
#include "lib.h"
#include "util.h"
#include "charset.h"

#ifdef HAVE_LIBCURL

#define VICEWIC64VERSION PACKAGE"-"VERSION
#define WIC64_VERSION_MAJOR 2
#define WIC64_VERSION_MINOR 1
#define WIC64_VERSION_PATCH 0
#define WIC64_VERSION_DEVEL 0
#define WIC64_SHORT_VERSION "2.1.0"
#define WIC64_VERSION_STRING "2.1.0 ("PACKAGE"; "VERSION")"
#define WIC64_PROT_LEGACY 'W'
#define WIC64_PROT_REVISED 'R'
#define WIC64_PROT_EXTENDED 'E'
#define WIC64_PROT_UNKNOWN 'U'
#define HTTP_AGENT_REVISED "WiC64/"WIC64_SHORT_VERSION" ("PACKAGE"; "VERSION")"
#define HTTP_AGENT_LEGACY "ESP32HTTPClient"
static char *http_user_agent = HTTP_AGENT_REVISED;

#include "log.h"
static log_t wic64_loghandle = LOG_DEFAULT;
static int userport_wic64_enabled = 0;

/* Some prototypes are needed */
static uint8_t userport_wic64_read_pbx(uint8_t orig);
static void userport_wic64_store_pbx(uint8_t val, int pulse);
static void userport_wic64_store_pa2(uint8_t value);
static int userport_wic64_write_snapshot_module(snapshot_t *s);
static int userport_wic64_read_snapshot_module(snapshot_t *s);
static int userport_wic64_enable(int value);
static void userport_wic64_reset(void);

static int wic64_set_default_server(const char *val, void *p);
static int wic64_set_macaddress(const char *val, void *p);
static int wic64_set_ipaddress(const char *val, void *p);
static int wic64_set_sectoken(const char *val, void *p);
static int wic64_set_timezone(int val, void *param);
static int wic64_set_dhcp(int val, void *param);
static int wic64_set_logenabled(int val, void *param);
static int wic64_set_loglevel(int val, void *param);
static int wic64_set_resetuser(int val, void *param);
static int wic64_set_hexdumplines(int val, void *param);

static int wic64_set_remote_timeout(int val, void *param);
static int wic64_cmdl_reset(const char *val, void *param);
static void wic64_log(const char *col, const char *fmt, ...);
static void debug_log(const char *col, const int lv, const char *fmt, ...);
static void handshake_flag2(void);
static void send_binary_reply(const uint8_t *reply, size_t len);
static void send_reply_revised(const uint8_t rcode, const char *msg, const uint8_t *payload, size_t len, const char *legacy_msg);
static void userport_wic64_reset(void);
static void wic64_reset_user_helper(void);
static void wic64_set_status(const char *status);
static void prep_wic64_str(void);
static void do_connect(uint8_t *url);
static void cmd_tcp_write(void);
static void cmd_timeout(int arm);
static void cmd_remote_timeout(int arm);
static int stage_dummy, stage_retcode, stage_length, stage_data, bsr, bsl, bsd;

static userport_device_t userport_wic64_device = {
    "Userport WiC64",                     /* device name */
    JOYSTICK_ADAPTER_ID_NONE,             /* this is NOT a joystick adapter */
    USERPORT_DEVICE_TYPE_WIFI,            /* device is a WIFI adapter */
    userport_wic64_enable,                /* enable function */
    userport_wic64_read_pbx,              /* read pb0-pb7 function */
    userport_wic64_store_pbx,             /* store pb0-pb7 function */
    NULL,                                 /* NO read pa2 pin function */
    userport_wic64_store_pa2,             /* store pa2 pin function */
    NULL,                                 /* NO read pa3 pin function */
    NULL,                                 /* NO store pa3 pin function */
    /* HACK: We put a 0 into the struct here, although pin 8 of the userport
       (which is PC2 on the C64) is actually used. This is needed so the device
       can be registered in xvic (where the pin is driven by PA6). */
    0,                                    /* pc pin IS needed */
    NULL,                                 /* NO store sp1 pin function */
    NULL,                                 /* NO read sp1 pin function */
    NULL,                                 /* NO store sp2 pin function */
    NULL,                                 /* NO read sp2 pin function */
    userport_wic64_reset,                 /* reset function */
    NULL,                                 /* NO powerup function */
    userport_wic64_write_snapshot_module, /* snapshot write function */
    userport_wic64_read_snapshot_module   /* snapshot read function */
};

static struct alarm_s *http_get_alarm = NULL;
static struct alarm_s *http_post_alarm = NULL;
static struct alarm_s *http_post_endalarm = NULL;
static struct alarm_s *tcp_get_alarm = NULL;
static struct alarm_s *tcp_send_alarm = NULL;
static struct alarm_s *cmd_timeout_alarm = NULL;
static struct alarm_s *cmd_remote_timeout_alarm = NULL;
static struct alarm_s *cmd_force_timeout_alarm = NULL;
static struct alarm_s *flag2_alarm = NULL;
static struct alarm_s *cycle_alarm = NULL;

static char sec_token[32];
static int sec_init = 0;
static const char *TOKEN_NAME = "sectokenname";
static char *default_server_hostname = NULL;

static char *wic64_mac_address = NULL; /* c-string std. notation e.g 0a:02:0b:04:05:0c */
static char *wic64_internal_ip = NULL; /* c-string std. notation e.g. 192.168.1.10 */
static unsigned char wic64_external_ip[4] = { 0, 0, 0, 0 }; /* just a dummy, report not implemented to user cmd 0x13 */

static uint8_t wic64_timezone[2] = { 0, 0};
static uint16_t wic64_udp_port = 0;
static uint16_t wic64_tcp_port = 0;
static uint8_t wic64_timeout = WIC64_DEFAULT_TRANSFER_TIMEOUT;
static int wic64_remote_timeout; /* used for resource */
static int remote_to = WIC64_DEFAULT_REMOTE_TIMEOUT;
static uint8_t wic64_remote_timeout_triggered = 0;
static char *wic64_sec_token = NULL;
static int current_tz = 2; /* WIC64Timezone */
static int current_dhcp = 1; /* WIC64DHCP */
static int wic64_logenabled = 0;
static int wic64_loglevel = 0;
static int wic64_resetuser = 0;
static int wic64_hexdumplines = 0;

static char wic64_protocol = WIC64_PROT_UNKNOWN; /* invalid, so we see in trace even the legacy */
static int big_load = 0;
static char wic64_last_status[40]; /* according spec 40 bytes, hold status string. incl. \0 */
static char *post_data = NULL;
static size_t post_data_rcvd;
static size_t post_data_new;
static size_t post_data_size;
static char *post_url = NULL;
static int post_error;
static int cheatlen = 0;

/* ---------------------------------------------------------------------*/
#define CONS_COL_NO ""

/** \brief  log message to console
 *
 * \param[in]  typical printf format string
 */
static void wic64_log(const char *col, const char *fmt, ...)
{
    char t[256];
    va_list args;

    if (!wic64_logenabled) {
        return;
    }

    va_start(args, fmt);
    vsnprintf(t, 256, fmt, args);
    log_message(wic64_loghandle, "%s%s" LOG_COL_OFF, col, t);
}

/** \brief  debug log message to console
 *
 * \param[in]  typical printf format string
 */
static void debug_log(const char *col, const int lv, const char *fmt, ...)
{
    char t[256];
    va_list args;

    if (wic64_loglevel < lv) {
        return;
    }
    va_start(args, fmt);
    vsnprintf(t, 256, fmt, args);
    wic64_log(col, "%s", t);
}

/** \brief  formatted hexdump, lines limited by value of "WIC64HexdumpLines"
 *
 * \param[in]  buf, len
 */
static void wic64_log_hexdump(const char *col, const char *buf, int len)
{
    int i;
    int idx = 0;
    int lines = 0;
    char linestr[256];

    while (len > 0) {
        if (big_load) {
            snprintf(linestr, 256, "%08x: ", (unsigned) idx);
        } else {
            snprintf(linestr, 256, "%04x: ", (unsigned) idx);
        }

        if (wic64_hexdumplines && lines++ >= wic64_hexdumplines) {
            strcat(linestr, "...");
            wic64_log(col, "%s", linestr);
            break;
        }
        for (i = 0; i < 16; i++) {
            if (i < len) {
                char t[4];
                snprintf(t, 4, "%02x ", (uint8_t) buf[idx + i]);
                strcat(linestr, t);
            } else {
                strcat(linestr, "   ");
            }
        }
        strcat(linestr, "|");
        for (i = 0; i < 16; i++) {
            if (i < len) {
                char t[2];
                char c;
                if (((unsigned char)buf[idx + i] > 31) && /* avoid tabs and other printable ctrl chars) */
                    isprint((unsigned char)buf[idx + i])) {
                    c= buf[idx + i];
                } else {
                    c = '.';
                }
                snprintf(t, 2, "%c", c);
                strcat(linestr, t);
            } else {
                strcat(linestr, " ");
            }
        }
        strcat(linestr, "|");
        wic64_log(col, "%s", linestr);
        idx += 16;
        len -= 16;
    }
}

/** \brief  formatted hexdump, lines limited by value of "WIC64HexdumpLines" for debug level
 *
 * \param[in]  lv, buf, len
 */
static void debug_hexdump(const char *col, const int lv, const char *buf, int len)
{
    if (wic64_loglevel < lv) {
        return;
    }
    wic64_log_hexdump(col, buf, len);
}

/* ---------------------------------------------------------------------*/

#define IPADDR_INVALID      "255.255.255.255"
#define MACADDR_INVALID     "FF:FF:FF:FF:FF:FF"
#define SECTOKEN_INVALID    "0123456789ab"

static const resource_string_t wic64_resources[] =
{
    { "WIC64DefaultServer", "http://x.wic64.net/", (resource_event_relevant_t)0, NULL,
      &default_server_hostname, wic64_set_default_server, NULL },
    { "WIC64MACAddress", MACADDR_INVALID, (resource_event_relevant_t)0, NULL,
      (char **) &wic64_mac_address, wic64_set_macaddress, NULL },
    { "WIC64IPAddress", IPADDR_INVALID, (resource_event_relevant_t)0, NULL,
      (char **) &wic64_internal_ip, wic64_set_ipaddress, NULL },
    { "WIC64SecToken", SECTOKEN_INVALID, (resource_event_relevant_t)0, NULL,
      (char **) &wic64_sec_token, wic64_set_sectoken, NULL },
    RESOURCE_STRING_LIST_END,
};

static const resource_int_t wic64_resources_int[] = {
    { "WIC64Timezone", 2, RES_EVENT_NO, NULL,
      &current_tz, wic64_set_timezone, NULL },
    { "WIC64DHCP", 1, RES_EVENT_NO, NULL,
      &current_dhcp, wic64_set_dhcp, NULL },
    { "WIC64Logenabled", 0, RES_EVENT_NO, NULL,
      &wic64_logenabled, wic64_set_logenabled, NULL },
    { "WIC64LogLevel", 0, RES_EVENT_NO, NULL,
      &wic64_loglevel, wic64_set_loglevel, NULL },
    { "WIC64Resetuser", 0, RES_EVENT_NO, NULL,
      &wic64_resetuser, wic64_set_resetuser, NULL },
    { "WIC64HexdumpLines", 8, RES_EVENT_NO, NULL,
      &wic64_hexdumplines, wic64_set_hexdumplines, NULL },
    { "WIC64RemoteTimeout", WIC64_DEFAULT_REMOTE_TIMEOUT, RES_EVENT_NO, NULL,
      &wic64_remote_timeout, wic64_set_remote_timeout, NULL },
    RESOURCE_INT_LIST_END
};

static tzones_t timezones[] = {
    { 0, "Greenwich Mean Time", 0, 0, 1},
    { 1, "Greenwich Mean Time", 0, 0, 1},
    { 2, "European Central Time", 1, 0, 1 },
    { 3, "Eastern European Time", 2, 0, 1 },
    { 4, "Arabic Egypt Time", 2, 0, 1 },
    { 5, "Arabic Egypt Time", 2, 0, 1 },
    { 6, "Arabic Egypt Time", 2, 0, 1 },
    { 7, "Near East Time", 4, 0, 1 },
    { 8, "India Standard Time", 5, 30, 0 },
    { 9, "Dont Know Time", 6, 0, 0 },
    { 10, "Dont Know Time", 7, 0, 0 },
    { 11, "China Standard Time", 8, 0, 0 },
    { 12, "Korean Standard Time", 9, 0, 0 },
    { 13, "Japan Standard Time", 9, 0, 0},
    { 14, "Australia Central Time", 9, 30, 0 },
    { 15, "Australia Eastern Time", 10, 0, 0 },
    { 16, "Dont Know Time", 11, 0,0 },
    { 17, "New Zealand Standart Time", 12, 0, 0 },
    { 18, "Midway Islands Time", -11, 0, 0 },
    { 19, "Hawaii Standard Time", -10, 0, 0 },
    { 20, "Alaska Standard Time", -8, 0, 0 },
    { 21, "Pacific Standard Time", -7, 0, 0 },
    { 22, "Phoenix Standard Time", -7, 0, 0 },
    { 23, "Mountain Standard Time", -6, 0, 0 },
    { 24, "Central Standard Time", -5, 0, 0 },
    { 25, "Eastern Standard Time", -4, 0, 0 },
    { 26, "Indiana Eastern Standard Time", -5, 0, 0 },
    { 27, "Puerto Rico Virg. Island Time", -4, 0, 0 },
    { 28, "Canada Newfoundland Time", -2, -30, 0 },
    { 29, "Dont Know Time", -2, 0, 0 },
    { 30, "Dont Know Time", -1, 0, 0 },
    { 31, "Dont Know Time", 0, 0, 0 },
};

static const cmdline_option_t cmdline_options[] =
{
    { "-wic64server", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64DefaultServer", NULL,
      "<URL>", "Specify default server URL" },
    { "-wic64timezone", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64Timezone", NULL,
      "<0..31>", "Specify default timezone index, e.g. 2: European Central Time" },
    { "-wic64ipaddress", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64IPAddress", NULL,
      "<IP>", "Specify WiC64 IP" },
    { "-wic64macaddress", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64MACAddress", NULL,
      "<MAC>", "Specify WiC64 MAC" },
    { "-wic64token", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64SecToken", NULL,
      "<token>", "Specify WiC64 security token" },
    { "-wic64dhcp", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "WIC64DHCP", (void *)1,
      NULL, "Enable WiC64 DHCP" },
    { "+wic64DHCP", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "WIC64DHCP", (void *)0,
      NULL, "Disable WiC64 DHCP" },
    { "-wic64trace", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "WIC64Logenabled", (void *)1,
      NULL, "Enable WiC64 tracing" },
    { "+wic64trace", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "WIC64Logenabled", (void *)0,
      NULL, "Disable WiC64 tracing" },
    { "-wic64tracelevel", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64LogLevel", NULL,
      "<0..4>", "Set WiC64 tracing level (0: off, 1: cmd-level, >2: debug-level), implicitly turns on/off WiC64 tracing" },
    { "-wic64reset", CALL_FUNCTION, CMDLINE_ATTRIB_NONE,
      wic64_cmdl_reset, (void *)2, NULL, NULL,
      NULL, "Reset WiC64 to factory defaults" },
    { "-wic64hexdumplines", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64HexdumpLines", NULL,
      "<value>", "Limit WiC64 hexdump lines (0: unlimited)" },
    { "-wic64remotetimeout", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "WIC64RemoteTimeout", NULL,
      "<value>", "Set WIC64 remote timeout (1 - 255)" },
    CMDLINE_LIST_END
};

/* WiC64 commands as defined */
#define WIC64_CMD_GET_VERSION_STRING  0x00
#define WIC64_CMD_GET_VERSION_NUMBERS 0x26

#define WIC64_CMD_SCAN_WIFI_NETWORKS       0x0c
#define WIC64_CMD_IS_CONFIGURED            0x2f
#define WIC64_CMD_IS_CONNECTED             0x2c
#define WIC64_CMD_CONNECT_WITH_SSID_STRING 0x02
#define WIC64_CMD_CONNECT_WITH_SSID_INDEX  0x0d

#define WIC64_CMD_GET_MAC  0x14
#define WIC64_CMD_GET_SSID 0x10
#define WIC64_CMD_GET_RSSI 0x11
#define WIC64_CMD_GET_IP   0x06

#define WIC64_CMD_HTTP_GET         0x01
#define WIC64_CMD_HTTP_GET_ENCODED 0x0f
#define WIC64_CMD_HTTP_POST_URL    0x28
#define WIC64_CMD_HTTP_POST_DATA   0x2b

#define WIC64_CMD_TCP_OPEN  0x21
#define WIC64_CMD_TCP_AVAILABLE 0x30
#define WIC64_CMD_TCP_READ  0x22
#define WIC64_CMD_TCP_WRITE 0x23
#define WIC64_CMD_TCP_CLOSE 0x2e

#define WIC64_CMD_GET_SERVER 0x12
#define WIC64_CMD_SET_SERVER 0x08

#define WIC64_CMD_GET_TIMEZONE   0x17
#define WIC64_CMD_SET_TIMEZONE   0x16
#define WIC64_CMD_GET_LOCAL_TIME 0x15

#define WIC64_CMD_UPDATE_FIRMWARE 0x27

#define WIC64_CMD_REBOOT 0x29
#define WIC64_CMD_GET_STATUS_MESSAGE 0x2a
#define WIC64_CMD_SET_TRANSFER_TIMEOUT 0x2d
#define WIC64_CMD_SET_REMOTE_TIMEOUT 0x32
#define WIC64_CMD_IS_HARDWARE 0x31

#define WIC64_CMD_FORCE_TIMEOUT 0xfc
#define WIC64_CMD_FORCE_ERROR 0xfd
#define WIC64_CMD_ECHO 0xfe

/* Deprecated commands */
#define WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_03 0x03
#define WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_04 0x04
#define WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_05 0x05
#define WIC64_CMD_DEPRECATED_FIRMWARE_UPDATE_REQUIRED_18 0x18
#define WIC64_CMD_DEPRECATED_GET_STATS_07 0x07
#define WIC64_CMD_DEPRECATED_LOG_TO_SERIAL_CONSOLE_09 0x09
#define WIC64_CMD_DEPRECATED_GET_UPD_0A 0x0a
#define WIC64_CMD_DEPRECATED_SEND_UPD_0B 0x0b
#define WIC64_CMD_DEPRECATED_SET_UPD_PORT_0E 0x0e
#define WIC64_CMD_DEPRECATED_GET_UPD_DUPLICATE_1E 0x1e
#define WIC64_CMD_DEPRECATED_SEND_UPD_DUPLICATE_1F 0x1f
#define WIC64_CMD_DEPRECATED_GET_EXTERNAL_IP_13 0x13
#define WIC64_CMD_DEPRECATED_GET_PREFERENCES_19 0x19
#define WIC64_CMD_DEPRECATED_SET_PREFERENCES_1A 0x1a
#define WIC64_CMD_DEPRECATED_SET_TCP_PORT_20 0x20
#define WIC64_CMD_DEPRECATED_BIG_LOADER_25 0x25
#define WIC64_CMD_DEPRECATED_FACTORY_RESET_63 0x63
#define WIC64_CMD_DEPRECATED_LEGACY_HTTP_POST_24 0x24

#define WIC64_CMD_NONE 0xff

#define INPUT_EXP_PROT 0
#define INPUT_EXP_CMD 1
#define INPUT_EXP_LL 2
#define INPUT_EXP_LH 3
#define INPUT_EXP_HL 4
#define INPUT_EXP_HH 5
#define INPUT_EXP_ARGS 6

static const char *cmd2string[256];
/* WiC64 return codes, adjust debug output accordingly if changes happen here, see func 'userport_wic64_read_pbx()' */
static uint8_t SUCCESS          = 0;
static uint8_t INTERNAL_ERROR   = 1;
static uint8_t CLIENT_ERROR     = 2;
static uint8_t CONNECTION_ERROR = 3;
static uint8_t NETWORK_ERROR    = 4;
static uint8_t SERVER_ERROR     = 5;
static char *err2string[] = { "SUCCESS", "INTERNAL", "CLIENT", "CONNECTION", "NETWORK", "SERVER" };

#define HTTPREPLY_MAXLEN ((unsigned)(16 * 1024 * 1024)) /* 16MB needed for potential large images to flash via bigloader */
static size_t httpbufferptr = 0;
static uint8_t *httpbuffer = NULL;
static char *replybuffer = NULL;
static uint8_t *commandbuffer = NULL;

#define COMMANDBUFFER_MAXLEN    0x100010
#define URL_MAXLEN              8192
static char *encoded_helper = NULL;

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifndef WINDOWS_COMPILE
#include <unistd.h>
#endif
#include <curl/curl.h>

#define MAX_PARALLEL 1 /* number of simultaneous transfers */
#define NUM_URLS 10
static int still_alive = 0;
static CURLM *cm = NULL;               /* used for http(s) */
static CURL *curl = NULL;              /* used for telnet */
static uint8_t curl_buf[240];          /* this slows down by smaller chunks sent to C64, improves BBSs  */
static uint8_t *curl_send_buf = NULL;
static uint16_t curl_send_len;

/* ------------------------------------------------------------------------- */
static int userport_wic64_enable(int value)
{
    int val = value ? 1 : 0;

    if (userport_wic64_enabled == val) {
        return 0;
    }
    if (wic64_loghandle == LOG_DEFAULT) {
        wic64_loghandle = log_open("WiC64");
    }

    userport_wic64_enabled = val;
    if (val) {
        httpbuffer = lib_malloc(HTTPREPLY_MAXLEN);
        debug_log(CONS_COL_NO, 2, "%s: httpreplybuffer allocated 0x%xkB", __FUNCTION__,
                   HTTPREPLY_MAXLEN / 1024);

        replybuffer = lib_malloc(HTTPREPLY_MAXLEN + 16);
        debug_log(CONS_COL_NO, 2, "%s: replybuffer allocated 0x%xkB", __FUNCTION__,
                   (HTTPREPLY_MAXLEN / 1024) + 16);

        encoded_helper = lib_malloc(COMMANDBUFFER_MAXLEN);
        debug_log(CONS_COL_NO, 2, "%s: encoded_helper allocated 0x%xkB", __FUNCTION__,
                   COMMANDBUFFER_MAXLEN / 1024);

        commandbuffer = lib_malloc(COMMANDBUFFER_MAXLEN);
        debug_log(CONS_COL_NO, 2, "%s: commandbuffer allocated 0x%xkB", __FUNCTION__,
                   COMMANDBUFFER_MAXLEN / 1024);

        curl_send_buf = lib_malloc(COMMANDBUFFER_MAXLEN);
        debug_log(CONS_COL_NO, 2, "%s: curl_send_buf allocated 0x%xkB", __FUNCTION__,
                   COMMANDBUFFER_MAXLEN / 1014);

        wic64_set_status("enabled");
        log_message(wic64_loghandle, "WiC64 enabled");

        prep_wic64_str();
        userport_wic64_reset();
    } else {
        if (httpbuffer) {
            lib_free(httpbuffer);
            httpbuffer = NULL;
        }
        if (replybuffer) {
            lib_free(replybuffer);
            replybuffer = NULL;
        }
        if (encoded_helper) {
            lib_free(encoded_helper);
            encoded_helper = NULL;
        }
        if (commandbuffer) {
            lib_free(commandbuffer);
            commandbuffer = NULL;
        }
        if (curl_send_buf) {
            lib_free(curl_send_buf);
            curl_send_buf = NULL;
        }
        if (post_data) {
            lib_free(post_data);
            post_data = NULL;
        }
        if (curl) {
            /* connection closed */
            curl_easy_cleanup(curl);
            curl_global_cleanup();
            curl = NULL;
        }
        if (post_url) {
            lib_free(post_url);
            post_url = NULL;
        }
        if (http_get_alarm) {
            alarm_destroy(http_get_alarm);
            http_get_alarm = NULL;
        }
        if (http_post_alarm) {
            alarm_destroy(http_post_alarm);
            http_post_alarm = NULL;
        }
        if (http_post_endalarm) {
            alarm_destroy(http_post_endalarm);
            http_post_endalarm = NULL;
        }
        if (tcp_get_alarm) {
            alarm_destroy(tcp_get_alarm);
            tcp_get_alarm = NULL;
        }
        if (tcp_send_alarm) {
            alarm_destroy(tcp_send_alarm);
            tcp_send_alarm = NULL;
        }
        if (cmd_timeout_alarm) {
            alarm_destroy(cmd_timeout_alarm);
            cmd_timeout_alarm = NULL;
        }
        if (cmd_remote_timeout_alarm) {
            alarm_destroy(cmd_remote_timeout_alarm);
            cmd_remote_timeout_alarm = NULL;
        }
        if (cmd_force_timeout_alarm) {
            alarm_destroy(cmd_force_timeout_alarm);
            cmd_force_timeout_alarm = NULL;
        }
        if (flag2_alarm) {
            alarm_destroy(flag2_alarm);
            flag2_alarm = NULL;
        }
        if (cycle_alarm) {
            alarm_destroy(cycle_alarm);
            cycle_alarm = NULL;
        }
        debug_log(CONS_COL_NO, 2, "%s: several WiC64 dynamic buffers freed",
                   __FUNCTION__);

        wic64_set_status("disabled");
        log_message(wic64_loghandle, "WiC64 disabled");
    }

    return 0;
}

static int wic64_set_default_server(const char *val, void *v)
{
    util_string_set(&default_server_hostname, val);
    return 0;
}

static int wic64_set_macaddress(const char *val, void *v)
{
    unsigned int a, b, c, d, e, f;
    int ret;
    /* validate string */
    ret = sscanf(val, "%02x:%02x:%02x:%02x:%02x:%02x", &a, &b, &c, &d, &e, &f);
    if ((ret == 6) &&
       (/*(a >= 0) &&*/ (a <= 255)) &&
       (/*(b >= 0) &&*/ (b <= 255)) &&
       (/*(c >= 0) &&*/ (c <= 255)) &&
       (/*(d >= 0) &&*/ (d <= 255)) &&
       (/*(e >= 0) &&*/ (e <= 255)) &&
       (/*(f >= 0) &&*/ (f <= 255))) {
        util_string_set((char **)&wic64_mac_address, val);
        return 0;
    }
    return -1;
}

static int wic64_set_ipaddress(const char *val, void *v)
{
    int ret, a, b, c, d;
    /* validate string */
    ret = sscanf(val, "%d.%d.%d.%d", &a, &b, &c, &d);
    if ((ret == 4) &&
       ((a >= 0) && (a <= 255)) &&
       ((b >= 0) && (b <= 255)) &&
       ((c >= 0) && (c <= 255)) &&
       ((d >= 0) && (d <= 255))) {
        util_string_set((char **)&wic64_internal_ip, val);
        return 0;
    }
    return -1;
}

static int wic64_set_sectoken(const char *val, void *v)
{
    /* TODO: validate string */
    util_string_set((char **)&wic64_sec_token, val);
    return 0;
}

static int wic64_set_timezone(int val, void *param)
{
    current_tz = val;
    return 0;
}

static int wic64_set_dhcp(int val, void *param)
{
    current_dhcp = val;
    return 0;
}

static int wic64_set_logenabled(int val, void *param)
{
    wic64_logenabled = val;
    return 0;
}

static int wic64_set_loglevel(int val, void *param)
{
    if (val > WIC64_MAXTRACELEVEL) {
      val = WIC64_MAXTRACELEVEL;
    }
    if (val < 0) {
      val = 0;
    }
    wic64_loglevel = val;
    if (wic64_loglevel == 0) {
        wic64_log(CONS_COL_NO, "setting log level to %d", wic64_loglevel);
        wic64_logenabled = 0;
        return 0;
    }
    wic64_logenabled = 1;
    wic64_log(CONS_COL_NO, "setting log level to %d", wic64_loglevel);
    return 0;
}

static int wic64_set_resetuser(int val, void *param)
{
    if (val > 32767) {
      val = 32767;
    }
    if (val < 0) {
      val = 0;
    }
    wic64_resetuser = val;
    return 0;
}

static int wic64_set_hexdumplines(int val, void *param)
{
    wic64_hexdumplines = val;
    return 0;
}

static int wic64_set_remote_timeout(int val, void *param)
{
    wic64_remote_timeout = val;
    /* overrule in case of wrong range */
    if (val < 1) {
        wic64_remote_timeout = 1;
    }
    if (val > 255) {
        wic64_remote_timeout = 255;
    }
    return 0;
}

static int wic64_cmdl_reset(const char *val, void *param)
{
    if (param == (void *)2) {
        /* cmdline option to reset user */
        log_message(wic64_loghandle, "cmdline option: factory reset");
        int save = wic64_resetuser;
        wic64_resetuser = 1;
        userport_wic64_factory_reset();
        wic64_resetuser = save;
    }
    return 0;
}

int userport_wic64_resources_init(void)
{
    if (resources_register_string(wic64_resources) < 0) {
        return -1;
    }
    if (resources_register_int(wic64_resources_int) < 0) {
        return -1;
    }
    userport_wic64_reset();
    return userport_device_register(USERPORT_DEVICE_WIC64, &userport_wic64_device);
}

/** \brief  Free memory used by WIC64 resources
 */
void userport_wic64_resources_shutdown(void)
{
    wic64_log(CONS_COL_NO, "%s: shutting down wic64", __FUNCTION__);
    lib_free(default_server_hostname);
    lib_free(wic64_mac_address);
    lib_free(wic64_internal_ip);
    lib_free(wic64_sec_token);
    if (httpbuffer) {
        lib_free(httpbuffer);
    }
}

int userport_wic64_cmdline_options_init(void)
{
    return cmdline_register_options(cmdline_options);
}

/* ---------------------------------------------------------------------*/
/** \brief  Get list of timezones
 *
 * \param[out]  num_zones  number of elements in the list
 *
 * \return  list of timezones, without sentinel value to indicate end-of-list
 */
const tzones_t *userport_wic64_get_timezones(size_t *num_zones)
{
    *num_zones = sizeof timezones / sizeof timezones[0];
    return timezones;
}

static void wic64_reset_user_helper(void)
{
    char tmp[32];
    snprintf(tmp, 32, "08:d1:f9:%02x:%02x:%02x",
             lib_unsigned_rand(0, 15),
             lib_unsigned_rand(0, 15),
             lib_unsigned_rand(0, 15));
    resources_set_string("WIC64MACAddress", tmp);
    resources_set_string("WIC64SecToken", SECTOKEN_INVALID);
}

void userport_wic64_factory_reset(void)
{
    int tz;
    int reset_user;
    char *defserver;

    resources_get_default_value("WIC64Timezone", (void *)&tz);
    resources_get_default_value("WIC64DefaultServer", (void *)&defserver);

    resources_set_int("WIC64Timezone", tz);
    resources_set_string("WIC64DefaultServer", defserver);
    resources_set_int("WIC64RemoteTimeout", WIC64_DEFAULT_REMOTE_TIMEOUT);

    resources_get_int("WIC64Resetuser", &reset_user);
    if (reset_user) {
        wic64_reset_user_helper();
    }
}

void wic64_set_status(const char *status)
{
    if (status) {
        strncpy(wic64_last_status, status, sizeof wic64_last_status);
        wic64_last_status[sizeof wic64_last_status - 1u] = '\0';
    }
}

static void prep_wic64_str(void)
{
    cmd2string[WIC64_CMD_GET_VERSION_STRING] = "WIC64_CMD_GET_VERSION_STRING";
    cmd2string[WIC64_CMD_GET_VERSION_NUMBERS] = "WIC64_CMD_GET_VERSION_NUMBERS";

    cmd2string[WIC64_CMD_SCAN_WIFI_NETWORKS] = "WIC64_CMD_SCAN_WIFI_NETWORKS";
    cmd2string[WIC64_CMD_IS_CONNECTED] = "WIC64_CMD_IS_CONNECTED";
    cmd2string[WIC64_CMD_IS_CONFIGURED] = "WIC64_CMD_IS_CONFIGURED";
    cmd2string[WIC64_CMD_CONNECT_WITH_SSID_STRING] = "WIC64_CMD_CONNECT_WITH_SSID_STRING";
    cmd2string[WIC64_CMD_CONNECT_WITH_SSID_INDEX] = "WIC64_CMD_CONNECT_WITH_SSID_INDEX";

    cmd2string[WIC64_CMD_GET_MAC] = "WIC64_CMD_GET_MAC";
    cmd2string[WIC64_CMD_GET_SSID] = "WIC64_CMD_GET_SSID";
    cmd2string[WIC64_CMD_GET_RSSI] = "WIC64_CMD_GET_RSSI";
    cmd2string[WIC64_CMD_GET_IP] = "WIC64_CMD_GET_IP";

    cmd2string[WIC64_CMD_HTTP_GET] = "WIC64_CMD_HTTP_GET";
    cmd2string[WIC64_CMD_HTTP_GET_ENCODED] = "WIC64_CMD_HTTP_GET_ENCODED";
    cmd2string[WIC64_CMD_HTTP_POST_URL] = "WIC64_CMD_HTTP_POST_URL";
    cmd2string[WIC64_CMD_HTTP_POST_DATA] = "WIC64_CMD_HTTP_POST_DATA";

    cmd2string[WIC64_CMD_TCP_OPEN] = "WIC64_CMD_TCP_OPEN";
    cmd2string[WIC64_CMD_TCP_AVAILABLE] = "WIC64_CMD_TCP_AVAILABLE";
    cmd2string[WIC64_CMD_TCP_READ] = "WIC64_CMD_TCP_READ";
    cmd2string[WIC64_CMD_TCP_WRITE] = "WIC64_CMD_TCP_WRITE";
    cmd2string[WIC64_CMD_TCP_CLOSE] = "WIC64_CMD_TCP_CLOSE";

    cmd2string[WIC64_CMD_GET_SERVER] = "WIC64_CMD_GET_SERVER";
    cmd2string[WIC64_CMD_SET_SERVER] = "WIC64_CMD_SET_SERVER";

    cmd2string[WIC64_CMD_GET_TIMEZONE] = "WIC64_CMD_GET_TIMEZONE";
    cmd2string[WIC64_CMD_SET_TIMEZONE] = "WIC64_CMD_SET_TIMEZONE";
    cmd2string[WIC64_CMD_GET_LOCAL_TIME] = "WIC64_CMD_GET_LOCAL_TIME";

    cmd2string[WIC64_CMD_UPDATE_FIRMWARE] = "WIC64_CMD_UPDATE_FIRMWARE";
    cmd2string[WIC64_CMD_REBOOT] = "WIC64_CMD_REBOOT";

    cmd2string[WIC64_CMD_GET_STATUS_MESSAGE] = "WIC64_CMD_GET_STATUS_MESSAGE";
    cmd2string[WIC64_CMD_SET_TRANSFER_TIMEOUT] = "WIC64_CMD_SET_TIMEOUT";
    cmd2string[WIC64_CMD_SET_REMOTE_TIMEOUT] = "WIC64_CMD_SET_REMOTE_TIMEOUT";
    cmd2string[WIC64_CMD_IS_HARDWARE] = "WIC64_CMD_IS_HARDWARE";
    cmd2string[WIC64_CMD_ECHO] = "WIC64_CMD_ECHO";
    cmd2string[WIC64_CMD_FORCE_TIMEOUT]= "WIC64_CMD_FORCE_TIMEOUT";
    cmd2string[WIC64_CMD_FORCE_ERROR] = "WIC64_CMD_FORCE_ERROR";

    cmd2string[WIC64_CMD_NONE] = "unknown";
    /* deprecated commands */
    cmd2string[WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_03] = "WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_03";
    cmd2string[WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_04] = "WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_04";
    cmd2string[WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_05] = "WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_05";
    cmd2string[WIC64_CMD_DEPRECATED_FIRMWARE_UPDATE_REQUIRED_18] = "WIC64_CMD_DEPRECATED_FIRMWARE_UPDATE_REQUIRED_18";
    cmd2string[WIC64_CMD_DEPRECATED_GET_STATS_07] = "WIC64_CMD_DEPRECATED_GET_STATS_07";
    cmd2string[WIC64_CMD_DEPRECATED_LOG_TO_SERIAL_CONSOLE_09] = "WIC64_CMD_DEPRECATED_LOG_TO_SERIAL_CONSOLE_09";
    cmd2string[WIC64_CMD_DEPRECATED_GET_UPD_0A] = "WIC64_CMD_DEPRECATED_GET_UPD_0A";
    cmd2string[WIC64_CMD_DEPRECATED_SEND_UPD_0B] = "WIC64_CMD_DEPRECATED_SEND_UPD_0B";
    cmd2string[WIC64_CMD_DEPRECATED_SET_UPD_PORT_0E] = "WIC64_CMD_DEPRECATED_SET_UPD_PORT_0E";
    cmd2string[WIC64_CMD_DEPRECATED_GET_UPD_DUPLICATE_1E] = "WIC64_CMD_DEPRECATED_GET_UPD_DUPLICATE_1E";
    cmd2string[WIC64_CMD_DEPRECATED_SEND_UPD_DUPLICATE_1F] = "WIC64_CMD_DEPRECATED_SEND_UPD_DUPLICATE_1F";
    cmd2string[WIC64_CMD_DEPRECATED_GET_EXTERNAL_IP_13] = "WIC64_CMD_DEPRECATED_GET_EXTERNAL_IP_13";
    cmd2string[WIC64_CMD_DEPRECATED_GET_PREFERENCES_19] = "WIC64_CMD_DEPRECATED_GET_PREFERENCES_19";
    cmd2string[WIC64_CMD_DEPRECATED_SET_PREFERENCES_1A] = "WIC64_CMD_DEPRECATED_SET_PREFERENCES_1A";
    cmd2string[WIC64_CMD_DEPRECATED_SET_TCP_PORT_20] = "WIC64_CMD_DEPRECATED_SET_TCP_PORT_20";
    cmd2string[WIC64_CMD_DEPRECATED_BIG_LOADER_25] = "WIC64_CMD_DEPRECATED_BIG_LOADER_25";
    cmd2string[WIC64_CMD_DEPRECATED_FACTORY_RESET_63] = "WIC64_CMD_DEPRECATED_FACTORY_RESET_63";
    cmd2string[WIC64_CMD_DEPRECATED_LEGACY_HTTP_POST_24] = "WIC64_CMD_DEPRECATED_LEGACY_HTTP_POST_24";
}

static size_t write_cb(char *data, size_t n, size_t l, void *userp)
{
    size_t tmp = httpbufferptr + n * l;
    debug_log(CONS_COL_NO, 3, "%s, %dx%d bytes/size", __FUNCTION__, n, l);

    if (tmp >= HTTPREPLY_MAXLEN) {
        wic64_log(CONS_COL_NO, "libcurl reply too long, dropping %"PRI_SIZE_T" bytes.\n",
                  tmp - HTTPREPLY_MAXLEN);
        return CURLE_WRITE_ERROR;
    }
    memcpy(&httpbuffer[httpbufferptr], data, n * l);
    httpbufferptr += (n * l);
    return n*l;
}

static void add_transfer(CURLM *cmulti, char *url)
{
    CURL *eh = curl_easy_init();
    if (wic64_loglevel > 1) {
        curl_easy_setopt(eh, CURLOPT_VERBOSE, 1L);
    } else {
        curl_easy_setopt(eh, CURLOPT_VERBOSE, 0L);
    }
    curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(eh, CURLOPT_URL, url);
    curl_easy_setopt(eh, CURLOPT_PRIVATE, url);
    curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1L);
    /* need to decied if we want to ship a certificate file
    curl_easy_setopt(eh, CURLOPT_CAINFO, PREFIX "/share/vice/etc/ca-bundle.crt");
    curl_easy_setopt(eh, CURLOPT_CAPATH, PREFIX "/share/vice/etc/ca-bundle.crt");
    */
    curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0L);

    /* work around bug 1964 (https://sourceforge.net/p/vice-emu/bugs/1964/) - maybe not needed anymore !*/
#ifdef CURLSSLOPT_NATIVE_CA
    curl_easy_setopt(eh, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
#endif

    /* set USERAGENT: otherwise the server won't return data, e.g. wicradio */
    if (wic64_protocol == WIC64_PROT_LEGACY) {
        http_user_agent = HTTP_AGENT_LEGACY;
    } else {
        http_user_agent = HTTP_AGENT_REVISED;
    }
    curl_easy_setopt(eh, CURLOPT_USERAGENT, http_user_agent);
    curl_multi_add_handle(cmulti, eh);
    curl_multi_poll(cm, NULL, 0, 1, NULL);
}

static void update_prefs(uint8_t *buffer, size_t len)
{
    /* manage preferences in memory only for now */
    wic64_log(CONS_COL_NO, "%s: requested", __FUNCTION__);
    wic64_log_hexdump(CONS_COL_NO, (char *)buffer, (int)len);
    char *t;
    char *p;
    char *pref = NULL;
    char *val = "";
    char *ret = "";

    if (len > 0) {
        p = (char *)buffer + 1; /* skip \001 */
        t = strchr(p, '\001');
        if ((t != NULL) && ((t - p) < 31)) {
            *t = '\0';
            pref = p;
        }
        p = t + 1; /* skip \0 */
        t = strchr(p, '\001');
        if ((t != NULL) && ((t - p) < 31)) {
            *t = '\0';
            val = p;
        }
        ret = t + 1; /* hope string is terminated */
        wic64_log(CONS_COL_NO, "WiC64: user-pref '%s' = '%s', ret = '%s'", pref, val, ret);
    } else {
        return;
    }

    if (sec_init &&
        (strcmp(pref, sec_token) == 0)) {
        resources_set_string("WIC64SecToken", val);
        wic64_log(CONS_COL_NO, "%s: session id = %s", __FUNCTION__, val);
    }
    if (strcmp(pref, TOKEN_NAME) == 0) {
        strncpy(sec_token, val, 31);
        wic64_log(CONS_COL_NO, "%s: token = %s", __FUNCTION__, sec_token);
        if (strcmp(sec_token, "KIGMYPLA2021") != 0) {
            wic64_log(CONS_COL_NO, "WiC64: sectoken changed: '%s' - resource won't match", val);
        }
        sec_init = 1;
    }
    send_reply_revised(SUCCESS, "Success", (uint8_t *)ret, strlen(ret), NULL);
}

static void http_get_alarm_handler(CLOCK offset, void *data)
{
    CURLMsg *msg = NULL;
    CURLMcode r;
    int msgs_left = -1;
    long response = -1;
    char *url = "<unknown>";

    still_alive = 0;
    r = curl_multi_perform(cm, &still_alive);
    if (r != CURLM_OK) {
        wic64_log(CONS_COL_NO, "%s: curl_multi_perform failed: %s", __FUNCTION__, curl_multi_strerror(r));
        msg = curl_multi_info_read(cm, &msgs_left);
        if (msg) {
            debug_log(LOG_COL_LRED, 2, "%s: msg: %u, %s", __FUNCTION__,
                       msg->data.result, curl_easy_strerror(msg->data.result));
            curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &url);
            debug_log(LOG_COL_LRED, 2, "%s, R: %u - %s <%s>", __FUNCTION__,
                       msg->data.result, curl_easy_strerror(msg->data.result), url);
        }
        send_reply_revised(NETWORK_ERROR, "Failed to read HTTP response", NULL, 0, "!0"); /* maybe wrong here */
        goto out;
    }
    debug_log(CONS_COL_NO, 3, "%s, running handles: %d, r = %d", __FUNCTION__, still_alive, r);

    if (wic64_remote_timeout_triggered) {
        debug_log(LOG_COL_LRED, 2, "Remote timout expired");
        send_reply_revised(NETWORK_ERROR, "Remote timeout", NULL, 0, "!0");
        wic64_remote_timeout_triggered = 0;
        remote_to = wic64_remote_timeout;
        msg = curl_multi_info_read(cm, &msgs_left); /* ensure all handles are freed when leaving below */
        goto out;
    }

    if (still_alive) {
        curl_multi_poll(cm, NULL, 0, 1, NULL);
        /* http request not yet finished */
        alarm_unset(http_get_alarm);
        alarm_set(http_get_alarm, maincpu_clk + (312 * 65));
        return;
    }

    alarm_unset(cmd_remote_timeout_alarm);
    remote_to = wic64_remote_timeout;

    msg = curl_multi_info_read(cm, &msgs_left);
    if (msgs_left > 0) {
        wic64_log(LOG_COL_LRED, "%s, msgs_left: %d (>0! not handled), msg = %p", __FUNCTION__, msgs_left, msg);
    }
    if (msg) {
        CURLcode res;
        res = curl_easy_getinfo(msg->easy_handle,
                                CURLINFO_RESPONSE_CODE,
                                &response);
        if (res != CURLE_OK) {
            wic64_log(LOG_COL_LRED, "%s: curl_easy_getinfo(...&response failed: %s", __FUNCTION__,
                      curl_easy_strerror(res));
            send_reply_revised(NETWORK_ERROR, "Failed to read HTTP response", NULL, 0, "!0");
            goto out;
        }
        res = curl_easy_getinfo(msg->easy_handle, CURLINFO_EFFECTIVE_URL, &url);
        if (res != CURLE_OK) {
            /* ignore problem, URL is only for debugging */
            debug_log(CONS_COL_NO, 2, "%s: curl_easy_getinfo(...&URL failed: %s", __FUNCTION__,
                      curl_easy_strerror(res));
            url = "<unknown>";
        }
    } else {
        wic64_log(LOG_COL_LRED, "%s, msgs_left: %d, msg = %p!", __FUNCTION__, msgs_left, msg);
    }

    if (response == 201) {
        /* prefs update requested, handles replies */
        update_prefs(httpbuffer, httpbufferptr);
        goto out;
    }

    if (response == 200) {
        wic64_log(CONS_COL_NO, "%s: got %lu bytes, URL: '%s', http code = %ld", __FUNCTION__,
                  httpbufferptr, url, response);
        if (wic64_protocol == WIC64_PROT_LEGACY) {
            char *t;
            /* check weird .prg -> cheat length */
            t = strrchr(url, '.');
            if ((t != NULL) && (strcmp(t, ".prg") == 0)){
                cheatlen = -2;
                wic64_log(LOG_COL_LGREEN, "prg URL -> reducing reported len by 2");
            }
        }
        send_reply_revised(SUCCESS, "Success", httpbuffer, httpbufferptr, NULL); /* raw send, supporting big_load */
        cheatlen = 0;
    } else if (response >= 301) {
        char t[32];
        wic64_log(LOG_COL_LRED, "URL '%s' returned %lu bytes (http code: %ld)", url, httpbufferptr, response);
        snprintf(t, 31, "http response: %ld", response);
        send_reply_revised(SERVER_ERROR, t, NULL, 0, "!0");      /* raw send supporting big_load */
    } else {
        /* firmeare handles codes: 301, 302, 307, 308 - check if needed with libcurl */
        char m[64];
        snprintf(m, 64, "Unhandled http response %ld, received %llu bytes", response, (unsigned long long)httpbufferptr);
        wic64_log(LOG_COL_LRED, m);
        if (httpbufferptr > 0) {
            send_reply_revised(SUCCESS, "Success", httpbuffer,
                               (httpbufferptr > 0xffff) ? 0xffff : httpbufferptr,
                               NULL);
        } else {
            send_reply_revised(INTERNAL_ERROR, m, NULL, 0, "!0");
        }
    }

  out:
    if (msg) {
        /* cleanup easy handly, fixes FD leak */
        curl_multi_remove_handle(cm, msg->easy_handle);
        curl_easy_cleanup(msg->easy_handle);
    }
    curl_multi_cleanup(cm);
    curl_global_cleanup();
    alarm_unset(http_get_alarm);
    memset(httpbuffer, 0, httpbufferptr);
    big_load = 0;
}

static void do_http_get(char *url)
{
    cmd_remote_timeout(1);
    cm = curl_multi_init();
    if (!cm) {
        send_reply_revised(CONNECTION_ERROR, "Can't send HTTP request", NULL, 0, "!0");
        return;
    }

    /* Limit the amount of simultaneous connections curl should allow: */
    curl_multi_setopt(cm, CURLMOPT_MAXCONNECTS, (long)MAX_PARALLEL);

    still_alive = 1;
    httpbufferptr = 0;
    add_transfer(cm, url);

    if (http_get_alarm == NULL) {
        http_get_alarm = alarm_new(maincpu_alarm_context, "HTTPGetAlarm",
                                   http_get_alarm_handler, NULL);
    }
    alarm_unset(http_get_alarm);
    alarm_set(http_get_alarm, maincpu_clk + (312 * 65));
}

/* ---------------------------------------------------------------------*/

#define FLAG2_ACTIVE    0
#define FLAG2_INACTIVE  1
#define FLAG2_TOGGLE_DELAY 3    /* delay in cycles to toggle flag2 */

static uint8_t input_state = 0, input_command = WIC64_CMD_NONE;
static uint8_t wic64_inputmode = 1;
static uint32_t input_length = 0, commandptr = 0;

static uint32_t replyptr = 0, reply_length = 0;
static uint8_t reply_port_value = 0;

static void flag2_alarm_handler(CLOCK offset, void *data)
{
    debug_log(LOG_COL_OFF, 4, "%s: handshake expired", __FUNCTION__);
    /* CAUTION: only call set_userport_flag() when we are actually the active userport device */
    if (userport_get_device() == USERPORT_DEVICE_WIC64) {
        set_userport_flag(FLAG2_INACTIVE);
    }
    alarm_unset(flag2_alarm);
}

/* a handshake is triggered after:
   - each byte the esp received from the c64 (inputmode = true)
   - each byte put on the userport for the c64 to fetch (inputmode = false, transferdata = true)
   - a command, to signal the c64 there is a reply
*/
static void handshake_flag2(void)
{
    if (flag2_alarm == NULL) {
        flag2_alarm = alarm_new(maincpu_alarm_context, "FLAG2Alarm", flag2_alarm_handler, NULL);
    }
    alarm_unset(flag2_alarm);
    alarm_set(flag2_alarm, maincpu_clk + FLAG2_TOGGLE_DELAY);
    debug_log(LOG_COL_OFF, 4, "%s: armed handshake", __FUNCTION__);

    /* CAUTION: only call set_userport_flag() when we are actually the active userport device */
    if (userport_get_device() == USERPORT_DEVICE_WIC64) {
        set_userport_flag(FLAG2_ACTIVE);
        /* set_userport_flag(FLAG2_INACTIVE); */
    }
}

static void cycle_alarm_handler(CLOCK offset, void *data)
{
    debug_log(LOG_COL_OFF, 3, "%s: expired, data = %p", __FUNCTION__, data);
    alarm_unset(cycle_alarm);
    alarm_destroy(cycle_alarm);
    cycle_alarm = NULL;
    if (data) {
        (*(void(*)(void))data)();               /* execute callback */
    }
}

static void wic64_sleep_cycles(int cycles, void *cb)
{
    if (cycle_alarm) {
        debug_log(LOG_COL_LRED, 3, "%s: refusing to re-arm pending cycle alarm", __FUNCTION__);
        return;
    }
    cycle_alarm = alarm_new(maincpu_alarm_context, "CycleAlarm", cycle_alarm_handler, cb);
    alarm_unset(cycle_alarm);
    alarm_set(cycle_alarm, maincpu_clk + cycles);
}

static void reply_next_byte(void)
{
    if (replyptr < reply_length) {
        reply_port_value = replybuffer[replyptr];
        /* debug_log(CONS_COL_NO, 2, "reply_next_byte: %3u/%3u - %02x'%c'", replyptr, reply_length, reply_port_value, isprint((unsigned char)reply_port_value)?reply_port_value:'.')); */
        replyptr++;
        if (replyptr == reply_length) {
            replyptr = reply_length = 0;
        }
    } else {
        replyptr = reply_length = 0;
    }
}

static void send_binary_reply(const uint8_t *reply, size_t len)
{
    int offs;

    /* highbyte first! */
    if (big_load) {
        offs = 4;
        replybuffer[3] = len & 0xff;
        replybuffer[2] = (len >> 8) & 0xff;
        replybuffer[1] = (len >> 16) & 0xff;
        replybuffer[0] = (len >> 24) & 0xff;

    } else {
        offs = 2;
        replybuffer[1] = (len + cheatlen) & 0xff;
        replybuffer[0] = ((len + cheatlen) >> 8) & 0xff;
    }

    memcpy((char*)replybuffer + offs, reply, len);
    reply_length = (uint32_t)(len + offs);
    replyptr = 0;
    if (len > 0) {
        wic64_log(LOG_COL_LGREEN, "sends %d/0x%x bytes...", len, len);
        wic64_log_hexdump(LOG_COL_LGREEN, replybuffer, reply_length);
    } else {
        wic64_log(LOG_COL_LGREEN, "handshake flag2");
    }
    handshake_flag2();
}

static void cmd_timeout_alarm_handler(CLOCK offset, void *data)
{
    wic64_log(LOG_COL_LRED, "timed out - '%s' command", cmd2string[input_command]);
    replyptr = reply_length = 0;
    input_state = INPUT_EXP_PROT;
    commandptr = 0;
    alarm_unset(cmd_timeout_alarm);
}

static void cmd_timeout(int arm)
{
    if (wic64_protocol == WIC64_PROT_LEGACY) {
        return;                 /* legacy won't support timeouts */
    }
    if (cmd_timeout_alarm == NULL) {
        cmd_timeout_alarm = alarm_new(maincpu_alarm_context, "CMDTimoutAlarm",
                                      cmd_timeout_alarm_handler, NULL);
    }
    alarm_unset(cmd_timeout_alarm);
    if (arm) {
        alarm_set(cmd_timeout_alarm, maincpu_clk + wic64_timeout * machine_get_cycles_per_second());
    }
}

static void cmd_remote_timeout_alarm_handler(CLOCK offset, void *data)
{
    wic64_log(LOG_COL_LRED, "remote time out - '%s' command", cmd2string[input_command]);
    wic64_remote_timeout_triggered = 1;
    alarm_unset(cmd_remote_timeout_alarm);
}

static void cmd_remote_timeout(int arm)
{
    if (cmd_remote_timeout_alarm == NULL) {
        cmd_remote_timeout_alarm = alarm_new(maincpu_alarm_context, "CMDRemoteTimoutAlarm",
                                             cmd_remote_timeout_alarm_handler, NULL);
    }
    alarm_unset(cmd_remote_timeout_alarm);
    if (arm) {
        debug_log(CONS_COL_NO, 2, "arming remote timeout with %ds", wic64_remote_timeout);
        wic64_remote_timeout_triggered = 0;
        alarm_set(cmd_remote_timeout_alarm, maincpu_clk + remote_to * machine_get_cycles_per_second());
    }
}

static void send_reply_revised(const uint8_t rcode, const char *msg, const uint8_t *payload, size_t len, const char *legacy_msg)
{
    int offs = 0;

    wic64_set_status(msg);

    if (wic64_protocol != WIC64_PROT_LEGACY) {
        const char *col;

        if (rcode == SUCCESS) {
            col = LOG_COL_LGREEN;
        } else {
            col = LOG_COL_LRED;
        }
        wic64_log(col, "replies %s", msg);
        replybuffer[0] = rcode;
        replybuffer[1] = len & 0xff; /* little endian */
        replybuffer[2] = (len >> 8) & 0xff;
        if (wic64_protocol == WIC64_PROT_EXTENDED) {
            offs = 2;
            replybuffer[3] = (len >> 16) & 0xff;
            replybuffer[4] = (len >> 24) & 0xff;
        }
        memcpy((char *) &replybuffer[3 + offs], (const char *)payload, len);
        debug_log(col, 2, "sends header...");
        debug_hexdump(col, 2, replybuffer, 3 + offs);
        if (len > 0) {
            wic64_log(col, "sends payload %d/0x%x bytes...", len, len);
            wic64_log_hexdump(col, &replybuffer[3 + offs], (int)len);
        }
        reply_length = (uint32_t)(len + 3 + offs);

        cmd_timeout(1);         /* arm alarm handler */
        stage_dummy = 1;
        bsr = stage_retcode = 1;
        bsl = stage_length = 2 + offs;
        bsd = stage_data = (int)len;
        handshake_flag2();
    } else {
        /* legacy protocol */
        stage_dummy = stage_retcode = stage_length = stage_data = -1;
        if (legacy_msg && payload) {
            wic64_log(LOG_COL_LRED,
                      "protocol error: can't send both payload and legacy message: '%s' discarded.",
                      legacy_msg);
            return;
        }
        if (legacy_msg) {
            /* legacy_msg becomes the payload */
            payload = (uint8_t *)legacy_msg;
            len = strlen(legacy_msg);
        }
        /* always send a response, even if payload == NULL and len == 0,
           so that a response header is always send, even if it's [$00, $00] */
        send_binary_reply(payload, len);
    }
}

/* ----------- WiC64 commands ----------- */
static void cmd_get_version(int variant)
{
    static uint8_t version[4] = {
        WIC64_VERSION_MAJOR,
        WIC64_VERSION_MINOR,
        WIC64_VERSION_PATCH,
        WIC64_VERSION_DEVEL,
    };
    if (variant == WIC64_CMD_GET_VERSION_STRING) {
        send_reply_revised(SUCCESS, "Success",
                           (uint8_t *)WIC64_VERSION_STRING,
                           strlen(WIC64_VERSION_STRING) + 1, NULL);
    } else {
        send_reply_revised(SUCCESS, "Success", version, 4, NULL);
    }
}

static int _encode(char **p, int len)
{
    int enc_it = 0;
    int i;
    static char hextab[16 + 1] = "0123456789abcdef";

    for (i = 0; i < len; i++) {
        encoded_helper[enc_it++] = hextab[((**p) >> 4) & 0xf];
        encoded_helper[enc_it++] = hextab[(**p) & 0xf];
        (*p)++;
    }
    encoded_helper[enc_it] = '\0';
    return enc_it;
}

/* encode binary after escape '$<' */
static void cmd_http_get_encoded(void)
{
    char *p;
    char *endmarker;
    char *cptr;
    char *tptr;
    char temppath[COMMANDBUFFER_MAXLEN];
    int len;
    int l;

    wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr);

    /* if encode is enabled, there might be binary data after <$, which is
       then encoded as a stream of hex digits */
    cptr = (char*)commandbuffer;
    tptr = temppath;
    endmarker = cptr + commandptr;
    len = 0;
    while ((len < COMMANDBUFFER_MAXLEN) &&
           ((p = strstr(cptr, "<$")) != NULL) &&
           (cptr < endmarker)) {
        l = (int)(p - cptr);
        len += l;
        debug_log(CONS_COL_NO, 2, "%s: escape sequence found, offset %d", __FUNCTION__, len);
        /* copy string before <$ */
        memcpy(tptr, cptr, (size_t)l);
        tptr += l;
        l = (unsigned char) p[2];
        l += (((unsigned char) p[3]) << 8);
        p += 4; /* skip escape sequence and len */
        debug_log(CONS_COL_NO, 2, "%s: length to encode = %d", __FUNCTION__, l);
        l = _encode(&p, l);
        debug_log(CONS_COL_NO, 2, "%s: encoded length = %d", __FUNCTION__, l);
        memcpy(tptr, encoded_helper, l);
        len += l;
        cptr = p;
        tptr += l;
    }
    l = 0;
    if (cptr < endmarker) {
        /* copy remaining commandbuffer */
        l = (int)(endmarker - cptr);
        memcpy(temppath + len, cptr, (size_t)l);
    }
    commandptr = len + l;
    memcpy(commandbuffer, temppath, commandptr);
    commandbuffer[commandptr] = '\0'; /* URL must be a valid string */
}

static int http_expand_url(char *final_url)
{
    char *p;
    char temppath[COMMANDBUFFER_MAXLEN];
    int i;
    char *cur = final_url;

    wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr); /* commands may contain '0' */

    if (commandptr == 0) {
        send_reply_revised(CLIENT_ERROR, "URL not specified", NULL, 0, "!E");
    }

    /* see below, noprintables in pid=.. need to be overruled, otherwise libcurl complains */
    p = strstr((const char *)commandbuffer, "&pid=");
    if (p != NULL) {
        if (!isprint((unsigned char)*(p+5))) {
            wic64_log(CONS_COL_NO, "%s: patching &pid=X.", __FUNCTION__);
            *(p + 5) = 'X';
        }
        if (!isprint((unsigned char)*(p+6))) {
            wic64_log(CONS_COL_NO, "%s: patching &pid=.Y", __FUNCTION__);
            *(p + 6) = 'Y';
        }
    }

    /* sanity check if URL is OK in principle */
    for (i = 0; i < commandptr; i++) {
        if (!isprint(commandbuffer[i])) {
            wic64_log(LOG_COL_LRED, "bad char '0x%02x' detected in URL at offet %d, %s",
                      commandbuffer[i], i, commandbuffer);
            send_reply_revised(CLIENT_ERROR, "Malformed URL", NULL, 0, "!0");
            return -1;
        }
    }

    /* if url begins with !, replace by default server */
    if (commandbuffer[0] == '!') {
        const char *sv;
        resources_get_string("WIC64DefaultServer", &sv);
        wic64_log(CONS_COL_NO, "URL starts with !, default server is: %s", sv);
        p = temppath;
        /* add the default server address */
        strcpy(p, sv);
        p += strlen(sv);
        /* copy command buffer */
        memcpy(p, commandbuffer + 1, COMMANDBUFFER_MAXLEN - strlen(sv));
        /* copy back to commandbuffer buffer */
        memcpy(commandbuffer, temppath, COMMANDBUFFER_MAXLEN);
    }

    /* detect protocol and split path/hostname */
    p = (char*)commandbuffer;
    if (!strncmp(p, "http://", 7)) {
        strcpy(cur, "http://");
        cur += 7;
        p += 7;
    } else {
        if (!strncmp(p, "https://", 8)) {
            strcpy(cur, "https://");
            cur += 8;
            p += 8;
        } else {
            wic64_log(LOG_COL_LRED, "malformed URL: %s", commandbuffer);
            send_reply_revised(CLIENT_ERROR, "Malformed URL", NULL, 0, "!0");
            return -1;
        }
    }

    p = strtok(p, "/");
    strcpy(cur, p);
    cur += strlen(p);
    *cur = '/'; cur++;
    *cur = '\0';
    p = (char*)commandbuffer;
    p += strlen(final_url);
    memcpy(cur, p, URL_MAXLEN - (p - (char*)commandbuffer));

    /* replace "%mac" by our MAC */
    p = strstr(cur, "%mac");
    if (p != NULL) {
        char macstring[64]; /* MAC + session_id */
        const char *sess;
        resources_get_string("WIC64SecToken", &sess);
        /* copy string before %mac */
        strncpy(temppath, cur, p - cur);
        temppath[p - cur] = 0;
        /* add the MAC address */
        sprintf(macstring, "%c%c%c%c%c%c%c%c%c%c%c%c%s",
                wic64_mac_address[0], wic64_mac_address[1],
                wic64_mac_address[3], wic64_mac_address[4],
                wic64_mac_address[6], wic64_mac_address[7],
                wic64_mac_address[9], wic64_mac_address[10],
                wic64_mac_address[12], wic64_mac_address[13],
                wic64_mac_address[15], wic64_mac_address[16],
                sess);
        strcat(temppath, macstring);
        strcat(temppath, p + 4);
        /* copy back to path buffer */
        strcpy(cur, temppath);
    }
    /* replace "%ser" by the default server */
    p = strstr(cur, "%ser");
    if (p != NULL) {
        const char *sv;
        resources_get_string("WIC64DefaultServer", &sv);
        /* copy string before %ser */
        strncpy(temppath, cur, p - cur);
        temppath[p - cur] = 0;
        /* add the default server address */
        strcat(temppath, sv);
        /* copy string after %ser */
        strcat(temppath, p + 4);
        /* copy back to path buffer */
        strcpy(cur, temppath);
        debug_log(CONS_COL_NO, 2, "temppath:%s", temppath);
    }
    /* now strip trailing whitespaces of path */
    p = cur + strlen(cur) - 1;
    while (isspace((unsigned char)*p)) {
        *p = '\0';
        p--;
    }
    /* remove trailing nonprintables - otherwise libcurl rejects the URL
       probably a bug on the app side, fixes at least artillery duel */
    while (!isprint((unsigned char)*p)) {
        *p = '\0';
        p--;
    }
    debug_log(CONS_COL_NO, 2, "%s: URL = '%s'", __FUNCTION__, final_url);

    if (strlen(final_url) > URL_MAXLEN) {
        send_reply_revised(CLIENT_ERROR, "URL too long (max 2000 bytes)", NULL, 0, "!E");
        return -1;
    }
    return 0;
}

/* http get */
static void cmd_http_get(void)
{
    char url[URL_MAXLEN];

    if (http_expand_url(url) < 0) {
        return;
    }
    do_http_get(url);
}

static void http_post_endalarm_handler(CLOCK offset, void *data)
{
    debug_log(CONS_COL_NO, 2, "http post endalarm triggered...");
    send_reply_revised(SUCCESS, "Success", (uint8_t *)post_data, post_data_rcvd, NULL);
    alarm_unset(http_post_endalarm);
    alarm_unset(http_post_alarm);
    lib_free(post_data);
    post_data = NULL;
    post_error = post_data_rcvd = post_data_new = 0;
    debug_log(CONS_COL_NO, 2, "http post done");
}

static void http_post_alarm_handler(CLOCK offset, void *data)
{
    debug_log(CONS_COL_NO, 2, "%s: post_data_rcvd = %d, expected = %d",
               __FUNCTION__, post_data_rcvd, post_data_size);

    if (post_error) {
        send_reply_revised(SERVER_ERROR, "Server error",
                           (uint8_t *)post_data,
                           post_data_rcvd, NULL);
        goto out;
    }
    if (post_data_rcvd >= post_data_size) { /* post_data_size is size_t so unsigned */
        send_reply_revised(SUCCESS, "Success", (uint8_t *)post_data, post_data_rcvd, NULL);
        goto out;
    }
    if (post_data_size == -1) {
        /* reply header didn't tell us the size, so read up to 64kB */
        if (post_data_rcvd > 0xffff) {
            send_reply_revised(SUCCESS, "Success", (uint8_t *)post_data, 0xffff, NULL);
            goto out;
        }
        /* if new data has arrived, re-arm end-timeout */
        if (post_data_new != post_data_rcvd) {
            alarm_unset(http_post_endalarm);
            /* reset alarm to 33% of wic64 timeout - should be fine in general */
            alarm_set(http_post_endalarm,
                      maincpu_clk + wic64_timeout * machine_get_cycles_per_second() / 3);
            post_data_new = post_data_rcvd;
        }
    }
    alarm_set(http_post_alarm, maincpu_clk +  machine_get_cycles_per_second() / 2);
    return;

out:
    alarm_unset(http_post_endalarm);
    alarm_unset(http_post_alarm);
    lib_free(post_data);
    post_data = NULL;
    post_error = post_data_new = post_data_rcvd = 0;
    debug_log(CONS_COL_NO, 2, "http post done");
}

static size_t post_write_func(char *buffer, size_t size, size_t nitems, void *userdata)
{
    CURLcode res;
    curl_off_t cl;
    size_t ret;

    ret = size * nitems;
    res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl);
    debug_log(CONS_COL_NO, 2, "http_post returned %d/0x%x bytes, expected len = %d...", ret, ret, cl);
    if (res != CURLE_OK) {
        debug_log(CONS_COL_NO, 2, "post callback failed to read length");
        post_error = 1;
        goto out;
    }
    if ((post_data_rcvd + ret) > HTTPREPLY_MAXLEN) {
        post_error = 1;
        ret = HTTPREPLY_MAXLEN - post_data_rcvd;
    }
    memcpy(post_data + post_data_rcvd, buffer, ret);
    post_data_rcvd += ret;
    debug_hexdump(CONS_COL_NO, 2, buffer, (int)ret);
  out:
    post_data_size = (size_t) cl; /* if -1 => largest size_t, so unknown */
    return ret;
}

static void cmd_http_post(int cmd)
{
    CURLcode res;
    static curl_mime *mime;
    static curl_mimepart *part;

    if (cmd == WIC64_CMD_HTTP_POST_URL) {
        if (post_url == NULL) {
            post_url = lib_malloc(URL_MAXLEN);
        }
        if (http_expand_url(post_url) < 0) {
            return;
        }
        send_reply_revised(SUCCESS, "Success", NULL, 0, NULL);
    } else {
        wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr);

        if (post_url == NULL) {
            send_reply_revised(CLIENT_ERROR, "URL not specified", NULL, 0, "!0");
            return;
        }

        if (!curl) {
            curl = curl_easy_init();
        }

        if (wic64_loglevel > 1) {
            curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        } else {
            curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
        }
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        /* need to decied if we want to ship a certificate file
           curl_easy_setopt(curl, CURLOPT_CAINFO, PREFIX "/share/vice/etc/ca-bundle.crt");
           curl_easy_setopt(curl, CURLOPT_CAPATH, PREFIX "/share/vice/etc/ca-bundle.crt");
        */
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        res = curl_easy_setopt(curl, CURLOPT_USERAGENT, http_user_agent);
        if (res != CURLE_OK) {
            wic64_log(CONS_COL_NO, "curl set user agent failed: %s", curl_easy_strerror(res));
            send_reply_revised(NETWORK_ERROR, "Failed to open connection", NULL, 0, "!0");
            return;
        }
        curl_easy_setopt(curl, CURLOPT_URL, post_url);
        res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, post_write_func);
        if (res != CURLE_OK) {
            wic64_log(CONS_COL_NO, "perform failed: %s", curl_easy_strerror(res));
            send_reply_revised(NETWORK_ERROR, "Failed to open connection", NULL, 0, "!0");
            return;
        }
        post_data_rcvd = post_data_new = post_error = 0;
        post_data_size = HTTPREPLY_MAXLEN;
        /* first time prepare for receiving post resonses */
        post_data = lib_malloc(HTTPREPLY_MAXLEN);
        if (!post_data) {
            send_reply_revised(INTERNAL_ERROR, "Out of memory", NULL, 0, NULL);
            return;
        }
        mime = curl_mime_init(curl);
        part = curl_mime_addpart(mime);
        curl_mime_data(part, (const char *) commandbuffer, (size_t) commandptr);
        /* Build an HTTP form with a single field named "data", */
        curl_mime_name(part, "data");
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            wic64_log(CONS_COL_NO, "perform failed: %s", curl_easy_strerror(res));
            send_reply_revised(NETWORK_ERROR, "Failed to send POST data to server", NULL, 0, "!0");
        }
        if (http_post_alarm == NULL) {
            http_post_alarm = alarm_new(maincpu_alarm_context, "HTTPPostAlarm",
                                        http_post_alarm_handler, NULL);
        }
        if (http_post_endalarm == NULL) {
            http_post_endalarm = alarm_new(maincpu_alarm_context, "HTTPPostAlarm",
                                           http_post_endalarm_handler, NULL);
        }
        alarm_unset(http_post_alarm);
        alarm_set(http_post_alarm, maincpu_clk + (312 * 65));
    }
}

/* set wlan ssid + password */
static void cmd_wifi(int cmd)
{
    wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */

    int l = (wic64_protocol == WIC64_PROT_LEGACY) ? 0 : 1; /* kludge to make it compatible */
    char sep = l ? '\0' : '\1';
    char rets[128];
    char r = '\0';

    switch (cmd) {
    case WIC64_CMD_SCAN_WIFI_NETWORKS: {
        snprintf(rets, 127, "0%cvice-emulation%c65%c%c", sep, sep, sep, 0xff);
        send_reply_revised(SUCCESS, "Success",
                           (uint8_t *) rets, 20+l,
                           NULL);
        break;
    }
    case WIC64_CMD_IS_CONFIGURED: {
        send_reply_revised(SUCCESS, "Success", (uint8_t *)&r, 1, NULL);
        break;
    }
    case WIC64_CMD_CONNECT_WITH_SSID_STRING:
    case WIC64_CMD_CONNECT_WITH_SSID_INDEX:
    case WIC64_CMD_IS_CONNECTED:
        send_reply_revised(SUCCESS, "wifi config changed", NULL, 0, "0");
        break;
    case WIC64_CMD_GET_SSID:
        send_reply_revised(SUCCESS, "Success",
                           (uint8_t*) "vice-emulation",
                           strlen("vice-emulation") + l,
                           NULL);
        break;
    case WIC64_CMD_GET_RSSI:
        send_reply_revised(SUCCESS, "Success",
                           (uint8_t *) "-65dBm", strlen("-65dBm") + l, NULL);
        break;
    default:
        break;
    }
}

/* get wic64 ip address */
static void cmd_get_network(int cmd)
{
    char buffer[0x20];
    if (cmd == WIC64_CMD_GET_MAC) {
        sprintf(buffer, "%s", wic64_mac_address);
    }
    if (cmd == WIC64_CMD_GET_IP) {
        sprintf(buffer, "%s", wic64_internal_ip);
    }
    send_reply_revised(SUCCESS, "Success", (uint8_t *)buffer, strlen(buffer) + 1, NULL);
}


/* get timezone+time */
static void cmd_get_local_time(void)
{
    int dst;

    static char timestr[64];
    time_t t = time(NULL);
    struct tm *tm = localtime(&t);

    if ((tm == NULL) ||
        (t < 0)) {
        send_reply_revised(INTERNAL_ERROR, "Could not get local time", NULL, 0, NULL);
        return;
    }

    dst = tm->tm_isdst; /* this is somehow wrong, get dst vom target tz */
    t = t + timezones[current_tz].hour_offs * 3600 +
        ((dst > 0) ? 3600 : 0) * timezones[current_tz].dst + /* some TZs have DST others not */
        timezones[current_tz].min_offs * 60;
    tm = gmtime(&t); /* now get the UTC */
    snprintf(timestr, 63, "%02d:%02d:%02d %02d-%02d-%04d",
             tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_mday, tm->tm_mon+1, tm->tm_year + 1900);
    wic64_log(CONS_COL_NO, "get timezone + time, returning '%s'", timestr);
    send_reply_revised(SUCCESS, "Success", (uint8_t *)timestr, strlen(timestr) + 1, NULL);
}

/* set timezone */
static void cmd_set_timezone(void)
{
    wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
    wic64_timezone[0] = commandbuffer[0];
    wic64_timezone[1] = commandbuffer[1];

    int tzidx = commandbuffer[1] * 10 + commandbuffer[0];
    if (tzidx < sizeof(timezones) / sizeof (tzones_t)) {
        wic64_log(CONS_COL_NO, "setting time to %s: %dh:%dm",
                  timezones[tzidx].tz_name,
                  timezones[tzidx].hour_offs,
                  timezones[tzidx].min_offs);
        resources_set_int("WIC64Timezone", tzidx);
    } else {
        wic64_log(CONS_COL_NO, "timezone index = %d - out of range", tzidx);
    }
    send_reply_revised(SUCCESS, "Success", NULL, 0, NULL);
}

/* get timezone */
static void cmd_get_timezone(void)
{
    char buf[16];
    wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
    snprintf(buf, 16, "%d",
             timezones[current_tz].hour_offs * 3600 +
             timezones[current_tz].min_offs * 60);
    wic64_log(CONS_COL_NO, "%s: get timezone, returning '%s'", __FUNCTION__, buf);
    send_reply_revised(SUCCESS, "Success", (uint8_t *)buf, strlen(buf) + 1, NULL);
}

/* open a curl connection */
static void do_connect(uint8_t *buffer)
{
    CURLcode res;

    curl = curl_easy_init();
    if (!curl) {
        send_reply_revised(NETWORK_ERROR, "Could not open connection", NULL, 0, "!E");
        return;
    }

    if (wic64_loglevel > 1) {
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    } else {
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
    }

    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    /* need to decied if we want to ship a certificate file
    curl_easy_setopt(curl, CURLOPT_CAINFO, PREFIX "/share/vice/etc/ca-bundle.crt");
    curl_easy_setopt(curl, CURLOPT_CAPATH, PREFIX "/share/vice/etc/ca-bundle.crt");
    */
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_URL, buffer);
    /* Do not do the transfer - only connect to host */
    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L);
    res = curl_easy_perform(curl);
    wic64_log(CONS_COL_NO, "%s: curl_easy_perform: %s",__FUNCTION__, curl_easy_strerror(res));
    if (res != CURLE_OK) {
        send_reply_revised(NETWORK_ERROR, "Could not open connection", NULL, 0, "!E");
    } else {
        send_reply_revised(SUCCESS, "Success", NULL, 0, "0");
    }
}

static void cmd_tcp_open(void)
{
    char tmp[COMMANDBUFFER_MAXLEN];
    wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr); /* commands may contain '0' */

    if (commandptr == 0) {
        send_reply_revised(CLIENT_ERROR, "No URL specified", NULL, 0, "!0");
        return;
    }

    strcpy(tmp, "telnet://");
    memcpy(tmp + 9, commandbuffer, commandptr + 1); /* copy '\0' */
    commandptr += 9;
    memcpy(commandbuffer, tmp, commandptr + 1);
    do_connect(commandbuffer);
}

static void cmd_tcp_available(void)
{
    CURLcode res;
    curl_socket_t sockfd;
    uint8_t t[2];
    int bytes_available;

    if (!curl) {
        send_reply_revised(NETWORK_ERROR, "NO CONNECTION", NULL, 0, NULL);
        return;
    }
    res = curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockfd);
    if (res != CURLE_OK) {
        send_reply_revised(NETWORK_ERROR, "NETWORK ERROR", NULL, 0, NULL);
        return;
    }

    if (archdep_socketpeek(sockfd, &bytes_available) < 0) {
        send_reply_revised(NETWORK_ERROR, "NETWORK ERROR", NULL, 0, NULL);
        return;
    }
    debug_log(CONS_COL_NO, 3, "%s: bytes_available = %d", __FUNCTION__, bytes_available);
    t[0] = bytes_available & 0xff;
    t[1] = (bytes_available >> 8) & 0xff;
    send_reply_revised(SUCCESS, "Success", t, 2, NULL);
}

static void tcp_get_alarm_handler(CLOCK offset, void *data)
{
    CURLcode res;
    size_t nread;
    static size_t total_read;

    if (!curl) {
        return;                 /* connection might be closed */
    }
    res = curl_easy_recv(curl,
                         curl_buf + total_read,
                         sizeof(curl_buf) - total_read,
                         &nread);
    alarm_set(tcp_get_alarm, maincpu_clk + (312 * 65 * 30));
    total_read += nread;
    if ((res == CURLE_OK) && (nread == 0)) {
        /* connection closed */
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        alarm_unset(tcp_get_alarm);
        curl = NULL;
        wic64_log(CONS_COL_NO, "%s: connection closed", __FUNCTION__);
    }

    if ((res == CURLE_OK) || (res == CURLE_AGAIN)) {
        if (nread) {
            wic64_log(CONS_COL_NO, "%s: nread = %lu, total_read = %lu", __FUNCTION__, nread, total_read);
        }
        big_load = 0;
        send_reply_revised(SUCCESS, "Success", curl_buf, total_read, NULL);
    } else {
        wic64_log(LOG_COL_LRED, "%s: curl_easy_recv: %s", __FUNCTION__, curl_easy_strerror(res));
        send_reply_revised(NETWORK_ERROR, "TCP connection closed", NULL, 0, "!E");
    }
    total_read = 0;
}

static void cmd_tcp_read(void)
{
    if (commandptr > 0) {
        wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
    }

    if (!curl) {
        wic64_log(LOG_COL_LRED, "%s: connection lost", __FUNCTION__);
        send_reply_revised(NETWORK_ERROR, "TCP connection closed", NULL, 0, "!E");
        return;
    }

    if (tcp_get_alarm == NULL) {
        tcp_get_alarm = alarm_new(maincpu_alarm_context, "TCPGetAlarm",
                                  tcp_get_alarm_handler, NULL);
    }
    alarm_unset(tcp_get_alarm);
    alarm_set(tcp_get_alarm, maincpu_clk + (312 * 65 / 2));
    /* no reply here, but from alarm handler */
}

static void tcp_send_alarm_handler(CLOCK offset, void *data)
{
    CURLcode res;
    size_t nsent;
    static size_t nsent_total;

    if (!curl) {
        return;                 /* connection might be closed */
    }
    alarm_set(tcp_send_alarm, maincpu_clk + (312 * 65));

    nsent = 0;
    res = curl_easy_send(curl, curl_send_buf + nsent_total,
                         curl_send_len - nsent_total, &nsent);
    nsent_total += nsent;

    if (nsent_total < curl_send_len) {
        return;
    }
    nsent_total = 0; /* reset ptr for sending */

    if (res == CURLE_OK) {
        alarm_unset(tcp_send_alarm);
        wic64_log(CONS_COL_NO, "%s: tcp sent successfully", __FUNCTION__);
        send_reply_revised(SUCCESS, "Success", NULL, 0, "0");
    } else {
        wic64_log(LOG_COL_LRED, "%s: curl_easy_send: %s", __FUNCTION__, curl_easy_strerror(res));
        send_reply_revised(NETWORK_ERROR, "Failed to write TCP data", NULL, 0, "!E");
    }
}

static void cmd_tcp_write(void)
{
    wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */

    if (!curl) {
        wic64_log(LOG_COL_LRED, "%s: connection lost", __FUNCTION__);
        send_reply_revised(CONNECTION_ERROR, "Can't execute TCP command", NULL, 0, "!0");
        return;
    }

    memcpy(curl_send_buf, commandbuffer, commandptr);
    curl_send_len = commandptr;

    if (tcp_send_alarm == NULL) {
        tcp_send_alarm = alarm_new(maincpu_alarm_context, "TCPSendAlarm",
                                   tcp_send_alarm_handler, NULL);
    }
    alarm_unset(tcp_send_alarm);
    alarm_set(tcp_send_alarm, maincpu_clk + (312 * 65));
    /* no reply here, but from alarm handler */
}

static void cmd_tcp_close(void)
{
    if (curl) {
        /* connection closed */
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        curl = NULL;
    }
    if (tcp_send_alarm) {
        alarm_unset(tcp_send_alarm);
    }
    if (tcp_get_alarm) {
        alarm_unset(tcp_get_alarm);
    }
    send_reply_revised(SUCCESS, "Success", NULL, 0, "0");
}

static void cmd_get_statusmsg(void)
{
    uint8_t t[40];
    int i;
    bool upcase = false;

    if (commandptr > 0) {
        wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr);
        upcase = (commandbuffer[0] > 0);
    }

    for (i = 0; wic64_last_status[i]; i++) {
        t[i] = upcase ? toupper((unsigned char)wic64_last_status[i]) : wic64_last_status[i];
        t[i] = charset_p_topetscii(t[i]);
    }
    t[i] = '\0';

    send_reply_revised(SUCCESS, "Success", t, strlen((char *)t) + 1, NULL);
}

static void cmd_force_timeout_alarm_handler(CLOCK offset, void *data)
{
    wic64_log(LOG_COL_LRED, "force timeout expired");
    replyptr = reply_length = 0;
    input_state = INPUT_EXP_PROT;
    commandptr = 0;
    alarm_unset(cmd_force_timeout_alarm);
    /* set_userport_flag(FLAG2_INACTIVE); */
}

static void cmd_force_timeout(void)
{
    if (wic64_protocol == WIC64_PROT_LEGACY) {
        return;                 /* legacy won't support timeouts */
    }
    int timeout = (commandptr > 0) ? commandbuffer[0] : 1;
    wic64_log(CONS_COL_NO, "forcing timeout after %ds", timeout);

    /* set_userport_flag(FLAG2_ACTIVE); */

    if (cmd_force_timeout_alarm == NULL) {
        cmd_force_timeout_alarm = alarm_new(maincpu_alarm_context, "CMDForceTimoutAlarm",
                                            cmd_force_timeout_alarm_handler, NULL);
    }
    alarm_unset(cmd_force_timeout_alarm);
    alarm_set(cmd_force_timeout_alarm, maincpu_clk + timeout * machine_get_cycles_per_second());
}

/* ---------------- legacy command -----------------*/

static void cmd_legacy(int cmd)
{

    switch (cmd) {
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_03:
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_05:
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_04:
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, "OK");
        break;
    case WIC64_CMD_DEPRECATED_GET_STATS_07:
    {
        char t[64];
        strncpy(t, __DATE__" " __TIME__, 64);
        charset_petconvstring((uint8_t*)t, CONVERT_TO_PETSCII);
        send_reply_revised(SUCCESS, "LEGACY",
                           (uint8_t *)t, strlen(t),
                           NULL);
        break;
    }
    case WIC64_CMD_DEPRECATED_LOG_TO_SERIAL_CONSOLE_09:
        wic64_loglevel++;
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_GET_UPD_0A:
    case WIC64_CMD_DEPRECATED_GET_UPD_DUPLICATE_1E:
        wic64_log(LOG_COL_LRED, "get udp/tcp package, not implemented");
        send_reply_revised(INTERNAL_ERROR, "LEGACY", NULL, 0, "");
        break;
    case WIC64_CMD_DEPRECATED_SEND_UPD_0B:
    case WIC64_CMD_DEPRECATED_SEND_UPD_DUPLICATE_1F:
        wic64_log(LOG_COL_LRED, "send udp/tcp package, not implemented");
        send_reply_revised(INTERNAL_ERROR, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_SET_UPD_PORT_0E:
        wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
        wic64_udp_port = commandbuffer[0];
        wic64_udp_port += commandbuffer[1] << 8;
        wic64_log(CONS_COL_NO, "set udp port to %d", wic64_udp_port);
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_GET_EXTERNAL_IP_13:
    {
        char buffer[0x20];
        sprintf(buffer, "%d.%d.%d.%d",
                wic64_external_ip[0], wic64_external_ip[1],
                wic64_external_ip[2], wic64_external_ip[3]);
        wic64_log(CONS_COL_NO, "get external IP address, returning %s", buffer);
        send_reply_revised(SUCCESS, "LEGACY", (uint8_t *)buffer, strlen(buffer), NULL);
        break;
    }
    case WIC64_CMD_DEPRECATED_FIRMWARE_UPDATE_REQUIRED_18:
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, "0");
        break;
    case WIC64_CMD_DEPRECATED_GET_PREFERENCES_19:
    {
        char buffer[256];
        wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
        snprintf(buffer, 255, "%s", "vice");
        wic64_log(LOG_COL_LRED, "read prefs not implemented, returning '%s'", buffer);
        send_reply_revised(INTERNAL_ERROR, "LEGACY", (uint8_t *) buffer, strlen(buffer), NULL);
        break;
    }
    case WIC64_CMD_DEPRECATED_SET_PREFERENCES_1A:
        wic64_log(LOG_COL_LRED, "write prefs not implemented");
        send_reply_revised(INTERNAL_ERROR, "LEGACY", NULL, 0, "");
        break;
    case WIC64_CMD_DEPRECATED_SET_TCP_PORT_20:
        wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
        wic64_tcp_port = commandbuffer[0];
        wic64_tcp_port += commandbuffer[1] << 8;
        wic64_log(CONS_COL_NO, "set tcp port to %d", wic64_tcp_port);
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_BIG_LOADER_25:
        wic64_log_hexdump(CONS_COL_NO, (const char *)commandbuffer, commandptr); /* commands may contain '0' */
        big_load = 1;
        cmd_http_get();
        break;
    case WIC64_CMD_DEPRECATED_FACTORY_RESET_63:
        userport_wic64_reset();
        userport_wic64_factory_reset();
        send_reply_revised(SUCCESS, "LEGACY", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_LEGACY_HTTP_POST_24:
        wic64_log(LOG_COL_LRED, "httppost - not implemented");
        send_reply_revised(INTERNAL_ERROR, "LEGACY", NULL, 0, "!E");
        break;
    default:
        break;
    }
}

/* ------------------------------ revised commands ---------------------*/
static void do_command(void)
{
    switch (input_command) {
    case WIC64_CMD_GET_VERSION_STRING:
    case WIC64_CMD_GET_VERSION_NUMBERS:
        cmd_get_version(input_command);
        break;
    case WIC64_CMD_HTTP_GET_ENCODED: /* send http with decoded url for PHP */
        cmd_http_get_encoded();
        /* show in log that we're entering http_get */
        wic64_log(LOG_COL_LBLUE, "URL decoded, proceed with %s", cmd2string[WIC64_CMD_HTTP_GET]);
        /* fall through */
    case WIC64_CMD_HTTP_GET:
        big_load = 0;
        cmd_http_get();
        break;
    case WIC64_CMD_HTTP_POST_URL:
    case WIC64_CMD_HTTP_POST_DATA:
        cmd_http_post(input_command);
        break;
    case WIC64_CMD_SCAN_WIFI_NETWORKS:
    case WIC64_CMD_CONNECT_WITH_SSID_STRING:
    case WIC64_CMD_CONNECT_WITH_SSID_INDEX:
    case WIC64_CMD_IS_CONFIGURED:
    case WIC64_CMD_IS_CONNECTED:
    case WIC64_CMD_GET_SSID:
    case WIC64_CMD_GET_RSSI:
        cmd_wifi(input_command);
        break;
    case WIC64_CMD_GET_IP:
    case WIC64_CMD_GET_MAC:
        cmd_get_network(input_command);
        break;
    case WIC64_CMD_GET_SERVER:
    {
        const char *sv;
        resources_get_string("WIC64DefaultServer", &sv);
        wic64_log(CONS_COL_NO, "get default server '%s'", sv);
        send_reply_revised(SUCCESS, "Success", (uint8_t *)sv, strlen(sv) + 1, NULL);
        break;
    }
    case WIC64_CMD_SET_SERVER:
        wic64_log(CONS_COL_NO, "set default server '%s'", commandbuffer);
        resources_set_string("WIC64DefaultServer", (char *)commandbuffer);
        send_reply_revised(SUCCESS, "Success", NULL, 0, "0");
        break;
    case WIC64_CMD_GET_LOCAL_TIME:
        cmd_get_local_time();
        break;
    case WIC64_CMD_SET_TIMEZONE:
        cmd_set_timezone();
        break;
    case WIC64_CMD_GET_TIMEZONE:
        cmd_get_timezone();
        break;
    case WIC64_CMD_TCP_OPEN:
        cmd_tcp_open();
        break;
    case WIC64_CMD_TCP_AVAILABLE:
        cmd_tcp_available();
        break;
    case WIC64_CMD_TCP_READ:
        cmd_tcp_read();
        break;
    case WIC64_CMD_TCP_WRITE:
        cmd_tcp_write();
        break;
    case WIC64_CMD_TCP_CLOSE:
        cmd_tcp_close();
        break;
    case WIC64_CMD_GET_STATUS_MESSAGE:
        cmd_get_statusmsg();
        break;
    case WIC64_CMD_ECHO:
        if (wic64_protocol != WIC64_PROT_EXTENDED)
            send_reply_revised(SUCCESS, "", commandbuffer, commandptr, NULL);
        else
            send_reply_revised(CLIENT_ERROR, "", NULL, 0, NULL);
        break;
    case WIC64_CMD_REBOOT:
        debug_log(CONS_COL_NO, 3, "%s: arming for reboot", __FUNCTION__);
        wic64_sleep_cycles(3 * (int)machine_get_cycles_per_second(), userport_wic64_reset); /* emulated a 3s reboot */
        break;
    case WIC64_CMD_SET_TRANSFER_TIMEOUT:
    case WIC64_CMD_SET_REMOTE_TIMEOUT:
        if (commandptr < 1) {
            send_reply_revised(CLIENT_ERROR, "Timeout value not specified", NULL, 0, NULL);
            break;
        }
        if (commandbuffer[0] == 0) {
            send_reply_revised(CLIENT_ERROR, "Timeout must be >= 1 second", NULL, 0, NULL);
            break;
        }
        if (input_command == WIC64_CMD_SET_TRANSFER_TIMEOUT) {
            wic64_timeout = commandbuffer[0]; /* timeout in secs */
            wic64_log(CONS_COL_NO, "setting transfer timeout to %ds", wic64_timeout);
        } else {
            remote_to = commandbuffer[0]; /* timeout in secs */
            wic64_log(CONS_COL_NO, "setting remote timeout to %ds", remote_to);
        }
        send_reply_revised(SUCCESS, "Success", NULL, 0, NULL);
        break;
    case WIC64_CMD_IS_HARDWARE:
        /* send_reply_revised(INTERNAL_ERROR, "WiC64 is emulated", NULL, 0, NULL); */
        send_reply_revised(SUCCESS, "Success", NULL, 0, NULL);
        break;
    case WIC64_CMD_FORCE_ERROR:
        wic64_log(LOG_COL_LRED, "forcing error...");
        send_reply_revised(INTERNAL_ERROR, "Test error", NULL, 0, NULL);
        break;
    case WIC64_CMD_FORCE_TIMEOUT:
        cmd_force_timeout();
        break;
    case WIC64_CMD_UPDATE_FIRMWARE:
        send_reply_revised(SUCCESS, "OK", NULL, 0, NULL);
        break;
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_03:
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_04:
    case WIC64_CMD_DEPRECATED_UPDATE_FIRMWARE_05:
    case WIC64_CMD_DEPRECATED_FIRMWARE_UPDATE_REQUIRED_18:
    case WIC64_CMD_DEPRECATED_GET_STATS_07:
    case WIC64_CMD_DEPRECATED_LOG_TO_SERIAL_CONSOLE_09:
    case WIC64_CMD_DEPRECATED_GET_UPD_0A:
    case WIC64_CMD_DEPRECATED_SEND_UPD_0B:
    case WIC64_CMD_DEPRECATED_SET_UPD_PORT_0E:
    case WIC64_CMD_DEPRECATED_GET_UPD_DUPLICATE_1E:
    case WIC64_CMD_DEPRECATED_SEND_UPD_DUPLICATE_1F:
    case WIC64_CMD_DEPRECATED_GET_EXTERNAL_IP_13:
    case WIC64_CMD_DEPRECATED_GET_PREFERENCES_19:
    case WIC64_CMD_DEPRECATED_SET_PREFERENCES_1A:
    case WIC64_CMD_DEPRECATED_SET_TCP_PORT_20:
    case WIC64_CMD_DEPRECATED_BIG_LOADER_25:
    case WIC64_CMD_DEPRECATED_FACTORY_RESET_63:
    case WIC64_CMD_DEPRECATED_LEGACY_HTTP_POST_24:
        cmd_legacy(input_command);
        break;
    default:
    {
        char it[64];
        wic64_log(LOG_COL_LRED, "WiC64: unsupported command 0x%02x (len: %d/0x%x)",
                  input_command, input_length, input_length);
        input_state = 0;
        snprintf(it, 63, "Undefined command id 0x%02x", input_command);
        send_reply_revised(CLIENT_ERROR, it, NULL, 0, "!E"); /* not sure for legacy */
        break;
    }
    }
}

static void wic64_prot_state(uint8_t value)
{
    switch (input_state) {
    case INPUT_EXP_CMD:         /* command */
        input_command = value;
        if (wic64_protocol != WIC64_PROT_LEGACY) {
            input_state = INPUT_EXP_LL;
        } else {
            input_state = INPUT_EXP_ARGS;
        }
        break;
    case INPUT_EXP_LL:
        input_length = value;   /* len low byte */
        input_state = INPUT_EXP_LH;
        break;
    case INPUT_EXP_LH:          /* len high byte */
        input_length = (value << 8) | input_length;
        if (wic64_protocol == WIC64_PROT_REVISED) {
            input_state = INPUT_EXP_ARGS;
        } else if (wic64_protocol == WIC64_PROT_EXTENDED) {
            input_state = INPUT_EXP_HL;
        } else if (wic64_protocol == WIC64_PROT_LEGACY) {
            input_state = INPUT_EXP_CMD;
            input_length -= 4;
        }
        break;
    case INPUT_EXP_HL:
        input_length = (value << 16) | input_length;
        input_state = INPUT_EXP_HH;
        break;
    case INPUT_EXP_HH:
        input_length = (value << 24) | input_length;
        input_state = INPUT_EXP_ARGS;
        break;
    case INPUT_EXP_ARGS:
        if (commandptr < input_length) {
            commandbuffer[commandptr] = value;
            commandptr++;
            if (commandptr >= COMMANDBUFFER_MAXLEN) {
                wic64_log(LOG_COL_LRED, "command %s exceeds maxlength, forcing timeout",
                          cmd2string[input_command]);
                wic64_log_hexdump(LOG_COL_LBLUE, (const char *)commandbuffer, commandptr - 1);
                commandbuffer[0] = 1; /* for timeout for 1s */
                cmd_force_timeout();
                return;
            }
            commandbuffer[commandptr] = 0;
        }
        break;
    default:
        wic64_log(LOG_COL_LRED, "unknown input state %d", input_state);
        break;
    }
}

/* this is run after handshake of last byte is completed */
static void run_command_helper(void)
{
    wic64_log(LOG_COL_LBLUE, "command %s (len=%d/0x%x, using %s protocol)",
              cmd2string[input_command],
              input_length, input_length,
              (wic64_protocol == WIC64_PROT_LEGACY) ? "legacy" :
              (wic64_protocol == WIC64_PROT_REVISED) ? "revised" :
              (wic64_protocol == WIC64_PROT_EXTENDED) ? "extended" :
              "unknown");
    do_command();
    cmd_timeout(0);
    commandptr = input_state = input_length = 0;
    input_command = WIC64_CMD_NONE;
    memset(commandbuffer, 0, COMMANDBUFFER_MAXLEN);
}

/* PC2 irq (pulse) triggers when C64 reads/writes to userport */
static void userport_wic64_store_pbx(uint8_t value, int pulse)
{
    if (pulse == 1) {
        if (wic64_inputmode) {
            debug_log(LOG_COL_LBLUE, 3, "receiving '%c'/0x%02x, input_state = %d",
                       isprint(value) ? value : '.',
                       value,
                       input_state);

            if (input_state == INPUT_EXP_PROT) {
                int old_prot = wic64_protocol;
                wic64_protocol = value;
                switch (value) {
                case WIC64_PROT_LEGACY:
                    input_state = INPUT_EXP_LL;
                    break;
                case WIC64_PROT_REVISED:
                case WIC64_PROT_EXTENDED:
                    input_state = INPUT_EXP_CMD;
                    break;
                default:
                    debug_log(LOG_COL_LRED, 3, "unknown protocol '%c'/0x%02x.",
                              isprint(value) ? value: '.', value);
                    wic64_protocol = WIC64_PROT_UNKNOWN;
                    input_state = INPUT_EXP_PROT;
                    break;
                }

                if (old_prot != wic64_protocol) {
                    wic64_log(CONS_COL_NO, "using %s protocol",
                              (wic64_protocol == WIC64_PROT_LEGACY) ? "legacy" :
                              (wic64_protocol == WIC64_PROT_REVISED) ? "revised" :
                              (wic64_protocol == WIC64_PROT_EXTENDED) ? "extended" :
                              "unknown");
                }
                input_length = 0;
                commandptr = 0;
            } else {
                wic64_prot_state(value);
            }
            handshake_flag2();

            cmd_timeout(1);
            if ((input_state == INPUT_EXP_ARGS) &&
                (commandptr == input_length)) {
                wic64_sleep_cycles(FLAG2_TOGGLE_DELAY + 1, run_command_helper);
            }
        } else {
            if (reply_length) {
                reply_next_byte();
            }
            handshake_flag2();
        }
    }
}

static uint8_t userport_wic64_read_pbx(uint8_t orig)
{
    uint8_t retval = reply_port_value;
    /* FIXME: what do we have to do with original value? */
    /* CIA read is triggered once more by wic64 lib on the host,
       even if all bytes are sent, so the last byte seems to be sent twice */
    char *stage = NULL;
    char datastr[32];
    int v = 0, b = 0, d = 1;

    cmd_timeout(0);
    /* FIXME: trigger mainloop */

    if (wic64_loglevel < 3) {
        return (wic64_protocol != WIC64_PROT_UNKNOWN) ? retval : 0xff;
    }

    if (stage_retcode < 0) {
        stage = NULL;
    } else if (stage_dummy) {
        stage = "dummy";
        b = v = 1;
        stage_dummy--;
    } else if (stage_retcode) {
        if (retval < 6) {       /* num of error codes defined for now */
            stage = err2string[retval];
        } else {
            stage = "UNKNOWN";
        }
        v = bsr - --stage_retcode;
        b = bsr;
    } else if (stage_length) {
        stage = "length";
        v = bsl - --stage_length;
        b = bsl;
    } else if (stage_data) {
        v = bsd - --stage_data;
        b = bsd;
        if ((bsd > 255) && (v == bsd)) d = 0;
        snprintf(datastr, 31, "data block %04d/%04d", (v / 256) + 1*d, ((b - 1) / 256) + 1);
        stage = datastr;
    }

    if (stage) {
        debug_log(LOG_COL_LGREEN, 3, "sending '%c'/0x%02x - 0x%02x/0x%04x %s\t - ptr = %d, rl = %d/0x%x",
                  isprint(retval) ? retval : '.', retval,
                  v, b, stage,
                  replyptr,
                  reply_length, reply_length);
    } else {
        if (wic64_protocol == WIC64_PROT_UNKNOWN) {
            retval = 0xff;
        }
        debug_log(LOG_COL_LGREEN, 3, "sending '%c'/0x%02x - ptr = %d, rl = %d/0x%x",
                  isprint(retval) ? retval : '.', retval,
                  replyptr,
                  reply_length, reply_length);
    }
    return retval;
}

/* PA2 interrupt toggles input/output mode */
static void userport_wic64_store_pa2(uint8_t value)
{
    debug_log(CONS_COL_NO, 2, "userport mode %s...(len = %d)",
               value ? "sending" : "receiving",
               reply_length);

    if ((wic64_inputmode == 1) &&
        (value == 0) &&
        (reply_length)) {
        debug_log(CONS_COL_NO, 2, "userport_wic64_store_pa2 val:%02x (host %s - rl = %u)",
                   value, value ? "sends" : "receives", reply_length);
        handshake_flag2();
    }
    wic64_inputmode = value;
    if (wic64_inputmode == 1) {
        if ((reply_length > 0) &&
            (replyptr > 0)) {
            replyptr--;         /* rewind by 1 byte */
            wic64_log(LOG_COL_LRED, "discarding %d bytes, which were not sent to host",
                      (reply_length - replyptr));
            wic64_log_hexdump(LOG_COL_LRED, &replybuffer[replyptr], (reply_length - replyptr));
        }
        replyptr = reply_length = 0; /* host decided to send, truncate outputbuffer */
    }
}

/*
    return a reasonable "local IP"

    this is used in two cases:
    - the WIC64IPAddress in the config is empty, and WIC64DHCP is 0. In that
      case we use the local IP as default
    - WIC64DHCP is 1. In that case we should always use the local IP

    Note: This is a surprisingly non trivial operation. For instance, due to
    the high level approach of the emulation, we can't really tell what our
    local IP is, without trying to connect somewhere.

    What we really intend to do here, is determining the IP address, which will
    be used by libcurl when we make connections, so it can be shown to the user
    in the emulation.
*/
static int getlocalip(char *str)
{
    char *ip;
    CURLcode res;
    CURL *c = curl_easy_init();

    *str = 0;

    /* try to connect to the default server */
    curl_easy_setopt(c, CURLOPT_URL, default_server_hostname);
    curl_easy_setopt(c, CURLOPT_CONNECT_ONLY, 0L);

    /* Perform the connect */
    res = curl_easy_perform(c);
    /* Check for errors */
    if((res == CURLE_OK) &&
        !curl_easy_getinfo(c, CURLINFO_LOCAL_IP, &ip) && ip) {
        /* if the connection was successful, libcurl will return the local ip */
        strcpy(str, ip);
    } else {
        /* as a last resort, just produce a random 'local' IP */
        snprintf(str, 16, "192.168.%u.%u",
            lib_unsigned_rand(1, 254),
            lib_unsigned_rand(1, 254));
    }

    /* always cleanup */
    curl_easy_reset(c);
    curl_easy_cleanup(c);

    return 0;
}

static void userport_wic64_reset(void)
{
    char *tmp;
    int tmp_tz;
    int tmp_dhcp;

    wic64_log(CONS_COL_NO, "%s", __FUNCTION__);
    commandptr = input_state = input_length = 0;
    input_command = WIC64_CMD_NONE;
    wic64_inputmode = 1;
    memset(sec_token, 0, 32);
    sec_init = 0;

    if (http_get_alarm) {
        alarm_unset(http_get_alarm);
    }
    if (http_post_alarm) {
        alarm_unset(http_post_alarm);
    }
    if (http_post_endalarm) {
        alarm_unset(http_post_endalarm);
    }
    if (tcp_get_alarm) {
        alarm_unset(tcp_get_alarm);
    }
    if (tcp_send_alarm) {
        alarm_unset(tcp_send_alarm);
    }
    if (cmd_timeout_alarm) {
        alarm_unset(cmd_timeout_alarm);
    }
    if (cmd_force_timeout_alarm) {
        alarm_unset(cmd_force_timeout_alarm);
    }
    /* flag2 and cycle alarms need to be preserverd !*/
    remote_to = wic64_remote_timeout; /* reset to given value */
    if (curl) {
        /* connection closed */
        curl_easy_cleanup(curl);
        curl_global_cleanup();
        curl = NULL;
    }
    curl_global_init(CURL_GLOBAL_ALL);
    if (post_url) {
        lib_free(post_url);
        post_url = NULL;
    }

    if (resources_get_int("WIC64Timezone", &tmp_tz) == -1) {
        current_tz = 2;
    } else {
        current_tz = tmp_tz;
    }

    if (resources_get_int("WIC64DHCP", &tmp_dhcp) == -1) {
        current_dhcp = 1;
    } else {
        current_dhcp = tmp_dhcp;
    }

    if ((resources_get_string("WIC64MACAddress", (const char **)&tmp) == -1) ||
        (tmp == NULL) ||
        (strcmp((const char*)tmp, MACADDR_INVALID) == 0)) {
        /* if MAC is empty in the config, then generate a random one and save it
           in the resource */
        wic64_mac_address = lib_malloc(32);
        snprintf(wic64_mac_address, 32, "08:d1:f9:%02x:%02x:%02x",
                 lib_unsigned_rand(0, 15),
                 lib_unsigned_rand(0, 15),
                 lib_unsigned_rand(0, 15));
        debug_log(CONS_COL_NO, 2, "WIC64: generated MAC: %s", wic64_mac_address);
    } else {
        wic64_mac_address = tmp;
        debug_log(CONS_COL_NO, 2, "WIC64: using saved MAC: %s", wic64_mac_address);
    }

    if (current_dhcp) {
        if (wic64_internal_ip == NULL) {
            wic64_internal_ip = lib_malloc(16);
        }
        getlocalip(wic64_internal_ip);
        debug_log(CONS_COL_NO, 2, "WIC64: generated internal IP: %s", wic64_internal_ip);
    }

    if ((resources_get_string("WIC64IPAddress", (const char **)&tmp) == -1) ||
        (tmp == NULL) ||
        (strcmp((const char *)tmp, IPADDR_INVALID) == 0)) {
        /* ip is empty */
        if (wic64_internal_ip == NULL) {
            wic64_internal_ip = lib_malloc(16);
        }
        getlocalip(wic64_internal_ip);
        debug_log(CONS_COL_NO, 2, "WIC64: generated internal IP: %s", wic64_internal_ip);
    } else {
        /* use saved ip */
        wic64_internal_ip = tmp;
    }


    /* wic64_set_status("RESET"); real HW doesn't tell this */

    wic64_log(LOG_COL_LBLUE, "cyan color: host -> WiC64 communication");
    wic64_log(LOG_COL_LGREEN, "green color: WiC64 -> host communication");
    wic64_log(LOG_COL_LRED, "red color: some error");
    wic64_log(CONS_COL_NO, "no color: other information");

    handshake_flag2();
}

/* ---------------------------------------------------------------------*/

/* USERPORT_WIC64 snapshot module format:

   type  | name           | description
   -----------------------------
   BYTE  | input_state    |
   BYTE  | input_length   |
*/

static char snap_module_name[] = "UPWIC64";
#define SNAP_MAJOR   0
#define SNAP_MINOR   1

static int userport_wic64_write_snapshot_module(snapshot_t *s)
{
    snapshot_module_t *m;

    m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);

    if (m == NULL) {
        return -1;
    }

    if (0
        || (SMW_B(m, input_state) < 0)
        || (SMW_B(m, input_command) < 0)) {
        snapshot_module_close(m);
        return -1;
    }
    return snapshot_module_close(m);
}

static int userport_wic64_read_snapshot_module(snapshot_t *s)
{
    uint8_t major_version, minor_version;
    snapshot_module_t *m;

    m = snapshot_module_open(s, snap_module_name, &major_version, &minor_version);

    if (m == NULL) {
        return -1;
    }

    /* Do not accept versions higher than current */
    if (snapshot_version_is_bigger(major_version, minor_version, SNAP_MAJOR, SNAP_MINOR)) {
        snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
        goto fail;
    }

    if (0
        || (SMR_B(m, &input_state) < 0)
        || (SMR_B(m, &input_command) < 0)) {
        goto fail;
    }
    return snapshot_module_close(m);

  fail:
    snapshot_module_close(m);
    return -1;
}

#endif /* WIC64 */
