// $Id: connect.c,v 1.45 2016/06/07 20:53:27 lpoulsen Exp $
//
// Copyright (c) 2008 by Afar Communications Inc, Santa Barbara, CA 93117 USA
//
// ECONSOLE connection module - multithreaded
//
// $Log: connect.c,v $
// Revision 1.45  2016/06/07 20:53:27  lpoulsen
// Resolve problem with multi-radio downloads which was intermittently but
//     frequently hanging at the end of a connect session because either
//     ReadThread or SendThread refused to exit due to a mutex deadlock.
//   We solved this by NOT using a global mutex for the WaitForSingleObject()
//     lock. Instead we use a local mutex that is just used as a placeholder
//     for the call to pthread_cond_timedwait().
// Additionally, we moved debugging of ECON protocol packets from debug_level
//     to debug_flags & DBFLG_ECON_PKTS.
//
// Revision 1.44  2014/05/21 20:05:56  lpoulsen
// uint32 is not long anymore, so remove long qualifiers in printf formats
//
// Revision 1.43  2013/04/19 17:14:16  lpoulsen
// Fix problem with checking if file has an extension.
//
// Revision 1.42  2012/05/03 20:38:42  lpoulsen
// Cleanup logic for whether to enable output file in "connect" session.
//
// Revision 1.41  2012/05/03 20:05:14  lpoulsen
// If output file was set on command line of connect, download or diagnose
//     command, clear it at the end of the command. If it was set on the
//     ECON command (-o outfile) or by "set output=" command, it will
//     remain set at the end of the connection session.
//
// Revision 1.40  2012/05/03 19:40:48  lpoulsen
// In diagnose, put "connecting" banner above first data from radio!
//
// Revision 1.39  2012/05/03 19:35:15  lpoulsen
// Add label to the top of diagnose output.
//
// Revision 1.38  2012/04/27 23:18:27  lpoulsen
// Check protocol version before waiting in diagnose ...
//
// Revision 1.37  2012/04/27 23:15:19  lpoulsen
// Fix problem with diagnose on slow links (wait for prompt before next
//     command). May not work with old ATC radios.
//
// Revision 1.36  2012/04/19 20:20:22  lpoulsen
// Move declaration of console_out_filename[] from util.c to connect.c
//
// Revision 1.35  2011/10/07 00:54:08  lpoulsen
// Add "diagnose" command.
// Handle automatic adding of the correct extension for a download file
//    in networks with mixed radio types.
//
// Revision 1.34  2011/08/29 22:29:27  lpoulsen
// Clean compilation on FC3 linux.
//
// Revision 1.33  2011/08/23 18:01:41  lpoulsen
// Better diagnostic when no device is given on a download command,
//    and the file name is interpreted as a device.
//
// Revision 1.32  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.31  2011/05/19 00:52:59  lpoulsen
// Clean out more warnings
//
// Revision 1.30  2011/05/19 00:32:04  lpoulsen
// When "connect dev=nosuch" cails to find the device, return to Econsole >
//   prompt instead of displaying device list.
//
// Revision 1.29  2011/05/06 22:15:14  lpoulsen
// Preparing for ECON 04.07 release
//
// Revision 1.28  2011/03/22 00:52:20  lpoulsen
// Assume download will succeed.
//
// Revision 1.27  2011/03/22 00:26:10  lpoulsen
// Resolve sequence errors on very weak links
// Resolve hangs when Ctrl-C between "dir" and "download"
// Resolve conflicting build date information
//
// Revision 1.26  2011/03/02 18:19:35  lpoulsen
// Allow Ctrl-C to close a session
//
// Revision 1.25  2011/02/22 00:02:23  lpoulsen
// Show device table after F4 disconnect
// Clear device table before new discovery
// Use device type = 0x8086 for PCs
//
// Revision 1.24  2011/02/04 22:29:06  lpoulsen
// Many little fixes.
//
// Revision 1.23  2011/01/22 01:29:05  lpoulsen
// Release candidate:
// - allow "show first=1 max=45" to support large systems
// - allow "sort by=distance" to support "connect all reboot"
// - improve download efficiency
//
// Revision 1.22  2011/01/11 00:51:13  lpoulsen
// When operating in batch mode,
// - print progress after each session (2 of 5)
// - save session result code to device table to be printed with "show"
//
// Revision 1.21  2010/12/23 02:25:41  lpoulsen
// Add "loss" command for testing retransmissions
//
// Revision 1.20  2010/12/22 17:36:33  lpoulsen
// Select/Deselect list of ranges
// Expand to up to 511 radios
// Protocol fixes
//
// Revision 1.19  2008/08/21 21:52:46  lpoulsen
// Preparing ECON 04.02
//
// Revision 1.18  2008/08/21 18:23:41  lpoulsen
// Works with scripts on v4 embedded protocols
//
// Revision 1.17  2008/08/19 00:38:29  lpoulsen
// Rewritten core protocol module
//
// Revision 1.16  2008/08/08 00:40:22  lpoulsen
// Fix hand when ReadThread() gets impatient during file transfer.
//
// Revision 1.15  2008/08/07 01:54:20  lpoulsen
// Fix hang in script processing (unitialized event flag)
//
// Revision 1.14  2008/08/06 22:48:16  lpoulsen
// "Nuno mode" - F4 prompts for reselect instead of Econsole>
//
// Revision 1.13  2008/08/05 23:58:37  lpoulsen
// User interface updates.
//
// Revision 1.12  2008/07/30 07:11:42  lpoulsen
// New option "Connect keyboard=yes"
// Fixes for timeout probelm at end of file.
//
// Revision 1.11  2008/07/28 22:54:26  lpoulsen
// Tight handshake for V4 scripting
//
// Revision 1.10  2008/07/26 02:00:48  lpoulsen
// Econ protocol version 4 - prompt bit
//
// Revision 1.9  2008/07/17 00:20:56  lpoulsen
// Clear errors on stdin at the end of econ session with device.
//
// Revision 1.8  2008/07/16 23:17:14  lpoulsen
// Resolve wrong error message in download command.
// Indicate in all "cannot open" messages which file it is.
//
// Revision 1.7  2008/07/16 00:23:53  lpoulsen
// Prepare for release CON 04.00
//
// Revision 1.6  2008/07/16 00:00:28  lpoulsen
// Add deselect command.
//
// Revision 1.5  2008/07/11 20:14:10  lpoulsen
// Resolve many compiler warnings on Linux
//
// Revision 1.4  2008/07/11 17:10:27  lpoulsen
// Resolve compiler warnings.
//
// Revision 1.3  2008/07/11 16:28:46  lpoulsen
// Econ 4.00 release candidate supports UPDATE and SELECT commands.
//
// Revision 1.2  2008/06/10 00:06:10  lpoulsen
// New -X option is now working
// Updated -I and -O implementation
//
// Revision 1.1  2008/05/31 00:35:09  lpoulsen
// Econ 4.x preview - now with command shell
//
//
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include "afartype.h"
#include "parser.h"
#include "cio.h"
#include "epacket.h"
#include "util.h"
#include "adapter.h"  // for adapter_receive();
#include "ucon.h"
#include "afarutil.h"

#ifdef LINUX
#include "linux_threads.h"
#include "win_emul.h"

pthread_cond_t v_buffer_space = PTHREAD_COND_INITIALIZER;
pthread_cond_t *buffer_space = &v_buffer_space;
pthread_cond_t v_available_data = PTHREAD_COND_INITIALIZER;
pthread_cond_t *available_data = &v_available_data;
pthread_cond_t v_server_flags_updated = PTHREAD_COND_INITIALIZER;
pthread_cond_t *server_flags_updated = &v_server_flags_updated;
pthread_cond_t v_never_set = PTHREAD_COND_INITIALIZER;
pthread_cond_t *never_set = &v_never_set;
#else
#include <windows.h>
// #include "windows_threads.h"

HANDLE buffer_space;
HANDLE available_data;
HANDLE server_flags_updated;
HANDLE never_set;
#endif

#define MAXPATH 80

char    console_in_filename[MAXPATH];
FILE   *console_in_file = NULL;
uint16  conn_output_mode = 0;

volatile uint32  packets_received = 0;

char    conn_output_buffer[32000];
char    console_out_filename[MAXPATH];

#define CONN_OUTPUT_SCREEN 1
#define CONN_OUTPUT_PRINT  2
#define CONN_OUTPUT_MEMORY 4

void  diagnose_session(int device_index, char *out);
void  download_session(int device_index, char *code, char *dest, char *out);
char *fmt_session_result(int sr);
void  new_script_loop(void);
void allow_time_for_response(uint32 min_ms);
void clear_option_O(void);

extern boolean has_extension(char *fname);
extern char    cvs_tag[];

// -----------------------------------------------------------------------

Arg connect_args[] = {
  { "device",       STRING,     0 },
  { "command-file", STRING,     0 },
  { "input-file",   PARAM_ALIAS, PARSER_NOHELP },
  { "output-file",  STRING,     0 },
  { "pause",        BOOL_VALUE, 0 },
  { NULL,          {UNDEF_ARG}, 0 }
};

cmd_ret_t
econ_connect(Argument_List *a, ucio_session *s)
{
    char *tmp;
    int   i = 0;
    int   j = 0;
    int   k = 0;
    boolean local_output_file = false;

    if (option_I) {
        free(option_I);
        option_I = NULL;
    }
    option_K = false;

    tmp = parser_get_value(a, "command-file");
    if (tmp)
    {
        console_in_file = fopen(tmp, "r");
        if (console_in_file == NULL) {
            fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "command file", tmp, errno);
            return CMD_BAD_ARG_VALUE;
        }
        fclose(console_in_file);
        console_in_file = NULL;

        option_I = malloc(strlen(tmp)+1);
        strcpy(option_I, tmp);
    }

    tmp = parser_get_value(a, "output-file");
    if (tmp) {
        if (option_O) free(option_O);
        option_O = malloc(strlen(tmp)+1);
        strcpy(option_O, tmp);
        local_output_file = true;
    }

    tmp = parser_get_value(a, "pause");
    if (tmp) {
        option_K = parse_boolean(tmp);
    }

    tmp = parser_get_value(a, "device");
    if (tmp) {
        int n = strlen(tmp);

        if (strcmpi(tmp, "all") == 0)
            goto connect_all;
        if (strcmpi(tmp, "selected") == 0)
            goto connect_selected;
        if ((n >= 3)
         && (strncmp(tmp, "selected", n) == 0))
            goto connect_selected;

        i = lookup_radio(tmp);
        if (i == 0) {
            fprintf(stderr, "Cannot identify device \"%s\"\n", tmp);
            return CMD_FAILED;
        }
    }

    if (i == 0) {
        if (numOfRadiosFound == 1) i = 1;
        else {
            fprintf(stderr, "No device selected.\n");

            show_radios(false, 0, 1, 45);
            i = select_radio();
            if (i == 0)
                return CMD_BAD_ARG_VALUE;
        }
    }
    run_a_session(i, option_I, option_O);
    printf("\nEnd of session - %s\n", fmt_session_result(session_result));
    if (local_output_file) clear_option_O();
    if (session_result == ES_F4_KEY)
        show_radios(false, 0, 1, 45);
    return CMD_SUCCESS;

connect_all:
    if (option_I == NULL) {
        fprintf(stderr,
          "To connect to multiple devices, a command file must be specified\n");
        return CMD_FAILED;
    }

    for (i = 1; i <= numOfRadiosFound; i++)
    {
        run_a_session(i, option_I, option_O);
        printf("\nEnd of session %d of %d - %s\n\n",
                i, numOfRadiosFound, fmt_session_result(session_result));
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;

connect_selected:
    if (option_I == NULL) {
        fprintf(stderr,
          "To connect to multiple devices, a command file must be specified\n");
        return CMD_FAILED;
    }

    k = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
            k++;

    j = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
    {
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
        {
            run_a_session(i, option_I, option_O);
            printf("\nEnd of session %d of %d - %s\n\n",
                   ++j, k, fmt_session_result(session_result));
        }
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;
}

// -----------------------------------------------------------------------

void
run_a_session(int device_index, char *input_file, char *output_file)
{

    if (debug_flags & DBFLG_THREADS)
        printf("run_a_session(%d=%s, %s)\n",
               device_index,
               afar_device[device_index].radio_name,
               input_file);

    if ( (afar_device[device_index].device_type == HW_TYPE_PC)
      || ((afar_device[device_index].device_type == 0)
       && (afar_device[device_index].proto_version >= 4 ))     )
    {
        session_result =
        afar_device[device_index].connect_result = ES_INVALID_DEVICE;
        return;
    }
    if (input_file) {
        strcpy(console_in_filename, input_file);

        console_in_file = fopen(console_in_filename, "r");

        if (console_in_file == NULL) {
            fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "input file", console_in_filename, errno);
            return;
        }
    }
    conn_output_mode = CONN_OUTPUT_SCREEN;

    if (output_file) {
        strcpy(console_out_filename, output_file);

        console_out_file = fopen(console_out_filename, "at");
        if (console_out_file == NULL) {
            fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "output logfile", console_out_filename, errno);
        } else {
            conn_output_mode |= CONN_OUTPUT_PRINT;
        }
    }

    if (console_in_file
     && (afar_device[device_index].proto_version < 4) )
    {
        fprintf(stderr,
                "[Warning: old ECON protocol - setting PAUSE=YES]\n");
        option_K = true;
    }

    afar_device[device_index].connect_result = 0;

    econ_trace_open(afar_device[device_index].radio_name);

    if (connect_to_server(device_index) == FALSE)
    {
        session_result = ES_CONNECT_FAILED;
        if (console_out_file) {
            fclose(console_out_file);
            console_out_file = NULL;
        }
        if (console_in_file) {
            fclose(console_in_file);
            console_in_file = NULL;
        }
        econ_trace_close();
        afar_device[device_index].connect_result = session_result;
        return;
    }
    epacket_clear_receive_stats();
    start_all_threads();
    wait_for_all_threads_to_terminate();
    active_device = -1;

    if (console_out_file) {
        fclose(console_out_file);
        console_out_file = NULL;
    }
    if (console_in_file) {
        fclose(console_in_file);
        console_in_file = NULL;
    }
    // flush the character queue
    econ_outq.get_ix = econ_outq.put_ix;
    clearerr(stdin);

    econ_trace_close();

    afar_device[device_index].connect_result = session_result;
    return;
}

char *
fmt_session_result(int sr)
{
    static char result[12];

    if (sr == ES_DOWNLOAD_GOOD)  return "Good Download";
    if (sr == ES_REMOTE_CLOSE)   return "Remote Close";
    if (sr == ES_GO_AWAY)        return "Session Rejected";
    if (sr == ES_INVALID_DEVICE) return "Invalid Device Type";
    if (sr == ES_CONNECT_FAILED) return "Connect Failed";
    if (sr == ES_SEND_FAILED)    return "Send Failed";
    if (sr == ES_FILESEND_FAILED) return "FileSend Failed";
    if (sr == ES_F3_KEY)         return "Closed with F3";
    if (sr == ES_F4_KEY)         return "Closed with F4";
    if (sr == ES_ESC_KEY)        return "Closed with ESC";
    if (sr == ES_READ_EOF)       return "End of File";
    if (sr == ES_READ_ERROR)     return "Error Reading File";
    if (sr == ES_NO_SPACE)       return "Not Enough Space";        
    if (sr == ES_DIR_FAILED)     return "Dir Command Failed";        
    if (sr == ES_CTRL_C)         return "Closed with Ctrl-C";        
    if (sr == ES_CMD_FAILED)     return "Command Failed on Server";        
    if (sr == 0)                 return "";
    sprintf(result, "%d", sr);
    return result;
}

// -----------------------------------------------------------------------

Arg diagnose_args[] = {
  { "device",        STRING,    0 },
  { "output-file",   STRING,    0 },
  { NULL,          {UNDEF_ARG}, 0 }
};


cmd_ret_t
econ_diagnose(Argument_List *a, ucio_session *s)
{
    char      *tmp;
    cmd_ret_t  retval = CMD_SUCCESS;
    int        i = 0;
    int        j = 0;
    int        k = 0;
    boolean    local_output_file = false;

    if (option_I) {
        free(option_I);
        option_I = NULL;
    }

    if (debug_flags & DBFLG_DOWNLOAD) {
        FILE *debug_log = afar_fopen_ext("debug_log", "wa", "txt");
        if (debug_log) {
            fprintf(debug_log,
                    "Debug log enabled\n");
            fclose(debug_log);
            printf("Debug log enabled for diagnose\n");
        } else {
            printf("Failed to open debug_log.txt\n");
        }
    }

    tmp = parser_get_value(a, "output-file");
    if (tmp) {
        if (option_O) {
            free(option_O);
            option_O = NULL;
        }
        option_O = malloc(strlen(tmp)+1);
        strcpy(option_O, tmp);
        local_output_file = true;
    }
    tmp = parser_get_value(a, "device");
    if (tmp) {
        int n = strlen(tmp);
        if (strcmpi(tmp, "all") == 0)
            goto diagnose_all;
        if (strcmpi(tmp, "selected") == 0)
            goto diagnose_selected;
        if ((n >= 3)
         && (strncmp(tmp, "selected", n) == 0))
            goto diagnose_selected;

        i = lookup_radio(tmp);
        if (i == 0) {
            fprintf(stderr, "Cannot identify device \"%s\"\n", tmp);
            return CMD_FAILED;
        }
    }
    if (i == 0) {
        printf("You must specify a device to diagnose.\n");
        return CMD_FAILED;
    }
    if (retval != CMD_SUCCESS)
        return retval;

    diagnose_session(i, option_O);

    afar_device[i].connect_result = session_result;
    printf("End of diagnose session - %s\n",
           fmt_session_result(session_result));
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;

diagnose_all:
    for (i = 1; i <= numOfRadiosFound; i++)
    {
        diagnose_session(i, option_O);
        afar_device[i].connect_result = session_result;
        printf("End of diagnose session %d of %d - %s\n\n",
               i, numOfRadiosFound,
               fmt_session_result(session_result));
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;

diagnose_selected:
    k = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
            k++;

    j = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
    {
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
        {
            diagnose_session(i, option_O);
            afar_device[i].connect_result = session_result;
            printf("End of diagnose session %d of %d - %s\n\n",
                   ++j, k,
                   fmt_session_result(session_result));
        }
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;
}

void
diagnose_session(int device_index, char *outfile)
{
    conn_output_mode = CONN_OUTPUT_SCREEN;

    if ((afar_device[device_index].device_type == 0)
     && (afar_device[device_index].proto_version >= 4 ))
    {
        session_result =
        afar_device[device_index].connect_result = ES_INVALID_DEVICE;
        return;
    }

    if (outfile && strlen(outfile)) {
        strcpy(console_out_filename, outfile);

        console_out_file = fopen(console_out_filename, "at");
        if (console_out_file == NULL) {
            fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "output logfile", console_out_filename, errno);
        } else {
            conn_output_mode |= CONN_OUTPUT_PRINT;
        }
    }
    if (conn_output_mode & CONN_OUTPUT_PRINT) {
        fprintf(console_out_file,"\n%s %s connecting to %s %s\n\n",
                "----------",
                (strlen(cvs_tag) > 9) ? &cvs_tag[7] :"Econ",
                afar_device[device_index].radio_name,
                "----------");
    }

    if (connect_to_server(device_index) == FALSE) {
        printf("Connection failed\n");
        fclose(console_out_file);
        session_result = ES_CONNECT_FAILED;
        return;
    }
    epacket_clear_receive_stats();
    session_result = ES_DOWNLOAD_GOOD;

    start_all_threads();

    add_to_char_q(&econ_outq, (void *)"display-configuration\r", 22);
    allow_time_for_response(1000);

    add_to_char_q(&econ_outq, (void *)"show\r", 5);
    allow_time_for_response(1000);

    add_to_char_q(&econ_outq, (void *)"ethernet\r", 9);
    allow_time_for_response(1000);

    if (afar_device[device_index].device_type < 100) {
        // Gateway device
        add_to_char_q(&econ_outq, (void *)"wan\r", 4);
        allow_time_for_response(1000);
    } else {
        // Radio
        add_to_char_q(&econ_outq, (void *)"show radio\r", 11);
        allow_time_for_response(1000);
    }

    add_to_char_q(&econ_outq, (void *)"display-log all\r", 16);
    allow_time_for_response(1000);

    add_to_char_q(&econ_outq, (void *)"show ethernet\r", 14);
    allow_time_for_response(1000);

    add_to_char_q(&econ_outq, (void *)"show ip\r", 8);
    allow_time_for_response(1000);

    add_to_char_q(&econ_outq, (void *)"logout\r", 7);
    allow_time_for_response(1000);

    econ_phase = ECON_CLOSE_THREADS;
    wait_for_all_threads_to_terminate();
    active_device = -1;
    printf("\n");

    // flush the character queue
    econ_outq.get_ix = econ_outq.put_ix;

    if (console_out_file) {
        fprintf(console_out_file, "\n");
        fclose(console_out_file);
    }
    return;
}

// -----------------------------------------------------------------------

Arg download_args[] = {
  { "device",        STRING,    0 },
  { "source",        STRING,    0 },
  { "destination",   STRING,    0 },
  { "output-file",   STRING,    0 },
  { NULL,          {UNDEF_ARG}, 0 }
};

void download_session(int device_index, char *code, char *dest, char *out);

cmd_ret_t
econ_download(Argument_List *a, ucio_session *s)
{
    char      *tmp;
    char      *code_name = NULL, *dest = NULL, *out_file = NULL;
    FILE      *firmware_file;
    cmd_ret_t  retval = CMD_SUCCESS;
    int        i = 0;
    int        j = 0;
    int        k = 0;
    boolean    local_output_file = false;

    if (option_I) {
        free(option_I);
        option_I = NULL;
    }

    if (debug_flags & DBFLG_DOWNLOAD) {
        FILE *debug_log = afar_fopen_ext("debug_log", "wa", "txt");
        if (debug_log) {
            fprintf(debug_log,
                    "Debug log enabled\n");
            fclose(debug_log);
            printf("Debug log enabled for download\n");
        } else {
            printf("Failed to open debug_log.txt\n");
        }
    }
    code_name = parser_get_value(a, "source");
    if (!code_name) {
        printf("You *MUST* specify a source (firmware) file\n");
        retval = CMD_BAD_ARG_NAME;
    } else {
        FILE *debug_log = afar_fopen_ext("debug_log", "wa", "txt");
        // Try to validate the firmware filename
        if (has_extension(code_name)) {
            // They gave an extension, so it had better match
            firmware_file = fopen(code_name, "r");
            if (debug_flags & DBFLG_DOWNLOAD) {
                fprintf(debug_log,
                        "Trying to open firmware file %s with explicit ext\n", 
                        code_name);
            }
            if (firmware_file != NULL) {
                fclose(firmware_file);
                if (debug_flags & DBFLG_DOWNLOAD) {
                    fprintf(debug_log, "Succeeded\n");
                }
            } else {
                fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "firmware file", code_name, errno);
                if (debug_flags & DBFLG_DOWNLOAD) {
                    fprintf(debug_log, "Failed - errno=%d\n", errno);
                    printf("Failed to open explicitly named %s\n", code_name);
                }
                fclose(debug_log);
                retval = CMD_BAD_ARG_VALUE;
                return retval;
            }
        } else {
            // We had better have a match for at least one of the
            //    3 possible extensions
            uint16  ext_matches = 0;
            char   *code_ext[] = { ".bin", ".bz", ".bze", NULL };
            if (debug_flags & DBFLG_DOWNLOAD) {
                fprintf(debug_log,
                        "Trying to open firmware file %s with implicit ext\n", 
                        code_name);
            }
            for (i = 0; i < 3; i++) {
                char ext_code_name[80];
                strncpy(ext_code_name, code_name, 72);
                strcat(ext_code_name, code_ext[i]);
                firmware_file = fopen(ext_code_name, "r");
                if (firmware_file) {
                    ext_matches |= 1 << i;
                    fclose(firmware_file);
                }
            }
            if (ext_matches == 0) {
                if (debug_flags & DBFLG_DOWNLOAD) {
                    fprintf(debug_log,
                        "Failed - none of the extensions match!\n");
                    printf("Failed to open code file %s with implicit extension\n", code_name);
                }
                fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "firmware file", code_name, errno);
                fclose(debug_log);
                retval = CMD_BAD_ARG_VALUE;
                return retval;
            }
        }
        fclose(debug_log);
    }

    dest     = parser_get_value(a, "destination");

    tmp = parser_get_value(a, "output-file");
    if (tmp) {
        if (option_O) {
            free(option_O);
            option_O = NULL;
        }
        out_file = malloc(strlen(tmp)+1);
        strcpy(out_file, tmp);
        local_output_file = true;
    }
    tmp = parser_get_value(a, "device");
    if (tmp) {
        int n = strlen(tmp);
        if (strcmpi(tmp, "all") == 0)
            goto update_all;
        if (strcmpi(tmp, "selected") == 0)
            goto update_selected;
        if ((n >= 3)
         && (strncmp(tmp, "selected", n) == 0))
            goto update_selected;

        i = lookup_radio(tmp);
        if (i == 0) {
            fprintf(stderr, "Cannot identify device \"%s\"\n", tmp);
            return CMD_FAILED;
        }
    }
    if (i == 0) {
        printf("You must specify a device to download to.\n");
        return CMD_FAILED;
    }
    if (retval != CMD_SUCCESS)
        return retval;

    download_session(i, code_name, dest, out_file);
    if (out_file)
        free(out_file);
    afar_device[i].connect_result = session_result;
    printf("End of download session - %s\n",
           fmt_session_result(session_result));
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;

update_all:
    for (i = 1; i <= numOfRadiosFound; i++)
    {
        download_session(i, code_name, dest, out_file);
        afar_device[i].connect_result = session_result;
        printf("End of download session %d of %d - %s\n\n",
               i, numOfRadiosFound,
               fmt_session_result(session_result));
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;

update_selected:
    k = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
            k++;

    j = 0;
    for (i = 1; i <= numOfRadiosFound; i++)
    {
        if (afar_device[i].device_flags & DEVFLAG_SELECTED)
        {
            download_session(i, code_name, dest, out_file);
            afar_device[i].connect_result = session_result;
            printf("End of download session %d of %d - %s\n\n",
                   ++j, k,
                   fmt_session_result(session_result));
        }
    }
    if (local_output_file) clear_option_O();
    return CMD_SUCCESS;
}

void
download_session(int device_index, char *code_name, char *dest, char *outfile)
{
    char *p1;
    int   freespace;
    char  downcmd[80];

    conn_output_mode = CONN_OUTPUT_MEMORY | CONN_OUTPUT_SCREEN;

    if ((afar_device[device_index].device_type == 0)
     && (afar_device[device_index].proto_version >= 4 ))
    {
        session_result =
        afar_device[device_index].connect_result = ES_INVALID_DEVICE;
        return;
    }

    if (outfile && strlen(outfile)) {
        strcpy(console_out_filename, outfile);

        console_out_file = fopen(console_out_filename, "at");
        if (console_out_file == NULL) {
            fprintf(stderr, "--- cannot open %s %s - errno=%d\n",
                            "output logfile", console_out_filename, errno);
        } else {
            conn_output_mode |= CONN_OUTPUT_PRINT;
        }
    }

    memset(conn_output_buffer, 0, sizeof(conn_output_buffer));

    if (connect_to_server(device_index) == FALSE) {
        printf("Connection failed\n");
        fclose(console_out_file);
        session_result = ES_CONNECT_FAILED;
        return;
    }
    epacket_clear_receive_stats();
    session_result = ES_DOWNLOAD_GOOD;

    start_all_threads();
    add_to_char_q(&econ_outq, (void *)"dir\r", 4);
    Sleep(1000);
    if (!strstr(conn_output_buffer, " KB available - ")) {
        goto dir_failed;
    }
    p1 = strstr(conn_output_buffer, "KB) - ");
    if (!p1) {
        goto dir_failed;
    }
    freespace = atoi(p1+6);
    if (freespace < 250) {
        printf("Insufficient free space\n");
        session_result = ES_NO_SPACE;
        goto shutdown;
    }
    sprintf(downcmd, "download %s %s\r", code_name, (dest ? dest : ""));
    add_to_char_q(&econ_outq, (void *)downcmd, strlen(downcmd));
    while ((active_session.state != SESSION_TRANSFER_MODE)
		&& (active_session.state != SESSION_CLOSED))
        Sleep(1);
	if (active_session.state == SESSION_CLOSED)
		goto xfer_aborted;
    while (active_session.state == SESSION_TRANSFER_MODE)
        Sleep(100);
    printf("Transfer completed\n");
    econ_phase = ECON_CLOSE_THREADS;
    wait_for_all_threads_to_terminate();
    active_device = -1;
    if (console_out_file)
		fclose(console_out_file);

    // flush the character queue
    econ_outq.get_ix = econ_outq.put_ix;

    return;

dir_failed:
	printf("download: dir command failed\n");
    session_result = ES_DIR_FAILED;
    if (debug_flags & DBFLG_DOWNLOAD) {
        FILE *debug_log = afar_fopen_ext("debug_log", "wa", "txt");
        if (debug_log) {
            fprintf(debug_log,
                    "-- %s %s - Directory command failed on radio [%d] %s --\n",
                    afar_date(0), afar_time(0), 
                    device_index, afar_device[device_index].radio_name);
            fprintf(debug_log,
                    "  Response buffer: %d bytes\n%s\n",
                    strlen(conn_output_buffer), conn_output_buffer);
            fclose(debug_log);
        } else {
            printf("Failed to open debug_log.txt\n");
        }
    }
xfer_aborted:
	printf("download aborted\n");
shutdown:
    econ_phase = ECON_CLOSE_THREADS;
    wait_for_all_threads_to_terminate();
    active_device = -1;
    printf("\n");

    // flush the character queue
    econ_outq.get_ix = econ_outq.put_ix;

    if (console_out_file) {
        fclose(console_out_file);
        console_out_file = NULL;
    }
    return;
}

void
terminate_session(int dummy, int result_code)
{
    // printf("terminate_session(%d) - %s\n", dummy,
    //        fmt_mac_addr(active_session.destination_address));
    if ((debug_flags & DBFLG_THREADS)
     || (debug_flags & DBFLG_ECON_PKTS))
        printf("\nterminate_session %d (state %d)\n",
                     result_code, active_session.state);

    if (result_code != ES_REMOTE_CLOSE)
    {
        // basically send a CLOSE_REQUEST message,
        active_session.PDU = CLOSE_REQUEST;
        ether_send(&active_session, NULL, 0);
    }
    econ_phase = ECON_CLOSING_SESSION;

    // now do some clean-up / garbage collection
    clean_session(dummy);
    if (result_code)
        session_result = result_code;
    terminate_all_threads(0);
    active_device = -1;
}

int
isLocalCommand(const char *line)
{
    long tdly = 0;
    char dtime[32];
    char *p;

    if (line == NULL)
        return 0;

    if (line[0] == '#')
        return 1;               // it's a comment - ignore it

    if (line[0] != '.')
        return 0;

    // it's a local command - extract it first
    p = strstr(line, "delay");
    if (p)
    {
        char dnum[32];
        size_t i;
        int j;

        strncpy(dtime, p + 5, 32);
        for (i = 0; i < strlen(dtime); i++)
            if (dtime[i] != ' ')
                break;

        j = 0;
        while ((dtime[i] != ' ') && (dtime[i] != 10) && (dtime[i] != 13) && (dtime[i] != 0))
        {
            dnum[j] = dtime[i];
            i++;
            j++;
        }
        dnum[j] = 0;

        for (i = 0; i < strlen(dnum); i++)
            if ((!isDigit(dnum[i])) && (dnum[i] != '.'))
                return 0;

        if (atof(dnum))
        {
            tdly = atoi(dnum) * 1000;
            printf("delaying for %d secs...\n", atoi(dnum));
            Sleep(tdly);
        }
        return 1;
    }
    p = strstr(line, "exit");
    if (p)
    {
        // terminate gracefully any active session and exit
        return 1;
    }
    return 0;
}

void
process_econ_output(uint8 *data, int nbytes)
{
    int i;

    if (conn_output_mode & CONN_OUTPUT_SCREEN)
        for (i = 0; i < nbytes; i++)
            e_print(data[i]);
	data[nbytes] = 0;
    if (conn_output_mode & CONN_OUTPUT_PRINT)
        fprintf(console_out_file, "%s", data);
    if (conn_output_mode & CONN_OUTPUT_MEMORY)
    {
        if (strlen(conn_output_buffer) + nbytes > sizeof(conn_output_buffer))
        {
            fprintf(stderr, "Console Output Memory Buffer Overflow!!\n");
            return;
        }
        strcpy(conn_output_buffer + strlen(conn_output_buffer), (void *)data);
    }
}

// -----------------------------------------------------------------------

// SendThread checks the output (character) buffer,
//     and if not empty sends the data to the network
ThreadReturnType
SendThread(void *ec_s)
{
    struct ether_session *econ_session = ec_s;
    int bytes_sent;

    self_init_thread();

    if (debug_flags & DBFLG_THREADS)
        printf("LJP01 starting SendThread()\n");


    while (TRUE)
    {
        bytes_sent = 0;
      wrap:
        if (econ_outq.get_ix > econ_outq.put_ix)
        {
            // data wraps past end-of-buffer
            int nbytes = sizeof(econ_outq.storage) - econ_outq.get_ix;

            ether_send(econ_session,
                       (void *)&econ_outq.storage[econ_outq.get_ix], nbytes);
            econ_outq.get_ix = 0;
            bytes_sent += nbytes;
        }
        if (econ_outq.get_ix != econ_outq.put_ix)
        {
            int nbytes = econ_outq.put_ix - econ_outq.get_ix;

            if (nbytes < 0)
                goto wrap;      // It wrapped since we tested
            // data lies from get_ix up to put_ix

            ether_send(econ_session,
                       (void *)&econ_outq.storage[econ_outq.get_ix], nbytes);
            econ_outq.get_ix += nbytes;
            bytes_sent += nbytes;
        }
        SetEvent(buffer_space);
        if (bytes_sent < 0)
        {
            terminate(-1, SEND_THREAD_ERROR);
            break;
        }
        if (econ_outq.get_ix == econ_outq.put_ix)
            WaitForSingleObject(available_data, 100L);
        if ((econ_phase == ECON_CLOSE_THREADS)
         || (econ_phase == ECON_CLOSING_SESSION))
            break;
    }
    self_terminate_thread();
    return 0;
}

// ReadThread reads characters from an input source
//     and puts them into the output buffer
ThreadReturnType
ReadThread(void *ec_s)
{
    int    bytes_read;
    BYTE   data[MAX_CHAR_PACKET];

    self_init_thread();
    initCIOSettings();

    if (console_in_file
     && (active_session.proto_version >= 4) )
    {
        new_script_loop();
        if (!option_K) {
            session_result = ES_READ_EOF;
            goto end_proc;
        }
    }
    if (econ_phase == ECON_CLOSE_THREADS)
        goto end_proc;

    while (TRUE)
    {
        if (console_in_file)
        {
            if (feof(console_in_file))
            {
                bytes_read = 0;
                fclose(console_in_file);
                console_in_file = NULL;
                continue;
            }
            else
            {
                Sleep(1000);

                data[0] = 0;
                if (NULL == fgets((void *)data, sizeof(data) - 1,
                                  console_in_file))
                    bytes_read = 0;
                else {
                    bytes_read = strlen((void *)data);
                    if (bytes_read > 0)
                        active_session.server_flags &= ~ECFLAG_WAITING;
                    while ((bytes_read > 0) && (data[bytes_read - 1] < ' '))
                    {
                        data[--bytes_read] = 0;
                    }
                    data[bytes_read++] = '\r';
#if 0
                    if (debug_level)
                        printf("[read %d bytes from file: %s]",
                               bytes_read, data);
#endif
                }
                if (isLocalCommand((void *)data))
                    continue;
            }
        }
        else
        {
            bytes_read = read_from_keyboard(data, MAX_CHAR_PACKET);
        }

        if (econ_phase == ECON_CLOSE_THREADS)
            break;
        if (bytes_read > 0)
        {
            if (active_session.state == SESSION_TRANSFER_MODE)
                continue;
            add_to_char_q(&econ_outq, data, bytes_read);
        }
        else
        {
            if (bytes_read == 0)
            {
                while (econ_outq.get_ix != econ_outq.put_ix)
                {
                    Sleep(100);
                    if (econ_phase == ECON_CLOSE_THREADS)
                        goto end_proc;
                }
                while (time(NULL) < (active_session.datestamp + 5))
                {
                    Sleep(100);
                    if (econ_phase == ECON_CLOSE_THREADS)
                        goto end_proc;
                }
                if (active_session.state == SESSION_TRANSFER_MODE)
                    continue;
                if (econ_outq.get_ix != econ_outq.put_ix)
                    continue;
                if (console_in_file && !feof(console_in_file))
                    continue;
                terminate_session(-1, ES_READ_EOF);
                break;          // EOF
            }
            else
            {
                terminate_session(-1, ES_READ_ERROR);
                break;
            }
        }
    }
end_proc:
    terminate_session(-1, 0);
    restoreCIOSettings();

    self_terminate_thread();
    return 0;
}

void
new_script_loop(void)
{
    char   *cp;
    BYTE    data[MAX_CHAR_PACKET];
    size_t  bytes_read;

    while (TRUE)
    {
        if (console_in_file
         && feof(console_in_file)
         && (econ_outq.get_ix == econ_outq.put_ix))
        {
            bytes_read = 0;
            if (option_K) {
                fclose(console_in_file);
                console_in_file = NULL;
            }
            break;
        }
        while (!(active_session.server_flags & ECFLAG_WAITING))
        {
            int idsw;

            if (econ_phase == ECON_CLOSE_THREADS)
                return;

            idsw = WaitForSingleObject(server_flags_updated, 1000L);
            if (idsw == WAIT_FAILED)
            {
                printf("Bad return from syscall\n");
                printf("server_flags_updated (%08x) %08x returned %08x\n",
                       (uint32)&server_flags_updated,
                       (uint32)server_flags_updated, (uint32)idsw);
                terminate_session(-1, ES_READ_ERROR);
                break;
            }
            if (idsw == WAIT_TIMEOUT)
            {
                // send an empty data packet to elicit an EMPTY (Ack)
                if (active_session.state != SESSION_TRANSFER_MODE)
                {
                    active_session.PDU = CONSOLE_COMMAND;
                    ether_send(&active_session, NULL, 0);
                }
                // else {
                //   active_session.PDU = FILE_DATA;
                // ... }
            }
        }
        data[0] = 0;
        cp = fgets((void *)data, sizeof(data) - 1, console_in_file);

        if (cp == NULL)
            bytes_read = 0;
        else {
            bytes_read = strlen((void *)data);
            if (bytes_read > 0) {
                active_session.server_flags &= ~ECFLAG_WAITING;
                while ((bytes_read > 0) && (data[bytes_read - 1] < ' '))
                {
                    data[--bytes_read] = 0;
                }
                data[bytes_read++] = '\r';
                data[bytes_read] = 0;
            }
#if 0
            if (debug_level)
                printf("[read %d bytes from file: %s\n]\n",
                       bytes_read, data);
#endif
        }
        if (isLocalCommand((void *)data))
            continue;

        if (econ_phase == ECON_CLOSE_THREADS)
            return;
        if (bytes_read > 0)
        {
            if (active_session.state == SESSION_TRANSFER_MODE)
                continue;
            add_to_char_q(&econ_outq, data, bytes_read);
        }
        else
        {
            if (bytes_read == 0)
            {
                while (econ_outq.get_ix != econ_outq.put_ix)
                {
                    Sleep(100);
                    if (econ_phase == ECON_CLOSE_THREADS)
                        return;
                }
                while (time(NULL) < (active_session.datestamp + 5))
                {
                    Sleep(100);
                    if (econ_phase == ECON_CLOSE_THREADS)
                        return;
                }
                if (active_session.state == SESSION_TRANSFER_MODE)
                    continue;
                if (console_in_file
                 && !feof(console_in_file))
                    continue;
                break;          // EOF
            }
            else
            {
                terminate_session(-1, ES_READ_ERROR);
                break;
            }
        }
    }
    // We got EOF on the input file
    return;
}

// ReceiveThread receives packets from the network,
//     and passes them into process_packet
ThreadReturnType
ReceiveThread(void *ec_s)
{
    u_char *rcvd_pkt;
    int ret_code;

    self_init_thread();

    rcvd_pkt = malloc(MAX_PACKET_SIZE);

    while (TRUE)
    {
        if (econ_phase == ECON_CLOSE_THREADS)
            break;
        ret_code = adapter_receive(rcvd_pkt);
        if (ret_code < 0)
        {
            // probably an error occured
            terminate(-1, RECEIVE_THREAD_ERROR);
            break;
        }
        else
        {
            econ_packet *econ_pkt;
            uint16       proto;

            if (loss_pct_recv
             && (rand() < ((RAND_MAX * loss_pct_recv) / 100.00)))
                ret_code = 0;

            // the ret_code is actually the packet size
            if (ret_code < MIN_PACKET_SIZE)
                continue;      // don't bother - it can't be an econsole packet

            econ_pkt = (void *)rcvd_pkt;
            proto    = ntohs(econ_pkt->econ_ehdr.epkt_proto);
            if (proto > 1500) {
                    // Not LLC encapsulated - probably IP or ARP
                    continue;
            }
            // Is this the copy of one we sent ?
            if (0 == memcmp(&my_mac_address, &econ_pkt->econ_ehdr.epkt_src, 6))
                continue;
            econ_trace_packet(econ_pkt, ret_code);
            process_packet(econ_pkt, ret_code);
        }
    }

    self_terminate_thread();
    return 0;
}


ThreadReturnType
FileThread(void *ec_s)
{
    struct ether_session *cur_session = ec_s;

    self_init_thread();

    while ((cur_session->state != SESSION_CLOSED)
       &&  (econ_phase != ECON_CLOSE_THREADS))
    {
        if (doFileTransfer(cur_session))
            break;
    }

    self_terminate_thread();
    return 0;
}

// ** ADD/REMOVE FROM INPUT BUFFER ...................................
// Called from ReadThread() in ucon.c
// bytes_read may be up to MAX_CHAR_PACKET (256) bytes
// We used to have a linear buffer; this required protection
// by a mutex. By making it a ring buffer, we avoid this.

int
add_to_char_q(ringbuf_256 * char_q, uchar * data, int bytes_read)
{
    int i;

    // printf("ready to add %c\n",data[0]);

    for (i = 0; i < bytes_read; i++)
    {
        char_q->storage[char_q->put_ix] = data[i];
        char_q->put_ix++;
        char_q->put_ix = char_q->put_ix % 256;
        if (char_q->put_ix == char_q->get_ix)
            WaitForSingleObject(buffer_space, INFINITE);
    }
    SetEvent(available_data);
    return bytes_read;
}

// get_from_char_q() is not actually called. In order to avoid 
//    buffer copying, an optimized equivalent is coded directly
//    in SendThread() above.
void
get_from_char_q(ringbuf_256 * char_q, uchar * data, int *bytes_sent)
{
    int i;
    int sent = 0;
  wrap:
    if (char_q->get_ix > char_q->put_ix)
    {
        // data wraps past end-of-buffer
        int nbytes = sizeof(char_q->storage) - char_q->get_ix;
        for (i = 0; i < nbytes; i++)
            data[i] = char_q->storage[char_q->get_ix + i];
        sent += nbytes;
        char_q->get_ix = 0;
    }
    if (char_q->get_ix != char_q->put_ix)
    {
        int nbytes = char_q->put_ix - char_q->get_ix;

        if (nbytes < 0)
            goto wrap;          // It wrapped since we tested
        // data lies from get_ix up to put_ix

        for (i = 0; i < nbytes; i++)
            data[sent + i] = char_q->storage[char_q->get_ix + i];
        sent += nbytes;
        char_q->get_ix += nbytes;
    }
    *bytes_sent = sent;
    SetEvent(buffer_space);
    return;
}

void
allow_time_for_response(uint32 min_ms)
{
    uint32                 pkts_seen_last = 0;
    extern volatile uint32 packets_received;

    pkts_seen_last = packets_received;
    Sleep(min_ms);

    while (pkts_seen_last != packets_received) {
        pkts_seen_last = packets_received;
        Sleep(min_ms);
    }
    while ( (active_session.state == SESSION_COMMAND_MODE)
        &&  (active_session.proto_version >= 4)
        && !(active_session.server_flags & ECFLAG_WAITING) )
    {
        Sleep(min_ms);
    }
}

void
clear_option_O(void)
{
    if (option_O) {
        printf("Clearing output file (was %s)\n", option_O);
        free(option_O);
        option_O = NULL;
    }
}
