// -*- mode: c++; c-basic-offset: 4 -*-
/*
 * ipsumdump_tcp.{cc,hh} -- IP transport summary dump unparsers
 * Eddie Kohler
 *
 * Copyright (c) 2002 International Computer Science Institute
 * Copyright (c) 2004 Regents of the University of California
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>

#include "ipsumdumpinfo.hh"
#include <click/packet.hh>
#include <clicknet/ip.h>
#include <clicknet/tcp.h>
#include <clicknet/udp.h>
CLICK_DECLS

enum { T_TCP_SEQ, T_TCP_ACK, T_TCP_FLAGS, T_TCP_WINDOW, T_TCP_URP, T_TCP_OPT,
       T_TCP_NTOPT, T_TCP_SACK };

namespace IPSummaryDump {

static bool tcp_extract(PacketDesc& d, int thunk)
{
    int transport_length = d.p->transport_length();
    switch (thunk & ~B_TYPEMASK) {
	
#define CHECK(l) do { if (!d.tcph || transport_length < (l)) return field_missing(d, MISSING_IP_TRANSPORT, "TCP", (l)); } while (0)
	
      case T_TCP_SEQ:
	CHECK(8);
	d.v = ntohl(d.tcph->th_seq);
	return true;
      case T_TCP_ACK:
	CHECK(12);
	d.v = ntohl(d.tcph->th_ack);
	return true;
      case T_TCP_FLAGS:
	CHECK(14);
	d.v = d.tcph->th_flags | (d.tcph->th_flags2 << 8);
	return true;
      case T_TCP_WINDOW:
	CHECK(16);
	d.v = ntohs(d.tcph->th_win);
	return true;
      case T_TCP_URP:
	CHECK(20);
	d.v = ntohs(d.tcph->th_urp);
	return true;
      case T_TCP_OPT:
	// need to check that d.tcph->th_off exists
	if (!d.tcph || transport_length < 13 || (d.tcph->th_off > 5 && transport_length < (int)(d.tcph->th_off << 2)))
	    goto no_tcp_opt;
	if (d.tcph->th_off <= 5)
	    d.vptr = 0, d.v2 = 0;
	else {
	    d.vptr = (const uint8_t *) (d.tcph + 1);
	    d.v2 = (int)(d.tcph->th_off << 2) - sizeof(click_tcp);
	}
	return true;
      case T_TCP_NTOPT:
      case T_TCP_SACK:
	// need to check that d.tcph->th_off exists
	if (!d.tcph || transport_length < 13)
	    goto no_tcp_opt;
	else if (d.tcph->th_off <= 5
		 || (d.tcph->th_off == 8 && transport_length >= 24
		     && *(reinterpret_cast<const uint32_t *>(d.tcph + 1)) == htonl(0x0101080A)))
	    d.vptr = 0, d.v2 = 0;
	else if (transport_length < (int)(d.tcph->th_off << 2))
	    goto no_tcp_opt;
	else {
	    d.vptr = (const uint8_t *) (d.tcph + 1);
	    d.v2 = (int)(d.tcph->th_off << 2) - sizeof(click_tcp);
	}
	return true;
	
#undef CHECK

      default:
	return false;
      no_tcp_opt:
	return field_missing(d, MISSING_IP_TRANSPORT, "TCP", transport_length + 1);
    }
}

static void tcp_outa(const PacketDesc& d, int thunk)
{
    switch (thunk & ~B_TYPEMASK) {
      case T_TCP_FLAGS:
	if (d.v == (TH_ACK | TH_PUSH))
	    *d.sa << 'P' << 'A';
	else if (d.v == TH_ACK)
	    *d.sa << 'A';
	else if (d.v == 0)
	    *d.sa << '.';
	else
	    for (int flag = 0; flag < 9; flag++)
		if (d.v & (1 << flag))
		    *d.sa << tcp_flags_word[flag];
	break;
      case T_TCP_OPT:
	if (!d.vptr)
	    *d.sa << '.';
	else
	    unparse_tcp_opt(*d.sa, d.vptr, d.v2, DO_TCPOPT_ALL_NOPAD);
	break;
      case T_TCP_NTOPT:
	if (!d.vptr)
	    *d.sa << '.';
	else
	    unparse_tcp_opt(*d.sa, d.vptr, d.v2, DO_TCPOPT_NTALL);
	break;
      case T_TCP_SACK:
	if (!d.vptr)
	    *d.sa << '.';
	else
	    unparse_tcp_opt(*d.sa, d.vptr, d.v2, DO_TCPOPT_SACK);
	break;
    }
}

static void tcp_outb(const PacketDesc& d, bool ok, int thunk)
{
    switch (thunk & ~B_TYPEMASK) {
      case T_TCP_OPT:
	if (!ok || !d.vptr)
	    *d.sa << '\0';
	else
	    unparse_tcp_opt_binary(*d.sa, d.vptr, d.v2, DO_TCPOPT_ALL);
	break;
      case T_TCP_NTOPT:
	if (!ok || !d.vptr)
	    *d.sa << '\0';
	else
	    unparse_tcp_opt_binary(*d.sa, d.vptr, d.v2, DO_TCPOPT_NTALL);
	break;
      case T_TCP_SACK:
	if (!ok || !d.vptr)
	    *d.sa << '\0';
	else
	    unparse_tcp_opt_binary(*d.sa, d.vptr, d.v2, DO_TCPOPT_SACK);
	break;
    }
}    

static const uint8_t* tcp_inb(PacketDesc& d, const uint8_t* s, const uint8_t* ends, int thunk)
{
    switch (thunk & ~B_TYPEMASK) {
      case T_TCP_OPT:
      case T_TCP_NTOPT:
      case T_TCP_SACK:
	if (s + s[0] + 1 <= ends) {
	    d.vptr = s + 1;
	    d.v2 = s[0];
	    return s + s[0] + 1;
	}
	break;
    }
    return ends;
}    


static int tcp_opt_mask_mapping[] = {
    DO_TCPOPT_PADDING, DO_TCPOPT_PADDING,	// EOL, NOP
    DO_TCPOPT_MSS, DO_TCPOPT_WSCALE,		// MAXSEG, WSCALE
    DO_TCPOPT_SACK, DO_TCPOPT_SACK,		// SACK_PERMITTED, SACK
    DO_TCPOPT_UNKNOWN, DO_TCPOPT_UNKNOWN,	// 6, 7
    DO_TCPOPT_TIMESTAMP				// TIMESTAMP
};

void unparse_tcp_opt(StringAccum& sa, const uint8_t* opt, int opt_len, int mask)
{
    int initial_sa_len = sa.length();
    const uint8_t *end_opt = opt + opt_len;
    const char *sep = "";
    
    while (opt < end_opt)
	switch (*opt) {
	  case TCPOPT_EOL:
	    if (mask & DO_TCPOPT_PADDING)
		sa << sep << "eol";
	    goto done;
	  case TCPOPT_NOP:
	    if (mask & DO_TCPOPT_PADDING) {
		sa << sep << "nop";
		sep = ";";
	    }
	    opt++;
	    break;
	  case TCPOPT_MAXSEG:
	    if (opt + opt[1] > end_opt || opt[1] != TCPOLEN_MAXSEG)
		goto bad_opt;
	    if (!(mask & DO_TCPOPT_MSS))
		goto unknown;
	    sa << sep << "mss" << ((opt[2] << 8) | opt[3]);
	    opt += TCPOLEN_MAXSEG;
	    sep = ";";
	    break;
	  case TCPOPT_WSCALE:
	    if (opt + opt[1] > end_opt || opt[1] != TCPOLEN_WSCALE)
		goto bad_opt;
	    if (!(mask & DO_TCPOPT_WSCALE))
		goto unknown;
	    sa << sep << "wscale" << (int)(opt[2]);
	    opt += TCPOLEN_WSCALE;
	    sep = ";";
	    break;
	  case TCPOPT_SACK_PERMITTED:
	    if (opt + opt[1] > end_opt || opt[1] != TCPOLEN_SACK_PERMITTED)
		goto bad_opt;
	    if (!(mask & DO_TCPOPT_SACK))
		goto unknown;
	    sa << sep << "sackok";
	    opt += TCPOLEN_SACK_PERMITTED;
	    sep = ";";
	    break;
	  case TCPOPT_SACK: {
	      if (opt + opt[1] > end_opt || (opt[1] % 8 != 2))
		  goto bad_opt;
	      if (!(mask & DO_TCPOPT_SACK))
		  goto unknown;
	      const uint8_t *end_sack = opt + opt[1];
	      for (opt += 2; opt < end_sack; opt += 8) {
		  uint32_t buf[2];
		  memcpy(&buf[0], opt, 8);
		  sa << sep << "sack" << ntohl(buf[0]) << '-' << ntohl(buf[1]);
		  sep = ";";
	      }
	      break;
	  }
	  case TCPOPT_TIMESTAMP: {
	      if (opt + opt[1] > end_opt || opt[1] != TCPOLEN_TIMESTAMP)
		  goto bad_opt;
	      if (!(mask & DO_TCPOPT_TIMESTAMP))
		  goto unknown;
	      uint32_t buf[2];
	      memcpy(&buf[0], opt + 2, 8);
	      sa << sep << "ts" << ntohl(buf[0]) << ':' << ntohl(buf[1]);
	      opt += TCPOLEN_TIMESTAMP;
	      sep = ";";
	      break;
	  }
	  default: {
	      if (opt + opt[1] > end_opt || opt[1] < 2)
		  goto bad_opt;
	      if (!(mask & DO_TCPOPT_UNKNOWN))
		  goto unknown;
	      sa << sep << (int)(opt[0]);
	      const uint8_t *end_this_opt = opt + opt[1];
	      char opt_sep = '=';
	      for (opt += 2; opt < end_this_opt; opt++) {
		  sa << opt_sep << (int)(*opt);
		  opt_sep = ':';
	      }
	      sep = ";";
	      break;
	  }
	  unknown:
	    opt += (opt[1] >= 2 ? opt[1] : 128);
	    break;
	}

  done:
    if (sa.length() == initial_sa_len)
	sa << '.';
    return;

  bad_opt:
    sa.set_length(initial_sa_len);
    sa << '?';
}

void unparse_tcp_opt(StringAccum& sa, const click_tcp* tcph, int mask)
{
    unparse_tcp_opt(sa, reinterpret_cast<const uint8_t *>(tcph + 1), (tcph->th_off << 2) - sizeof(click_tcp), mask);
}

void unparse_tcp_opt_binary(StringAccum& sa, const uint8_t* opt, int opt_len, int mask)
{
    if (mask == (int)DO_TCPOPT_ALL) {
	// store all options
	sa.append((char)opt_len);
	sa.append(opt, opt_len);
    }

    const uint8_t *end_opt = opt + opt_len;
    int initial_sa_len = sa.length();
    sa.append('\0');
    
    while (opt < end_opt) {
	// one-byte options
	if (*opt == TCPOPT_EOL) {
	    if (mask & DO_TCPOPT_PADDING)
		sa.append(opt, 1);
	    goto done;
	} else if (*opt == TCPOPT_NOP) {
	    if (mask & DO_TCPOPT_PADDING)
		sa.append(opt, 1);
	    opt++;
	    continue;
	}
	
	// quit copying options if you encounter something obviously invalid
	if (opt[1] < 2 || opt + opt[1] > end_opt)
	    break;

	int this_content = (*opt > TCPOPT_TIMESTAMP ? (int)DO_TCPOPT_UNKNOWN : tcp_opt_mask_mapping[*opt]);
	if (mask & this_content)
	    sa.append(opt, opt[1]);
	opt += opt[1];
    }

  done:
    sa[initial_sa_len] = sa.length() - initial_sa_len - 1;
}

void unparse_tcp_opt_binary(StringAccum& sa, const click_tcp *tcph, int mask)
{
    unparse_tcp_opt_binary(sa, reinterpret_cast<const uint8_t *>(tcph + 1), (tcph->th_off << 2) - sizeof(click_tcp), mask);
}


void tcp_register_unparsers()
{
    register_unparser("tcp_seq", T_TCP_SEQ | B_4, ip_prepare, tcp_extract, num_outa, outb, inb);
    register_unparser("tcp_ack", T_TCP_ACK | B_4, ip_prepare, tcp_extract, num_outa, outb, inb);
    register_unparser("tcp_flags", T_TCP_FLAGS | B_1, ip_prepare, tcp_extract, tcp_outa, outb, inb);
    register_unparser("tcp_window", T_TCP_WINDOW | B_2, ip_prepare, tcp_extract, num_outa, outb, inb);
    register_unparser("tcp_urp", T_TCP_URP | B_2, ip_prepare, tcp_extract, num_outa, outb, inb);
    register_unparser("tcp_opt", T_TCP_OPT | B_SPECIAL, ip_prepare, tcp_extract, tcp_outa, tcp_outb, tcp_inb);
    register_unparser("tcp_ntopt", T_TCP_NTOPT | B_SPECIAL, ip_prepare, tcp_extract, tcp_outa, tcp_outb, tcp_inb);
    register_unparser("tcp_sack", T_TCP_SACK | B_SPECIAL, ip_prepare, tcp_extract, tcp_outa, tcp_outb, tcp_inb);

    register_synonym("tcp_seqno", "tcp_seq");
    register_synonym("tcp_ackno", "tcp_ack");
    register_synonym("tcp_win", "tcp_window");
}


enum { T_UDP_LEN };

static bool udp_extract(PacketDesc& d, int thunk)
{
    int transport_length = d.p->transport_length();
    switch (thunk & ~B_TYPEMASK) {
	
#define CHECK(l) do { if (!d.udph || transport_length < (l)) return field_missing(d, MISSING_IP_TRANSPORT, "UDP", (l)); } while (0)
	
      case T_UDP_LEN:
	CHECK(6);
	d.v = ntohs(d.udph->uh_ulen);
	return true;
	
#undef CHECK

      default:
	return false;
    }
}

void udp_register_unparsers()
{
    register_unparser("udp_len", T_UDP_LEN | B_4, ip_prepare, udp_extract, num_outa, outb, inb);
}

}

ELEMENT_REQUIRES(userlevel IPSummaryDump)
ELEMENT_PROVIDES(IPSummaryDump_TCP)
CLICK_ENDDECLS


syntax highlighted by Code2HTML, v. 0.9.1