/*  Register (and deregister) RPC services

    ©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 "rpc_register.h"

#include "util.h"

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

#ifdef AMITCP
 #include <amitcp/socketbasetags.h>
#else
 #include <bsdsocket/socketbasetags.h>
#endif

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

static int32 make_xid(void)
{
    struct timeval now;

    GetSysTime(&now);
    return ((int32)now.tv_secs ^ (int32)now.tv_micro ^ (int32)FindTask(NULL));
}

static int32 mem[14];
static char buffer[1024];

/* This is called very rarely - psychotic optimisations would be
    self-defeating */
static BOOL pmap_action(int socket, int action, int prog, int version, int port)
{
    int l, addr_len, id;

    struct timeval try_until, resend_at;

    int32 *ptr;

    struct sockaddr_in addr;

    id = make_xid();
    ptr = mem;
    *ptr++ = P_UINT(id);
    *ptr++ = P_ENUM(CALL);
    *ptr++ = P_UINT(RPC_VERSION);
    *ptr++ = P_UINT(PMAP_PROGRAM);
    *ptr++ = P_UINT(PMAP_VERSION);
    *ptr++ = P_UINT(action);

    *ptr++ = P_ENUM(AUTH_NONE);
    *ptr++ = P_UINT(0);
    *ptr++ = P_ENUM(AUTH_NONE);
    *ptr++ = P_UINT(0);

    *ptr++ = P_UINT(prog);
    *ptr++ = P_UINT(version);
    *ptr++ = P_UINT(IPPROTO_UDP);
    *ptr   = P_UINT(port);

    // 14 int32s

    GetSysTime(&try_until);
    resend_at = try_until;
    try_until.tv_secs += RPC_REGISTER_TIMEOUT_AFTER;

    while (TRUE) {
        struct timeval now, timeout;
        fd_set fdset;
        int r;

        GetSysTime(&now);

        /* There is a wait so long? */
        if (CmpTime(&now, &try_until) <= 0) {
            puts("Portmap operation attempt timed out");
            return FALSE;
        }

        /* Should we resend? */
        if (CmpTime(&now, &resend_at) <= 0) {
            int length = 14 * sizeof(int32);
            addr.sin_family = AF_INET;
            addr.sin_port = htons(PMAP_PORT);
            addr.sin_addr.s_addr = LOCAL_ADDR;

            l = sendto(socket, (char *)mem, length, 0, &addr, sizeof(addr));
            if (l != length) {
                printSocketError("Could not send to localhost");
                return FALSE;
            }
            resend_at.tv_sec += RPC_REGISTER_SEND_EVERY;
        }

        /* Want to wait for a valid response - what's the next scheduled
            checkpoint? */
        if (CmpTime(&resend_at, &try_until) < 0) {
            timeout = try_until;
        } else {
            timeout = resend_at;
        }
        SubTime(&timeout, &now);

        FD_ZERO(&fdset);
        FD_SET(socket, &fdset);

        r = WaitSelect(FD_SETSIZE, &fdset, NULL, NULL, &timeout, NULL);

        if (r > 0) {
            addr_len = sizeof(addr);
            l = recvfrom(socket, buffer, sizeof(buffer), 0, &addr, &addr_len);
            if (l < 0) {
                printSocketError("recvfrom() failed");
                return FALSE;
            }

            /* Make sure it's from 127.0.0.1 - that's where we sent to */
            if ((addr_len < sizeof(addr)) || (addr.sin_addr.s_addr != LOCAL_ADDR))
                continue;

            l /= sizeof(int32);
            if (l > 5) {
                int32 *ptr = (int32 *)buffer;
                int32 *limit = ptr + l;

                if ((id != G_INT(*ptr++)) || (G_INT(*ptr++) != REPLY))
                    continue;

                if (G_INT(*ptr++) != MSG_ACCEPTED) {
                    puts("Portmapper did not accept our registration message");
                    return FALSE;
                }

                if (G_INT(*ptr++) != SUCCESS) {
                    puts("Portmapper registration failed remotely");
                    return FALSE;
                }

                /* We don't care about authorisation */
                ptr++;
                ptr += INT32S(G_UINT(*ptr)) + 1;

                if (ptr < limit) {
                    /* This is the actual return code from the remote call */
                    return (BOOL)(1 == G_INT(*ptr));
                }
            }
        } else if (r < 0) {
            printSocketError("select() failed");
            return FALSE;
        }
    }
}

/* Simple user-visible wrappers */
BOOL pmap_set(int socket, int prog, int version, int port)
{
    return pmap_action(socket, PMAPPROC_SET, prog, version, port);
}

BOOL pmap_unset(int socket, int prog, int version)
{
    return pmap_action(socket, PMAPPROC_UNSET, prog, version, 0);
}

