/* eth_parser.c
 *
 * This file contains functions to parse Ethernet II headers.
 *
 * File begun on 2018-11-13
 *
 * Created by:
 *  - Théo Bertin (theo.bertin@advens.fr)
 *
 * With:
 *  - François Bernard (francois.bernard@isen.yncrea.fr)
 *  - Tianyu Geng (tianyu.geng@isen.yncrea.fr)
 *
 * This file is part of rsyslog.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "config.h"
#include "parsers.h"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpacked"
#pragma GCC diagnostic ignored "-Wattributes"
struct __attribute__((__packed__)) eth_header_s {
    uint8_t addrDst[6];
    uint8_t addrSrc[6];
    uint16_t type;
};

struct __attribute__((__packed__)) vlan_header_s {
    uint8_t addrDst[6];
    uint8_t addrSrc[6];
    uint16_t vlanCode;
    uint16_t vlanTag;
    uint16_t type;
};
#pragma GCC diagnostic pop

typedef struct eth_header_s eth_header_t;
typedef struct vlan_header_s vlan_header_t;


/*
 *  Get an ethernet header type as uint16_t
 *   and return the correspondence as string
 *  NOTE : Only most common types are present, to complete if needed
 */
static const char *eth_type_to_string(uint16_t eth_type) {
    switch (eth_type) {
        case 0x00bb:  // Extreme Networks Discovery Protocol
            return "EDP";
        case 0x0200:  // PUP protocol
            return "PUP";
        case 0x0800:  // IP protocol
            return "IP";
        case 0x0806:  // address resolution protocol
            return "ARP";
        case 0x88a2:  // AoE protocol
            return "AOE";
        case 0x2000:  // Cisco Discovery Protocol
            return "CDP";
        case 0x2004:  // Cisco Dynamic Trunking Protocol
            return "DTP";
        case 0x8035:  // reverse addr resolution protocol
            return "REVARP";
        case 0x8100:  // IEEE 802.1Q VLAN tagging
            return "802.1Q";
        case 0x88a8:  // IEEE 802.1ad
            return "802.1AD";
        case 0x9100:  // Legacy QinQ
            return "QINQ1";
        case 0x9200:  // Legacy QinQ
            return "QINQ2";
        case 0x8137:  // Internetwork Packet Exchange
            return "IPX";
        case 0x86DD:  // IPv6 protocol
            return "IPv6";
        case 0x880B:  // PPP
            return "PPP";
        case 0x8847:  // MPLS
            return "MPLS";
        case 0x8848:  // MPLS Multicast
            return "MPLS_MCAST";
        case 0x8863:  // PPP Over Ethernet Discovery Stage
            return "PPPoE_DISC";
        case 0x8864:  // PPP Over Ethernet Session Stage
            return "PPPoE";
        case 0x88CC:  // Link Layer Discovery Protocol
            return "LLDP";
        case 0x6558:  // Transparent Ethernet Bridging
            return "TEB";
        default:
            return "UNKNOWN";
    }
}


/*
 *  This function parses the bytes in the received packet to extract Ethernet II metadata.
 *
 *  its parameters are:
 *    - a pointer on the list of bytes representing the packet
 *        the first byte must be the beginning of the ETH header
 *    - the size of the list passed as first parameter
 *    - a pointer on a json_object, containing all the metadata recovered so far
 *      this is also where ETH metadata will be added
 *
 *  This function returns a structure containing the data unprocessed by this parser
 *  or the ones after (as a list of bytes), and the length of this data.
 */
data_ret_t *eth_parse(const uchar *packet, int pktSize, struct json_object *jparent) {
    DBGPRINTF("entered eth_parse\n");
    DBGPRINTF("packet size %d\n", pktSize);
    if (pktSize < 14) { /* too short for eth header */
        DBGPRINTF("ETH packet too small : %d\n", pktSize);
        RETURN_DATA_AFTER(0)
    }

    eth_header_t *eth_header = (eth_header_t *)packet;
    char ethMacSrc[20], ethMacDst[20];
    uint8_t hdrLen = 14;

    ether_ntoa_r((struct ether_addr *)eth_header->addrSrc, ethMacSrc);
    ether_ntoa_r((struct ether_addr *)eth_header->addrDst, ethMacDst);

    json_object_object_add(jparent, "ETH_src", json_object_new_string((char *)ethMacSrc));
    json_object_object_add(jparent, "ETH_dst", json_object_new_string((char *)ethMacDst));

    uint16_t ethType = (uint16_t)ntohs(eth_header->type);

    if (ethType == ETHERTYPE_VLAN) {
        if (pktSize < (int)sizeof(vlan_header_t)) {
            DBGPRINTF("VLAN packet too small : %d\n", pktSize);
            RETURN_DATA_AFTER(0)
        }
        vlan_header_t *vlan_header = (vlan_header_t *)packet;
        json_object_object_add(jparent, "ETH_tag", json_object_new_int(ntohs(vlan_header->vlanTag)));
        ethType = (uint16_t)ntohs(vlan_header->type);
        hdrLen += 4;
    }

    data_ret_t *ret;

    if (ethType < 1500) {
        /* this is a LLC header */
        json_object_object_add(jparent, "ETH_len", json_object_new_int(ethType));
        ret = llc_parse(packet + hdrLen, pktSize - hdrLen, jparent);
        if (ret == NULL) return NULL;

        /* packet has the minimum allowed size, so the remaining data is
         * most likely padding, this should not appear as data, so remove it
         * */
        // TODO this is a quick win, a more elaborate solution would be to check if all data
        //  is indeed zero, but that would take more processing time
        if (pktSize <= 60 && ret->pData != NULL) {
            if (!ret->pData[0]) ret->size = 0;
        }
        return ret;
    }

    json_object_object_add(jparent, "ETH_type", json_object_new_int(ethType));
    json_object_object_add(jparent, "ETH_typestr", json_object_new_string((char *)eth_type_to_string(ethType)));
    ret = eth_proto_parse(ethType, (packet + hdrLen), (pktSize - hdrLen), jparent);
    if (ret == NULL) return NULL;

    /* packet has the minimum allowed size, so the remaining data is
     * most likely padding, this should not appear as data, so remove it */
    if (pktSize <= 60 && ret->pData != NULL) {
        if (!ret->pData[0]) ret->size = 0;
    }
    return ret;
}
