/*---------------------------------------------------------------------------*/
/* debug.cpp                                                                 */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/
#include "platform/platform.h"
#include "common/interface/time.h"
#ifdef _IOS_
extern "C" void nslog_print(const char *msg);
extern "C" void nslog_printv(const char *format, va_list ap);
#endif
#ifdef __ANDROID__
#include <android/log.h>
#endif

/*---------------------------------------------------------------------------*/
/* class Debug      		                                                 */
/*---------------------------------------------------------------------------*/
class Debug : public IDebug {
    bool logEnabled;
    bool hexdumpEnabled;

    short ip_checksum(byte *addr, byte len);
    void add_pcap_header(byte ** prepend, dword * length, dword timestamp_sec, dword timestamp_usec);
    void add_ethernet_header(byte ** prepend, dword * length, word ether_prot, bool set_remote_mac, bool send);
    void add_ip4_header(byte ** prepend, dword * length, const IP4addr src, const IP4addr dst, byte protocol, byte tos);
    void add_ip_header(byte ** prepend, dword * length, const IPaddr * src, const IPaddr * dst, byte protocol);
    void add_udp_header(byte ** prepend, dword * length, word src_port, word dst_port);

public:
    Debug();
    ~Debug();
    void printf(const char * format, ...) override;
    void iprintf(const char * format, ...) override;
    void hexdump(const void * buffer, size_t size) override;
    void appPrintf(const char * appName, const char * appDomain, const char * format, ...) override;
    void appPrintfV(const char * appName, const char * appDomain, const char * format, va_list ap, bool iprintf = false) override;
    void appHexdump(const char * appName, const char * appDomain, const void * buffer, size_t size) override;
    void appOSILayer4(const char * appName, const char * appDomain, const IPaddr * src, const IPaddr * dst, byte protocol, const byte * data, dword length, bool send, byte tos) override;
    void SetLogging(bool on) override { logEnabled = on; };
    void SetHexdump(bool on) override { hexdumpEnabled = on; };
#if !defined(PLATFORM_NO_PACKET)
    void osi_layer_4(IP4addr src, IP4addr dst, byte protocol, class packet * data, word src_port, word dst_port, bool send, byte tos) override;
    void osi_layer_4(const IPaddr * src6, const IPaddr * dst6, byte protocol, class packet * data, word src_port, word dst_port, bool send, byte tos) override;
#endif
};

IDebug   * debug = IDebug::Create();
IDebugIpos * debug_ipos = 0;

Debug::Debug()
{
    this->logEnabled = true;
    this->hexdumpEnabled = true;
}

Debug::~Debug()
{
    debug = NULL;
}

void Debug::appPrintfV(const char * appName, const char * appDomain, const char * format, va_list ap, bool iprintf)
{
#ifdef _IOS_
    if (!logEnabled) return;    // ignore new logs
    if (iprintf) {
        char b[10000];
        ivsnprintf(b, sizeof(b), format, ap);
        b[sizeof(b) - 1] = '\0';
        nslog_print(b);
    }
    else {
        nslog_printv(format, ap);
    }
#endif
#ifdef __ANDROID__
    char b[10000];
    int i, n;
    char c;
    if (iprintf) {
        n = ivsnprintf(b, sizeof(b), format, ap);
    }
    else {
        n = vsnprintf(b, sizeof(b), format, ap);
    }
    b[sizeof(b) - 1] = '\0';
    if (n >= ((int)(sizeof(b)))) {
        n = sizeof(b) - 1;
    }
    i = 0;
    while(i + 1023 < n) {
        c = b[i + 1023];
        b[i + 1023] = '\0';
        __android_log_print(PHONE_ANDROID_LOG_PRIORITY, PHONE_ANDROID_LOG_APP_NAME, "%s", &b[i]);
        b[i + 1023] = c;
        i += 1023;
    }
    __android_log_print(PHONE_ANDROID_LOG_PRIORITY, PHONE_ANDROID_LOG_APP_NAME, "%s", &b[i]);
#endif
}

void Debug::appPrintf(const char * appName, const char * appDomain, const char * format, ...)
{
    va_list     ap;
    va_start(ap, format);
    appPrintfV(appName, appDomain, format, ap);
    va_end(ap);
}

void Debug::printf(const char * format, ...)
{
    va_list     ap;
    va_start(ap, format);
    appPrintfV(NULL, NULL, format, ap);
    va_end(ap);
}

void Debug::iprintf(const char * format, ...)
{
    va_list     ap;
    va_start(ap, format);
    appPrintfV(NULL, NULL, format, ap, true);
    va_end(ap);
}

void Debug::appHexdump(const char * appName, const char * appDomain, const void * buffer, size_t size)
{
    static char hex_char_table[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    char hex_buffer[100];
    dword index, d;
    byte b;
    unsigned int i, j, k, n;

    if (!logEnabled || !hexdumpEnabled) return; // logging in general or hexdumps disabled
    if (!buffer) return;
    // dump byte by byte
	//       00000000 - 65 0d 0a 43  6f 6e 74 65  6e 74 2d 74  79 70 65 3a   e..Content-type:
	// <-6--><--8---><3><------------------50----------------------------><3><----16-------->
    for(i = 0; i < 6; i++) hex_buffer[i] = ' ';
    hex_buffer[6+8] = ' ';
    hex_buffer[6+8+1] = '-';
    hex_buffer[6+8+3+50+1] = ' ';
    hex_buffer[6+8+3+50+2] = ' ';
    for(index = 0; index < size; index += 16) {
        i = 6;
        d = ((dword)(((const byte *) buffer) - ((byte *) 0))) + index;
        for(k = 0; k < sizeof(d); k++) {
            hex_buffer[i++] = hex_char_table[d >> 28];
            d <<= 4;
            hex_buffer[i++] = hex_char_table[d >> 28];
            d <<= 4;
        }
        i = 6+8+2;
        n = (unsigned)(size - index);
        if(n > 16) n = 16;
        j = 0;
        while(j < n) {
            b = ((const byte *) buffer)[index + j];
            hex_buffer[i++] = ' ';
            hex_buffer[i++] = hex_char_table[b >> 4];
            hex_buffer[i++] = hex_char_table[b & 0xf];
            hex_buffer[6+8+3+50+3 + j] = ((b >= 0x20) && (b <= 0x7e)) ? b : '.';
            j++;
            if((j & 0x3) == 0) hex_buffer[i++] = ' ';
        }
        hex_buffer[6+8+3+50+3 + j] = '\0';
        while(j < 16) {
            hex_buffer[i++] = ' ';
            hex_buffer[i++] = ' ';
            hex_buffer[i++] = ' ';
            j++;
            if((j & 0x3) == 0) hex_buffer[i++] = ' ';
        }
#ifdef _IOS_
        nslog_print(hex_buffer);
#endif
#ifdef __ANDROID__
        __android_log_print(PHONE_ANDROID_LOG_PRIORITY, PHONE_ANDROID_LOG_APP_NAME, "%s", hex_buffer);
#endif
    }
}

void Debug::hexdump(const void * buffer, size_t size)
{
    this->appHexdump(NULL, NULL, buffer, size);
}

/*---------------------------------------------------------------------------*/

struct pcap_packet_header
{
    dword ts_sec;           /* timestamp seconds */
    dword ts_usec;          /* timestamp microseconds */
    dword incl_len;         /* number of octets of packet saved in file */
    dword orig_len;         /* actual length of packet */
};

struct ethernet_header
{
    byte dst_mac[6];
    byte src_mac[6];
    word type;
};

struct ip_header
{
    char    ip_verlen;      /* IP version & header length (in longs) */
    byte    ip_tos;         /* type of service */
    short   ip_len;         /* total packet length (in octets) */
    short   ip_id;          /* datagram id */
    short   ip_fragoff;     /* fragment offset (in 8-octet's) */
    byte    ip_ttl;         /* time to live, in gateway hops */
    char    ip_proto;       /* IP protocol (see IPT_* above) */
    short   ip_chksum;      /* header checksum */
    IP4addr  ip_src;        /* IP address of source */
    IP4addr  ip_dst;        /* IP address of destination */
};

struct ip6_header {
    dword     ip6_vtf;        /* version:4 traffic_class:8 flow_label:20 */
    short     ip6_plen;
    byte      ip6_nxt;
    byte      ip6_hoplim;
    IPaddr    ip6_src;
    IPaddr    ip6_dst;
};

struct udp_header
{
    word src_port;
    word dst_port;
    word len;
    word chksum;
};

struct tcp_header
{
    word src_port;
    word dst_port;
    dword seq_number;
    dword ack_number;
    unsigned data_offset : 4;
    unsigned reserved : 6;
    unsigned flags : 6;
    word window_size;
    word chksum;
    word urgent_pointer;
};

struct inno_header
{
    byte type;
};

short Debug::ip_checksum(byte *addr, byte len)
{
    int i;
    dword sum;

    for(i=0, sum=0; i<len; i++) {
        sum += i&1? addr[i]:addr[i]<<8;
    }

    sum = (sum >> 16) + (sum & 0xffff);     // add in carry
    sum += (sum >> 16);                     // maybe one more
    return (short)~sum;
}

void Debug::add_pcap_header(byte ** prepend, dword * length, dword timestamp_sec, dword timestamp_usec)
{
    struct pcap_packet_header * p_header;

    *prepend -= sizeof(struct pcap_packet_header);
    p_header = (struct pcap_packet_header *)(*prepend);
    WRITE_DWORD_NET(&p_header->incl_len, *length);
    WRITE_DWORD_NET(&p_header->orig_len, *length);
    WRITE_DWORD_NET(&p_header->ts_sec, timestamp_sec);
    WRITE_DWORD_NET(&p_header->ts_usec, timestamp_usec);
    *length += sizeof(struct pcap_packet_header);
}

void Debug::add_ethernet_header(byte ** prepend, dword * length, word ether_prot, bool set_remote_mac, bool send)
{
    struct ethernet_header * eth;

    *prepend -= sizeof(struct ethernet_header);
    eth = (struct ethernet_header *)(*prepend);
#ifdef PLATFORM_NO_PACKET
    byte mac[] = { 0x00, 0x90, 0x33, 0x00, 0x00, 0x00 };
#else
    const byte * mac = kernel->get_mac();
#endif
    if(set_remote_mac) {
        if(send) {
            eth->src_mac[0] = mac[0];
            eth->src_mac[1] = mac[1];
            eth->src_mac[2] = mac[2];
            eth->src_mac[3] = mac[3];
            eth->src_mac[4] = mac[4];
            eth->src_mac[5] = mac[5];
            eth->dst_mac[0] = 0x00;
            eth->dst_mac[1] = 0x90;
            eth->dst_mac[2] = 0x33;
            eth->dst_mac[3] = 0x00;
            eth->dst_mac[4] = 0x00;
            eth->dst_mac[5] = 0x00;
        }
        else {
            eth->dst_mac[0] = mac[0];
            eth->dst_mac[1] = mac[1];
            eth->dst_mac[2] = mac[2];
            eth->dst_mac[3] = mac[3];
            eth->dst_mac[4] = mac[4];
            eth->dst_mac[5] = mac[5];
            eth->src_mac[0] = 0x00;
            eth->src_mac[1] = 0x90;
            eth->src_mac[2] = 0x33;
            eth->src_mac[3] = 0x00;
            eth->src_mac[4] = 0x00;
            eth->src_mac[5] = 0x00;
        }
    }
    else {
        eth->dst_mac[0] = eth->src_mac[0] = mac[0];
        eth->dst_mac[1] = eth->src_mac[1] = mac[1];
        eth->dst_mac[2] = eth->src_mac[2] = mac[2];
        eth->dst_mac[3] = eth->src_mac[3] = mac[3];
        eth->dst_mac[4] = eth->src_mac[4] = mac[4];
        eth->dst_mac[5] = eth->src_mac[5] = mac[5];
    }
    WRITE_WORD_NET(&eth->type, ether_prot);
    *length += sizeof(struct ethernet_header);
}

void Debug::add_ip4_header(byte ** prepend, dword * length, const IP4addr src, const IP4addr dst, byte protocol, byte tos)
{
    struct ip_header  * ip;

    *prepend -= sizeof(struct ip_header);
    ip = (struct ip_header *)(*prepend);
    *length += sizeof(struct ip_header);
    ip->ip_verlen = 0x45;    // 4=Version 4 5=5*32 bit header len
    ip->ip_tos = tos;
    WRITE_WORD_NET(&ip->ip_len, *length);
    ip->ip_id = 0;
    WRITE_WORD_NET(&ip->ip_fragoff, 0x0000);
    ip->ip_ttl = 128;
    ip->ip_proto = protocol;
    ip->ip_src = src;
    ip->ip_dst = dst;
    ip->ip_chksum = 0;
    WRITE_WORD_NET(&ip->ip_chksum, ip_checksum((byte *)ip, sizeof(struct ip_header)));
}

void Debug::add_ip_header(byte ** prepend, dword * length, const IPaddr * src, const IPaddr * dst, byte protocol)
{
    struct ip6_header * ip6_hdr;

    *prepend -= sizeof(struct ip6_header);
    ip6_hdr = (struct ip6_header *)(*prepend);
    WRITE_DWORD_NET(&ip6_hdr->ip6_vtf,(6<<28));
    ip6_hdr->ip6_nxt = protocol;
    ip6_hdr->ip6_hoplim = 0xff;
    ip6_hdr->ip6_src = *src;
    ip6_hdr->ip6_dst = *dst;
    WRITE_WORD_NET(&ip6_hdr->ip6_plen, *length);
    *length += sizeof(struct ip6_header);
}

void Debug::add_udp_header(byte ** prepend, dword * length, word src_port, word dst_port)
{
    // UPD-Header
    struct udp_header * udp;

    *prepend -= sizeof(struct udp_header);
    udp = (struct udp_header *)(*prepend);
    *length += sizeof(struct udp_header);
    WRITE_WORD_NET(&udp->src_port, src_port);
    WRITE_WORD_NET(&udp->dst_port, dst_port);
    udp->chksum = 0;
    WRITE_WORD_NET(&udp->len, *length);
}

void Debug::appOSILayer4(const char * appName, const char * appDomain, const IPaddr * src, const IPaddr * dst, byte protocol, const byte * data, dword length, bool send, byte tos)
{
    static char hex_char_table[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    char hex_buffer[100];
    byte header[sizeof(struct pcap_packet_header) + sizeof(struct ethernet_header) + sizeof(struct ip6_header) + sizeof(struct udp_header)];
    IP4addr src4, dst4;
    byte * ptr;
    const byte * dat;
    dword len, index;
    byte b;
    unsigned int i, j, n;

    if (!logEnabled || !hexdumpEnabled) return; // logging in general or hexdumps disabled
    ptr = header + sizeof(header);
    len = length;
    if(src && is_ip4(*src) && dst && is_ip4(*dst)) {
        src4 = *((IP4addr *)&src->d[3]);
        dst4 = *((IP4addr *)&dst->d[3]);
        add_ip4_header(&ptr, &len, src4, dst4, protocol, tos);  // IP-Header
        add_ethernet_header(&ptr, &len, 0x0800, true, send);                     // Ethernet-Header
    }
    else {
        add_ip_header(&ptr, &len, src, dst, protocol);  // IP6-Header
        add_ethernet_header(&ptr, &len, 0x86dd, true, send);                   // Ethernet-Header
    }
    long64 timestamp = ITime::TimeStampMilliseconds();
    add_pcap_header(&ptr, &len, (dword)(timestamp / 1000), ((dword)(timestamp % 1000)) * 1000);               // PCAP-Header
#ifdef _IOS_
    _sprintf(hex_buffer, "%s %u", "PCAP", len);
    nslog_print(hex_buffer);
#endif
#ifdef __ANDROID__
    __android_log_print(PHONE_ANDROID_LOG_PRIORITY, PHONE_ANDROID_LOG_APP_NAME, "%s %u", "PCAP", len);
#endif
    // dump bulk
	//       00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
	// <-6--><-----------------------------64------------------------------->
    for(i = 0; i < 6; i++) hex_buffer[i] = ' ';
    dat = data - (len - length);
    for(index = 0; index < len; index += 32) {
        i = 6;
        j = 0;
        if(index < len - length) {
            n = len - length - index;
            if(n > 32) n = 32;
            while(j < n) {
                b = header[sizeof(header) - (len - length) + index + j];
                hex_buffer[i++] = hex_char_table[b >> 4];
                hex_buffer[i++] = hex_char_table[b & 0xf];
                j++;
            }
        }
        n = len - index;
        if(n > 32) n = 32;
        while(j < n) {
            b = dat[index + j];
            hex_buffer[i++] = hex_char_table[b >> 4];
            hex_buffer[i++] = hex_char_table[b & 0xf];
            j++;
        }
        hex_buffer[i] = '\0';
#ifdef _IOS_
        nslog_print(hex_buffer);
#endif
#ifdef __ANDROID__
        __android_log_print(PHONE_ANDROID_LOG_PRIORITY, PHONE_ANDROID_LOG_APP_NAME, "%s", hex_buffer);
#endif
    }
}

#if !defined(PLATFORM_NO_PACKET)

void Debug::osi_layer_4(IP4addr src, IP4addr dst, byte protocol, class packet * data, word src_port, word dst_port, bool send, byte tos = 0)
{
    byte * buf;
    class packet_ptr ptr;

    if (!logEnabled || !hexdumpEnabled) return; // logging in general or hexdumps disabled
    if (!data) return;
    buf = (byte *)(malloc(data->length()));
    data->read(&ptr, buf, data->length());
    // for local calls, put scr port into ip address to differ different local calls in wireshark
    if(src.b[0] == 127 && src.b[1] == 0 && src.b[2] == 0) WRITE_WORD_NET(&src.b[1], src_port);
    if(dst.b[0] == 127 && dst.b[1] == 0 && dst.b[2] == 0) WRITE_WORD_NET(&dst.b[1], dst_port);
    IP4addr2IPaddr(src, src6);
    IP4addr2IPaddr(dst, dst6);
    appOSILayer4(NULL, NULL, &src6, &dst6, protocol, buf, data->length(), send, tos);
    free(buf);
}

void Debug::osi_layer_4(const IPaddr * src6, const IPaddr * dst, byte protocol, class packet * data, word src_port, word dst_port, bool send, byte tos = 0)
{
    byte * buf;
    class packet_ptr ptr;

    if (!logEnabled || !hexdumpEnabled) return; // logging in general or hexdumps disabled
    if (!data) return;
    buf = (byte *)(malloc(data->length()));
    data->read(&ptr, buf, data->length());
    appOSILayer4(NULL, NULL, src, dst, protocol, buf, data->length(), send, tos);
    free(buf);
}

#endif

/*---------------------------------------------------------------------------*/
/* class IDebug                                                              */
/*---------------------------------------------------------------------------*/

IDebug::~IDebug() {

}

IDebug * IDebug::Create()
{
    // as we do not know, when the static IDebug::Create is called, we provide a "singleton" interface here
    // with this, you can call IDebug::Create() multiple times which always returns just the first instance
    static class IDebug * inst = debug = new Debug();
    return inst;
}
