/*
 * $Id: atp_packet.c,v 1.5 2002/01/17 06:08:55 srittau Exp $
 *
 * 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
 */

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

#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <netinet/in.h>

#include <netatalk/at.h>
#include <netatalk/endian.h>

#include <atalk/netddp.h>
#include <atalk/ddp.h>
#include <atalk/atp.h>
#include <atalk/util.h>

#include "atp_internals.h"

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

#ifdef EBUG
#include <stdio.h>

static void print_func( ctrlinfo )
    u_int8_t	ctrlinfo;
{
    switch ( ctrlinfo & ATP_FUNCMASK ) {
    case ATP_TREQ:
	printf( "TREQ" );
	break;
    case ATP_TRESP:
	printf( "TRESP" );
	break;
    case ATP_TREL:
	printf( "ANY/TREL" );
	break;
    case ATP_TIDANY:
	printf( "*" );
	break;
    default:
	printf( "%x", ctrlinfo & ATP_FUNCMASK );
    }
}

static void dump_packet( buf, len )
    char	*buf;
    int		len;
{
    int		i;

    for ( i = 0; i < len; ++i ) {
	printf( "%x-%c ", buf[i], buf[i] );
    }
    putchar( '\n' );
}

void atp_print_addr( s, saddr )
    char		*s;
    struct sockaddr_at	*saddr;
{
    printf( "%s ", s );
    saddr->sat_family == AF_APPLETALK ? printf( "at." ) :
      printf( "%d.", saddr->sat_family );
    saddr->sat_addr.s_net == ATADDR_ANYNET ? printf( "*." ) :
      printf( "%d.", ntohs( saddr->sat_addr.s_net ));
    saddr->sat_addr.s_node == ATADDR_ANYNODE ? printf( "*." ) :
      printf( "%d.", saddr->sat_addr.s_node );
    saddr->sat_port == ATADDR_ANYPORT ? printf( "*" ) :
      printf( "%d", saddr->sat_port );
}
#endif /* EBUG */


void atp_build_req_packet( struct atpbuf *pktbuf,
			   u_int16_t tid,
			   u_int8_t ctrl,
			   struct atp_block *atpb )
{
    struct atphdr	hdr;

    /* fill in the packet fields
    */
    hdr.atphd_ctrlinfo = ctrl;
    hdr.atphd_bitmap = atpb->atp_bitmap;
    hdr.atphd_tid = htons( tid );
    *(pktbuf->atpbuf_info.atpbuf_data) = DDPTYPE_ATP;
    memcpy(pktbuf->atpbuf_info.atpbuf_data + 1, &hdr, sizeof( struct atphdr ));
    memcpy(pktbuf->atpbuf_info.atpbuf_data + ATP_HDRSIZE, 
	   atpb->atp_sreqdata, atpb->atp_sreqdlen ); 

    /* set length
    */
    pktbuf->atpbuf_dlen = ATP_HDRSIZE + (size_t) atpb->atp_sreqdlen;
}

void atp_build_resp_packet( struct atpbuf *pktbuf,
			    u_int16_t tid,
			    u_int8_t ctrl,
			    struct atp_block *atpb,
			    u_int8_t seqnum )
{
    struct atphdr	hdr;

    /* fill in the packet fields */
    *(pktbuf->atpbuf_info.atpbuf_data) = DDPTYPE_ATP;
    hdr.atphd_ctrlinfo = ctrl;
    hdr.atphd_bitmap = seqnum;
    hdr.atphd_tid = htons( tid );
    memcpy(pktbuf->atpbuf_info.atpbuf_data + 1, &hdr, 
	   sizeof( struct atphdr ));
    memcpy(pktbuf->atpbuf_info.atpbuf_data + ATP_HDRSIZE,
	  atpb->atp_sresiov[ seqnum ].iov_base,
	  atpb->atp_sresiov[ seqnum ].iov_len ); 

    /* set length
    */
    pktbuf->atpbuf_dlen = ATP_HDRSIZE + (size_t) atpb->atp_sresiov[ seqnum ].iov_len;
}


int
atp_recv_atp( ATP ah,
	      struct sockaddr_at *fromaddr,
	      u_int8_t *func,
	      u_int16_t tid,
	      char *rbuf,
	      int wait )
{
/* 
  Receive a packet from address fromaddr of the correct function type
  and with the correct tid.  fromaddr = AT_ANY... and function == ATP_TYPEANY
  and tid == ATP_TIDANY can be used to wildcard match.
  
  recv_atp returns the length of the packet received (or -1 if error)
  The function code for the packet received is returned in *func (ATP_TREQ or
    ATP_TRESP).
*/
    struct atpbuf	*pq, *cq;
    struct atphdr	ahdr;
    u_int16_t		rfunc;
    u_int16_t		rtid;
    int			i;
    int			dlen = -1;
    int			recvlen;
    struct sockaddr_at	faddr;
    SOCKLEN_T		faddrlen;
    struct atpbuf	*inbuf;

    tid = htons( tid );

    /* first check the queue
    */
#ifdef EBUG
    atp_print_bufuse( ah, "recv_atp checking queue" );
#endif /* EBUG */
    for ( pq = NULL, cq = ah->atph_queue; cq != NULL;
      pq = cq, cq = cq->atpbuf_next ) {
	memcpy(&ahdr, cq->atpbuf_info.atpbuf_data + 1, 
	       sizeof( struct atphdr ));
	rfunc = ahdr.atphd_ctrlinfo & ATP_FUNCMASK;
#ifdef EBUG
	printf( "<%d> checking", getpid());
	printf( " tid=%hu func=", ntohs( ahdr.atphd_tid ));
	print_func( rfunc );
	atp_print_addr( " from", &cq->atpbuf_addr );
	putchar( '\n' );
#endif /* EBUG */
	if ((( tid & ahdr.atphd_tid ) == ahdr.atphd_tid ) &&
	    (( *func & rfunc ) == rfunc )
	    && at_addr_eq( fromaddr, &cq->atpbuf_addr )) {
	    break;
	}
    }
    if ( cq != NULL ) {
	/* we found one in the queue -- copy to rbuf
	*/
	dlen = (int) cq->atpbuf_dlen;
	*func = rfunc;
	memcpy( fromaddr, &cq->atpbuf_addr, sizeof( struct sockaddr_at ));
	memcpy( rbuf, cq->atpbuf_info.atpbuf_data, cq->atpbuf_dlen );

	/* remove packet from queue and free buffer
	*/
	if ( pq == NULL ) {
	    ah->atph_queue = NULL;
	} else {
	    pq->atpbuf_next = cq->atpbuf_next;
	}
	atp_free_buf( cq );
	return( dlen );
    }

    /* we need to get it the net -- call on ddp to receive a packet
    */
#ifdef EBUG
    printf( "<%d>", getpid());
    atp_print_addr( " waiting on address", &ah->atph_saddr );
    printf( "\nfor tid=%hu func=", ntohs( tid ));
    print_func( *func );
    atp_print_addr( " from", fromaddr );
    putchar( '\n' );
#endif /* EBUG */

    do {
#ifdef EBUG
    fflush( stdout );
#endif /* EBUG */
	faddrlen = sizeof( struct sockaddr_at );
	memset( &faddr, 0, sizeof( struct sockaddr_at ));

	if (( recvlen = netddp_recvfrom( ah->atph_socket, rbuf, 
					 ATP_BUFSIZ, 0,
					 (struct sockaddr *) &faddr,
					 &faddrlen )) < 0 ) {
	    return -1;
	}
	memcpy( &ahdr, rbuf + 1, sizeof( struct atphdr ));
	if ( recvlen >= ATP_HDRSIZE && *rbuf == DDPTYPE_ATP) {
	    /* this is a valid ATP packet -- check for a match */
	    rfunc = ahdr.atphd_ctrlinfo & ATP_FUNCMASK;
	    rtid = ahdr.atphd_tid;
#ifdef EBUG
	    printf( "<%d> got tid=%hu func=", getpid(), ntohs( rtid ));
	    print_func( rfunc );
	    atp_print_addr( " from", &faddr );
	    putchar( '\n' );
	    bprint( rbuf, recvlen );
#endif /* EBUG */
	    if ( rfunc == ATP_TREL ) {
		/* remove response from sent list */
		for ( pq = NULL, cq = ah->atph_sent; cq != NULL;
		  pq = cq, cq = cq->atpbuf_next ) {
		    if ( at_addr_eq( &faddr, &cq->atpbuf_addr ) &&
		      cq->atpbuf_info.atpbuf_xo.atpxo_tid == ntohs( rtid )) 
			break;
		}
		if ( cq != NULL ) {
#ifdef EBUG
	printf( "<%d> releasing transaction %hu\n", getpid(), ntohs( rtid ));
#endif /* EBUG */
		    if ( pq == NULL ) {
			ah->atph_sent = cq->atpbuf_next;
		    } else {
			pq->atpbuf_next = cq->atpbuf_next;
		    }
		    for ( i = 0; i < 8; ++i ) {
			if ( cq->atpbuf_info.atpbuf_xo.atpxo_packet[ i ]
				    != NULL ) {
			    atp_free_buf ( cq->atpbuf_info.atpbuf_xo.atpxo_packet[ i ] );
			}
		    }
		    atp_free_buf( cq );
		}

	    } else if ((( tid & rtid ) == rtid ) &&
		    (( *func & rfunc ) == rfunc ) &&
		    at_addr_eq( fromaddr, &faddr )) { /* got what we wanted */
		*func = rfunc;
		dlen = recvlen;
		memcpy( fromaddr, &faddr, sizeof( struct sockaddr_at ));

	    } else {
		/* add packet to incoming queue */
#ifdef EBUG
	printf( "<%d> queuing incoming...\n", getpid() );
#endif /* EBUG */
		if (( inbuf = atp_alloc_buf()) == NULL ) {
		    return -1;
		}
		memcpy( &inbuf->atpbuf_addr, &faddr, 
			sizeof( struct sockaddr_at ));
		inbuf->atpbuf_next = ah->atph_queue;
		inbuf->atpbuf_dlen = (size_t) recvlen;
		memcpy( inbuf->atpbuf_info.atpbuf_data, rbuf, recvlen );
	    }
	}
	if ( !wait && dlen < 0 ) {
	    return( 0 );
	}

    } while ( dlen < 0 );

    return( dlen );
}


int at_addr_eq( paddr, saddr )
    struct sockaddr_at	*paddr;		/* primary address */
    struct sockaddr_at	*saddr;		/* secondary address */
{
/* compare two atalk addresses -- only check the non-zero fields
    of paddr against saddr.
   return zero if not equal, non-zero if equal
*/
    return (( paddr->sat_port == ATADDR_ANYPORT || paddr->sat_port == saddr->sat_port )
	&&  ( paddr->sat_addr.s_net == ATADDR_ANYNET ||
	      paddr->sat_addr.s_net == saddr->sat_addr.s_net )
	&&  ( paddr->sat_addr.s_node == ATADDR_ANYNODE ||
	      paddr->sat_addr.s_node == saddr->sat_addr.s_node ));
}

