/*  Receives, decodes the headers and delegates incoming RPC packets

    ©1999 Joseph Walton

    This software is distributed under the terms of the GNU General Public
    License; either version 2 of the License, or (at your option) any
    later version.
*/

#include "rpc.h"
#include "nfs.h"
#include "mount.h"
#include "pcnfsd.h"

#include "nfsd.h"
#include "memory.h"
#include "handle_list.h"
#include "nfs_utils.h"

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/socket.h>
#include <proto/timer.h>

extern BYTE *send_buffer, *recv_buffer; /* memory.c */

int handlePacket(int32 *ptr, int32 *limit, struct sockaddr_in *from);
int pcnfsd_procedure(struct Call *call, int32 *ptr, int32 *limit,
    int32 *rptr, int32 *rlimit);

void service(int s)
{
    struct sockaddr_in remote_sa;
    int l, addr_len;
    struct timeval timeout;
    struct timeval last_flushed;
    BOOL check_handles_pending = TRUE;

    fd_set fdset;

    GetSysTime(&last_flushed);

    while (TRUE) {
        FD_ZERO(&fdset);
        FD_SET(s, &fdset);

        if (check_handles_pending) {
            DEBUG(puts("Waiting for a request with a timeout"));
            timeout.tv_secs = FLUSH_HANDLES_EVERY;
            timeout.tv_micro = 0;
            l = WaitSelect(FD_SETSIZE, &fdset, NULL, NULL, &timeout, NULL);
        } else {
            DEBUG(puts("Waiting for a request without timeout"));
            l = WaitSelect(FD_SETSIZE, &fdset, NULL, NULL, NULL, NULL);
        }

        if (l < 0) {
            printSocketError("WaitSelect() failed");
            break;
        }

        if (l > 0) {
            addr_len = sizeof(remote_sa);
            l = recvfrom(s, (char *)recv_buffer, RECV_BUFFER_BYTES, 0, &remote_sa, &addr_len);

            if (l < 0) {
                printSocketError("recvfrom() failed");
                break;
            }

            DEBUG(printf("Packet received: %d bytes\n", l));

            /* Round it down */
            l -= (l % SINT32);

            if (addr_len == sizeof(remote_sa)) {
                int response_len =
                    handlePacket((int32 *)recv_buffer, (int32 *)(recv_buffer + l), &remote_sa);
                if (response_len >= 0) {
                    int res = sendto(s, (char *)send_buffer, response_len * SINT32, 0, &remote_sa, sizeof(remote_sa));
                    DEBUG(printf("Response sent: %d bytes\n", res));
                    if (res < 0) {
                        printSocketError("Could not send");
                    }
                }

                /* That may have had some effect on the handle list */
                check_handles_pending = TRUE;
            } else {
                printf("Unexpected address length - %d\n", addr_len);
            }
        }

        /* Keep a periodic check on wasted memory */
        struct timeval time_now;
        GetSysTime(&time_now);
        SubTime(&time_now, &last_flushed);
        if (time_now.tv_secs > FLUSH_HANDLES_EVERY) {
            if (verbose) {
                puts("Flushing unused NFS handles...");
            }
            check_handles_pending = flush_handles();
            GetSysTime(&last_flushed);
        }
    }
}

static int handlePacket(int32 *ptr, int32 *limit, struct sockaddr_in *from)
{
    int32 id;
    int l;

    struct Call call;

    int32 *rptr = send_buffer;
    int32 *rlimit = rptr + (SEND_BUFFER_BYTES / SINT32);

    if ((limit - ptr) < 6)
        return -1;

    call.c_hasBeenPrinted = FALSE;

    // Assignment of structures
    call.c_from = *from;

    id = G_UINT(*ptr++);

    /* One could, in theory, keep track of IDs and the corresponding
        responses for swift retransmissions of repeated requests.
       This is not done here, as it would require a fairly complex
        cache and a fair bit of memory management, and would only benefit
        us if packets were getting regularly lost on the way to the client.
    */

    if (G_ENUM(*ptr++) != CALL) { // This is a call?
        // We should simply ignore this silently
        return -1;
    }

    if (G_UINT(*ptr++) != RPC_VERSION) {
        /* Requires 6 int32s */
        *rptr++ = P_UINT(id);
        *rptr++ = P_ENUM(REPLY);
        *rptr++ = P_ENUM(MSG_DENIED);
        *rptr++ = P_ENUM(RPC_MISMATCH);
        *rptr++ = P_UINT(RPC_VERSION);
        *rptr++ = P_UINT(RPC_VERSION);

        return 6;
    }

    call.c_program = G_UINT(*ptr++);
    call.c_version = G_UINT(*ptr++);
    call.c_procedure = G_UINT(*ptr++);

//    printf("Program: %d, Version: %d, Procedure: %d\n", call.c_program,
//        call.c_version, call.c_procedure);

    /* Authorisation */
    ptr = readAuth(ptr, limit, &call.c_auth);
    if (!ptr)
        return -1;

    if ((limit - ptr) < 2)
        return -1;

    /* We're not bothered about the verifier */
    *ptr++;
    l = G_UINT(*ptr++);
    ptr += INT32S(l);
    if (ptr >= limit) /* Check we've not overcommitted */
        return -1;

    /* Check what authentication we got */
/*    if (call.c_auth.isUnix) {
        printf(" Name: %s, UID: %u GID: %u (%u secondary)\n",
            call.c_auth.machineName, call.c_auth.uid,
            call.c_auth.gid, call.c_auth.numGids);
    } else {
        puts("Non-UNIX authentication!");
*/
        /* Ironically, we'd fail DES as 'too weak' */
/*        *rptr++ = P_UINT(id);
        *rptr++ = P_ENUM(REPLY);
        *rptr++ = P_ENUM(MSG_DENIED);
        *rptr++ = P_ENUM(AUTH_ERROR);
        *rptr = P_ENUM(AUTH_TOOWEAK);
        return 5;
    }
*/
    /* We've accepted the request */
    rptr = PrepareAcceptedHeader(id, rptr);
    // 5 int32s

    switch (call.c_program) {
        case NFS_PROGRAM:
            return nfs_procedure(&call, ptr, limit, rptr, rlimit);

        case MOUNT_PROGRAM:
            return mount_procedure(&call, ptr, limit, rptr, rlimit);

        case PCNFSD_PROGRAM:
            return pcnfsd_procedure(&call, ptr, limit, rptr, rlimit);

        default:
            *rptr = P_ENUM(PROG_UNAVAIL);
            return 6;
    }
}

