//+ "/ GPServer - General Purpose Server für Envoy /"
/* GPServer - General Purpose Server für Envoy */

#define VERSION     "1.0"
#define DATE        "12.9.94"
#define PROGNAME    "GPServer"
//-
//+ "/ Includes /"
/* Includes */

#define USE_BUILTIN_MATH
#include <string.h>

#include <dos/dostags.h>
#include <envoy/envoy.h>
#include <envoy/errors.h>
#include <exec/memory.h>

#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#include <clib/intuition_protos.h>
#include <pragmas/intuition_pragmas.h>
#include <clib/nipc_protos.h>
#include <pragmas/nipc_pragmas.h>
#include <clib/timer_protos.h>
#include <pragmas/timer_pragmas.h>
//-

#include "GPServer.h"

//+ "/ libraries /"
/* libraries */
extern struct Library *DOSBase;
extern struct Library *SysBase;
static struct Library *IntuitionBase;
static struct Library *NIPCBase;

/* version string */
static const char version[] = "\0$VER: " PROGNAME " " VERSION " (" DATE ")";

/* Daten für unsere Entity */
static struct Entity *entity;
static ULONG entitysignal;
//-
//+ "/ Prototypen für Funktionen in dieser Datei /"
/* Prototypen für Funktionen in dieser Datei */
static BOOL getsystemtime(struct timeval *tv);
static BOOL openlibraries(void);
static void closelibraries(void);
static void handle_transaction(struct Transaction *ta);
static void processmessages(void);
static void transaction_cmdline(struct Transaction *ta);
static void transaction_gettime(struct Transaction *ta);
static void transaction_request(struct Transaction *ta);
//-

void main()
{   if (SysBase->lib_Version < 37) return; /* unter Kick 2.04 geht nichts! */

    /* alle benötigten Libraries öffnen */
    if (openlibraries())
    {   /* öffentliche Entity schaffen */
        if (entity = CreateEntity(ENT_Name, "GPServer",
                                  ENT_AllocSignal, (ULONG)&entitysignal,
                                  ENT_Public, TRUE, TAG_END))
        {   /* auf einkommende Nachrichten warten und diese bearbeiten */
            processmessages();

            /* Entity wieder schlissen */
            DeleteEntity(entity);
        }
        else PutStr("Kann Entity nicht erzeugen!\n");
    }
    /* alle geöffneten Libraries wieder schließen */
    closelibraries();
}

static BOOL openlibraries(void)
{   /* Hier öffnen wir alle benötigten Libraries. */
    if (!(IntuitionBase = OpenLibrary("intuition.library", 37)))
    {   PutStr("Kann die intuition.library ab V37 nicht öffnen!\n");
        return(FALSE);
    }
    if (!(NIPCBase = OpenLibrary("nipc.library", 39)))
    {   PutStr("Kann die nipc.library ab V39 nicht öffnen!\n");
        return(FALSE);
    }
    return(TRUE);
}

static void closelibraries(void)
{   /*  Hier schliessen wir alle Libraries */
    if (NIPCBase) CloseLibrary(NIPCBase);
    if (IntuitionBase) CloseLibrary(IntuitionBase);
}

static void processmessages(void)
{
    struct Transaction *ta;
    ULONG entitysigmask = 1 << entitysignal;
    ULONG sigs;

    while (TRUE)
    {   /*  Wir warten so lange auf Nachrichten an unsere Entity
         *  bis ein CTRL_C-Signal ankommt ... */
        sigs = Wait(entitysigmask | SIGBREAKF_CTRL_C);

        /*  Ist eine Transaction angekommen? */
        if (sigs & entitysigmask)
        {   /* Alle angekommenen Transactions bearbeiten ... */
            while (ta = GetTransaction(entity))
            {  handle_transaction(ta); }
        }
        /*  wenn wir ein CTRL_C Signal bekommen, beenden wir diese
         *  Endlosschleife und somit das ganze Programm */
        if (sigs & SIGBREAKF_CTRL_C) return;
    }
}

static void handle_transaction(struct Transaction *ta)
{
    if (ta->trans_Type == TYPE_SERVICING)
    {   /*  Hier behandeln wir alle Transactions vom Typ TYPE_SERVICING
            (siehe Phasen einer Transaction). Andere Typen treten in
            diesem einfachen Programm allerdings sowieso nicht auf. */

        ta->trans_RespDataActual = 0;
        ta->trans_Error = ENVOYERR_NOERROR;

        /*  Je nach Kommando der Transaction machen wir was anderes  */
        switch (ta->trans_Command)
        {
            case TACMD_REQUEST: transaction_request(ta);
                                break;
            case TACMD_CMDLINE: transaction_cmdline(ta);
                                break;
            case TACMD_GETTIME: transaction_gettime(ta);
                                break;
            default: /* Dieses Kommando kennen wir nicht ... */
                                ta->trans_Error = ENVOYERR_CMDUNKNOWN;
                                break;
        }
        /*  Nun schicken wir die Transaction zurück. */
        ReplyTransaction(ta);
    }
}

//+ "static void transaction_request(struct Transaction ta)"
static void transaction_request(struct Transaction *ta)
{
    static struct EasyStruct easyreq =
    {
        sizeof (struct EasyStruct), /* ULONG es_StructSize      */
        0,                          /* ULONG es_Flags           */
        NULL,                       /* UBYTE *es_Title          */
        NULL,                       /* UBYTE *es_TextFormat     */
        NULL,                       /* UBYTE *es_GadgetFormat   */
    };
    unsigned char *data;
    ULONG datalen;
    LONG *response;
    ULONG responselen;

    /* Bei dieser Transaction bekommen wir Daten und wir liefern eine
     * Antwort zurück.
     */
    data = ta->trans_RequestData;           /* Daten, die wir bekommen haben */
    datalen = ta->trans_ReqDataActual;      /* Länge dieser Daten in Bytes   */
    response = ta->trans_ResponseData;      /* Speicher für unsere Antwort   */
    responselen = ta->trans_RespDataLength; /* max. Länge unserer Antwort    */

    /*  Zur Sicherheit überprüfen wir die gewonnen Daten:
     *  + data und response dürfen nicht NULL sein
     *  + datalen muß größer 3 sein, da data mindestens 3 Nullbytes
     *    enthalten muß
     *  + das letzte Zeichen in data muß ein Nullbyte sein
     *  + responselen muß mindestens 4 sein, damit das Ergebnis Platz hat
     */
    if (data && datalen >= 3 && data[datalen - 1] == '\0' &&
        response && responselen >= sizeof(LONG))
    {
        /*  Für EasyRequest() müssen wir nun die einzelnen Strings für
         *  Titel, Text und Gadgets aus dem Speicherbereich, auf den data
         *  zeigt, ermitteln: in Data folgen die drei Strings hintereinander,
         *  getrennt durch Nullbytes.
         */
        easyreq.es_Title = data;            /* der Titel                    */
        data = data + strlen(data) + 1;     /* etwas Pointerarithmetik      */
        easyreq.es_TextFormat = data;       /* der Text                     */
        data = data + strlen(data) + 1;     /* noch etwas Pointerarithmetik */
        easyreq.es_GadgetFormat = data;     /* die Gadgets                  */

        /*  Nun den Requester öffnen und das Ergebnis als Antwort in die
         *  Transaction eintragen.
         */
        *response = EasyRequestArgs(NULL, &easyreq, NULL, NULL);

        /*  Die Länge unserer Antwort nach trans_RespDataActual. */
        ta->trans_RespDataActual = sizeof(LONG);
    }
}
//-
//+ "static void transaction_cmdline(struct Transaction ta)"
static void transaction_cmdline(struct Transaction *ta)
{
    BPTR infh;
    BPTR outfh;
    unsigned char *data;
    unsigned char *command;
    unsigned char *output;
    ULONG datalen;

    /* Bei dieser Transaction bekommen wir nur Daten, liefern aber keine
     * Antwort (abgesehen von trans_Error) zurück.
     */
    data = ta->trans_RequestData;           /* Daten, die wir bekommen haben */
    datalen = ta->trans_ReqDataActual;      /* Länge dieser Daten in Bytes   */

    /*  Zur Sicherheit überprüfen wir die gewonnen Daten:
     *  + data darf nicht NULL sein
     *  + datalen muß größer 2 sein, da data mindestens zwei Nullbytes
     *    enthalten muß
     *  + das letzte Zeichen in data muß ein Nullbyte sein
     *
     *  (Antwort gibt es hier keine, abgesehen von trans_Error)
     */
    if (data && datalen >= 2 && data[datalen - 1] == '\0')
    {
        /* Die Länge unserer Antwort ist Null. */
        ta->trans_RespDataActual = 0;

        /* In Data folgen die beiden Strings für das Kommando
         * und die Ausgabedatei hintereinander, getrennt durch ein Nullbyte.
         */
        command = data;                     /* das Kommando                 */
        output = data + strlen(data) + 1;   /* die Ausgabedatei             */

        if (*output == '\0') output = "CON:";

        if (infh = Open("NIL:", MODE_NEWFILE))
        {
            if (outfh = Open(output, MODE_NEWFILE))
            {
                if (SystemTags(command, SYS_Asynch, TRUE,
                                        SYS_Input, infh,
                                        SYS_Output, outfh,
                                        TAG_END) == 0)
                {
                    /* Kein Fehler aufgetreten: zurück. */
                    return;
                }
                else ta->trans_Error = GPSERR_SYSTEMFAILED;

                /* Wenn SystemTags() fehlschlug, müssen wir die Filehandles
                 * wieder freigeben.
                 */
                Close(outfh);
            }
            else ta->trans_Error = GPSERR_OPENOUTFH;

            Close(infh);
        }
        else ta->trans_Error = GPSERR_OPENINFH;
    }
}
//-
//+ "static void transaction_gettime(struct Transaction ta)"
static void transaction_gettime(struct Transaction *ta)
{
    struct timeval *response;
    ULONG responselen;

    /* Bei dieser Transaction bekommen wir keine Daten, aber wir liefern eine
     * Antwort.
     */
    response = ta->trans_ResponseData;      /* Speicher für unsere Antwort   */
    responselen = ta->trans_RespDataLength; /* max. Länge unserer Antwort    */

    /*  Zur Sicherheit überprüfen wir die gewonnen Daten:
     *  + response darf nicht NULL sein
     *  + responselen == sizeof(struct timeval)
     */
    if (response && responselen == sizeof(struct timeval))
    {
        if (getsystemtime(response))
        {
            /* Die Länge unserer Antwort */
            ta->trans_RespDataActual = sizeof(struct timeval);
        }
        else
        {
            /* es ist ein Fehler aufgetreten */
            ta->trans_RespDataActual = 0;
            ta->trans_Error = GPSERR_NOTIME;
        }
    }
}
//-
//+ "static BOOL getsystemtime(struct timeval tv)"
static BOOL getsystemtime(struct timeval *tv)
{
    #define TimerBase ((struct Library *)timerio->tr_node.io_Device)
    struct timerequest *timerio;
    BOOL ok = FALSE;

    if (timerio = AllocMem(sizeof(struct timerequest), MEMF_PUBLIC | MEMF_CLEAR))
    {
        if (!OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)timerio, 0))
        {
            GetSysTime(tv);
            ok = TRUE;
            CloseDevice((struct IORequest *)timerio);
        }
        FreeMem(timerio, sizeof(struct timerequest));
    }
    return(ok);
}
//-

