/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe is free software; you can redistribute it and/or modify            *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   Nprobe is distributed in the hope that it will be useful,                 *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
*   GNU General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/


#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#undef __STDC__
#include <netinet/ip.h>
#include <netinet/udp.h>
#ifdef __linux__
#define __FAVOR_BSD
#endif
#include <netinet/tcp.h>

#include "basic_defs.h"

#include "list.h"
#include "pkt.h"

unsigned int csum_partial(const unsigned char * buff, int len, unsigned int sum);

static inline unsigned int csum_fold(unsigned int sum)
{
    __asm__("addl %1, %0 ; adcl $0xffff, %0"
            : "=r" (sum)
            : "r" (sum << 16), "0" (sum & 0xffff0000));
    return (~sum) >> 16;
} 
 
static inline unsigned int csum_tcpudp_nofold(
    unsigned int saddr, unsigned int daddr, unsigned short len, unsigned short proto, unsigned int sum) 
{
    __asm__("addl %1, %0 ; adcl %2, %0 ; adcl %3, %0 ; adcl $0, %0"
	: "=r" (sum)
	: "g" (daddr), "g"(saddr), "g"((ntohs(len)<<16)+proto*256), "0"(sum));
    return sum;
}


static inline unsigned short csum_tcpudp_magic(
    unsigned int saddr, unsigned int daddr, unsigned short len, unsigned short proto, unsigned int sum)
{
    return csum_fold(csum_tcpudp_nofold(saddr,daddr,len,proto,sum));
}

/*
 * Calculate(/check) TCP checksum
 */
static inline unsigned short tcp_v4_check(struct tcphdr *th, int len,
				   unsigned int saddr, unsigned int daddr, 
				   unsigned int base)
{
	return csum_tcpudp_magic(saddr,daddr,len,IPPROTO_TCP,base);
}

unsigned short ip_fast_csum(unsigned char * iph,
                                          unsigned int ihl) {
        unsigned int sum;

        __asm__ __volatile__("\n\
            movl (%1), %0\n\
            subl $4, %2\n\
            jbe 2f\n\
            addl 4(%1), %0\n\
            adcl 8(%1), %0\n\
            adcl 12(%1), %0\n\
1:          adcl 16(%1), %0\n\
            lea 4(%1), %1\n\
            decl %2\n\
            jne 1b\n\
            adcl $0, %0\n\
            movl %0, %2\n\
            shrl $16, %0\n\
            addw %w2, %w0\n\
            adcl $0, %0\n\
            notl %0\n\
2:\n\
            "
        /* Since the input registers which are loaded with iph and ipl
           are modified, we must also specify them as outputs, or gcc
           will assume they contain their original values. */
        : "=r" (sum), "=r" (iph), "=r" (ihl)
        : "1" (iph), "2" (ihl));
        return(sum);
}

int 
tcp_csum(struct ip *ipp, struct tcphdr *tcpp, prec_t *pp)
{
  unsigned short claimed = tcpp->th_sum;
  unsigned short actual;
  unsigned int partial;

  partial = ~claimed;
  partial = csum_partial(pp->buf, pp->len, partial);
  actual = tcp_v4_check(tcpp, pp->len, 
			*((unsigned int *)&ipp->ip_src), 
			*((unsigned int *)&ipp->ip_dst), partial);

  return (actual == claimed);
}

int 
udp_csum(struct ip *ipp, struct udphdr *udpp, prec_t *pp)
{
  unsigned short claimed = udpp->check;
  unsigned short actual;
  unsigned int partial;

  partial = ~claimed;
  partial = csum_partial(pp->buf, pp->len, partial);
  actual = tcp_v4_check(udpp, pp->len, 
			*((unsigned int *)&ipp->ip_src), 
			*((unsigned int *)&ipp->ip_dst), partial);

  return (actual == claimed);
}
