/* @(#) $Header: ax25dump.c,v 1.5 91/02/24 20:16:33 deyke Exp $ */

/* AX25 header tracing
 * Copyright 1991 Phil Karn, KA9Q
 */
#include <stdio.h>
#include "global.h"

#define	LAPB_UNKNOWN	0
#define	LAPB_COMMAND	1
#define	LAPB_RESPONSE	2

#define	SEG_FIRST	0x80
#define	SEG_REM		0x7F

#define	PID_SEGMENT	0x08
#define	PID_ARP		0xCD
#define	PID_NETROM	0xCF
#define	PID_IP		0xCC
#define	PID_X25		0x01
#define	PID_TEXNET	0xC3
#define	PID_FLEXNET	0xCE
#define	PID_NO_L3	0xF0

#define	I		0x00
#define	S		0x01
#define	RR		0x01
#define	RNR		0x05
#define	REJ		0x09
#define	U		0x03
#define	SABM		0x2F
#define	SABME		0x6F
#define	DISC		0x43
#define	DM		0x0F
#define	UA		0x63
#define	FRMR		0x87
#define	UI		0x03
#define	PF		0x10
#define	EPF		0x01

#define	MMASK		7

#define	HDLCAEB		0x01
#define	SSID		0x1E
#define	REPEATED	0x80
#define	C		0x80
#define	SSSID_SPARE	0x60
#define	ESSID_SPARE	0x20

#define	ALEN		6
#define	AXLEN		7

#define	W		1
#define	X		2
#define	Y		4
#define	Z		8

static int  ftype(unsigned char *, int *, int *, int *, int *, int);
static char *decode_type(int);

/* Dump an AX.25 packet header */
void ax25_dump(unsigned char *data, int length)
{
	char tmp[15];
	int ctlen, nr, ns, pf, pid, seg, type, end, cmdrsp, extseq;

	/* Extract the address header */
	if (length < (AXLEN + AXLEN + 1)) {
		/* Something wrong with the header */
		fprintf(stdout, "AX25: bad header!\n");
		return;
	}

	if ((data[AXLEN + ALEN] & SSSID_SPARE) == SSSID_SPARE) {
		extseq = 0;
		fprintf(stdout, "AX25: ");
	} else {
		extseq = 1;
		fprintf(stdout, "EAX25: ");
	}
	
	fprintf(stdout, "%s", pax25(tmp, data + AXLEN));
	fprintf(stdout, "->%s", pax25(tmp, data));

	cmdrsp = LAPB_UNKNOWN;

	if ((data[ALEN] & C) && !(data[AXLEN + ALEN] & C))
		cmdrsp = LAPB_COMMAND;

	if ((data[AXLEN + ALEN] & C) && !(data[ALEN] & C))
		cmdrsp = LAPB_RESPONSE;

	end = (data[AXLEN + ALEN] & HDLCAEB);

	data   += (AXLEN + AXLEN);
	length -= (AXLEN + AXLEN);

	if (!end) {
		fprintf(stdout, " v");

		while (!end) {
			/* Print digi string */
			fprintf(stdout," %s%s", pax25(tmp, data), (data[ALEN] & REPEATED) ? "*" : "");
			
			end = (data[ALEN] & HDLCAEB);
			
			data   += AXLEN;
			length -= AXLEN;
		}
	}
	
	if (length == 0) return;
	
	ctlen = ftype(data, &type, &ns, &nr, &pf, extseq);
	
	data   += ctlen;
	length -= ctlen;

	fprintf(stdout, " <%s", decode_type(type));

	switch (cmdrsp) {
		case LAPB_COMMAND:
			fprintf(stdout, " C");
			if (pf) fprintf(stdout, " P");
			break;
		case LAPB_RESPONSE:
			fprintf(stdout, " R");
			if (pf) fprintf(stdout, " F");
			break;
		default:
			break;
	}

	if (type == I)
		fprintf(stdout, " S%d", ns);

	if ((type & 0x3) != U)   /* I or S frame? */
		fprintf(stdout, " R%d", nr);

	fputc('>', stdout);

	if (type == I || type == UI) {
		/* Decode I field */
		if (length > 0) {        /* Get pid */
			pid = *data++;
			length--;
		
			if (pid == PID_SEGMENT) {
				seg = *data++;
				length--;
				fprintf(stdout, "%s remain %u", seg & SEG_FIRST ? " First seg;" : "", seg & SEG_REM);

				if (seg & SEG_FIRST) {
					pid = *data++;
					length--;
				}
			}

			switch (pid) {
				case PID_SEGMENT:
					putc('\n', stdout);
					break;  /* Already displayed */
				case PID_ARP:
					fprintf(stdout," pid=ARP\n");
					arp_dump(data, length);
					break;
				case PID_NETROM:
					fprintf(stdout," pid=NET/ROM\n");
					netrom_dump(data, length);
					break;
				case PID_IP:
					fprintf(stdout," pid=IP\n");
					ip_dump(data, length);
					break;
				case PID_X25:
					fprintf(stdout, " pid=X.25\n");
					data_dump(data, length);
					break;
				case PID_TEXNET:
					fprintf(stdout, " pid=TEXNET\n");
					data_dump(data, length);
					break;
				case PID_FLEXNET:
					fprintf(stdout, " pid=FLEXNET\n");
					data_dump(data, length);
					break;
				case PID_NO_L3:
					fprintf(stdout, " pid=Text\n");
					data_dump(data, length);
					break;
				default:
					fprintf(stdout, " pid=0x%x\n", pid);
					data_dump(data, length);
					break;
			}
		}
	} else if (type == FRMR && length >= 3) {
	/* FIX ME XXX
		fprintf(stdout, ": %s", decode_type(ftype(data[0])));
	*/
		fprintf(stdout, ": %02X", data[0]);
		fprintf(stdout, " Vr = %d Vs = %d",
			(data[1] >> 5) & MMASK,
			(data[1] >> 1) & MMASK);
		if(data[2] & W)
			fprintf(stdout, " Invalid control field");
		if(data[2] & X)
			fprintf(stdout, " Illegal I-field");
		if(data[2] & Y)
			fprintf(stdout, " Too-long I-field");
		if(data[2] & Z)
			fprintf(stdout, " Invalid seq number");
		putc('\n',stdout);
	}
}

static char *decode_type(int type)
{
	switch (type) {
		case I:
			return "I";
		case SABM:
			return "C";
		case SABME:
			return "CE";
		case DISC:
			return "D";
		case DM:
			return "DM";
		case UA:
			return "UA";
		case RR:
			return "RR";
		case RNR:
			return "RNR";
		case REJ:
			return "REJ";
		case FRMR:
			return "FRMR";
		case UI:
			return "UI";
		default:
			return "[invalid]";
	}
}

char *pax25(char *buf, unsigned char *data)
{
	int i, ssid;
	char *s;
	char c;
	
	s = buf;
	
	for (i = 0; i < ALEN; i++) {
		c = (data[i] >> 1) & 0x7F;
		
		if (c != ' ') *s++ = c;
	}	

	if ((ssid = (data[ALEN] & SSID)) != 0)
		sprintf(s, "-%d", ssid >> 1);
	else
		*s = '\0';

	return(buf);
}

static int ftype(unsigned char *data, int *type, int *ns, int *nr, int *pf, int extseq)
{
	if (extseq) {
		if ((*data & 0x01) == 0) {	/* An I frame is an I-frame ... */
			*type = I;
			*ns   = (*data >> 1) & 127;
			data++;
			*nr   = (*data >> 1) & 127;
			*pf   = *data & EPF;
			return 2;
		}
		if (*data & 0x02) {
			*type = *data & ~PF;
			*pf   = *data & PF;
			return 1;
		} else {
			*type = *data;
			data++;
			*nr   = (*data >> 1) & 127;
			*pf   = *data & EPF;
			return 2;
		}
	} else {
		if ((*data & 0x01) == 0) {	/* An I frame is an I-frame ... */
			*type = I;
			*ns   = (*data >> 1) & 7;
			*nr   = (*data >> 5) & 7;
			*pf   = *data & PF;
			return 1;
		}
		if (*data & 0x02) {		/* U-frames use all except P/F bit for type */
			*type = *data & ~PF;
			*pf   = *data & PF;
			return 1;
		} else {			/* S-frames use low order 4 bits for type */
			*type = *data & 0x0F;
			*nr   = (*data >> 5) & 7;
			*pf   = *data & PF;
			return 1;
		}
	}
}
