// --------------------------------------------------------------------------------------------------------------
//
//   AmiGnut 0.9.0 Network Module
//
//   Copyright (C) 2001 Jay Cornwall <jay@realfantasy.demon.co.uk>
//
//   This is free software distributed under the terms of the GNU Public License. See the file License.txt for
//   details.
//
// --------------------------------------------------------------------------------------------------------------

#include "Network.h"

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

void NetworkHandler()
{
    BOOL connected = FALSE, running = TRUE;
    BYTE ipcsigbit, netsigbit;
    LONG sock;
    struct ConnectionNode *connectionnode, *nextconnectionnode;
    struct IPCMTNMessage *ipcmtnmessage;
    struct Library *SocketBase;
    struct Task *thistask;
    UBYTE branchtimer = 0;
    ULONG eventmask, ipcsigmask, netsigmask, sigs, timersigmask;

    // Network process, runs asynchronously to the main process

    // Lock the semaphore on IPCMTNMsgPort to prevent the main process from freeing it before we exit

    ObtainSemaphore(&IPCMTNMsgPortSemaphore);

    // Set the flag to tell the main process we've locked the semaphore on IPCMTNMsgPort. This prevents a
    // potential deadlock of the main process exiting before the network process has locked the semaphore.

    NetworkGotSemaphore = TRUE;

    // Allocate a signal bit for IPC

    if (!(ipcsigbit = AllocSignal(-1)))
    {
        // Failed to allocate a signal bit

        ShowErrorMessage("[Network Process] Can't allocate signal bit", ERRORMSG_LOWMEM);

        // Release the semaphore lock on IPCMTNMsgPort

        ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

        // Exit the network process

        return;
    } /* if */

    ipcsigmask = 1 << ipcsigbit;

    // Set the IPCMTNMsgPort to signal us

    IPCMTNMsgPort->mp_SigBit =  ipcsigbit;
    IPCMTNMsgPort->mp_SigTask = (struct Task *)NetworkProcess;

    // Allocate a signal bit for network communications

    if (!(netsigbit = AllocSignal(-1)))
    {
        // Failed to allocate a signal bit

        ShowErrorMessage("[Network Process] Can't allocate signal bit", ERRORMSG_LOWMEM);

        // Free the IPC signal bit

        FreeSignal(ipcsigbit);

        // Release the semaphore lock on IPCMTNMsgPort

        ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

        // Exit the network process

        return;
    } /* if */

    netsigmask = 1 << netsigbit;

    // Open a local copy of bsdsocket.library v4+

    if (!(SocketBase = OpenLibrary("bsdsocket.library", 4)))
    {
        // Failed to open bsdsocket.library v4+

        ShowErrorMessage("Can't open bsdsocket.library v4+", ERRORMSG_EASYREQ);

        // Free the IPC signal bit

        FreeSignal(netsigbit);
        FreeSignal(ipcsigbit);

        // Release the semaphore lock on IPCMTNMsgPort

        ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

        // Exit the network process

        return;
    } /* if */

    // Store the pointer to SocketBase in our task's tc_UserData field. This makes it easy for other functions
    // running in our context to obtain the same pointer.

    thistask = FindTask(NULL);
    thistask->tc_UserData = SocketBase;

    // Tell bsdsocket.library which mask to use when signalling us

    LOC_SocketBaseTags(SocketBase, SBTM_SETVAL(SBTC_SIGEVENTMASK), netsigmask, TAG_DONE);

    // Prepare the timer.device for 1 second interval signals

    if (!InitTimer())
    {
        // Failed to prepare timer.device

        // Free the IPC signal bit

        FreeSignal(netsigbit);
        FreeSignal(ipcsigbit);

        // Release the semaphore lock on IPCMTNMsgPort

        ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

        // Exit the network process

        return;
    } /* if */

    timersigmask = 1 << TimerMsgPort->mp_SigBit;

    // Initialise the lists used by this process

    NewList((struct List *)&ConnectionList);
    NewList((struct List *)&HostCacheList);

    // Get our IP address

    if (!(LocalIP = GetLocalIP()))
    {
        // Failed to get our IP address

        // Free the timer.device resources

        FreeTimer();

        // Free the IPC signal bit

        FreeSignal(netsigbit);
        FreeSignal(ipcsigbit);

        // Release the semaphore lock on IPCMTNMsgPort

        ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

        // Exit the network process

        return;
    } /* if */

    // Loop until we're told to exit

    while (running)
    {
        // Wait for signals

        sigs = Wait(ipcsigmask | netsigmask | timersigmask);

        if (sigs & ipcsigmask)
        {
            // The main process sent us a message

            while (((struct Message *)ipcmtnmessage = GetMsg(IPCMTNMsgPort)) != 0)
            {
                // Check the message type

                switch(ipcmtnmessage->ipcmtn_MsgType)
                {
                    case IPCMTNMSG_CONNECT:

                        // Main process told us to begin connecting

                        if (!BeginConnect())
                        {
                            // Connection to network failed

                            // Were we interrupted by the main task?

                            if (MNInterrupt)
                            {
                                // Absorb the Ctrl-C

                                SetSignal(0, SIGBREAKF_CTRL_C);
                                MNInterrupt = FALSE;

                                break;
                            } /* if */

                            // Did we run out of memory?

                            if (OutOfMemory)
                            {
                                // We ran out of memory, exit immediately

                                running = FALSE;

                                break;
                            } /* if */

                            // Enable gadgets/menu items which can't be used whilst connected

                            DoMethod(App,   MUIM_Application_PushMethod,    Menus[MID_ABOUT_MAIN],                  3,  MUIM_Set,   MUIA_Menuitem_Enabled,  TRUE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_HCHOSTNAME_STRING_SETUP],   3,  MUIM_Set,   MUIA_Disabled,          FALSE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_HCPORT_STRING_SETUP],       3,  MUIM_Set,   MUIA_Disabled,          FALSE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_LISTENPORT_STRING_SETUP],   3,  MUIM_Set,   MUIA_Disabled,          FALSE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_MAXINCOMING_SLIDER_SETUP],  3,  MUIM_Set,   MUIA_Disabled,          FALSE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_MAXOUTGOING_SLIDER_SETUP],  3,  MUIM_Set,   MUIA_Disabled,          FALSE);

                            // Toggle the Connect/Disconnect button states

                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_CONNECT_BUTTON_MAIN],       3,  MUIM_Set,   MUIA_Disabled,          FALSE);
                            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_DISCONNECT_BUTTON_MAIN],    3,  MUIM_Set,   MUIA_Disabled,          TRUE);
                        } /* if */

                        else
                        {
                            // Connection to network succeeded

                            connected = TRUE;

                            // Since we have no current connections, we'll form our first branch here

                            GnutellaBranch();

                            // Reset the branch timer

                            branchtimer = 0;
                        } /* else */

                        break;

                    case IPCMTNMSG_DIE:

                        // Main process told us to die

                        running = FALSE;

                        break;

                    case IPCMTNMSG_DISCONNECT:

                        // Main process told us to disconnect

                        Disconnect();

                        // Disconnected from the network

                        connected = FALSE;

                        // Were we interrupted by the main task?

                        if (MNInterrupt)
                        {
                            // Absorb the Ctrl-C

                            SetSignal(0, SIGBREAKF_CTRL_C);
                            MNInterrupt = FALSE;

                            break;
                        } /* if */

                        break;
                } /* switch */

                // Free the message

                FreeVec(ipcmtnmessage);
            } /* while */
        } /* if */

        if (sigs & timersigmask)
        {
            // We were signalled at the 1 second interval

            // Send Connection Manager NList to sleep whilst we carry out modifications

            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 3, MUIM_Set, MUIA_NList_Quiet, TRUE);

            // Decrease all ConnectTimeouts and KillTimeouts, and reset all flood levels

            connectionnode = (struct ConnectionNode *)ConnectionList.mlh_Head;

            while (connectionnode->c_MinNode.mln_Succ)
            {
                // Are we connected?

                if (connected)
                {
                    // Is this ConnectionNode currently connecting?

                    if (connectionnode->c_Status == STATUS_CONNECTING)
                    {
                        // Decrement the ConnectTimeout

                        connectionnode->c_ConnectTimeout -= 1;

                        // Has the connect() timeout expired?

                        if (connectionnode->c_ConnectTimeout == 0)
                        {
                            // connect() has timed out, cancel it

                            CloseSocket(connectionnode->c_Socket);

                            // Mark the ConnectionNode as failed to connect

                            connectionnode->c_Status    = STATUS_FAILED;
                            connectionnode->c_Socket    = -1;

                            // Begin the count until the ConnectionNode is destroyed

                            connectionnode->c_KillTimeout = TIME_KILL;

                            // Update the connection count

                            PendingConnections --;

                            // Update the entry in the Connection Manager NList

                            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_List_Redraw, MUIV_NList_Redraw_All);
                        } /* if */

                        // Go to next ConnectionNode

                        connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;

                        continue;
                    } /* if */

                    // Reset the flood level

                    if (connectionnode->c_Status == STATUS_CONNECTED)
                    {
                        connectionnode->c_FloodLevel = 0;

                        // Go to next ConnectionNode

                        connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;

                        continue;
                    } /* if */
                } /* if */

                if ( (connectionnode->c_Status != STATUS_CONNECTED) && (connectionnode->c_Status != STATUS_CONNECTING) )
                {
                    // The ConnectionNode is counting down until it is destroyed

                    // Decrement the KillTimeout

                    connectionnode->c_KillTimeout -= 1;

                    // Has the ConnectionNode expired?

                    if (connectionnode->c_KillTimeout == 0)
                    {
                        // ConnectionNode has expired

                        // Remove the entry from the Connection Manager NList

                        RemoveConnectionListEntry(connectionnode);

                        // Remember the next ConnectionNode

                        nextconnectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;

                        // Remove and free the ConnectionNode

                        Remove((struct Node *)connectionnode);

                        if (connectionnode->c_RecvBuffer)
                        {
                            FreeVec(connectionnode->c_RecvBuffer);
                        } /* if */

                        if (connectionnode->c_SendBuffer)
                        {
                            FreeVec(connectionnode->c_SendBuffer);
                        } /* if */

                        FreeVec(connectionnode);

                        // Go to next ConnectionNode

                        connectionnode = nextconnectionnode;

                        continue;
                    } /* if */

                    else
                    {
                        // Go to next ConnectionNode

                        connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;

                        continue;
                    } /* else */
                } /* if */

                else
                {
                    // Go to next ConnectionNode

                    connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;

                    continue;
                } /* else */
            } /* while */

            // Wake the Connection Manager NList up again

            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 3, MUIM_Set, MUIA_NList_Quiet, FALSE);

            // Are we connected?

            if (connected)
            {
                // Increment the branch timer

                branchtimer += 1;

                // Is it time to form a new branch?

                if (branchtimer == TIME_BRANCH)
                {
                    // Should we form a new branch?

                    if (OutgoingConnections + PendingConnections < MaxOutgoingConns)
                    {
                        // Form a new branch to the network

                        if (!GnutellaBranch())
                        {
                            // The host cache is empty

                            // Do we have any (pending) links to the network?

                            if (OutgoingConnections + PendingConnections == 0)
                            {
                                // No links to the network left, disconnect

                                Disconnect();

                                // Disconnected from the network

                                connected = FALSE;
                            } /* if */
                        } /* if */
                    } /* if */

                    // Reset the branch timer

                    branchtimer = 0;
                } /* if */
            } /* if */

            // Tell timer.device to signal us in another 1 second

            TimerIO->tr_node.io_Command = TR_ADDREQUEST;
            TimerIO->tr_time.tv_secs    = 1;
            TimerIO->tr_time.tv_micro   = 0;

            SendIO((struct IORequest *)TimerIO);
        } /* if */

        if (sigs & netsigmask)
        {
            // bsdsocket.library notified us of an event

            while ((sock = GetSocketEvents((ULONG *)&eventmask)) != -1)
            {
                // An event occured at the socket, pass it on to the handlers

                if (eventmask & FD_READ)
                {
                    // Read handler, acts upon incoming data

                    if (!ReadHandler(sock))
                    {
                        // Out of memory

                        running = FALSE;

                        break;
                    } /* if */
                } /* if */

                if (eventmask & FD_WRITE)
                {
                    // Write handler, send data waiting to be sent

                    if (!WriteHandler(sock))
                    {
                        // Out of memory

                        running = FALSE;

                        break;
                    } /* if */
                } /* if */

                if (eventmask & FD_CONNECT)
                {
                    // Connect handler, performs handshake and establishes pending connections

                    if (!ConnectHandler(sock, CONNECTION_OUTGOING))
                    {
                        // Out of memory

                        running = FALSE;

                        break;
                    } /* if */
                } /* if */

                if ( (eventmask & FD_CLOSE) || (eventmask & FD_ERROR))
                {
                    // Close/Error handler, removes connections

                    CloseHandler(sock);
                } /* if */
            } /* while */
        } /* if */
    } /* while */

    // Free the lists used by this process

    FreeMinList(&ConnectionList, LISTTYPE_CONNECTION);
    FreeMinList(&HostCacheList, LISTTYPE_HOSTCACHE);

    // Free the timer.device resources

    FreeTimer();

    // Close our local copy of bsdsocket.library

    CloseLibrary(SocketBase);

    // Release the semaphore lock on IPCMTNMsgPort so the main process can free it

    ReleaseSemaphore(&IPCMTNMsgPortSemaphore);

    // Free the signal bits

    FreeSignal(netsigbit);
    FreeSignal(ipcsigbit);
} /* NetworkHandler() */

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

BOOL InitTimer()
{
    // Prepare the timer.device for 1 second interval signals

    // Allocate a MsgPort

    if (!(TimerMsgPort = CreatePort(0, 0)))
    {
        // Out of memory

        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

        return(FALSE);
    } /* if */

    // Allocate an IORequest

    if (!(TimerIO = (struct timerequest *)CreateExtIO(TimerMsgPort, sizeof(struct timerequest))))
    {
        // Out of memory

        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

        return(FALSE);
    } /* if */

    // Open timer.device

    if (OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *)TimerIO, 0))
    {
        // Failed to open timer.device

        ShowErrorMessage("[Network Process] Can't open timer.device", ERRORMSG_LOWMEM);

        return(FALSE);
    } /* if */

    // Bug workaround (WaitIO() may hang if no requests have been sent, workaround is to set ln_Type to 0)

    TimerIO->tr_node.io_Message.mn_Node.ln_Type = 0;

    // Send initial timer request

    TimerIO->tr_node.io_Command = TR_ADDREQUEST;
    TimerIO->tr_time.tv_secs    = 1;
    TimerIO->tr_time.tv_micro   = 0;

    SendIO((struct IORequest *)TimerIO);

    return(TRUE);
} /* InitTimer() */

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

void FreeTimer()
{
    // Free the timer.device resources

    if (TimerIO)
    {
        // Abort any pending IO requests

        if (!CheckIO((struct IORequest *)TimerIO))
        {
            AbortIO((struct IORequest *)TimerIO);
        } /* if */

        WaitIO((struct IORequest *)TimerIO);

        // Close timer.device

        CloseDevice((struct IORequest *)TimerIO);

        // Free the IORequest

        DeleteExtIO((struct IORequest *)TimerIO);
    } /* if */

    // Free the MsgPort

    DeletePort(TimerMsgPort);
} /* FreeTimer() */

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

BOOL BeginConnect()
{
    // Begin connecting to the network

    // Clear the ConnectionList and Connection Manager NList

    ClearConnectionList();

    // Tell the user we're connecting to the cache server

    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Connecting to cache server...");

    // Connect to the cache server to obtain a list of servents

    if (!GnutellaCache())
    {
        // Failed to obtain the list of servents

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Exit quietly

            return(FALSE);
        } /* if */

        // Did we run out of memory?

        if (OutOfMemory)
        {
            // We ran out of memory, exit immediately

            return(FALSE);
        } /* if */

        return(FALSE);
    } /* if */

    // Reset the connection counters

    IncomingConnections = 0;
    OutgoingConnections = 0;
    PendingConnections  = 0;

    // Tell the user we will begin branching the network shortly

    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_INFO_TEXT_MAIN],    3,  MUIM_Set,   MUIA_Text_Contents, "Branching network...");
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_STATUS_LAMP_MAIN],  3,  MUIM_Set,   MUIA_Lamp_Color,    MUIV_Lamp_Color_Connecting);

    return(TRUE);
} /* BeginConnect() */

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

void Disconnect()
{
    struct ConnectionNode *connectionnode;
    struct Library *SocketBase;

    // Disconnect from the network

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Send Connection Manager NList to sleep whilst we carry out modifications

    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 3, MUIM_Set, MUIA_NList_Quiet, TRUE);

    // Clean up the ConnectionList

    connectionnode = (struct ConnectionNode *)ConnectionList.mlh_Head;

    while (connectionnode->c_MinNode.mln_Succ)
    {
        // Do we need to close this connection?

        if ( (connectionnode->c_Status == STATUS_CONNECTING) || (connectionnode->c_Status == STATUS_CONNECTED) )
        {
            // Close the socket

            CloseSocket(connectionnode->c_Socket);

            // Mark the ConnectionNode as closed

            connectionnode->c_Status    = STATUS_CLOSED;
            connectionnode->c_Socket    = -1;

            // Begin the count until the ConnectionNode is destroyed

            connectionnode->c_KillTimeout = TIME_KILL;
        } /* if */

        // Go to the next ConnectionNode

        connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;
    } /* while */

    // Wake the Connection Manager NList up again

    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 3,  MUIM_Set,   MUIA_NList_Quiet,       FALSE);

    // Free the HostCacheList

    FreeMinList(&HostCacheList, LISTTYPE_HOSTCACHE);

    // Reset the connection counts

    IncomingConnections = 0;
    OutgoingConnections = 0;
    PendingConnections  = 0;

    // Tell the user we've disconnected from the network

    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_INFO_TEXT_MAIN],            3,  MUIM_Set,   MUIA_Text_Contents,     "Not connected.");
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_STATUS_LAMP_MAIN],          3,  MUIM_Set,   MUIA_Lamp_Color,        MUIV_Lamp_Color_Off);

    // Enable gadgets/menu items which can't be used whilst connected

    DoMethod(App,   MUIM_Application_PushMethod,    Menus[MID_ABOUT_MAIN],                  3,  MUIM_Set,   MUIA_Menuitem_Enabled,  TRUE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_HCHOSTNAME_STRING_SETUP],   3,  MUIM_Set,   MUIA_Disabled,          FALSE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_HCPORT_STRING_SETUP],       3,  MUIM_Set,   MUIA_Disabled,          FALSE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_LISTENPORT_STRING_SETUP],   3,  MUIM_Set,   MUIA_Disabled,          FALSE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_MAXINCOMING_SLIDER_SETUP],  3,  MUIM_Set,   MUIA_Disabled,          FALSE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_MAXOUTGOING_SLIDER_SETUP],  3,  MUIM_Set,   MUIA_Disabled,          FALSE);

    // Toggle the Connect/Disconnect button states

    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_CONNECT_BUTTON_MAIN],       3,  MUIM_Set,   MUIA_Disabled,          FALSE);
    DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_DISCONNECT_BUTTON_MAIN],    3,  MUIM_Set,   MUIA_Disabled,          TRUE);
} /* Disconnect() */

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

BOOL ConnectHandler(LONG sock, UBYTE type)
{
    LONG value;
    struct ConnectionNode *connectionnode;
    struct GnutellaDescriptor gd;
    struct Library *SocketBase;
    UBYTE tmpbyte;

    // Connect handler, performs handshake and establishes pending connections

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Check if this is an incoming or outgoing connection

    if (type == CONNECTION_OUTGOING)
    {
        // Find the ConnectionNode associated with this socket

        if (!(connectionnode = FindConnectionNode(sock)))
        {
            // Can't find socket in list, this shouldn't happen

            ShowErrorMessage("Can't find socket in ConnectionList!", ERRORMSG_EASYREQ);

            return(TRUE);
        } /* if */

        // Can we open one more connection?

        if (!(OutgoingConnections < MaxOutgoingConns))
        {
            // Already have enough outgoing connections

            CloseSocket(sock);

            // Mark the ConnectionNode as failed to connect

            connectionnode->c_Status    = STATUS_FAILED;
            connectionnode->c_Socket    = -1;

            // Begin the count until the ConnectionNode is destroyed

            connectionnode->c_KillTimeout = TIME_KILL;

            // Update the connection count

            PendingConnections --;

            // Update the entry in the Connection Manager NList

            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);

            return(TRUE);
        } /* if */

        // Set socket to synchronous, blocking

        value = 0;
        IoctlSocket(sock,   FIOASYNC,   (char *)&value);
        IoctlSocket(sock,   FIONBIO,    (char *)&value);

        // Perform an outgoing handshake with the servent

        if (!GnutellaHandshake(sock, HANDSHAKE_OUTGOING))
        {
            // Handshake failed

            CloseSocket(sock);

            // Mark the ConnectionNode as failed to connect

            connectionnode->c_Status    = STATUS_FAILED;
            connectionnode->c_Socket    = -1;

            // Begin the count until the ConnectionNode is destroyed

            connectionnode->c_KillTimeout = TIME_KILL;

            // Update the connection count

            PendingConnections --;

            // Update the entry in the Connection Manager NList

            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);

            return(TRUE);
        } /* if */

        // Set the socket to asynchronous, non-blocking and set the events

        value = 1;
        IoctlSocket(sock,   FIOASYNC,   (char *)&value);
        IoctlSocket(sock,   FIONBIO,    (char *)&value);

        value = FD_READ | FD_WRITE | FD_CLOSE | FD_ERROR;
        setsockopt(sock, SOL_SOCKET, SO_EVENTMASK, &value, sizeof(LONG));

        // Mark the ConnectionNode as having connected

        connectionnode->c_Status = STATUS_CONNECTED;

        // Check if there is any more data in the packet

        if (recv(sock, &tmpbyte, 1, MSG_PEEK) > 0)
        {
            // Bearshare clients like to chuck in a few packets after the GNUTELLA OK\n\n message.
            // We have to catch this here, because we won't receive an FD_READ signal for it

            if (!ReadHandler(sock))
            {
                // Out of memory

                return(FALSE);
            } /* if */
        } /* if */

        // Ping the user to keep the connection alive

        // Fill out a GnutellaDescriptor structure to represent a Gnutella ping of TTL 1

        gd.gd_ID        = NULL;
        gd.gd_Payload   = PAYLOAD_PING;
        gd.gd_TTL       = 1;
        gd.gd_Hops      = 0;
        gd.gd_Length    = 0;
        gd.gd_Data      = NULL;

        // Send the ping to the servent

        GnutellaSend(sock, &gd);

        // Update the connection count

        PendingConnections --;
        OutgoingConnections ++;

        // Update the entry in the Connection Manager NList

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);

        // Is this our only link to the network?

        if (OutgoingConnections == 1)
        {
            // We now have at least one link to the network

            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_INFO_TEXT_MAIN],    3,  MUIM_Set,   MUIA_Text_Contents, "Connected.");
            DoMethod(App,   MUIM_Application_PushMethod,    Gadgets[GID_STATUS_LAMP_MAIN],  3,  MUIM_Set,   MUIA_Lamp_Color,    MUIV_Lamp_Color_Ok);
        } /* if */

        return(TRUE);
    } /* if */

    else
    {
        // NOT YET IMPLEMENTED
    } /* else */

    return(TRUE);
} /* ConnectHandler() */

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

void CloseHandler(long sock)
{
    struct ConnectionNode *connectionnode;
    struct Library *SocketBase;

    // Close the given socket

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Find the ConnectionNode

    if (!(connectionnode = FindConnectionNode(sock)))
    {
        // Connection has already been closed

        return;
    } /* if */

    // Close the socket

    CloseSocket(connectionnode->c_Socket);

    // Due to a peculiarity (?) of sockets, we may receive an FD_CLOSE on an asynchronous connect which has
    // just failed. So we have to check we did actually connect before deciding how to update the connection
    // count.

    // Update the connection count

    if (connectionnode->c_Status == STATUS_CONNECTING)
    {
        PendingConnections--;
    } /* if */

    else
    {
        if (connectionnode->c_Type == CONNECTION_OUTGOING)
        {
            OutgoingConnections--;
        } /* if */

        else
        {
            IncomingConnections--;
        } /* else */
    } /* else */

    // Mark the ConnectionNode as dropped or failed

    if (connectionnode->c_Status == STATUS_CONNECTING)
    {
        connectionnode->c_Status = STATUS_FAILED;
    } /* if */

    else
    {
        connectionnode->c_Status = STATUS_DROPPED;
    } /* else */

    connectionnode->c_Socket = -1;

    // Begin the count until the ConnectionNode is destroyed

    connectionnode->c_KillTimeout = TIME_KILL;

    // Update the entry in the Connection Manager

    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);
} /* CloseHandler() */

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

BOOL ReadHandler(LONG sock)
{
    LONG bytesread;
    struct ConnectionNode *connectionnode;
    struct Library *SocketBase;
    UBYTE *bodybuffer, headerbuffer[23], tmpbyte;
    ULONG bodylength;

    // Read handler, acts upon incoming data

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Find the ConnectionNode associated with this socket

    if (!(connectionnode = FindConnectionNode(sock)))
    {
        // Can't find socket in list, we've probably disconnected it

        return(TRUE);
    } /* if */

    // Keep reading while there is data to be read

    while ( (recv(sock, &tmpbyte, 1, MSG_PEEK) > 0) && (connectionnode->c_Status == STATUS_CONNECTED) )
    {
        // Check if we already have the header or not, or part of it

        if (connectionnode->c_BytesRead < 23)
        {
            // Descriptor header incomplete or non-existant, read more

            bytesread = recv(sock, headerbuffer, (23 - connectionnode->c_BytesRead), 0);

            if (bytesread > 0)
            {
                // Increment flood level

                connectionnode->c_FloodLevel += bytesread;

                // Have we reached the flood limit?

                if (connectionnode->c_FloodLevel >= FloodLimit)
                {
                    // Disconnect this servent, the bandwidth is too much for us to handle

                    CloseSocket(connectionnode->c_Socket);

                    // Mark the ConnectionNode as flooded

                    connectionnode->c_Status    = STATUS_FAILED;
                    connectionnode->c_Socket    = -1;

                    // Begin the count until the ConnectionNode is destroyed

                    connectionnode->c_KillTimeout = TIME_KILL;

                    // Update the entry in the Connection Manager

                    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);

                    // Update the connection count

                    if (connectionnode->c_Type == CONNECTION_OUTGOING)
                    {
                        OutgoingConnections--;
                    } /* if */

                    else
                    {
                        IncomingConnections--;
                    } /* else */

                    // Update the entry in the Connection Manager NList

                    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_List_Redraw, MUIV_NList_Redraw_All);

                    return(TRUE);
                } /* if */

                // Do we already have a buffer

                if (!(connectionnode->c_RecvBuffer))
                {
                    // No previous buffer, allocate a new one

                    if (!(connectionnode->c_RecvBuffer = AllocVec(bytesread, MEMF_CLEAR)))
                    {
                        // Out of memory

                        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                        // Set a flag to indicate we should try and exit immediately

                        OutOfMemory = TRUE;

                        return(FALSE);
                    } /* if */
                } /* if */

                else
                {
                    // Reallocate old buffer to accomodate new data

                    if (!(connectionnode->c_RecvBuffer = ReAllocVec(connectionnode->c_RecvBuffer, connectionnode->c_BytesRead, (connectionnode->c_BytesRead + bytesread) )))
                    {
                        // Out of memory

                        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                        // Set a flag to indicate we should try and exit immediately

                        OutOfMemory = TRUE;

                        return(FALSE);
                    } /* if */
                } /* else */
            } /* if */

            else
            {
                // No data read, wait for more

                continue;
            } /* else */

            // Update ConnectionNode's recv() buffer and byte count

            memcpy(&connectionnode->c_RecvBuffer[connectionnode->c_BytesRead], headerbuffer, bytesread);

            connectionnode->c_BytesRead += bytesread;

            // Do we now have the whole header?

            if (connectionnode->c_BytesRead < 23)
            {
                // Header incomplete, return later

                continue;
            } /* if */
        } /* if */

        // We have the whole header, check if there is any more data left to receive

        bodylength = connectionnode->c_RecvBuffer[19] | (connectionnode->c_RecvBuffer[20] << 8) | (connectionnode->c_RecvBuffer[21] << 16) | (connectionnode->c_RecvBuffer[22] << 24);

        // Perform a check on the length to check the stream hasn't fallen out of sync

        if (bodylength > 65535)
        {
            // This is a fairly safe bet the stream is out of sync. Disconnect immediately

            CloseSocket(connectionnode->c_Socket);

            // Mark the ConnectionNode as out of sync

            connectionnode->c_Status    = STATUS_OUTOFSYNC;
            connectionnode->c_Socket    = -1;

            // Begin the count until the ConnectionNode is destroyed

            connectionnode->c_KillTimeout = TIME_KILL;

            // Update the entry in the Connection Manager

            DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 2, MUIM_NList_Redraw, MUIV_NList_Redraw_All);

            // Update the connection count

            if (connectionnode->c_Type == CONNECTION_OUTGOING)
            {
                OutgoingConnections--;
            } /* if */

            else
            {
                IncomingConnections--;
            } /* else */

            continue;
        } /* if */

        if (connectionnode->c_BytesRead < (23 + bodylength))
        {
            // More body data to read

            // Allocate a temporary buffer to place the data in

            if (!(bodybuffer = AllocVec((bodylength + 23 - connectionnode->c_BytesRead), MEMF_CLEAR)))
            {
                // Out of memory

                ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                // Set a flag to indicate we should try and exit immediately

                OutOfMemory = TRUE;

                return(FALSE);
            } /* if */

            bytesread = recv(sock, bodybuffer, (bodylength + 23 - connectionnode->c_BytesRead), 0);

            if (bytesread > 0)
            {
                // Reallocate old buffer to accomodate new data

                if (!(connectionnode->c_RecvBuffer = ReAllocVec(connectionnode->c_RecvBuffer, connectionnode->c_BytesRead, (connectionnode->c_BytesRead + bytesread) )))
                {
                    // Out of memory

                    ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                    // Set a flag to indicate we should try and exit immediately

                    OutOfMemory = TRUE;

                    return(FALSE);
                } /* if */

                // Update ConnectionNode's recv() buffer and byte count

                memcpy(&connectionnode->c_RecvBuffer[connectionnode->c_BytesRead], bodybuffer, bytesread);

                connectionnode->c_BytesRead += bytesread;

                // Free the temporary buffer

                FreeVec(bodybuffer);

                // Do we now have the whole body?

                if (connectionnode->c_BytesRead < (bodylength + 23))
                {
                    // Body incomplete, return later

                    continue;
                } /* if */
            } /* if */

            else
            {
                // No data read, wait for more

                FreeVec(bodybuffer);

                continue;
            } /* else */
        } /* if */

        // Whole descriptor now received, parse it

//        GnutellaParse(connectionnode);

        // Free the ConnectionNode's recv() buffer and reset the byte count

        FreeVec(connectionnode->c_RecvBuffer);

        connectionnode->c_RecvBuffer = NULL;
        connectionnode->c_BytesRead = 0;
    } /* while */

    return(TRUE);
} /* ReadHandler() */

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

BOOL WriteHandler(long sock)
{
    long bytessent;
    struct ConnectionNode *connectionnode;
    struct Library *SocketBase;

    // Write handler, sends data waiting to be sent

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Find the ConnectionNode associated with this socket

    if (!(connectionnode = FindConnectionNode(sock)))
    {
        // Can't find socket in list, we've probably disconnected it

        return(TRUE);
    } /* if */

    // Do we have any data to send?

    if (!(connectionnode->c_SendBuffer))
    {
        // No data to send

        return(TRUE);
    } /* if */

    // Send as much as possible of the data

    if ((bytessent = send(sock, connectionnode->c_SendBuffer, connectionnode->c_BytesToSend, 0)) < 1)
    {
        // No data could be sent, try again later

        return(TRUE);
    } /* if */

    // Was all the data sent?

    if (bytessent < connectionnode->c_BytesToSend)
    {
        // Only some of the data was sent, adjust the buffer accordingly

        // Overwrite the sent data with the unsent data

        memcpy(connectionnode->c_SendBuffer, &connectionnode->c_SendBuffer[bytessent], (connectionnode->c_BytesToSend - bytessent));

        // Adjust the buffer size

        if (!(connectionnode->c_SendBuffer = ReAllocVec(connectionnode->c_SendBuffer, connectionnode->c_BytesToSend, (connectionnode->c_BytesToSend - bytessent))))
        {
            // Out of memory

            ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

            // Set a flag to indicate we should try and exit immediately

            OutOfMemory = TRUE;

            return(FALSE);
        } /* if */

        // Update the sent byte count

        connectionnode->c_BytesToSend -= bytessent;
    } /* if */

    else
    {
        // All data sent, free the buffer and reset sent byte count

        FreeVec(connectionnode->c_SendBuffer);
        connectionnode->c_SendBuffer = NULL;

        connectionnode->c_BytesToSend = 0;
    } /* else */
} /* WriteHandler() */

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

BOOL GnutellaCache()
{
    LONG sock;
    struct GnutellaDescriptor gd;
    struct HostCacheNode *hostcachenode;
    struct hostent *he;
    struct Library *SocketBase;
    struct sockaddr_in sin;
    UBYTE recvbuffer[370];
    UWORD loop;

    // Connect to the cache server to obtain a list of servents

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Perform the hostname lookup

    if (!(he = gethostbyname(HCHostname)))
    {
        // Failed to lookup hostname

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Exit quietly

            return(FALSE);
        } /* if */

        // Tell the user the hostname lookup failed

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Host cache lookup failed.");

        return(FALSE);
    } /* if */

    // Create a socket for the connection

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        // Failed to create socket

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Exit quietly

            return(FALSE);
        } /* if */

        // Tell the user we failed to create a socket

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Can't create socket.");

        return(FALSE);
    } /* if */

    // Fill in the sockaddr_in structure

    memcpy(&sin.sin_addr, he->h_addr, he->h_length);

    sin.sin_family = AF_INET;
    sin.sin_port = htons(HCPort);

    // Make a connection to the host cache

    if (connect(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) == -1)
    {
        // Connection to the host cache failed

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Close the socket

            CloseSocket(sock);

            // Exit quietly

            return(FALSE);
        } /* if */

        // Tell the user we failed to connect to the host cache

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Can't connect to host cache.");

        // Close the socket

        CloseSocket(sock);

        return(FALSE);
    } /* if */

    // Perform an outgoing handshake with the host cache

    if (!GnutellaHandshake(sock, HANDSHAKE_OUTGOING))
    {
        // Handshake with cache server failed

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Close the socket

            CloseSocket(sock);

            // Exit quietly

            return(FALSE);
        } /* if */

        // Tell the user the handshake failed

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Handshake failed.");

        // Close the socket

        CloseSocket(sock);

        return(FALSE);
    } /* if */

    // Tell the user we're receiving the servent list

    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Receiving servent list...");

    // Fill out a GnutellaDescriptor structure to represent a Gnutella ping of TTL 1

    gd.gd_ID        = NULL;
    gd.gd_Payload   = PAYLOAD_PING;
    gd.gd_TTL       = 1;
    gd.gd_Hops      = 0;
    gd.gd_Length    = 0;
    gd.gd_Data      = NULL;

    // Send the ping to the host cache

    if (!GnutellaSend(sock, &gd))
    {
        // Failed to send the ping

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Close the socket

            CloseSocket(sock);

            // Exit quietly

            return(FALSE);
        } /* if */

        // Did we run out of memory?

        if (OutOfMemory)
        {
            // We ran out of memory, exit immediately

            // Close the socket

            CloseSocket(sock);

            return(FALSE);
        } /* if */

        // Tell the user we failed to send the ping

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Can't send ping.");

        // Close the socket

        CloseSocket(sock);

        return(FALSE);
    } /* if */

    // Wait for a reply of 10 Gnutella pongs ( = 370 bytes)

    if (recv(sock, recvbuffer, 370, MSG_WAITALL) != 370)
    {
        // Failed to receive 10 pongs from the host cache

        // Were we interrupted by the main task?

        if (MNInterrupt)
        {
            // Close the socket

            CloseSocket(sock);

            // Exit quietly

            return(FALSE);
        } /* if */

        // Tell the user we received an invalid response

        DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_INFO_TEXT_MAIN], 3, MUIM_Set, MUIA_Text_Contents, "Invalid response from host cache.");

        // Close the socket

        CloseSocket(sock);

        return(FALSE);
    } /* if */

    // Disconnect from the cache server

    CloseSocket(sock);

    // Produce 10 HostCacheNodes from the Gnutella pongs just received

    for (loop = 0; loop < 370; loop += 37)
    {
        // Allocate a HostCacheNode

        if (!(hostcachenode = AllocVec(sizeof(struct HostCacheNode), 0)))
        {
            // Out of memory

            ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

            // Set a flag to indicate we should try and exit immediately

            OutOfMemory = TRUE;

            return(FALSE);
        } /* if */

        // Fill in the HostCacheNode

        hostcachenode->hc_Port = (recvbuffer[loop + 23]) | (recvbuffer[loop + 24] << 8);
        hostcachenode->hc_Addr.s_addr = (recvbuffer[loop + 25] << 24) | (recvbuffer[loop + 26] << 16) | (recvbuffer[loop + 27] << 8) | (recvbuffer[loop + 28]);

        // Add the HostCacheNode to the HostCacheList

        AddTail((struct List *)&HostCacheList, (struct Node *)hostcachenode);
    } /* for */

    // Set the HostCacheCount

    HostCacheCount = 10;

    // Initial list of servents received OK

    return(TRUE);
} /* GnutellaCache() */

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

BOOL GnutellaHandshake(LONG sock, UBYTE type)
{
    struct Library *SocketBase;
    UBYTE recvbuffer[13];

    // Perform an incoming/outgoing handshake with the given servent

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Which type of handshake do we need to perform?

    if (type == HANDSHAKE_OUTGOING)
    {
        // Perform an outgoing handshake

        // Send initial connect message

        if (send(sock, "GNUTELLA CONNECT/0.4\n\n", 22, 0) == -1)
        {
            // Failed to send message

            return(FALSE);
        } /* if */

        // Wait for a handshake reply

        if (recv(sock, recvbuffer, 13, MSG_WAITALL) != 13)
        {
            // Failed to receive handshake reply

            return(FALSE);
        } /* if */

        // Check the reply was correct

        if (memcmp(recvbuffer, "GNUTELLA OK\n\n", 13) != 0)
        {
            // Incorrect response to handshake

            return(FALSE);
        } /* if */

        // Handshake OK

        return(TRUE);
    } /* if */

    else
    {
        // Perform an incoming handshake

        // NOT YET IMPLEMENTED

        return(FALSE);
    } /* else */
} /* GnutellaHandshake() */

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

BOOL GnutellaSend(LONG sock, struct GnutellaDescriptor *gd)
{
    struct ConnectionNode *connectionnode;
    struct Library *SocketBase;
    UBYTE *sendbuffer;

    // Send a Gnutella message through the socket

    // Note: if the socket is blocking, it is assumed we're communicating with a host cache

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Allocate a buffer to store the message in

    if (!(sendbuffer = AllocVec((23 + gd->gd_Length), 0)))
    {
        // Out of memory

        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

        // Set a flag to indicate we should try and exit immediately

        OutOfMemory = TRUE;

        return(FALSE);
    } /* if */

    // Fill out the buffer

    // Were we given an ID?

    if (gd->gd_ID)
    {
        // Copy given ID to message

        memcpy(sendbuffer, gd->gd_ID, 16);
    } /* if */

    else
    {
        // Generate a random ID

        RandomID(sendbuffer);
    } /* else */

    sendbuffer[16] = gd->gd_Payload;
    sendbuffer[17] = gd->gd_TTL;
    sendbuffer[18] = gd->gd_Hops;
    sendbuffer[19] = (gd->gd_Length & 0xff);
    sendbuffer[20] = ((gd->gd_Length >> 8) & 0xff);
    sendbuffer[21] = ((gd->gd_Length >> 16) & 0xff);
    sendbuffer[22] = (gd->gd_Length >> 24);

    // Do we have extra data to add?

    if (gd->gd_Length > 0)
    {
        // Copy the extra data to our buffer

        memcpy(&sendbuffer[23], gd->gd_Data, gd->gd_Length);
    } /* if */

    // Attempt to send the message

    if (send(sock, sendbuffer, (23 + gd->gd_Length), 0) < 23)
    {
        // Failed to send the whole message

        if (Errno() != EAGAIN)
        {
            // The socket is blocking: send() either failed or was interrupted

            FreeVec(sendbuffer);

            return(FALSE);
        } /* if */

        // Data can't be sent at the moment, store for sending later

        // Find the ConnectionNode associated with this socket

        if (!(connectionnode = FindConnectionNode(sock)))
        {
            // Can't find socket in list, this shouldn't happen

            ShowErrorMessage("Can't find socket in ConnectionList!", ERRORMSG_EASYREQ);

            FreeVec(sendbuffer);

            return(FALSE);
        } /* if */

        // Store the data to send in the send buffer

        // Do we already have a buffer?

        if (!(connectionnode->c_SendBuffer))
        {
            // No previous buffer, allocate a new one

            if (!(connectionnode->c_SendBuffer = AllocVec((23 + gd->gd_Length), 0)))
            {
                // Out of memory

                ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                // Set a flag to indicate we should try and exit immediately

                OutOfMemory = TRUE;

                return(FALSE);
            } /* if */
        } /* if */

        else
        {
            // Reallocate old buffer to accomodate new data

            if (!(connectionnode->c_SendBuffer = ReAllocVec(connectionnode->c_SendBuffer, connectionnode->c_BytesToSend, (connectionnode->c_BytesToSend + (23 + gd->gd_Length)))))
            {
                // Out of memory

                ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

                // Set a flag to indicate we should try and exit immediately

                OutOfMemory = TRUE;

                return(FALSE);
            } /* if */
        } /* else */

        memcpy(&connectionnode->c_SendBuffer[connectionnode->c_BytesToSend], sendbuffer, (23 + gd->gd_Length));

        // Update the send count

        connectionnode->c_BytesToSend += (23 + gd->gd_Length);
    } /* if */

    // Free the buffer

    FreeVec(sendbuffer);

    // Message sent OK

    return(TRUE);
} /* GnutellaSend() */

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

BOOL GnutellaBranch()
{
    LONG sock, value;
    struct ConnectionNode *connectionnode;
    struct HostCacheNode *hostcachenode;
    struct Library *SocketBase;
    struct sockaddr_in sin;

    // Form a new network branch

    // Retrieve the pointer to our task's copy of SocketBase

    SocketBase = GetSocketBase();

    // Do we have any hosts left to branch to?

    if (IsMinListEmpty(&HostCacheList))
    {
        // Host cache is empty

        return(FALSE);
    } /* if */

    // Retrieve and remove the next entry in the HostCacheList

    hostcachenode = (struct HostCacheNode *)HostCacheList.mlh_Head;

    Remove((struct Node *)hostcachenode);

    // Check we're not trying to connect to ourself (which would be rather embarassing ;))

    if (hostcachenode->hc_Addr.s_addr == LocalIP)
    {
        // Oh dear, we *did* try to connect to ourselves. Let's leave before anyone notices ;)

        // Free the HostCacheNode

        FreeVec(hostcachenode);

        return(FALSE);
    } /* if */

    // Check we haven't already make a connection to this host

    connectionnode = (struct ConnectionNode *)ConnectionList.mlh_Head;

    while (connectionnode->c_MinNode.mln_Succ)
    {
        // Compare IP addresses

        if ( (connectionnode->c_Addr.s_addr == hostcachenode->hc_Addr.s_addr) && ( (connectionnode->c_Status == STATUS_CONNECTED) || (connectionnode->c_Status == STATUS_CONNECTING) ) )
        {
            // We've already made/making a connection to this host

            // Free the HostCacheNode

            FreeVec(hostcachenode);

            // Ignore this branch attempt

            return(TRUE);
        } /* if */

        connectionnode = (struct ConnectionNode *)connectionnode->c_MinNode.mln_Succ;
    } /* while */

    // Fill in the sockaddr_in structure

    sin.sin_addr    = hostcachenode->hc_Addr;
    sin.sin_family  = AF_INET;
    sin.sin_port    = htons(hostcachenode->hc_Port);

    // Free the HostCacheNode

    FreeVec(hostcachenode);

    // Create a socket for the connection

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        // Failed to create the socket

        // Tell the user we failed to create the socket

        ShowErrorMessage("Can't create socket", ERRORMSG_EASYREQ);

        // Ignore this branch attempt

        return(TRUE);
    } /* if */

    // Set the socket to asynchronous, non-blocking, keep alive and set its events

    value = 1;
    IoctlSocket(sock,   FIOASYNC,   (char *)&value);
    IoctlSocket(sock,   FIONBIO,    (char *)&value);
    IoctlSocket(sock,   FIOSETOWN,  (char *)&NetworkProcess);

    value = 1;
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(LONG));

    value = FD_CONNECT | FD_CLOSE | FD_ERROR;
    setsockopt(sock, SOL_SOCKET, SO_EVENTMASK, &value, sizeof(LONG));

    // Allocate a ConnectionNode

    if (!(connectionnode = AllocVec(sizeof(struct ConnectionNode), 0)))
    {
        // Out of memory

        ShowErrorMessage("[Network Process] Out of memory", ERRORMSG_LOWMEM);

        // Set a flag to indicate we should try and exit immediately

        OutOfMemory = TRUE;

        return(FALSE);
    } /* if */

    // Fill out the ConnectionNode

    connectionnode->c_Socket            = sock;
    connectionnode->c_Addr.s_addr       = sin.sin_addr.s_addr;
    connectionnode->c_Status            = STATUS_CONNECTING;
    connectionnode->c_Type              = CONNECTION_OUTGOING;
    connectionnode->c_ConnectTimeout    = TIME_CONNECTTIMEOUT;
    connectionnode->c_KillTimeout       = 0;
    connectionnode->c_RecvBuffer        = NULL;
    connectionnode->c_BytesRead         = 0;
    connectionnode->c_SendBuffer        = NULL;
    connectionnode->c_BytesToSend       = 0;
    connectionnode->c_FloodLevel        = 0;

    sprintf(connectionnode->c_AddrStr,  "%s",   Inet_NtoA(sin.sin_addr.s_addr));
    sprintf(connectionnode->c_PortStr,   "hu",   sin.sin_port);

    // Add it to the ConnectionList

    AddTail((struct List *)&ConnectionList, (struct Node *)connectionnode);

    // Add the entry to the Connection Manager NList

    DoMethod(App, MUIM_Application_PushMethod, Gadgets[GID_CONNECTIONS_NLIST_MANAGER], 3, MUIM_NList_InsertSingle, connectionnode, MUIV_NList_Insert_Bottom);

    // Begin the connection

    connect(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr_in));

    // Update the connection count

    PendingConnections ++;

    return(TRUE);
} /* GnutellaBranch() */

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

// End Of Text
