#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "anep.h"

#include <sys/uio.h>
#include <unistd.h>

#include <stdio.h>
#include <strings.h>
#include <stdlib.h>

#define EXIT_ERR    1
#define EXIT_BUG    2
#define EXIT_LEAVE  3
#define EXIT_FAILED 4

#define BUG(args...)    {fprintf(stderr, "BUG: "    args); return EXIT_BUG;   }
#define LEAVE(args...)  {fprintf(stderr, "LEAVE: "  args); return EXIT_LEAVE; }
#define FAILED(args...) {fprintf(stderr, "FAILED: " args); return EXIT_FAILED;}

#define ANEP_OPT_SZ_RNDUP(size) ((size+3)>>2)

void *xmalloc(size_t size) {
    void *data = malloc(size);
    if (data)
	return data;
    abort(); /* arghh */
}

int test_host(char *hostname, int type, int domain);
int test_send(struct sockaddr_anep *sock, int domain);

int test_host(char *hostname, int type, int family) 
{
    char			  addrstringbuf[INET6_ADDRSTRLEN];
    struct sockaddr_anep	  servaddr;
    struct hostent		 *hptr;
    char			**palias;
    char			**paddress;

    /* Call the resolver */
    hptr = gethostbyname2(hostname, family);
    if (!hptr) 
	LEAVE("can't resolve \"%s\" (%s)\n", hostname, hstrerror(h_errno));
    
    /* Official name */
    printf("Official name   : %s\n", hptr->h_name);

    /* Aliases */
    printf("Known alias(es) : "); 
    if (*hptr->h_aliases == NULL)
	printf("<none>\n");
    else 
	printf("%s\n", *hptr->h_aliases);

    for (palias = hptr->h_aliases + 1 ; *palias ; palias++)
	printf("                  %s\n", *palias);

    /* Addresses */
    bzero(&servaddr, sizeof(struct sockaddr_anep));
    switch (hptr->h_addrtype) {
    case AF_INET:
	for (paddress = hptr->h_addr_list ; *paddress ; paddress++) {
	    printf("Test address: %s\n", 
		   inet_ntop(hptr->h_addrtype, *paddress, 
			     addrstringbuf, INET6_ADDRSTRLEN));

	    servaddr.sanep_family	= hptr->h_addrtype;
	    servaddr.sanep_type		= htons(type);
	    bcopy(*paddress, &servaddr.sanep_ipv4_addr, 
		  sizeof(struct in_addr));

	    test_send(&servaddr, PF_INET);
	}
	break;
    case AF_INET6:
	for (paddress = hptr->h_addr_list ; *paddress ; paddress++) {
	    printf("Test address: %s\n", 
		   inet_ntop(hptr->h_addrtype, *paddress, 
			     addrstringbuf, INET6_ADDRSTRLEN));

	    servaddr.sanep_family	= hptr->h_addrtype;
	    servaddr.sanep_type		= htons(type);
	    bcopy(*paddress, &servaddr.sanep_ipv6_addr, 
		  sizeof(struct in6_addr));
	    
	    test_send(&servaddr, PF_INET6);
	}
	break;
    default:
	LEAVE("unknown address type (%d)", hptr->h_addrtype);
    }

    return(0);
}


int test_send(struct sockaddr_anep *sock, int domain) 
{
    int				sockfd;
    int				flags	= 0;
    struct msghdr		msg;
    struct iovec	       *iov;
    int				iov_len;
    struct anep_optionhdr      *aoh;
    struct cmsghdr	       *cmsg;
    struct cmsghdr	       *cmsg_list;
    int				cmsg_size;
    char		       *test_data = "Hello ANEP univers! (without options)\n";
    char		       *test_data2= "Hello ANEP (with optbuff)\n";
    int		 option1_type,  option2_type;
    int		 option1_size,  option2_size;
    char	*option1_data, *option2_data;
    int		 option1_data_size, option2_data_size;
    struct anep_optbuffer       optbuff[3] = {{NULL, 0, ANEP_OPTBUFFER_SEND}};

    /*
     * Building data
     */
    /* Build data options to send */
    option1_data	= "Option1";
    option1_data_size	= strlen(option1_data) + 1;
    option1_type	= 15;
    option1_size	= sizeof(struct anep_optionhdr) + option1_data_size;
    
    option2_data	= "Option2";
    option2_data_size	= strlen(option2_data) + 1;
    option2_type	= 16;
    option2_size	= sizeof(struct anep_optionhdr) + option2_data_size;

    /* Build data to send */
    iov_len = 4;
    iov = (struct iovec *)xmalloc(iov_len * sizeof(struct iovec));

    iov[0].iov_base = "Hello ";
    iov[0].iov_len  = strlen(iov[0].iov_base);
    iov[1].iov_base = "ANEP";
    iov[1].iov_len  = strlen(iov[1].iov_base);
    iov[2].iov_base = " world";
    iov[2].iov_len  = strlen(iov[2].iov_base);
    iov[3].iov_base = "!";
    iov[3].iov_len  = strlen(iov[3].iov_base)+1; /* include \0 */

    /* Allocate memory for the control messages */
    cmsg_size = CMSG_SPACE(option1_size) + CMSG_SPACE(option2_size);
    cmsg_list = (struct cmsghdr *)xmalloc(cmsg_size);

    /* Create message */
    msg.msg_name       = (struct sockaddr *)sock;
    msg.msg_namelen    = sizeof(struct sockaddr_anep);
    msg.msg_iov        = iov;
    msg.msg_iovlen     = iov_len; 
    msg.msg_control    = cmsg_list;
    msg.msg_controllen = cmsg_size;
    msg.msg_flags      = 0;

    /* Build options */
    cmsg = cmsg_list;
    cmsg->cmsg_len   = CMSG_LEN(option1_size);
    cmsg->cmsg_level = SOL_ANEP;
    cmsg->cmsg_type  = ANEP_OPTION; 
    aoh = (struct anep_optionhdr *)CMSG_DATA(cmsg);
    aoh->type   = htons(ANEP_OPT_DISCARD |option1_type);
    aoh->length = htons(ANEP_OPT_SZ_RNDUP(option1_size));
    bcopy(option1_data, (char *)(aoh+1), option1_data_size);
    optbuff[1].buff   = aoh;
    optbuff[1].length = option1_size;
    optbuff[1].type   = option1_type;

    cmsg = (struct cmsghdr *)((char *)cmsg + CMSG_SPACE(option1_size));
    cmsg->cmsg_len   = CMSG_LEN(option2_size);
    cmsg->cmsg_level = SOL_ANEP;
    cmsg->cmsg_type  = ANEP_OPTION; 
    aoh = (struct anep_optionhdr *)CMSG_DATA(cmsg);
    aoh->type   = htons(ANEP_OPT_DISCARD|option2_type);
    aoh->length = htons(ANEP_OPT_SZ_RNDUP(option2_size));
    bcopy(option2_data, (char *)(aoh+1), option2_data_size);
    optbuff[2].buff   = aoh;
    optbuff[2].length = option2_size;
    optbuff[2].type   = option2_type;

    /* Sendmsg flags */
    flags = 0;

    /*
     * Sending
     */
    /* Create socket */
    printf("\tcreate socket...");   fflush(stdout);
    sockfd = socket(domain, SOCK_DGRAM, SOL_ANEP);
    if (sockfd < 0)
	goto out;
    printf("Ok\n");

    /* Sendmsg */
    printf("\tsendmsg.........");   fflush(stdout);
    if (sendmsg(sockfd, &msg, flags) < 0)
	goto out;
    printf("Ok\n");

    /* Sendto */
    printf("\tsendto..........");   fflush(stdout);
    if (sendto(sockfd, test_data, strlen(test_data)+1, flags, 
	       (struct sockaddr *)sock, sizeof(struct sockaddr_anep)) < 0)
	goto out;
    printf("Ok\n");

    /* Setsockopt + sendto */
    printf("\tsetsockopt......");   fflush(stdout);
    if (setsockopt(sockfd, SOL_ANEP, ANEP_OPTBUFFER,
		   optbuff, 3 * sizeof(struct anep_optbuffer)) < 0)
	goto out;
    printf("Ok\n");
    printf("\tsendto..........");   fflush(stdout);
    if (sendto(sockfd, test_data2, strlen(test_data2)+1, flags, 
	       (struct sockaddr *)sock, sizeof(struct sockaddr_anep)) < 0)
	goto out;
    printf("Ok\n");

    /* Close */
    printf("\tclose socket....");   fflush(stdout);
    if (close(sockfd) < 0)
	goto out;
    printf("Ok\n");

   return(0);

 out:
    printf("Error: %m\n");
    return 1;
}


int main(int argc, char *argv[]) {
    char       *hostname;
    int		type;
    char       *progname;

    progname = (char *)strrchr(argv[0], '/');
    if (!progname++)
	progname = argv[0];

    /* Arguments */
    if (argc != 3) {
	printf("usage: prog hostname typeid\n");
	exit(0);
    }
    hostname = argv[1];
    type     = atoi(argv[2]);

    /* Welcome message */
    printf("Test asked for  : %s on %d\n", hostname, type);

    /* Sanity check */
    if (type < 0 || type > 0xffff) 
	LEAVE("incorrect ANEP type id\n");
    
    /* Do it */
    if (!strcmp(progname, "client_v4")) {
	printf(">> Using IPv4 code\n");
	test_host(hostname, type, AF_INET);
    } else {
	printf(">> Using IPv6 code\n");
	test_host(hostname, type, AF_INET6);
    }

    exit(0);
}
