/*
* Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
*      The Regents of the University of California.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* From: NetBSD: print-arcnet.c,v 1.2 2000/04/24 13:02:28 itojun Exp
*/
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: print-arcnet.c,v 1.10 2024/09/02 16:15:30 christos Exp $");
#endif

/* \summary: Attached Resource Computer NETwork (ARCNET) printer */

#include <config.h>

#include "netdissect-stdinc.h"

#include "netdissect.h"
#include "extract.h"

/*
* from: NetBSD: if_arc.h,v 1.13 1999/11/19 20:41:19 thorpej Exp
*/

/*
* Structure of a 2.5MB/s Arcnet header on the BSDs,
* as given to interface code.
*/
struct  arc_header {
       nd_uint8_t  arc_shost;
       nd_uint8_t  arc_dhost;
       nd_uint8_t  arc_type;
       /*
        * only present for newstyle encoding with LL fragmentation.
        * Don't use sizeof(anything), use ARC_HDR{,NEW}LEN instead.
        */
       nd_uint8_t  arc_flag;
       nd_uint16_t arc_seqid;

       /*
        * only present in exception packets (arc_flag == 0xff)
        */
       nd_uint8_t  arc_type2;  /* same as arc_type */
       nd_uint8_t  arc_flag2;  /* real flag value */
       nd_uint16_t arc_seqid2; /* real seqid value */
};

#define ARC_HDRLEN              3
#define ARC_HDRNEWLEN           6
#define ARC_HDRNEWLEN_EXC       10

/* RFC 1051 */
#define ARCTYPE_IP_OLD          240     /* IP protocol */
#define ARCTYPE_ARP_OLD         241     /* address resolution protocol */

/* RFC 1201 */
#define ARCTYPE_IP              212     /* IP protocol */
#define ARCTYPE_ARP             213     /* address resolution protocol */
#define ARCTYPE_REVARP          214     /* reverse addr resolution protocol */

#define ARCTYPE_ATALK           221     /* Appletalk */
#define ARCTYPE_BANIAN          247     /* Banyan Vines */
#define ARCTYPE_IPX             250     /* Novell IPX */

#define ARCTYPE_INET6           0xc4    /* IPng */
#define ARCTYPE_DIAGNOSE        0x80    /* as per ANSI/ATA 878.1 */

/*
* Structure of a 2.5MB/s Arcnet header on Linux.  Linux has
* an extra "offset" field when given to interface code, and
* never presents packets that look like exception frames.
*/
struct  arc_linux_header {
       nd_uint8_t  arc_shost;
       nd_uint8_t  arc_dhost;
       nd_uint16_t arc_offset;
       nd_uint8_t  arc_type;
       /*
        * only present for newstyle encoding with LL fragmentation.
        * Don't use sizeof(anything), use ARC_LINUX_HDR{,NEW}LEN
        * instead.
        */
       nd_uint8_t  arc_flag;
       nd_uint16_t arc_seqid;
};

#define ARC_LINUX_HDRLEN        5
#define ARC_LINUX_HDRNEWLEN     8

static int arcnet_encap_print(netdissect_options *, u_char arctype, const u_char *p,
   u_int length, u_int caplen);

static const struct tok arctypemap[] = {
       { ARCTYPE_IP_OLD,       "oldip" },
       { ARCTYPE_ARP_OLD,      "oldarp" },
       { ARCTYPE_IP,           "ip" },
       { ARCTYPE_ARP,          "arp" },
       { ARCTYPE_REVARP,       "rarp" },
       { ARCTYPE_ATALK,        "atalk" },
       { ARCTYPE_BANIAN,       "banyan" },
       { ARCTYPE_IPX,          "ipx" },
       { ARCTYPE_INET6,        "ipv6" },
       { ARCTYPE_DIAGNOSE,     "diag" },
       { 0, NULL }
};

static void
arcnet_print(netdissect_options *ndo, const u_char *bp, u_int length, int phds,
            u_int flag, u_int seqid)
{
       const struct arc_header *ap;
       const char *arctypename;

       ndo->ndo_protocol = "arcnet";
       ap = (const struct arc_header *)bp;

       if (ndo->ndo_qflag) {
               ND_PRINT("%02x %02x %u: ",
                            GET_U_1(ap->arc_shost),
                            GET_U_1(ap->arc_dhost),
                            length);
               return;
       }

       arctypename = tok2str(arctypemap, "%02x", GET_U_1(ap->arc_type));

       if (!phds) {
               ND_PRINT("%02x %02x %s %u: ",
                            GET_U_1(ap->arc_shost),
                            GET_U_1(ap->arc_dhost),
                            arctypename,
                            length);
               return;
       }

       if (flag == 0) {
               ND_PRINT("%02x %02x %s seqid %04x %u: ",
                       GET_U_1(ap->arc_shost),
                       GET_U_1(ap->arc_dhost),
                       arctypename, seqid,
                       length);
               return;
       }

       if (flag & 1)
               ND_PRINT("%02x %02x %s seqid %04x "
                       "(first of %u fragments) %u: ",
                       GET_U_1(ap->arc_shost),
                       GET_U_1(ap->arc_dhost),
                       arctypename, seqid,
                       (flag + 3) / 2, length);
       else
               ND_PRINT("%02x %02x %s seqid %04x "
                       "(fragment %u) %u: ",
                       GET_U_1(ap->arc_shost),
                       GET_U_1(ap->arc_dhost),
                       arctypename, seqid,
                       flag/2 + 1, length);
}

/*
* This is the top level routine of the printer.  'p' points
* to the ARCNET header of the packet, 'h->ts' is the timestamp,
* 'h->len' is the length of the packet off the wire, and 'h->caplen'
* is the number of bytes actually captured.
*/
void
arcnet_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h, const u_char *p)
{
       u_int caplen = h->caplen;
       u_int length = h->len;
       const struct arc_header *ap;

       int phds;
       u_int flag = 0, archdrlen = 0;
       u_int seqid = 0;
       u_char arc_type;

       ndo->ndo_protocol = "arcnet";
       if (caplen < ARC_HDRLEN) {
               ndo->ndo_ll_hdr_len += caplen;
               nd_trunc_longjmp(ndo);
       }

       ap = (const struct arc_header *)p;
       arc_type = GET_U_1(ap->arc_type);

       switch (arc_type) {
       default:
               phds = 1;
               break;
       case ARCTYPE_IP_OLD:
       case ARCTYPE_ARP_OLD:
       case ARCTYPE_DIAGNOSE:
               phds = 0;
               archdrlen = ARC_HDRLEN;
               break;
       }

       if (phds) {
               if (caplen < ARC_HDRNEWLEN) {
                       arcnet_print(ndo, p, length, 0, 0, 0);
                       ND_PRINT(" phds");
                       ndo->ndo_ll_hdr_len += caplen;
                       nd_trunc_longjmp(ndo);
               }

               flag = GET_U_1(ap->arc_flag);
               if (flag == 0xff) {
                       if (caplen < ARC_HDRNEWLEN_EXC) {
                               arcnet_print(ndo, p, length, 0, 0, 0);
                               ND_PRINT(" phds extended");
                               ndo->ndo_ll_hdr_len += caplen;
                               nd_trunc_longjmp(ndo);
                       }
                       flag = GET_U_1(ap->arc_flag2);
                       seqid = GET_BE_U_2(ap->arc_seqid2);
                       archdrlen = ARC_HDRNEWLEN_EXC;
               } else {
                       seqid = GET_BE_U_2(ap->arc_seqid);
                       archdrlen = ARC_HDRNEWLEN;
               }
       }


       if (ndo->ndo_eflag)
               arcnet_print(ndo, p, length, phds, flag, seqid);

       /*
        * Go past the ARCNET header.
        */
       length -= archdrlen;
       caplen -= archdrlen;
       p += archdrlen;

       if (phds && flag && (flag & 1) == 0) {
               /*
                * This is a middle fragment.
                */
               ndo->ndo_ll_hdr_len += archdrlen;
               return;
       }

       if (!arcnet_encap_print(ndo, arc_type, p, length, caplen))
               ND_DEFAULTPRINT(p, caplen);

       ndo->ndo_ll_hdr_len += archdrlen;
}

/*
* This is the top level routine of the printer.  'p' points
* to the ARCNET header of the packet, 'h->ts' is the timestamp,
* 'h->len' is the length of the packet off the wire, and 'h->caplen'
* is the number of bytes actually captured.  It is quite similar
* to the non-Linux style printer except that Linux doesn't ever
* supply packets that look like exception frames, it always supplies
* reassembled packets rather than raw frames, and headers have an
* extra "offset" field between the src/dest and packet type.
*/
void
arcnet_linux_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h, const u_char *p)
{
       u_int caplen = h->caplen;
       u_int length = h->len;
       const struct arc_linux_header *ap;

       int archdrlen = 0;
       u_char arc_type;

       ndo->ndo_protocol = "arcnet_linux";
       if (caplen < ARC_LINUX_HDRLEN) {
               ndo->ndo_ll_hdr_len += caplen;
               nd_trunc_longjmp(ndo);
       }

       ap = (const struct arc_linux_header *)p;
       arc_type = GET_U_1(ap->arc_type);

       switch (arc_type) {
       default:
               archdrlen = ARC_LINUX_HDRNEWLEN;
               if (caplen < ARC_LINUX_HDRNEWLEN) {
                       ndo->ndo_ll_hdr_len += caplen;
                       nd_trunc_longjmp(ndo);
               }
               break;
       case ARCTYPE_IP_OLD:
       case ARCTYPE_ARP_OLD:
       case ARCTYPE_DIAGNOSE:
               archdrlen = ARC_LINUX_HDRLEN;
               break;
       }

       if (ndo->ndo_eflag)
               arcnet_print(ndo, p, length, 0, 0, 0);

       /*
        * Go past the ARCNET header.
        */
       length -= archdrlen;
       caplen -= archdrlen;
       p += archdrlen;

       if (!arcnet_encap_print(ndo, arc_type, p, length, caplen))
               ND_DEFAULTPRINT(p, caplen);

       ndo->ndo_ll_hdr_len += archdrlen;
}

/*
* Prints the packet encapsulated in an ARCnet data field,
* given the ARCnet system code.
*
* Returns non-zero if it can do so, zero if the system code is unknown.
*/


static int
arcnet_encap_print(netdissect_options *ndo, u_char arctype, const u_char *p,
   u_int length, u_int caplen)
{
       switch (arctype) {

       case ARCTYPE_IP_OLD:
       case ARCTYPE_IP:
               ip_print(ndo, p, length);
               return (1);

       case ARCTYPE_INET6:
               ip6_print(ndo, p, length);
               return (1);

       case ARCTYPE_ARP_OLD:
       case ARCTYPE_ARP:
       case ARCTYPE_REVARP:
               arp_print(ndo, p, length, caplen);
               return (1);

       case ARCTYPE_ATALK:     /* XXX was this ever used? */
               if (ndo->ndo_vflag)
                       ND_PRINT("et1 ");
               atalk_print(ndo, p, length);
               return (1);

       case ARCTYPE_IPX:
               ipx_print(ndo, p, length);
               return (1);

       default:
               return (0);
       }
}