// $Id: epacket.c,v 1.69 2016/06/07 21:16:08 lpoulsen Exp $
// Copyright (C) 2001 Wi_LAN Technologies, Inc
//
// $Log: epacket.c,v $
// Revision 1.69  2016/06/07 21:16:08  lpoulsen
// Resolve problem in Linux ECON causing a hard hanging process when you
//     do a multi-radio connect/download sequence. The resolution was to
//     use a local mutex instead of a global mutex to bracket the
//     pthread_cond_timedwait() in WaitForSingleObject()
// Also, use DBFLG_ECON_PKTS to turn on debugging output of ECON packets
//     instead of debug_level
//
// Revision 1.68  2015/10/28 21:04:58  lpoulsen
// Print out protocol error messages only when debug output is turned on
//
// Revision 1.67  2014/05/21 20:05:57  lpoulsen
// uint32 is not long anymore, so remove long qualifiers in printf formats
//
// Revision 1.66  2013/04/19 17:14:16  lpoulsen
// Fix problem with checking if file has an extension.
//
// Revision 1.65  2012/05/08 18:24:00  lpoulsen
// Compile all modules with -pthread on Linux
// Clean up all compiler warnings
// Count packets in OUT OF MEMORY message
//
// Revision 1.64  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.63  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.62  2011/05/19 00:39:42  lpoulsen
// Tighten types to avoid warning
//
// Revision 1.61  2011/05/17 23:01:34  lpoulsen
// Be sure to wait for ACK after we send data (by clearing the Receive_ACK
//    event before transmitting, so that we know if it is set we received
//    an ack after we sent this data packet).
//
// Revision 1.60  2011/05/06 22:15:14  lpoulsen
// Preparing for ECON 04.07 release
//
// Revision 1.59  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.58  2011/02/22 01:31:43  lpoulsen
// Do proper byteswaps on HW_TYPE_PC
//
// Revision 1.57  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.56  2011/02/08 22:07:02  lpoulsen
// evived the LINUX build of ECON - now ECON-PLUS
//
// Revision 1.55  2011/02/04 22:29:07  lpoulsen
// Many little fixes.
//
// Revision 1.54  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.53  2010/12/28 20:07:11  lpoulsen
// Fix several problems in download command
//
// Revision 1.52  2010/12/23 02:25:41  lpoulsen
// Add "loss" command for testing retransmissions
//
// Revision 1.51  2010/12/22 17:36:33  lpoulsen
// Select/Deselect list of ranges
// Expand to up to 511 radios
// Protocol fixes
//
// Revision 1.50  2008/09/04 00:36:42  lpoulsen
// Fix more session bugs (make download work)
//
// Revision 1.49  2008/08/21 21:52:46  lpoulsen
// Preparing ECON 04.02
//
// Revision 1.48  2008/08/21 18:23:41  lpoulsen
// Works with scripts on v4 embedded protocols
//
// Revision 1.47  2008/08/19 00:38:29  lpoulsen
// Rewritten core protocol module
//
// Revision 1.46  2008/08/08 00:40:22  lpoulsen
// Fix hand when ReadThread() gets impatient during file transfer.
//
// Revision 1.45  2008/08/05 23:58:37  lpoulsen
// User interface updates.
//
// Revision 1.44  2008/07/30 07:11:42  lpoulsen
// New option "Connect keyboard=yes"
// Fixes for timeout probelm at end of file.
//
// Revision 1.43  2008/07/28 22:54:26  lpoulsen
// Tight handshake for V4 scripting
//
// Revision 1.42  2008/07/26 02:00:48  lpoulsen
// Econ protocol version 4 - prompt bit
//
// Revision 1.41  2008/07/11 20:14:10  lpoulsen
// Resolve many compiler warnings on Linux
//
// Revision 1.40  2008/07/11 16:28:46  lpoulsen
// Econ 4.00 release candidate supports UPDATE and SELECT commands.
//
// Revision 1.39  2008/06/10 00:06:10  lpoulsen
// New -X option is now working
// Updated -I and -O implementation
//
// Revision 1.38  2008/05/31 00:35:09  lpoulsen
// Econ 4.x preview - now with command shell
//
// Revision 1.37  2008/05/10 01:17:37  lpoulsen
// Making econ -x filename work (not yet complete)
//
// Revision 1.36  2006/10/05 17:45:22  lpoulsen
// Increase buffer size for XP GetAdapterInfo call
//
// Revision 1.35  2006/06/06 02:03:29  lpoulsen
// First attempt at compiling on MAC OS X
//
// Revision 1.34  2005/06/23 00:13:30  lpoulsen
// Reduce dependency on pthread.h implementation specifics.
//
// Revision 1.33  2005/05/11 17:04:33  lpoulsen
// Solve problems on WinXP by moving to WinPCap 3.1 beta 4
// Use IP helper API to get MAC addresses (no longer works on Win95)
// Change references to "radios" to say "devices"
//
// Revision 1.32  2004/07/14 00:34:46  lpoulsen
// Updated for latest Linux version and merged with 02.07
//
// Revision 1.31  2004/07/13 18:37:22  lpoulsen
// Fix bug where F3 or F4 did not send CLOSE_REQUEST
//
// Revision 1.30  2004/02/04 20:56:54  lpoulsen
// Send IDENTIFY_RESPONSE as unicast, not broadcast
//
// Revision 1.29  2004/01/16 02:10:37  lpoulsen
// Use malloc instead of static for stored packet
//
// Revision 1.28  2003/12/23 21:38:05  lpoulsen
// Display firmware version if available
//
// Revision 1.27  2003/12/23 04:35:42  lpoulsen
// EConsole protocol version 2 support.
// Respond to IDENTIFY_REQUEST (version 1 and version 2 format) with
//      version 2 format IDENTIFY_RESPONSE.
//
// Revision 1.26  2003/12/17 18:19:08  lpoulsen
// Respond to IDENTIFY_REQUEST (to support "show econ" on radio/mux)
//
// Revision 1.25  2003/08/25 20:43:02  lpoulsen
// Restore FileThread exit cleanup which fell out in merge.
//
// Revision 1.24  2003/08/25 20:21:44  lpoulsen
// Replace get_next_packet() by simple call to adapter_receive().
// Clean up some formatting.
//
// Revision 1.23  2003/08/22 01:46:32  lpoulsen
// Make econ compile on Linux again.
//
// Revision 1.22  2003/08/21 23:46:51  lpoulsen
// Improved handling of -r option to allow selection by name
// Miscellaneous minor cleanup
//
// Revision 1.21  2003/08/19 03:08:25  lpoulsen
// Remove many compiler warnings.
//
// Revision 1.20  2003/03/11 01:15:30  lpoulsen
// Fix compile warning.
//
// Revision 1.19  2003/03/11 01:14:13  lpoulsen
// Correct compile errors found in Calgary.
//
// Revision 1.18  2002/10/16 16:59:47  mourlouk
// Improved way of retrieving MAC addresses - VAL support
//
// Revision 1.15  2002/05/20 19:14:11  lpoulsen
// Improve readability.
//
// Revision 1.14  2002/05/16 17:14:58  mourlouk
// 1.19 Release candidate
//
// Revision 1.13  2002/05/14 23:44:01  mourlouk
// more minor changes ...
//
// Revision 1.12  2002/04/23 21:51:30  mourlouk
// Another bug fixed: We discarding invalid CLOSE REQUEST messages,
//     that were resulting in "Connection with other host terminated",
// and also changed code in alreadyReceivedMessage
//
// Revision 1.11  2002/04/19 19:04:44  mourlouk
// Release 1.15 - It fixes a bug that was introduced in version 1.13
//  (econ unable to read properly multiple MAC addresses)
//
// Revision 1.9  2001/11/13 01:23:33  lpoulsen
// Remove compiler warnings
//
// Revision 1.8  2001/11/12 23:06:48  mourlouk
// making happy compiler (II)
//
// Revision 1.7  2001/11/08 23:56:14  mourlouk
// Changed linux_adapter.c so you can see the MAC + IP address
//     of each ethernet adapter
//
// Revision 1.6  2001/11/07 02:47:25  lpoulsen
// Running econ on linux:
// - make the executable name be "econ"
// - remove a few compiler warnings
//
// Revision 1.5  2001/11/06 00:51:12  mourlouk
// Fixed a bug with regard to discovery phase - pointed out by Nuno
//
// Revision 1.4  2001/10/31 19:14:57  mourlouk
// Set the console title ...
//
// Revision 1.3  2001/10/31 02:27:35  mourlouk
// Special handling of last file packet (if %256 -> break it into two packets)
//
// Revision 1.2  2001/10/30 01:07:45  lpoulsen
// Add "module ID" statement, audit trail generator and copyright line
// Remove "win_or_linux.h" and use a -D in Linux makefile instead.
//

#define _CRT_SECURE_NO_WARNINGS

#include <time.h>
#include <errno.h>
#include <assert.h>
#include "afartype.h"
#include "epacket.h"
#include "eprotocol.h"
#include "util.h"
#include "adapter.h"
#include "cio.h"
#include "ucon.h"

#ifdef LINUX
//***  .................. Linux specific ............................***/
#include "win_emul.h"           // contains Windows thread emulation functions

pthread_cond_t v_Receive_ACK = PTHREAD_COND_INITIALIZER;
pthread_cond_t v_File_ACK = PTHREAD_COND_INITIALIZER;
pthread_cond_t v_download_Completed = PTHREAD_COND_INITIALIZER;
pthread_cond_t v_command_Completed = PTHREAD_COND_INITIALIZER;

pthread_cond_t *Receive_ACK = &v_Receive_ACK;
pthread_cond_t *File_ACK = &v_File_ACK;
pthread_cond_t *download_Completed = &v_download_Completed;
pthread_cond_t *command_Completed = &v_command_Completed;
extern pthread_cond_t *server_flags_updated;

#else
//***  ................. Windows specific ............................***/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include <mmsystem.h>
#include <process.h>
#include <conio.h>
#include <windowsx.h>

HANDLE Receive_ACK;
HANDLE File_ACK;
HANDLE download_Completed;
HANDLE command_Completed;
extern HANDLE server_flags_updated;

#endif

#define htons(a) swap_uint16(a)
#define htonl(a) swap_uint32(a)
#define ntohs(a) swap_uint16(a)
#define ntohl(a) swap_uint32(a)
boolean has_extension(char *fname);
void strip_extension(char *fname);

struct ether_session active_session = {0};
int                  active_device = -1;

//*** ............. end of specific definitions ........................***/

void send_identify_response(econ_packet * e_pkt, int ConnectionID);
void process_rseq(struct ether_session *sess, uint32 rseq);

boolean FREEZE_HEADER = FALSE;

unsigned char econ_snap_prefix[8] =
{
    0xAA, 0xAA, 0x03, 0x00, 0xD0, 0x39, 0x00, 0x01
};

struct vlan_tag gVLAN_tag;
boolean gVLAN_mode;

long gAck = 0;
int clientTimeIsIDLE = 0;
#define clientIDLEtimeoutSECS 60*30
// that implies clientIDLEtimeoutSECS sec timeout period
// - after that client will exit
int CLIENT_IDLE_TIMEOUT = (clientIDLEtimeoutSECS * 1000) / FILE_TIMEOUT;

int   req_filename_size;
char  requested_filename[128];

uint8 file_response_header[24];

extern FILE *console_in_file;
extern FILE *session_text_file;

// ** Connection establishment ......................................

boolean 
connect_to_server(int conn_index)
{
    static econ_packet *econ_pkt = NULL;
    clock_t startTime, curTime, lastRetransmit;
    time_t startime, curtime;
    double waitDuration, retranDuration;
    int res;
    int i, j;
    char tmpName[128];

    startTime = clock();
    startime = time(NULL);
    lastRetransmit = startTime;
    waitDuration = 0.0;
    retranDuration = 0.0;

    econ_phase = ECON_CONNECT_DEV;

    if (econ_pkt == NULL)
    {
        econ_pkt = malloc(1640);
    }

    // Send a CONNECT_REQUEST message,
    active_session.PDU = CONNECT_REQUEST;
    active_session.TSEQ = 1;
    active_session.RSEQ = 0;
    active_session.NBytes = 0;

    active_session.state = SESSION_CONNECT_WAIT;
    active_session.last_TSEQ_acked = 0;
    active_session.last_ack_RSEQ = 0;
    active_session.ConnectionID = getRandomID();

    memcpy(active_session.destination_address,
           afar_device[conn_index].mac_address, sizeof(macAddr));

    // remove spaces from the localHostName - if present
    i = j = 0;
    while (localHostName[i])
        if ((localHostName[i] != ' ')
            && (localHostName[i] != 7))  // 7 is the tab

            tmpName[j++] = localHostName[i++];
        else
            i++;
    tmpName[j] = 0;

    tmpName[PROTO_POSITION - 1] = active_session.proto_version
        = afar_device[conn_index].proto_version;
    ether_send(&active_session, (uint8 *)tmpName, PROTO_POSITION);

    active_session.TSEQ = 1;    // <-- data field in connect request
                                //     is not counted in sequence space

    while ((econ_phase == ECON_CONNECT_DEV)
           && (waitDuration < CONNECTION_ESTABLISHMENT_TIMEOUT))
    {
        res = adapter_receive((u_char *) econ_pkt);
        if (res < MIN_PACKET_SIZE)  // don't bother - can't be an econsole packet
            ;
        else {
            uint16 proto = ntohs(econ_pkt->econ_ehdr.epkt_proto);
            if (proto > 1500)   // Not LLC encapsulated
                continue;
            econ_trace_packet(econ_pkt, res);
            process_packet(econ_pkt, res);     // response to CONN_REQ
        }

        if (active_session.state == SESSION_COMMAND_MODE)
        {
            active_device = conn_index;
            econ_phase = ECON_CONNECTED;
            return TRUE;
        }

        curTime = clock();
        waitDuration = (double) (curTime - startTime) * 1000 / CLOCKS_PER_SEC;
        retranDuration =
            (double) (curTime - lastRetransmit) * 1000 / CLOCKS_PER_SEC;

        if (retranDuration > DISCOVERY_RETRANSMISSION_INTERVAL)
        {
            active_session.TSEQ = 0;
            ether_send(&active_session, (uint8 *)tmpName, PROTO_POSITION);
            active_session.TSEQ = 1;
            lastRetransmit = curTime;
        }
        curtime = time(NULL);
        if ((difftime(curtime, startime) * 1000) >
            CONNECTION_ESTABLISHMENT_TIMEOUT)
            break;
    }
    return FALSE;
}

// ..... VLAN support .....

boolean
isVLANtagged(u_char * new_packet, int *packet_size)
{
    if (gVLAN_mode)
    {
        // check for the presence of a VLAN tag
        struct vlan_tag *tag;
        tag = (struct vlan_tag *) &new_packet[12];
        if (ntohs(tag->tpid) == 0x8100)
        {
            return TRUE;
        }
    }
    return FALSE;
}

void
checkAndStripVLANtag(u_char * econ_pkt, u_char * new_packet, int *packet_size)
{
    if (isVLANtagged(new_packet, packet_size))
    {
        // Strip the VLAN tag
        memcpy(econ_pkt, new_packet, 12);
        memcpy(econ_pkt + 12, new_packet + 16, *packet_size - 12 - VLAN_TAG_SIZE);
        *packet_size -= VLAN_TAG_SIZE;
    }
    else
        memcpy(econ_pkt, new_packet, *packet_size);
}

int
process_packet(econ_packet *new_packet, int packet_size)
{
    static econ_packet *econ_pkt;
    static boolean      firstTime = TRUE;
    int                 returnCode;

    uint16  PDU;
    uint16  ConnectionID;
    uint32  TSEQ;
    uint32  RSEQ;
    uint32  NBytes;
    uint8  *payload;

    uint32  file_length;
    uint32  file_request_status;
    uint8   MD5[16];
    int32   temp;

    if (firstTime)
    {
        firstTime = FALSE;
        econ_pkt = malloc(MAX_PACKET_SIZE);
    }

    checkAndStripVLANtag((void *) econ_pkt, (void *)new_packet, &packet_size);

    if (!isEtherConsolePacket(econ_pkt))
    {
        return INVALID_PACKET;
    }

    // extract protocol parameters
    PDU          = ntohs(econ_pkt->econ_chdr.econ_pdu);
    ConnectionID = ntohs(econ_pkt->econ_chdr.econ_conn_id);
    TSEQ         = ntohl(econ_pkt->econ_chdr.econ_tseq);
    RSEQ         = ntohl(econ_pkt->econ_chdr.econ_rseq);
    NBytes       = ntohl(econ_pkt->econ_chdr.econ_nbytes);
    payload      = (uint8 *) econ_pkt->econ_chdr.econ_data;

    returnCode = 0;             // 0 value indicates "No more response"


    if ((PDU == IDENTIFY_REQUEST) || (PDU == IDENTIFY_REQUEST << 8))
    {
        send_identify_response((void *) econ_pkt, ConnectionID);
        return 0;
    }
    else if (PDU == IDENTIFY_RESPONSE)
    {
        return 0;
    }

    // We eliminated IDENTIFY, so any other incoming packets must belong 
    //    to an established session.
    // First check if the session has expired. If so, just ignore the packet.

    clientTimeIsIDLE = 0;

    if (ConnectionID != active_session.ConnectionID)
    {
        // Probably just a close-confirmation from previous session
        return 0;
    }

    if (RSEQ > active_session.TSEQ) {
        if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
            fprintf(session_text_file,
               "*** HELP *** Ack for data we have not sent %08X vs %08X\n",
               RSEQ, active_session.TSEQ);
        return INVALID_ACK;
    }

    process_rseq(&active_session, RSEQ);

    if (active_session.state == SESSION_CONNECT_WAIT)
    {   // The PDU should be CONNECT_RESPONSE or CONSOLE_TEXT_DATA
        // but there is no special need to enforce that

        memcpy(active_session.destination_address,
               econ_pkt->econ_ehdr.epkt_src, 6);

        active_session.last_rec_pkt = LastReceivedPacketBuf;
        active_session.datestamp = time(NULL);

        active_session.state = SESSION_COMMAND_MODE;
        active_session.PDU = CONSOLE_TEXT_DATA;

        returnCode = 0;
    }

    switch (PDU)
    {
    case CONSOLE_COMMAND:
        // We don't really use this PDU type, we just use CONSOLE_TEXT_DATA
        // in both directions. If we used it, we would not use it in this
        // direction, so it is really an invalid PDU type.

        // ackownledge the reception of data
        active_session.PDU = EMPTY;
        // active_session.ConnectionID <--  Remain
        // active_session.TSEQ         <--  Unaffected

        // active_session.NBytes       <--  Will be set by ether_send

        if (TSEQ < active_session.RSEQ)
        {    // this is a duplicate of something we already received/processed
            if (TSEQ + NBytes > active_session.RSEQ)
            {   // Part of the buffer is useful; this is new territory
                if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                    fprintf(session_text_file,
                      "[*** HELP *** half-duplicate data %08X+%d vs %08X]\n",
                       TSEQ, NBytes, active_session.RSEQ);
            }
            return INVALID_ACK;
        }
        if (TSEQ > active_session.RSEQ)
        {   // We are missing something before this buffer ... toss it
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file,
                    "[*** HELP *** missing intermediate data %08X vs %08X]\n",
                    TSEQ, active_session.RSEQ);
            return INVALID_ACK;
        }

        // send acknowledgement

        active_session.RSEQ += NBytes;
        ether_send(&active_session, NULL, 0);

        // now ignore the command ...

        break;

    case CONSOLE_TEXT_DATA:
        // ackownledge the reception of data
        active_session.PDU = EMPTY;
        // active_session.ConnectionID <--
        // active_session.TSEQ         <--  Remain unaffected
        // active_session.NBytes       <--  Will be set by ether_send

        if (TSEQ > active_session.RSEQ)
        {
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file,
                   "*** HELP: Data is premature %08X vs %08X\n",
                   TSEQ, active_session.RSEQ);
            return 0;
        }
        if (TSEQ == active_session.RSEQ)
        {
            if (NBytes > 0)
            {
                process_econ_output(payload, NBytes);
                active_session.RSEQ += NBytes;
                active_session.state = SESSION_COMMAND_MODE;
            }
            if (active_session.last_TSEQ_acked == active_session.TSEQ)
            {
                active_session.server_flags =
                                         ntohs(econ_pkt->econ_chdr.econ_flags);
                SetEvent(server_flags_updated);
            }
        }
        else
        {
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file,
                        "*** HELP: Duplicate data %08X vs %08X\n",
                        TSEQ, active_session.RSEQ);
            returnCode = INVALID_ACK;
        }

        // send your acknowledgement
        ether_send(&active_session, NULL, 0);

        break;

    case END_OF_DATA:
        // ackownledge the reception of data
        active_session.PDU = EMPTY;
        // active_session.ConnectionID <--
        // active_session.TSEQ         <--  Remain unaffected
        // active_session.NBytes       <--  Will be set by ether_send

        if (TSEQ == active_session.RSEQ)
        {
            active_session.RSEQ += NBytes;
            memcpy(active_session.last_rec_pkt, payload, NBytes);
            // send your acknowledgement
            ether_send(&active_session, NULL, 0);
            returnCode = 0;     // no more response is expected ...

        }
        else if (!memcmp(active_session.last_rec_pkt,
                         payload, NBytes))
        {
            // re-send your acknowledgement - probably got lost
            ether_send(&active_session, NULL, 0);
        }
        else
        {
            char econ_diagnostic_msg[120];
            sprintf(econ_diagnostic_msg,
                    "b:Wrong sequence number recieved: %d instead of %d\n",
                    TSEQ, active_session.RSEQ);
            diagnostic_message(econ_diagnostic_msg);
            returnCode = INVALID_ACK;
            break;
        }
        break;

    case EMPTY:
        // update your sequence numbers
        // active_session.TSEQ   <-- remain unaffected
        // active_session.RSEQ   <--

        // Update your window
        // ...

        // check your mode

        if (active_session.state == SESSION_COMMAND_MODE)
        {
            if (active_session.last_TSEQ_acked == active_session.TSEQ) {
                active_session.server_flags =
                                         ntohs(econ_pkt->econ_chdr.econ_flags);
                SetEvent(server_flags_updated);
            }

            returnCode = 1;     // more response is expected
        }
        break;

    case GO_AWAY:
        {
            active_session.datestamp = time(NULL);
            SetEvent(Receive_ACK);
            terminate_session(-1, ES_GO_AWAY);
        }
        break;

    case BREAK:
        // something bad happened - operation cancelled or incomplete
        // this corresponds to other-side errors
        // update your sequence numbers
        // active_session.TSEQ   <-- remain unaffected
        // active_session.RSEQ   <--

        // Update your window
        // ...

        // check your mode
        if (active_session.state == SESSION_TRANSFER_MODE)
        {
            printf
                ("\nError!! File transfer failed on server side. Please try again\n");
            if (NBytes > 0)
                printf("Error description: %s\n", payload);
            active_session.state = SESSION_COMMAND_MODE;
            terminate_session(-1, ES_CMD_FAILED);
            break;
        }

        returnCode = 0;

        // now check if you received an error message ...
        if (NBytes > 0)
        {
            // get error description
            if (TSEQ == active_session.RSEQ)
                active_session.RSEQ += NBytes;
            else
            {
                char econ_diagnostic_msg[120];
                sprintf(econ_diagnostic_msg,
                        "c:%s: %d instead of %d [CID:%d]\n",
                        "Wrong sequence number received",
                        TSEQ, active_session.RSEQ, ConnectionID);
                diagnostic_message(econ_diagnostic_msg);
                returnCode = INVALID_ACK;
            }

            payload[NBytes] = 0;
            if ((NBytes == 1) && (payload[0] == 0))
                NBytes = 0;
            if (NBytes > 0)
                printf("Error description: %s\n", payload);
        }
        SetEvent(Receive_ACK);
        break;

    case IDENTIFY_REQUEST:
        // Ignore in the client
        break;

    case IDENTIFY_RESPONSE:
        // Someone else has started the discovery phase - ignore packet
        returnCode = INVALID_SESSION;
        break;

    case CONNECT_REQUEST:
        active_session.PDU = CONNECT_RESPONSE;
        active_session.ConnectionID = ConnectionID;
        active_session.TSEQ = 0;
        active_session.RSEQ = 1;
        active_session.NBytes = 0;

        ether_send(&active_session, NULL, 0);

        active_session.state = SESSION_COMMAND_MODE;
        active_session.PDU = CONSOLE_TEXT_DATA;
        break;

    case CONNECT_RESPONSE:
        // The response to a CONNECT_REQUEST can be a CONNECT_RESPONSE
        // but in actuality we skip that and jsut send CONSOLE_TEXT_DATA.
        // There is code before the switch(PDU) statement to process
        // the state transition, so there is nothing left to do here.
        // If we get a duplicate CONNECT_RESPONSE later, just ignore it ...

        returnCode = 0;
        break;

    case CLOSE_REQUEST:
        // Return in order to avoid sending an unnecessary ACK
        if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file, "[Received CLOSE_REQUEST]\n");
        returnCode = CLOSING_SESSION;
        terminate_session(-1, ES_REMOTE_CLOSE);
        break;

    case FILE_REQUEST:
        if (TSEQ > active_session.RSEQ)
        {
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file,
                        "*** HELP: Data is premature %08X vs %08X\n",
                        TSEQ, active_session.RSEQ);
            return 0;
        }
        if (TSEQ < active_session.RSEQ)
        {
            // This is a duplicate of something we already Ack'ed
            //   and possibly responded to - ignore it
            // For a FILE_REQUEST, this implies that he missed the
            //   file response. If that's the case, he will not have
            //   sent an Ack for it, and the file thread will
            //   retransmit it when we time out.
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file,
                        "*** HELP: Duplicate FILE_REQ %08X vs %08X\n",
                        TSEQ, active_session.RSEQ);
            return INVALID_ACK;
        }
        if (NBytes == 0)
        {
            if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                fprintf(session_text_file, "*** HELP: Zero-length FILE_REQ\n");
            return 0;
        }

        active_session.PDU = FILE_RESPONSE;
        // active_session.TSEQ      <-- will be set by ether_send
        // active_session.NBytes    <--

        // initially I assume that everything is OK
        file_request_status = 0;

        // extract filename

        active_session.RSEQ += NBytes;

        if (NBytes > sizeof(requested_filename) - 1)
            NBytes = sizeof(requested_filename) - 1;
        payload[NBytes] = 0;
        strncpy(requested_filename, (void *) payload, NBytes + 1);

#if 0
        printf("requested filename:%s payload:%s NBytes:%d\n",
               requested_filename, payload, NBytes);
#endif

        // check the version of the protocol
        //                             - if it's the old one remove the prefix
        if (active_session.proto_version == 0)
        {
            char prefix[128];
            char req_filename[128];
            if (verifyPrefix(requested_filename, prefix, req_filename))
            {
                printf("Invalid prefix:%s. Should be either tmp or flash\n",
                       prefix);
                file_request_status = -2;
                file_length = -1;
                goto send_file_resp;
            }
            strcpy(requested_filename, req_filename);
        }

        // check if it has any extension
        // if not try the extensions .bz, .bze or .bin as alternative filenames
        // nevertheless, [filename] has priority over [filename].bin if
        // both of them do exist.
        if (!fileExist(requested_filename))
        {
            if (!has_extension(requested_filename))
            {
            // try with the extension .bz or .bin
            if ((active_device >= 0)
             && (afar_device[active_device].device_type >= 2560))
            {
                strcat(requested_filename, ".bze");
            } else
            {
                strcat(requested_filename, ".bz");
            }

            if (!fileExist(requested_filename))
            {
                strip_extension(requested_filename);
                strcat(requested_filename, ".bin");
            }
            }
        }

        if (!fileExist(requested_filename))
        {
            file_request_status = -1;
            file_length = -1;

            memset(MD5, 0, 16);

            printf("Error: Requested file [%s] is not available\n",
                   requested_filename);
            active_session.state = SESSION_COMMAND_MODE;
            goto send_file_resp;
        }
        file_length = filelen(requested_filename);
        req_filename_size = file_length;
        // MD5[]=??

        // open file stream
        active_session.file_in = fopen(requested_filename, "rb");
        if (active_session.file_in == NULL)
        {
            printf("Error: Unable to open requested file [%s]\n\r",
                       requested_filename);
            file_request_status = -1;
            file_length = -1;
        }

      send_file_resp:
        temp = htonl(file_request_status);
        memcpy(&file_response_header[0], &temp, 4);
        temp = htonl(file_length);
        memcpy(&file_response_header[4], &temp, 4);
        memcpy(&file_response_header[8], MD5, 16);

        active_session.state = SESSION_FILE_REQUEST;
        if (file_request_status) {
            // The request failed
        } else {
            // get ready to tranfer data ...
            printf("The requested file [%s] was opened successfully\n\r",
                   requested_filename);
            active_session.file_in_position = 0;
        }
        // Wake up FileThread()
        SetEvent(File_ACK);
        // That wakes up FileThread() which does ...
        // ether_send(&active_session,file_response_header, 24);

        returnCode = 1;
        break;

    case FILE_RESPONSE:
        // active_session.state remains in SESSION_TRANSFER_MODE
        getLong(&file_request_status, (void *) payload);
        getLong(&file_length, (void *) (payload + 4));
        memcpy(MD5, payload + 8, 16);

        if (file_request_status != 0)  // there's a problem

        {
            // print/log some diagnostic message
            printf("Server abandoned file transfer. Request status:%d\n",
                   file_request_status);
        }
        else
        {
            // everyhting was OK
            // open a file stream
            // ...
            if (
                   (active_session.file_out =
                    fopen(active_session.filename_out, "w")) == NULL)
            {
                // notify the other end that the operation must be cancelled
                sendCancelOperationMessage(0, "Could not open output file");
                returnCode = 0; // so you can return in Command Mode

            }
            else
            {
                active_session.file_out_position = 0;  // currently not used

            }

        }

        // send an acknowledgement
        active_session.PDU = EMPTY;

        if (TSEQ == active_session.RSEQ)
        {
            active_session.RSEQ += NBytes;
            ether_send(&active_session, NULL, 0);
        }
        else
        {
            char econ_diagnostic_msg[120];
            sprintf(econ_diagnostic_msg,
                    "e:Wrong sequence number recieved: %d instead of %d\n",
                    TSEQ, active_session.RSEQ);
            diagnostic_message(econ_diagnostic_msg);
        }
        break;

    case FILE_DATA:
        // the following code is executed currently only in the server side
        // therefore, here is commented

    default:
        returnCode = INVALID_PDU;  // invalid response (Unknown PDU type)

    }                           // end of switch (PDU)
    return returnCode;
}

// process_rseq ... process the piggybacked acknowledgement in this packet
//    rseq  argument      is what was in the present incoming packet
//    sess->TSEQ          is what we have sent
//    sess->last_ack_RSEQ is what has been acked
// this code assumes that sequence numbers never wrap
void
process_rseq(struct ether_session *sess, uint32 rseq)
{
    if (rseq > sess->last_TSEQ_acked)
        sess->last_TSEQ_acked = rseq;

    if (sess->last_TSEQ_acked == sess->TSEQ)
    {
        // Everything is ACKed

        if (sess->state != SESSION_CONNECT_WAIT)
        {   // Connect handshake is done before we start multi-tasking
            int res;

            if ((sess->state == SESSION_TRANSFER_MODE)
             || (sess->state == SESSION_FILE_REQUEST) ) {
                if (debug_flags & DBFLG_THREADS) printf("LJP14 - Waking FileThread\n");
                res = SetEvent(File_ACK);
            } else {
                res = SetEvent(Receive_ACK);
            }
#ifdef LINUX
            if (res != 0) printf("LJP13 SetEvent failed errno=%d\n", errno);
#else
            if (res == 0) printf("LJP13 SetEvent error %d\n", GetLastError());
#endif
        }
    }
    else
    {
        if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
            fprintf(session_text_file,
                    "*** HELP: Incomplete ACK - rseq=%08X wanted %08X\n",
                    rseq, sess->TSEQ);
    }
}

void
build_econ_packet(econ_packet * packetbuff, struct ether_session *cur_session,
                  uint8 *data, size_t *payload_size)
{
    uint16 plen;
    int data_size = *payload_size;

    if ((data_size > 0) && (cur_session->PDU == EMPTY))
    {
        if (cur_session->state == SESSION_COMMAND_MODE)
            cur_session->PDU = CONSOLE_TEXT_DATA;
        else
        {
            cur_session->PDU = FILE_DATA;
        }
    }
    if ((data_size == 0) && (cur_session->state == SESSION_COMMAND_MODE))
        if ((cur_session->PDU != CLOSE_REQUEST)
         && (cur_session->PDU != CONSOLE_COMMAND)
         && (cur_session->PDU != CONSOLE_TEXT_DATA))
            cur_session->PDU = EMPTY;

    // destination Ethernet address
    // source Ethernet address
    // length
    memcpy(packetbuff->econ_ehdr.epkt_dest, cur_session->destination_address,
           6);
    memcpy(packetbuff->econ_ehdr.epkt_src, my_mac_address, 6);

    plen = ECON_HEADER_SIZE + data_size;
    packetbuff->econ_ehdr.epkt_proto = htons(plen);

    memcpy(packetbuff->econ_chdr.econ_snapid, econ_snap_prefix, 8);

    // PDU-type
    // Connection-ID
    // PAD:0000
    if (cur_session->proto_version < 2)
    {
        packetbuff->econ_chdr.econ_pdu = cur_session->PDU;
        packetbuff->econ_chdr.econ_conn_id = cur_session->ConnectionID;
        packetbuff->econ_chdr.econ_flags = 0x0000;

        // TSEQ - Sequence number
        memcpy(&packetbuff->econ_chdr.econ_tseq, &cur_session->TSEQ, 4);

        if (!FREEZE_HEADER)     // it will affect next packet

            cur_session->TSEQ += data_size;

        // RSEQ - Acknowledgement
        memcpy(&packetbuff->econ_chdr.econ_rseq, &cur_session->RSEQ, 4);
        // packetbuff->econ_chdr.econ_rseq = cur_session->RSEQ;

        // NBytes
        cur_session->NBytes = data_size;  // actual data size - without headers

        packetbuff->econ_chdr.econ_nbytes = cur_session->NBytes;
    }
    else
    {
        packetbuff->econ_chdr.econ_pdu = htons(cur_session->PDU);
        packetbuff->econ_chdr.econ_conn_id = htons(cur_session->ConnectionID);
        packetbuff->econ_chdr.econ_flags = 0x0000;
        if ((cur_session->proto_version >= 4)
         && (console_in_file))
            packetbuff->econ_chdr.econ_flags |= htons(ECFLAG_FROM_FILE);

        // TSEQ - Sequence number
        packetbuff->econ_chdr.econ_tseq = htonl(cur_session->TSEQ);

        if (!FREEZE_HEADER)     // it will affect next packet

            cur_session->TSEQ += data_size;

        // RSEQ - Acknowledgement
        packetbuff->econ_chdr.econ_rseq = htonl(cur_session->RSEQ);

        // NBytes
        cur_session->NBytes = data_size;  // data size - without headers

        packetbuff->econ_chdr.econ_nbytes = htonl(cur_session->NBytes);
    }

    // Payload
    memcpy(packetbuff->econ_chdr.econ_data, data, data_size);

    if (gVLAN_mode)
    {
        // add your VLAN tag
        unsigned char *tmp_p = (unsigned char *) &packetbuff->econ_ehdr.epkt_proto;
        memmove(tmp_p + VLAN_TAG_SIZE, tmp_p, plen - 2 * sizeof(macAddr));
        memcpy(tmp_p, &gVLAN_tag, VLAN_TAG_SIZE);
        *payload_size += VLAN_TAG_SIZE;
    }
}

uint32    econ_pkts_recvd;
uint32    econ_wrong_snap;
uint32    econ_wrong_dest;
uint32    econ_wrong_peer;
uint32    econ_wrong_conn;
uint32    econ_good_pkts;

boolean 
isEtherConsolePacket(econ_packet * ecp)
{
    uint16 PDU;
    uint16 ConnectionID;

    econ_pkts_recvd++;
    // first check if it is an EtherConsole protocol packet
    if (memcmp(ecp->econ_chdr.econ_snapid, econ_snap_prefix, 8))
    {
        econ_wrong_snap++;
        return FALSE;
    }

    // then check if the packet is for you
    if ((memcmp(ecp->econ_ehdr.epkt_dest, bcastMacAddress, 6))
        && (memcmp(ecp->econ_ehdr.epkt_dest, my_mac_address, 6)))
    {
        econ_wrong_dest++;
        return FALSE;
    }

    // check also if the source address of the packet matches the
    // server's address of the existing session

    PDU = ntohs(ecp->econ_chdr.econ_pdu);

    if (PDU == CLOSE_REQUEST) {
        static int closes;
        closes++;
    }

    if ((PDU == IDENTIFY_REQUEST)
     || (PDU == (IDENTIFY_REQUEST << 8))
     || (PDU == CONNECT_REQUEST))
    {
        econ_good_pkts ++;
        return TRUE;
    }

    ConnectionID = ntohs(ecp->econ_chdr.econ_conn_id);

    if (ConnectionID != active_session.ConnectionID)
    {
        econ_wrong_conn++;
        return FALSE;
    }

    if (memcmp(ecp->econ_ehdr.epkt_src, active_session.destination_address, 6))
    {
        econ_wrong_peer++;
        return FALSE;
    }
    econ_good_pkts ++;
    return TRUE;
}
void
epacket_clear_receive_stats(void)
{
    econ_pkts_recvd = 0;
    econ_wrong_snap = 0;
    econ_wrong_dest = 0;
    econ_wrong_peer = 0;
    econ_wrong_conn = 0;
    econ_good_pkts  = 0;
}
void
epacket_print_receive_stats(void)
{
    printf("\n--- ConnectionID %08X ---\n", active_session.ConnectionID);
    printf("Pcap pkts received %10u\n", econ_pkts_recvd);
    printf("Not Econ Snap ID   %10u\n", econ_wrong_snap);
    printf("Not for me or bcst %10u\n", econ_wrong_dest);
    printf("Not this peer mac  %10u\n", econ_wrong_peer);
    printf("Wrong connection   %10u\n", econ_wrong_conn);
    printf("Good econ packets  %10u\n", econ_good_pkts);
}


/****************************************************************************/
/************** Send functions **********************************************/
/****************************************************************************/

void
sendCancelOperationMessage(int sessionId, char *msg)
{
    // send a BREAK message to the other end
    active_session.PDU = BREAK;
    active_session.state = SESSION_COMMAND_MODE;
    ether_send(&active_session, (uint8 *)msg, strlen(msg) + 1);

    // update your status
    // ...
}

boolean
ether_send(struct ether_session *cur_session, uint8 *input_data,
           size_t payload_size)
{
    econ_packet *packet_to_send;
    int    Snaplen, res;
    uint32 ack_timeout = ACK_TIMEOUT;
    uint32 TSEQ_after;

    assert(payload_size <= (1500 - ECON_HEADER_SIZE));

    if ((cur_session->state == SESSION_TRANSFER_MODE)
     || (cur_session->state == SESSION_FILE_REQUEST) )
        res = ResetEvent(File_ACK);
    else
        res = ResetEvent(Receive_ACK);

    packet_to_send = malloc(MAX_ENET_PACKET_SIZE);

    build_econ_packet(packet_to_send, cur_session, input_data, &payload_size);
    // Note: TSEQ got updated in build_econ_packet() !!

    TSEQ_after = cur_session->TSEQ;

    Snaplen = ECON_HEADER_SIZE + payload_size;

    if (loss_pct_xmit
     && (rand() < ((RAND_MAX*loss_pct_xmit) / 100.0)))
        /* skip it */ ;
    else {
        econ_trace_packet(packet_to_send, Snaplen + sizeof(epkt_header));
        adapter_send((void *) packet_to_send, Snaplen + sizeof(epkt_header));
    }

    if ((payload_size > 0)
        && (cur_session->PDU != CONNECT_REQUEST)
        && (cur_session->PDU != IDENTIFY_RESPONSE))
    {
        int retransmissions = 0;

        while (cur_session->last_TSEQ_acked < TSEQ_after)
        {
            if (retransmissions >= NUM_OF_RETRANSMISSIONS)
            {
                // we failed to transmit this particular packet
                if (session_text_file && (debug_flags & DBFLG_ECON_PKTS))
                    fprintf(session_text_file, "[Too many retransmissions]\n");
                free(packet_to_send);
                terminate_session(-1, ES_SEND_FAILED);
                return FALSE;
            }
          
            if ((cur_session->state == SESSION_TRANSFER_MODE)
             || (cur_session->state == SESSION_FILE_REQUEST) )
            res = WaitForSingleObject(File_ACK, ack_timeout);
            else
            res = WaitForSingleObject(Receive_ACK, ack_timeout);

            if (econ_phase == ECON_CLOSE_THREADS)
                return FALSE;

            if (res == WAIT_FAILED)
            {
                // we failed to transmit this particular packet

                free(packet_to_send);
                terminate_session(-1, ES_SEND_FAILED);
                return FALSE;
            }
            // Is was signaled OR it timed out
            if (cur_session->last_TSEQ_acked >= TSEQ_after)
                break;

            // The packet processor may have woken us up because we
            //   received a packet with an Ack, but if we are still here,
            //   it did not acknowledge what we were waiting for.
            //   Still, we may get more partial Ack's, so don't retransmit
            //   until we reach the timeout point.
            if (res != WAIT_TIMEOUT)
                continue;

            // retransmit X times ...

            if (retransmissions == 4)
                printf("Retransmitting data ... please wait\n");

            packet_to_send->econ_chdr.econ_flags |= htons(ECFLAG_RETRANS);
            packet_to_send->econ_chdr.econ_rseq = htonl(cur_session->RSEQ);

            if (loss_pct_xmit
             && (rand() < ((RAND_MAX*loss_pct_xmit) / 100.0)))
                /* skip it */ ;
            else {
                econ_trace_packet(packet_to_send,
                                                Snaplen + sizeof(epkt_header));
                adapter_send((void *) packet_to_send,
                                                Snaplen + sizeof(epkt_header));
            }
            ack_timeout = (uint32) (ack_timeout * 1.3);
            retransmissions++;
        }
    }
    free(packet_to_send);
    fflush(stdout);
    return TRUE;                // everything OK
}

// ----- doFileTransfer is the working part of FileThread
//       it is called repeatedly until global flags say it is time to exit
//    That structure allow it to do a file tranfer per call or to do
//       a buffer per call. We are now doing a file per call.
//    We spend our idle time waiting for the File_ACK event flag.

#define FILE_BUFFER_SIZE 1350

int
doFileTransfer(struct ether_session *cur_session)
{
    int     res;
    uint8   file_buffer[FILE_BUFFER_SIZE];

    // We sit here while the session is in command mode.

    res = WaitForSingleObject(File_ACK, FILE_TIMEOUT);
    if (debug_flags & DBFLG_THREADS)
        printf("LJP11 - FileThread() woke up - res=%d\n", res);
    if (econ_phase == ECON_CLOSE_THREADS)
        return 1;

    if (res == WAIT_TIMEOUT)  // our time is up
    {
        if ((cur_session->state != SESSION_FILE_REQUEST)
         && (cur_session->state != SESSION_TRANSFER_MODE)) {
            // Periodic timeout when we are not doing file transfer
            return 0;
        }

        if (cur_session->last_TSEQ_acked == cur_session->TSEQ) {
            res = 0;
            goto pretend_we_are_fine;
        }
        if (cur_session->state == SESSION_TRANSFER_MODE)
            clientTimeIsIDLE += 1;  // FileRetransmissionRequired=TRUE;

        // check also the time you have remained idle
        if (clientTimeIsIDLE > CLIENT_IDLE_TIMEOUT)
        {
            terminate_session(-1, ES_FILESEND_FAILED);
        }
    }
    else if (res != 0)
    {
        printf("Error: File Transfering Thread failed [%d]: %s\n",
               res, "unable to transfer files");
        cur_session->state = SESSION_COMMAND_MODE;
        return 1;
    }
    // OK, we were woken up because it is time to actually transfer
    //    a file

pretend_we_are_fine:

    if (res == 0)
    {   // We were awakened due to the cond. variable being signaled

        if (active_session.state == SESSION_FILE_REQUEST)
        {
            // We are responding to a file request from the embedded
            //    device. The file response has already been filled
            //    in by the receive thread.

            ether_send(&active_session, file_response_header, 24);

            if (file_response_header[0] || file_response_header[1]
             || file_response_header[2] || file_response_header[3])
                active_session.state = SESSION_COMMAND_MODE;
            else 
                active_session.state = SESSION_TRANSFER_MODE;
        }
        if (active_session.state == SESSION_TRANSFER_MODE)
        {
            int     percentage;
            size_t  bytes_read;

            // check if you have reached the end of file
            while (!feof(active_session.file_in)
               &&  (active_session.state == SESSION_TRANSFER_MODE))
            {
                active_session.PDU = FILE_DATA;
                bytes_read =
                        fread(file_buffer, 1, FILE_BUFFER_SIZE,
                              active_session.file_in);
                active_session.file_in_position += bytes_read;
                percentage =
                    (active_session.file_in_position * 100) /
                    req_filename_size;
                printf("\r [Transferring file %7d bytes sent (%d%% completed)]",
                         active_session.file_in_position, percentage);
                if (percentage == 100) printf("\n");
                ether_send(&active_session, file_buffer, bytes_read);
            }
            fclose(active_session.file_in);
            fflush(stdout);
            if (active_session.state == SESSION_TRANSFER_MODE)
            {
                uint16 dummy = 0;
                active_session.PDU = END_OF_DATA;
                ether_send(&active_session, (void *)&dummy, 1);
                active_session.state = SESSION_COMMAND_MODE;
            }
            SetEvent(download_Completed);
        }
    }
    return 0;
}

void
send_identify_response(econ_packet * e_pkt, int ConnectionID)
{
    struct ether_session tmpSession;
    econ_header tmp_econ_pdu;
    int i, j;
    char c;
    extern uint32 start_time;
    extern char software_version[16];

    memcpy(tmpSession.destination_address,
           e_pkt->econ_ehdr.epkt_src, 6);
    tmpSession.PDU = IDENTIFY_RESPONSE;
    tmpSession.ConnectionID = ConnectionID;
    tmpSession.TSEQ = 0;
    tmpSession.RSEQ = 0;
    tmpSession.NBytes = 0;      // overridden below

    i = j = 0;
    while ((j < PROTO_POSITION) && (c = localHostName[i++]))
    {
        if (c <= ' ')
            continue;
        tmp_econ_pdu.econ_data[j++] = c;
    }
    tmp_econ_pdu.econ_data[j] = 0;
    tmp_econ_pdu.econ_version = PROTOCOL_VERSION;
    tmp_econ_pdu.econ_ip_addr = 0;
    tmp_econ_pdu.econ_uptime = (time(0) - start_time) * 100;
    strncpy(tmp_econ_pdu.econ_sw_type, software_version, 16);
    tmp_econ_pdu.econ_hw_type = htonl(HW_TYPE_PC);

    ether_send(&tmpSession, (uint8 *)&tmp_econ_pdu.econ_data[0],
                                                          sizeof(econ_header));
}

boolean
has_extension(char *fname)
{
    int i, l;
    l = strlen(fname);
    for (i = 1; i <= 4; i++)
    {
        if ((l - i) < 0)
            return false;
        if (fname[l - i] == '.')
            return true;
    }
    return false;
}

void
strip_extension(char *fname)
{
    int i, l;
    l = strlen(fname);
    for (i = 1; i <= 4; i++)
    {
        if ((l - i) < 0)
            return;
        if (fname[l - i] == '.') {
            fname[l - i] = 0;
            return;
        }
    }
}
