/*
 * Copyright (c) 1990,1991 Regents of The University of Michigan.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation, and that the name of The University
 * of Michigan not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. This software is supplied as is without expressed or
 * implied warranties of any kind.
 *
 *	Research Systems Unix Group
 *	The University of Michigan
 *	c/o Mike Clark
 *	535 W. William Street
 *	Ann Arbor, Michigan
 *	+1-313-763-0525
 *	netatalk@itd.umich.edu
 */

/*!
 * @file
 * AppleTalk Echo Protocol Client
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <netatalk/at.h>
#include <atalk/compat.h>
#include <atalk/aep.h>
#include <atalk/nbp.h>
#include <atalk/netddp.h>
#include <atalk/ddp.h>
#include <atalk/util.h>

/* FIXME/SOCKLEN_T: socklen_t is a unix98 feature */
#ifndef SOCKLEN_T
#define SOCKLEN_T unsigned int
#endif /* ! SOCKLEN_T */

static struct sockaddr_at	target;
static int			sock;
static unsigned int		nsent = 0, nrecv = 0;
static time_t			totalms = 0, minms = -1, maxms = -1;
static unsigned int     	pings = 0;

static void usage(char *);

static void done(int sig _U_)
{
    if (nsent) {
        printf("\n----%d.%d AEP Statistics----\n",
               ntohs(target.sat_addr.s_net), target.sat_addr.s_node);
        printf("%d packets sent, %d packets received, %d%% packet loss\n",
               nsent, nrecv, ((nsent - nrecv) * 100) / nsent);

        if (nrecv) {
            printf("round-trip (ms)  min/avg/max = %ld/%ld/%ld\n",
                   (unsigned long) minms, (unsigned long) totalms / nrecv, (unsigned long) maxms);
        }
    }

    exit(0);
}

static void aep_send(int sig _U_)
{
    struct timeval	tv;
    char		*p, buf[1024];
    static unsigned int	seq = 0;
    p = buf;
    *p++ = DDPTYPE_AEP;
    *p++ = AEPOP_REQUEST;
    memcpy(p, &seq, sizeof(unsigned int));
    p += sizeof(unsigned int);
    seq++;

    if (gettimeofday(&tv, (struct timezone *)0) < 0) {
        perror("gettimeofday");
        exit(1);
    }

    memcpy(p, &tv, sizeof(struct timeval));
    p += sizeof(struct timeval);

    if (netddp_sendto(sock, buf, p - buf, 0, (struct sockaddr *) &target,
                      sizeof(struct sockaddr_at)) < 0) {
        perror("sendto");
        exit(1);
    }

    nsent++;

    if (pings && nsent > pings) {
        done(0);
    }
}

int main(int ac, char **av)
{
    struct servent	*se;
    struct sigaction	sv;
    struct itimerval	it;
    struct sockaddr_at	sat, saddr;
    struct timeval	tv, atv;
    struct nbpnve	nn;
    char		*obj = NULL, *type = "Workstation", *zone = "*";
    int			cc;
    SOCKLEN_T		satlen;
    unsigned int	seq;
    time_t		ms;
    char		buf[1024], *p;
    unsigned char	port;
    extern char		*optarg;
    extern int		optind;
    memset(&saddr, 0, sizeof(saddr));

    while ((cc = getopt(ac, av, "c:A:")) != EOF) {
        switch (cc) {
        case 'A':
            if (!atalk_aton(optarg, &saddr.sat_addr)) {
                fprintf(stderr, "Bad address.\n");
                exit(1);
            }

            break;

        case 'c' :
            pings = atoi(optarg);
            break;

        default :
            usage(av[0]);
            exit(1);
        }
    }

    if (ac - optind != 1) {
        usage(av[0]);
        exit(1);
    }

    /*
     * Save the port, since nbp_lookup calls getservbyname() to get the
     * nbp port.
     */
    if ((se = getservbyname("echo", "ddp")) == NULL) {
        port = 4;
    } else {
        port = ntohs(se->s_port);
    }

    memset(&target, 0, sizeof(struct sockaddr_at));
#ifdef BSD4_4
    target.sat_len = sizeof(struct sockaddr_at);
#endif /* BSD4_4 */
    target.sat_family = AF_APPLETALK;

    if (!atalk_aton(av[optind], &target.sat_addr)) {
        if (nbp_name(av[optind], &obj, &type, &zone) || !obj) {
            fprintf(stderr, "Bad name: %s\n", av[optind]);
            exit(1);
        }

        if (nbp_lookup(obj, type, zone, &nn, 1, &saddr.sat_addr) <= 0) {
            fprintf(stderr, "Can't find: %s\n", av[optind]);
            exit(1);
        }

        memcpy(&target, &nn.nn_sat, sizeof(struct sockaddr_at));
    }

    target.sat_port = port;

    if ((sock = netddp_open(saddr.sat_addr.s_net || saddr.sat_addr.s_node ?
                            &saddr : NULL, NULL)) < 0) {
        perror("ddp_open");
        exit(1);
    }

    sv.sa_handler = aep_send;
    sigemptyset(&sv.sa_mask);
    sigaddset(&sv.sa_mask, SIGINT);
    sv.sa_flags = SA_RESTART;

    if (sigaction(SIGALRM, &sv, (struct sigaction *)0) < 0) {
        perror("sigaction");
        exit(1);
    }

    sv.sa_handler = done;
    sigemptyset(&sv.sa_mask);
    sigaddset(&sv.sa_mask, SIGALRM);
    sv.sa_flags = SA_RESTART;

    if (sigaction(SIGINT, &sv, (struct sigaction *)0) < 0) {
        perror("sigaction");
        exit(1);
    }

    it.it_interval.tv_sec = 1L;
    it.it_interval.tv_usec = 0L;
    it.it_value.tv_sec = 1L;
    it.it_value.tv_usec = 0L;

    if (setitimer(ITIMER_REAL, &it, (struct itimerval *)0) < 0) {
        perror("setitimer");
        exit(1);
    }

    for (;;) {
        satlen = sizeof(struct sockaddr_at);
        memset(&sat, 0, sizeof(struct sockaddr_at));

        if ((cc = netddp_recvfrom(sock, buf, sizeof(buf), 0,
                                  (struct sockaddr *) &sat,
                                  &satlen)) < 0) {
            if (errno == EINTR) {
                errno = 0;
                continue;
            } else {
                perror("recvfrom");
                exit(1);
            }
        }

        p = buf;

        if (*p++ != DDPTYPE_AEP || *p++ != AEPOP_REPLY) {
            fprintf(stderr, "%s: bad packet!\n", av[0]);
            continue;
        }

        if (gettimeofday(&tv, (struct timezone *)0) < 0) {
            perror("gettimeofday");
            exit(1);
        }

        memcpy(&seq, p, sizeof(unsigned int));
        p += sizeof(unsigned int);
        memcpy(&atv, p, sizeof(struct timeval));
        nrecv++;
        ms = (tv.tv_sec - atv.tv_sec) * 1000 +
             (tv.tv_usec - atv.tv_usec) / 1000;
        totalms += ms;

        if (ms > maxms) {
            maxms = ms;
        }

        if (ms < minms || minms == -1) {
            minms = ms;
        }

        printf("%d bytes from %u.%u: aep_seq=%d. time=%ld. ms\n",
               cc, ntohs(sat.sat_addr.s_net), sat.sat_addr.s_node,
               seq, (unsigned long) ms);

        if (pings && seq + 1 >= pings) {
            done(0);
        }
    }
}

static void usage(char *av0)
{
    fprintf(stderr,
            "usage:\t%s [-A source address ] [-c count] ( addr | nbpname )\n", av0);
    exit(1);
}
