// Copyright (c) 2018 Arista Networks, Inc.  All rights reserved.

/* \summary: EtherType protocol for Arista Networks printer */

#include <config.h>

#include "netdissect-stdinc.h"

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

/*

From Bill Fenner:

The Arista timestamp header consists of the following fields:
1. The Arista ethertype (0xd28b)
2. A 2-byte subtype field; 0x01 indicates the timestamp header
3. A 2-byte version field, described below.
4. A 48-bit or 64-bit timestamp field, depending on the contents of the version field

This header is then followed by the original ethertype and the remainder of the original packet.

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            dst mac                            |
+                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
|                            src mac                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        ethertype 0xd28b       |          subtype 0x1          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            version            |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               +
|                          timestamp...                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The two-byte version value is split into 3 fields:
1. The timescale in use.  Currently assigned values include:
   0 = TAI
   1 = UTC
2. The timestamp format and length.  Currently assigned values include:
   1 = 64-bit timestamp
   2 = 48-bit timestamp
3. The hardware info
   0 = R/R2 series
   1 = R3 series

0                   1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   timescale   | format|hw info|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


See also: https://www.arista.com/assets/data/pdf/Whitepapers/Overview_Arista_Timestamps.pdf

*/

#define ARISTA_SUBTYPE_TIMESTAMP 0x0001
static const struct tok subtype_str[] = {
       { ARISTA_SUBTYPE_TIMESTAMP, "Timestamp" },
       { 0, NULL }
};

static const struct tok ts_timescale_str[] = {
       { 0, "TAI" },
       { 1, "UTC" },
       { 0, NULL }
};

#define FORMAT_64BIT 0x1
#define FORMAT_48BIT 0x2
static const struct tok ts_format_str[] = {
       { FORMAT_64BIT, "64-bit" },
       { FORMAT_48BIT, "48-bit" },
       { 0, NULL }
};

static const struct tok hw_info_str[] = {
       { 0, "R/R2" },
       { 1, "R3" },
       { 0, NULL }
};

static inline void
arista_print_date_hms_time(netdissect_options *ndo, uint32_t seconds,
               uint32_t nanoseconds)
{
       time_t ts;
       char buf[sizeof("-yyyyyyyyyy-mm-dd hh:mm:ss")];

       ts = seconds + (nanoseconds / 1000000000);
       nanoseconds %= 1000000000;
       ND_PRINT("%s.%09u",
           nd_format_time(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S",
              gmtime(&ts)), nanoseconds);
}

int
arista_ethertype_print(netdissect_options *ndo, const u_char *bp, u_int len _U_)
{
       uint16_t subTypeId;
       u_short bytesConsumed = 0;

       ndo->ndo_protocol = "arista";

       subTypeId = GET_BE_U_2(bp);
       bp += 2;
       bytesConsumed += 2;

       ND_PRINT("SubType %s (0x%04x), ",
                tok2str(subtype_str, "Unknown", subTypeId),
                subTypeId);

       // TapAgg Header Timestamping
       if (subTypeId == ARISTA_SUBTYPE_TIMESTAMP) {
               uint64_t seconds;
               uint32_t nanoseconds;
               uint8_t ts_timescale = GET_U_1(bp);
               bp += 1;
               bytesConsumed += 1;
               ND_PRINT("Timescale %s (%u), ",
                        tok2str(ts_timescale_str, "Unknown", ts_timescale),
                        ts_timescale);

               uint8_t ts_format = GET_U_1(bp) >> 4;
               uint8_t hw_info = GET_U_1(bp) & 0x0f;
               bp += 1;
               bytesConsumed += 1;

               // Timestamp has 32-bit lsb in nanosec and remaining msb in sec
               ND_PRINT("Format %s (%u), HwInfo %s (%u), Timestamp ",
                        tok2str(ts_format_str, "Unknown", ts_format),
                        ts_format,
                        tok2str(hw_info_str, "Unknown", hw_info),
                        hw_info);
               switch (ts_format) {
               case FORMAT_64BIT:
                       seconds = GET_BE_U_4(bp);
                       nanoseconds = GET_BE_U_4(bp + 4);
                       arista_print_date_hms_time(ndo, seconds, nanoseconds);
                       bytesConsumed += 8;
                       break;
               case FORMAT_48BIT:
                       seconds = GET_BE_U_2(bp);
                       nanoseconds = GET_BE_U_4(bp + 2);
                       seconds += nanoseconds / 1000000000;
                       nanoseconds %= 1000000000;
                       ND_PRINT("%" PRIu64 ".%09u", seconds, nanoseconds);
                       bytesConsumed += 6;
                       break;
               default:
                       return -1;
               }
       } else {
               return -1;
       }
       ND_PRINT(": ");
       return bytesConsumed;
}