// 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"
#include "timeval-operations.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, const uint32_t seconds,
		const uint32_t nanoseconds)
{
	const time_t ts = seconds;
	char buf[sizeof("-yyyyyyyyyy-mm-dd hh:mm:ss")];

	ND_PRINT("%s.%09u",
	    nd_format_time(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S",
	       gmtime(&ts)), nanoseconds);
	if (nanoseconds > ND_NANO_PER_SEC - 1)
		ND_PRINT(" " ND_INVALID_NANO_SEC_STR);
}

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) {
		uint32_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);
			ND_PRINT("%u.%09u", seconds, nanoseconds);
			if (nanoseconds > ND_NANO_PER_SEC - 1)
				ND_PRINT(" " ND_INVALID_NANO_SEC_STR);
			bytesConsumed += 6;
			break;
		default:
			return -1;
		}
	} else {
		return -1;
	}
	ND_PRINT(": ");
	return bytesConsumed;
}
