/*
 * netsniff-ng - the packet sniffing beast
 * Subject to the GPL, version 2.
 */

#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/ethernet.h>

#include "trafgen_l2.h"
#include "trafgen_l3.h"
#include "trafgen_l4.h"
#include "trafgen_proto.h"
#include "trafgen_dump.h"

#define DUMP(fmt, ...) fprintf((ctx)->file, fmt, ##__VA_ARGS__)

#define PKT_START() DUMP("{\n")
#define PKT_END() DUMP("}\n")

#define HDR_START(h) DUMP("  %s(", h)
#define HDR_END(h) DUMP("  ),\n")

#define FIELD_START(fmt, ...) DUMP(fmt",\n", ##__VA_ARGS__)
#define FIELD_END(fmt, ...) DUMP("    "fmt"\n", ##__VA_ARGS__)
#define FIELD(fmt, ...) DUMP("    "fmt",\n", ##__VA_ARGS__)

struct dump_ctx {
	FILE *file;
};

static int proto_dump_eth(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	uint8_t *mac;

	HDR_START("eth");

	mac = proto_hdr_field_get_bytes(hdr, ETH_DST_ADDR);
	FIELD_START("da=%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	mac = proto_hdr_field_get_bytes(hdr, ETH_SRC_ADDR);
	FIELD("sa=%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	FIELD_END("type=0x%x", proto_hdr_field_get_u16(hdr, ETH_TYPE));

	HDR_END();
	return 0;
}

static int proto_dump_vlan(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	HDR_START("vlan");

	FIELD_START("id=%d", proto_hdr_field_get_u16(hdr, VLAN_VID));
	FIELD("pcp=%d", proto_hdr_field_get_u16(hdr, VLAN_PCP));
	FIELD("dei=%d", proto_hdr_field_get_u16(hdr, VLAN_DEI));
	FIELD_END("tpid=0x%x", proto_hdr_field_get_u16(hdr, VLAN_TPID));

	HDR_END();
	return 0;
}

static int proto_dump_arp(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	char ip_str[INET_ADDRSTRLEN];
	uint16_t oper;
	uint8_t *mac;
	uint32_t ip;

	HDR_START("arp");

	mac = proto_hdr_field_get_bytes(hdr, ARP_SHA);
	FIELD_START("smac=%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	ip = proto_hdr_field_get_be32(hdr, ARP_SPA);
	inet_ntop(AF_INET, &ip, ip_str, sizeof(ip_str));
	FIELD("sip=%s", ip_str);

	mac = proto_hdr_field_get_bytes(hdr, ARP_THA);
	FIELD("tmac=%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

	ip = proto_hdr_field_get_be32(hdr, ARP_TPA);
	inet_ntop(AF_INET, &ip, ip_str, sizeof(ip_str));
	FIELD("tip=%s", ip_str);

	oper = proto_hdr_field_get_u16(hdr, ARP_OPER);

	if (oper == ARPOP_REQUEST)
		FIELD_END("op=request");
	else if (oper == ARPOP_REPLY)
		FIELD_END("op=reply");
	else
		FIELD_END("op=0x%x", oper);

	HDR_END();
	return 0;
}

static int proto_dump_ip4(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	char ip_sa_str[INET_ADDRSTRLEN];
	char ip_da_str[INET_ADDRSTRLEN];
	uint32_t ip;

	ip = proto_hdr_field_get_be32(hdr, IP4_SADDR);
	inet_ntop(AF_INET, &ip, ip_sa_str, sizeof(ip_sa_str));

	ip = proto_hdr_field_get_be32(hdr, IP4_DADDR);
	inet_ntop(AF_INET, &ip, ip_da_str, sizeof(ip_da_str));

	HDR_START("ip4");

	FIELD_START("ver=0x%x", proto_hdr_field_get_u8(hdr, IP4_VER));
	FIELD("ihl=0x%x", proto_hdr_field_get_u8(hdr, IP4_IHL));
	FIELD("dscp=0x%x", proto_hdr_field_get_u8(hdr, IP4_DSCP));
	FIELD("ecn=0x%x", proto_hdr_field_get_u8(hdr, IP4_ECN));
	FIELD("tos=0x%x", proto_hdr_field_get_u8(hdr, IP4_TOS));
	FIELD("len=%d", proto_hdr_field_get_u16(hdr, IP4_LEN));
	FIELD("id=0x%x", proto_hdr_field_get_u16(hdr, IP4_ID));
	FIELD("flags=0x%x", proto_hdr_field_get_u16(hdr, IP4_FLAGS));
	if (proto_hdr_field_get_u16(hdr, IP4_MF))
		FIELD("mf");
	if (proto_hdr_field_get_u16(hdr, IP4_DF))
		FIELD("df");
	FIELD("frag=0x%x", proto_hdr_field_get_u16(hdr, IP4_FRAG_OFFS));
	FIELD("ttl=%d", proto_hdr_field_get_u8(hdr, IP4_TTL));
	FIELD("proto=0x%x", proto_hdr_field_get_u8(hdr, IP4_PROTO));
	FIELD("csum=0x%x", proto_hdr_field_get_u16(hdr, IP4_CSUM));
	FIELD("sa=%s", ip_sa_str);
	FIELD_END("da=%s", ip_da_str);

	HDR_END();
	return 0;
}

static int proto_dump_udp(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	HDR_START("udp");

	FIELD_START("dp=%d", proto_hdr_field_get_u16(hdr, UDP_SPORT));
	FIELD("sp=%d", proto_hdr_field_get_u16(hdr, UDP_DPORT));
	FIELD("len=%d", proto_hdr_field_get_u16(hdr, UDP_LEN));
	FIELD_END("csum=0x%x", proto_hdr_field_get_u16(hdr, UDP_CSUM));

	HDR_END();
	return 0;
}

static int proto_dump_tcp(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	HDR_START("tcp");

	FIELD_START("dp=%d", proto_hdr_field_get_u16(hdr, TCP_SPORT));
	FIELD("sp=%d", proto_hdr_field_get_u16(hdr, TCP_DPORT));
	FIELD("seq=0x%x", proto_hdr_field_get_u32(hdr, TCP_SEQ));
	FIELD("ackseq=0x%x", proto_hdr_field_get_u32(hdr, TCP_ACK_SEQ));
	FIELD("hlen=%d", proto_hdr_field_get_u16(hdr, TCP_DOFF));
	if (proto_hdr_field_get_u16(hdr, TCP_CWR))
		FIELD("cwr");
	if (proto_hdr_field_get_u16(hdr, TCP_ECE))
		FIELD("ecn");
	if (proto_hdr_field_get_u16(hdr, TCP_URG))
		FIELD("urg");
	if (proto_hdr_field_get_u16(hdr, TCP_ACK))
		FIELD("ack");
	if (proto_hdr_field_get_u16(hdr, TCP_PSH))
		FIELD("psh");
	if (proto_hdr_field_get_u16(hdr, TCP_RST))
		FIELD("rst");
	if (proto_hdr_field_get_u16(hdr, TCP_SYN))
		FIELD("syn");
	if (proto_hdr_field_get_u16(hdr, TCP_FIN))
		FIELD("fin");
	FIELD("win=%d", proto_hdr_field_get_u16(hdr, TCP_WINDOW));
	FIELD("csum=0x%x", proto_hdr_field_get_u16(hdr, TCP_CSUM));
	FIELD_END("urgptr=0x%x", proto_hdr_field_get_u16(hdr, TCP_URG_PTR));

	HDR_END();
	return 0;
}

static int proto_dump_hdr(struct dump_ctx *ctx, struct proto_hdr *hdr)
{
	switch (hdr->ops->id) {
	case PROTO_ETH:
		return proto_dump_eth(ctx, hdr);
	case PROTO_VLAN:
		return proto_dump_vlan(ctx, hdr);
	case PROTO_ARP:
		return proto_dump_arp(ctx, hdr);
	case PROTO_IP4:
		return proto_dump_ip4(ctx, hdr);
	case PROTO_UDP:
		return proto_dump_udp(ctx, hdr);
	case PROTO_TCP:
		return proto_dump_tcp(ctx, hdr);
	default:
		return -1;
	}
}

int packet_dump_fd(struct packet *pkt, int fd)
{
	struct proto_hdr *hdr;
	enum proto_id pid;
	struct dump_ctx _ctx;
	struct dump_ctx *ctx = &_ctx;
	size_t dump_len = 0;
	uint32_t i;

	_ctx.file = fdopen(fd, "w");
	if (!_ctx.file)
		return -1;

	/* handle case if there is already proto headers */
	if (pkt->headers_count == 0) {
		hdr = proto_packet_apply(PROTO_ETH, pkt);

		while ((pid = proto_hdr_get_next_proto(hdr)) != __PROTO_MAX) {
			if (hdr->pkt_offset + hdr->len >= pkt->len)
				break;

			hdr = proto_packet_apply(pid, pkt);
		}
	}

	PKT_START();
	for (i = 0; i < pkt->headers_count; i++) {
		hdr = pkt->headers[i];

		if (proto_dump_hdr(ctx, hdr))
			break;

		dump_len += hdr->len;
	}

	/* print rest as a bytes */
	if (dump_len < pkt->len) {
		int j = 1;

		DUMP("  ");
		for (i = dump_len; i < pkt->len; ++i, ++j) {
			if (j % 15 == 0)
				DUMP("\n  ");
			DUMP("0x%02x,", pkt->payload[i]);
		}
		DUMP("\n");
	}
	PKT_END();

	fflush(ctx->file);
	return 0;
}