/*
    NetDump library - tcpdump-like packet logger facility

    Copyright (c) 2018 David Gauchard. All rights reserved.
    This file is part of the esp8266 core for Arduino environment.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "Netdump.h"
#include <lwip/init.h>

namespace NetCapture
{

void Packet::printDetail(Print& out, const String& indent, const char* data, size_t size, PacketDetail pd) const
{
    if (pd == PacketDetail::NONE)
    {
        return;
    }

    uint16_t charCount = (pd == PacketDetail::CHAR) ? 80 : 24;

    size_t start = 0;
    while (start < size)
    {
        size_t end = start + charCount;
        if (end > size)
        {
            end = size;
        }
        out.printf("%s", indent.c_str());
        if (pd != PacketDetail::CHAR)
        {
            for (size_t i = start; i < end; i++)
            {
                out.printf("%02x ", (unsigned char)data[i]);
            }
            for (size_t i = end; i < start + charCount; i++)
            {
                out.print("   ");
            }
        }
        for (size_t i = start; i < end; i++)
        {
            out.printf("%c", data[i] >= 32 && data[i] < 128 ? data[i] : '.');
        }
        out.println();

        start += charCount;
    }
}

void Packet::setPacketType(PacketType pt)
{
    thisPacketType = pt;
    thisAllPacketTypes.emplace_back(pt);
}

void Packet::setPacketTypes()
{
    if (isARP())
    {
        setPacketType(PacketType::ARP);
    }
    else if (isIP())
    {
        setPacketType(PacketType::IP);
        setPacketType(isIPv4() ? PacketType::IPv4 : PacketType::IPv6);
        if (isUDP())
        {
            setPacketType(PacketType::UDP);
            if (isMDNS())
            {
                setPacketType(PacketType::MDNS);
            }
            if (isDNS())
            {
                setPacketType(PacketType::DNS);
            }
            if (isSSDP())
            {
                setPacketType(PacketType::SSDP);
            }
            if (isDHCP())
            {
                setPacketType(PacketType::DHCP);
            }
            if (isWSDD())
            {
                setPacketType(PacketType::WSDD);
            }
            if (isNETBIOS())
            {
                setPacketType(PacketType::NETBIOS);
            }
            if (isSMB())
            {
                setPacketType(PacketType::SMB);
            }
            if (isOTA())
            {
                setPacketType(PacketType::OTA);
            }
        }
        if (isTCP())
        {
            setPacketType(PacketType::TCP);
            if (isHTTP())
            {
                setPacketType(PacketType::HTTP);
            }
        }
        if (isICMP())
        {
            setPacketType(PacketType::ICMP);
        }
        if (isIGMP())
        {
            setPacketType(PacketType::IGMP);
        }
    }
    else
    {
        setPacketType(PacketType::UKNW);
    }
}

const PacketType Packet::packetType() const
{
    return thisPacketType;
}

const std::vector<PacketType>& Packet::allPacketTypes() const
{
    return thisAllPacketTypes;
}

void Packet::MACtoString(int dataIdx, StreamString& sstr) const
{
    for (int i = 0; i < 6; i++)
    {
        sstr.printf_P(PSTR("%02x"), (unsigned char)data[dataIdx + i]);
        if (i < 5)
        {
            sstr.print(':');
        }
    }

}

void Packet::ARPtoString(PacketDetail netdumpDetail, StreamString& sstr) const
{
    switch (getARPType())
    {
    case 1 : sstr.printf_P(PSTR("who has %s tell %s"), getIP(ETH_HDR_LEN + 24).toString().c_str(), getIP(ETH_HDR_LEN + 14).toString().c_str());
        break;
    case 2 : sstr.printf_P(PSTR("%s is at "), getIP(ETH_HDR_LEN + 14).toString().c_str());
        MACtoString(ETH_HDR_LEN + 8, sstr);
        break;
    }
    sstr.printf("\r\n");
    printDetail(sstr, PSTR("           D "), &data[ETH_HDR_LEN], packetLength - ETH_HDR_LEN, netdumpDetail);
}

void Packet::DNStoString(PacketDetail netdumpDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("%s>%s "), sourceIP().toString().c_str(), destIP().toString().c_str());
    sstr.printf_P(PSTR("ID=0x%04x "), ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8));
    sstr.printf_P(PSTR("F=0x%04x "), ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8 + 2));
    if (uint16_t t = ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8 + 4))
    {
        sstr.printf_P(PSTR("Q=%d "), t);
    }
    if (uint16_t t = ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8 + 6))
    {
        sstr.printf_P(PSTR("R=%d "), t);
    }
    if (uint16_t t = ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8 + 8))
    {
        sstr.printf_P(PSTR("TR=%d "), t);
    }
    if (uint16_t t = ntoh16(ETH_HDR_LEN + getIpHdrLen() + 8 + 10))
    {
        sstr.printf_P(PSTR("DR=%d "), t);
    }
    sstr.printf_P(PSTR("\r\n"));
    printDetail(sstr, PSTR("           H "), &data[ETH_HDR_LEN + getIpHdrLen()], getUdpHdrLen(), netdumpDetail);
    printDetail(sstr, PSTR("           D "), &data[ETH_HDR_LEN + getIpHdrLen() + getUdpHdrLen()], getUdpLen(), netdumpDetail);
}

void Packet::UDPtoString(PacketDetail netdumpDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("%s>%s "), sourceIP().toString().c_str(), destIP().toString().c_str());
    sstr.printf_P(PSTR("%d:%d"), getSrcPort(), getDstPort());
    sstr.printf_P(PSTR("\r\n"));
    printDetail(sstr, PSTR("           H "), &data[ETH_HDR_LEN + getIpHdrLen()], getUdpHdrLen(), netdumpDetail);
    printDetail(sstr, PSTR("           D "), &data[ETH_HDR_LEN + getIpHdrLen() + getUdpHdrLen()], getUdpLen(), netdumpDetail);
}

void Packet::TCPtoString(PacketDetail netdumpDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("%s>%s "), sourceIP().toString().c_str(), destIP().toString().c_str());
    sstr.printf_P(PSTR("%d:%d "), getSrcPort(), getDstPort());
    uint16_t flags = getTcpFlags();
    sstr.print('[');
    const char chars [] = "FSRPAUECN";
    for (uint8_t i = 0; i < sizeof chars; i++)
        if (flags & (1 << i))
        {
            sstr.print(chars[i]);
        }
    sstr.print(']');
    sstr.printf_P(PSTR(" len: %u seq: %u, ack: %u, wnd: %u "), getTcpLen(), getTcpSeq(), getTcpAck(), getTcpWindow());
    sstr.printf_P(PSTR("\r\n"));
    printDetail(sstr, PSTR("           H "), &data[ETH_HDR_LEN + getIpHdrLen()], getTcpHdrLen(), netdumpDetail);
    printDetail(sstr, PSTR("           D "), &data[ETH_HDR_LEN + getIpHdrLen() + getTcpHdrLen()], getTcpLen(), netdumpDetail);
}

void Packet::ICMPtoString(PacketDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("%s>%s "), sourceIP().toString().c_str(), destIP().toString().c_str());
    if (isIPv4())
    {
        switch (getIcmpType())
        {
        case 0 : sstr.printf_P(PSTR("ping reply")); break;
        case 8 : sstr.printf_P(PSTR("ping request")); break;
        default: sstr.printf_P(PSTR("type(0x%02x)"), getIcmpType()); break;
        }
    }
    if (isIPv6())
    {
        switch (getIcmpType())
        {
        case 129 : sstr.printf_P(PSTR("ping reply")); break;
        case 128 : sstr.printf_P(PSTR("ping request")); break;
        case 135 : sstr.printf_P(PSTR("Neighbour solicitation")); break;
        case 136 : sstr.printf_P(PSTR("Neighbour advertisement")); break;
        default: sstr.printf_P(PSTR("type(0x%02x)"), getIcmpType()); break;
        }
    }
    sstr.printf("\r\n");
}

void Packet::IGMPtoString(PacketDetail, StreamString& sstr) const
{
    switch (getIgmpType())
    {
    case 1 : sstr.printf_P(PSTR("Create Group Request")); break;
    case 2 : sstr.printf_P(PSTR("Create Group Reply")); break;
    case 3 : sstr.printf_P(PSTR("Join Group Request")); break;
    case 4 : sstr.printf_P(PSTR("Join Group Reply")); break;
    case 5 : sstr.printf_P(PSTR("Leave Group Request")); break;
    case 6 : sstr.printf_P(PSTR("Leave Group Reply")); break;
    case 7 : sstr.printf_P(PSTR("Confirm Group Request")); break;
    case 8 : sstr.printf_P(PSTR("Confirm Group Reply")); break;
    case 0x11 : sstr.printf_P(PSTR("Group Membership Query")); break;
    case 0x12 : sstr.printf_P(PSTR("IGMPv1 Membership Report")); break;
    case 0x22 : sstr.printf_P(PSTR("IGMPv3 Membership Report")); break;
    default: sstr.printf_P(PSTR("type(0x%02x)"), getIgmpType()); break;
    }
    sstr.printf_P(PSTR("\r\n"));
}

void Packet::IPtoString(PacketDetail netdumpDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("%s>%s "), sourceIP().toString().c_str(), destIP().toString().c_str());
    sstr.printf_P(PSTR("Unknown IP type : %d\r\n"), ipType());
    printDetail(sstr, PSTR("           H "), &data[ETH_HDR_LEN], getIpHdrLen(), netdumpDetail);
    printDetail(sstr, PSTR("           D "), &data[ETH_HDR_LEN + getIpHdrLen()], getIpTotalLen() - getIpHdrLen(), netdumpDetail);
}

void Packet::UKNWtoString(PacketDetail, StreamString& sstr) const
{
    sstr.printf_P(PSTR("Unknown EtherType 0x%04x Src : "), ethType());
    MACtoString(0, sstr);
    sstr.printf_P(PSTR(" Dst : "));
    MACtoString(6, sstr);
    sstr.printf_P(PSTR("\r\n"));
}

const String Packet::toString() const
{
    return toString(PacketDetail::NONE);
}


const String Packet::toString(PacketDetail netdumpDetail) const
{
    StreamString sstr;
    sstr.reserve(128);

    sstr.printf_P(PSTR("%d %3s %-4s "), netif_idx, out ? "out" : "in ", packetType().toString().c_str());

    if (netdumpDetail == PacketDetail::RAW)
    {
        sstr.printf_P(PSTR(" : "));
        for (auto at : thisAllPacketTypes)
        {
            sstr.printf_P(PSTR("%s "), at.toString().c_str());
        }
        sstr.printf_P(PSTR("\r\n"));
        printDetail(sstr, PSTR("           D "), data, packetLength, netdumpDetail);
        return sstr;
    }

    switch (thisPacketType)
    {
    case PacketType::ARP :
    {
        ARPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::MDNS :
    case PacketType::DNS :
    {
        DNStoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::SSDP :
    case PacketType::DHCP :
    case PacketType::WSDD :
    case PacketType::NETBIOS :
    case PacketType::SMB :
    case PacketType::OTA :
    case PacketType::UDP :
    {
        UDPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::TCP :
    case PacketType::HTTP :
    {
        TCPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::ICMP :
    {
        ICMPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::IGMP :
    {
        IGMPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::IPv4 :
    case PacketType::IPv6 :
    {
        IPtoString(netdumpDetail, sstr);
        break;
    }
    case PacketType::UKNW :
    {
        UKNWtoString(netdumpDetail, sstr);
        break;
    }
    default :
    {
        sstr.printf_P(PSTR("Non identified packet\r\n"));
        break;
    }
    }
    return sstr;
}

} // namespace NetCapture
