/***************************************************************************
                          buffer.cpp  -  description
                             -------------------
    begin                : Sun Mar 10 2002
    copyright            : (C) 2002 by Vladimir Shutoff
    email                : vovan@shutoff.ru
 ***************************************************************************/

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

#include "buffer.h"

#include <stdio.h>

#ifdef WIN32
#include <winsock.h>
#else
#include <netinet/in.h>
#endif

#include <vector>
using namespace std;
using namespace SIM;

#ifdef WORDS_BIGENDIAN

#define SWAP_S(s)	s = ((s&0xFF)<<8) + ((s&0xFF00)>>8);  
#define SWAP_L(s)	s = ((s&0xFF)<<24) + ((s&0xFF00)<<8) + ((s&0xFF0000)>>8) + ((s&0xFF000000)>>24); 

#else

#define SWAP_S(s)
#define SWAP_L(s)

#endif

Tlv::operator unsigned short ()
{
    return (unsigned short)((m_nSize >= 2) ? htons(*((unsigned short*)m_data)) : 0);
}

Tlv::operator unsigned long ()
{
    return (m_nSize >= 4) ? htonl(*((unsigned long*)m_data)) : 0;
}

Buffer::Buffer(unsigned size)
        : m_alloc_size(0), m_data(NULL)
{
    init(size);
}

Buffer::Buffer(Tlv &tlv)
        : m_alloc_size(0), m_data(NULL)
{
    init(tlv.Size());
    pack((char*)tlv, tlv.Size());
}

Buffer::~Buffer()
{
    if (m_data) free(m_data);
}

void Buffer::init(unsigned size)
{
    allocate(size, 0);
    m_size = size;
    m_posRead = 0;
    m_posWrite = 0;
    m_packetStartPos = 0;
}

void Buffer::add(unsigned size)
{
    allocate(m_size + size, 0);
    m_size += size;
}

void Buffer::allocate(unsigned size, unsigned add_size)
{
    if (size <= m_alloc_size) return;
    m_alloc_size = size + add_size;
    if (m_data){
        m_data = (char*)realloc(m_data, m_alloc_size);
    }else{
        m_data = (char*)malloc(m_alloc_size);
    }
}

void Buffer::insert(unsigned size)
{
    if (size == 0)
        return;
    allocate(m_size + size, 0);
    if (m_size)
        memmove(data(size), data(), m_size);
    m_size += size;
}

void Buffer::incReadPos(int n)
{
    m_posRead += n;
    if (m_posRead > m_posWrite) m_posRead = m_posWrite;
}

void Buffer::setSize(unsigned size)
{
    if (size >= m_alloc_size)
        return;
    m_size = size;
    if (m_posWrite > m_size)
        m_posWrite = m_size;
    if (m_posRead > m_size)
        m_posRead = m_size;
}

void Buffer::setWritePos(unsigned n)
{
    m_posWrite = n;
    if (m_posRead > m_posWrite) m_posRead = m_posWrite;
    if (m_posWrite > m_size){
        m_size = m_posWrite;
        allocate(m_size, 0);
    }
}

void Buffer::setReadPos(unsigned n)
{
    if (n > m_posWrite) n = m_posWrite;
    m_posRead = n;
}

void Buffer::pack(const char *d, unsigned size)
{
    allocate(m_posWrite + size, 1024);
    memcpy(m_data + m_posWrite, d, size);
    m_posWrite += size;
    if (m_posWrite > m_size) m_size = m_posWrite;
}

unsigned Buffer::unpack(char *d, unsigned size)
{
    unsigned readn = m_size - m_posRead;
    if (size < readn) readn = size;
    memcpy(d, m_data + m_posRead, readn);
    m_posRead += readn;
    return readn;
}

string Buffer::unpackScreen()
{
    char len;
    string res;

    *this >> len;
    /* 13 isn't right, AIM allows 16. But when we get a longer
    name, we *must* unpack them if we won't lose the TLVs
    behind the Screenname ... */
    if (len > 16)
        log(L_DEBUG,"Too long Screenname! Length: %d",len);
    res.append((unsigned)len, '\x00');
    unpack((char*)res.c_str(), len);
    return res;
}

void Buffer::unpack(string &s)
{
    unsigned short size;
    unpack(size);
    s.erase();
    if (size == 0) return;
    if (size > m_size - m_posRead)
        size = (unsigned short)(m_size - m_posRead);
    s.append(size, '\x00');
    unpack((char*)s.c_str(), size);
}

void Buffer::unpackStr(string &s)
{
    unsigned short size;
    *this >> size;
    s.erase();
    if (size == 0) return;
    if (size > m_size - m_posRead)
        size = (unsigned short)(m_size - m_posRead);
    s.append(size, '\x00');
    unpack((char*)s.c_str(), size);
}

void Buffer::unpackStr32(string &s)
{
    unsigned long size;
    *this >> size;
    size = htonl(size);
    s.erase();
    if (size == 0) return;
    if (size > m_size - m_posRead)
        size = m_size - m_posRead;
    s.append(size, '\x00');
    unpack((char*)s.c_str(), size);
}

Buffer &Buffer::operator >> (string &s)
{
    unsigned short size;
    *this >> size;
    size = htons(size);
    s.erase();
    if (size){
        if (size > m_size - m_posRead)
            size = (unsigned short)(m_size - m_posRead);
        s.append((unsigned)size, '\x00');
        unpack((char*)s.c_str(), size);
    }
    return *this;
}

Buffer &Buffer::operator >> (char &c)
{
    if (unpack(&c, 1) != 1) c = 0;
    return *this;
}

Buffer &Buffer::operator >> (unsigned short &c)
{
    if (unpack((char*)&c, 2) != 2) c = 0;
    c = htons(c);
    return *this;
}

Buffer &Buffer::operator >> (unsigned long &c)
{
    if (unpack((char*)&c, 4) != 4) c = 4;
    c = htonl(c);
    return *this;
}

Buffer &Buffer::operator >> (int &c)
{
    if (unpack((char*)&c, 4) != 4) c = 4;
    c = htonl(c);
    return *this;
}

Buffer &Buffer::operator >> (char **s)
{
    unsigned short size;
    *this >> size;
    size = htons(size);
    if (size){
        string str;
        str.append(size, '\x00');
        unpack((char*)str.c_str(), size);
        set_str(s, str.c_str());
    }else{
        set_str(s, NULL);
    }
    return *this;
}

void Buffer::unpack(char &c)
{
    *this >> c;
}

void Buffer::unpack(unsigned char &c)
{
    *this >> c;
}

void Buffer::unpack(unsigned short &c)
{
    if (unpack((char*)&c, 2) != 2) c = 0;
    SWAP_S(c)
}

void Buffer::unpack(unsigned long &c)
{
    // FIXME: This needs to be rewritten for 64-bit machines.
    // Kludge for now.
    c = 0;
    if (unpack((char*)&c, 4) != 4) c = 0;
    SWAP_L(c);
}

void Buffer::pack(const string &s)
{
    unsigned short size = (unsigned short)(s.size());
    *this << size;
    pack(s.c_str(), size);
}

void Buffer::pack(unsigned short s)
{
    SWAP_S(s)
    pack((char*)&s, 2);
}

void Buffer::pack(unsigned long s)
{
    SWAP_L(s)
    pack((char*)&s, 4);
}

void Buffer::packStr32(const char *s)
{
    if (s) {
        unsigned long size = strlen(s);
        pack(size);
        pack(s, strlen(s));
    } else {
        pack((unsigned long)0);
        pack("", 0);
    }
}

Buffer &Buffer::operator << (const Buffer &b)
{
    unsigned short size = (unsigned short)(b.size() - b.readPos());
    *this << (unsigned short)htons(size);
    pack(b.data(b.readPos()), size);
    return *this;
}

void Buffer::pack32(const Buffer &b)
{
    unsigned long size = b.size() - b.readPos();
    *this << (unsigned long)htonl(size);
    pack(b.data(b.readPos()), size);
}

Buffer &Buffer::operator << (const string &s)
{
    unsigned short size = (unsigned short)(s.size() + 1);
    *this << (unsigned short)htons(size);
    pack(s.c_str(), size);
    return *this;
}

Buffer &Buffer::operator << (char **str)
{
    string s;
    if (*str)
        s = *str;
    return *this << s;
}

Buffer &Buffer::operator << (const char *str)
{
    pack(str, strlen(str));
    return *this;
}

Buffer &Buffer::operator << (char c)
{
    pack(&c, 1);
    return *this;
}

Buffer &Buffer::operator << (bool b)
{
    char c = b ? (char)1 : (char)0;
    pack(&c, 1);
    return *this;
}

Buffer &Buffer::operator << (unsigned short c)
{
    c = htons(c);
    pack((char*)&c, 2);
    return *this;
}

Buffer &Buffer::operator << (unsigned long c)
{
    c = htonl(c);
    pack((char*)&c, 4);
    return *this;
}

void Buffer::packScreen(const char *screen)
{
    char len = (char)(strlen(screen));
    pack(&len, 1);
    pack(screen, len);
}

bool Buffer::scan(const char *substr, string &res)
{
    char c = *substr;
    for (unsigned pos = readPos(); pos < writePos(); pos++){
        if (*data(pos) != c)
            continue;
        const char *sp = substr;
        for (unsigned pos1 = pos; *sp; pos1++, sp++){
            if (pos1 >= writePos())
                break;
            if (*data(pos1) != *sp)
                break;
        }
        if (*sp == 0){
            res = "";
            if (pos - readPos()){
                res.append(pos - readPos(), '\x00');
                unpack((char*)res.c_str(), pos - readPos());
            }
            incReadPos(pos + strlen(substr) - readPos());
            return true;
        }
    }
    return false;
}

void Buffer::tlv(unsigned short n, const char *data, unsigned short len)
{
    *this << n << len;
    pack(data, len);
}

void Buffer::tlvLE(unsigned short n, const char *data, unsigned short len)
{
    pack(n);
    pack(len);
    pack(data, len);
}

void Buffer::tlv(unsigned short n, const char *data)
{
    if (data == NULL)
        data = "";
    tlv(n, data, (unsigned short)strlen(data));
}

void Buffer::tlvLE(unsigned short n, const char *data)
{
    if (data == NULL)
        data = "";
    unsigned short len = strlen(data) + 1;
    pack(n);
    pack((unsigned short)(len + 2));
    pack(len);
    pack(data, len);
}

void Buffer::tlv(unsigned short n, unsigned short c)
{
    c = htons(c);
    tlv(n, (char*)&c, 2);
}

void Buffer::tlvLE(unsigned short n, unsigned short c)
{
    pack(n);
    pack((unsigned short)2);
    pack(c);
}

void Buffer::tlv(unsigned short n, unsigned long c)
{
    c = htonl(c);
    tlv(n, (char*)&c, 4);
}

void Buffer::tlvLE(unsigned short n, unsigned long c)
{
    pack(n);
    pack((unsigned short)4);
    pack(c);
}

void Buffer::packetStart()
{
    m_packetStartPos = writePos();
}

unsigned long Buffer::packetStartPos()
{
    return m_packetStartPos;
}

class listTlv : public vector<Tlv*>
{
public:
    listTlv();
    ~listTlv();
};

listTlv::listTlv()
{
}

listTlv::~listTlv()
{
    vector<Tlv*>::iterator it;
    for (it = begin(); it != end(); it++)
        delete *it;
}

TlvList::TlvList()
{
    m_tlv = new listTlv;
}

TlvList::TlvList(Buffer &b, unsigned nTlvs)
{
    m_tlv = new listTlv;
    for (unsigned n = 0; (b.readPos() < b.size()) && (n < nTlvs); n++){
        unsigned short num, size;
        b >> num >> size;
        if (b.readPos() + size > b.size())
            break;
        *this + new Tlv(num, size, b.data(b.readPos()));
        b.incReadPos(size);
    }
}

TlvList::~TlvList()
{
    delete static_cast<listTlv*>(m_tlv);
}

TlvList &TlvList::operator +(Tlv *tlv)
{
    static_cast<listTlv*>(m_tlv)->push_back(tlv);
    return *this;
}

Tlv *TlvList::operator()(unsigned short num)
{
    vector<Tlv*>::iterator it;
    for (it = static_cast<listTlv*>(m_tlv)->begin(); it != static_cast<listTlv*>(m_tlv)->end(); it++)
        if ((*it)->Num() == num) return *it;
    return NULL;
}

Tlv *TlvList::operator[](unsigned n)
{
    listTlv *l = static_cast<listTlv*>(m_tlv);
    if (n >= l->size())
        return NULL;
    return (*l)[n];
}

Buffer &Buffer::operator << (TlvList &tlvList)
{
    listTlv *l = static_cast<listTlv*>(tlvList.m_tlv);
    unsigned size = 0;
    vector<Tlv*>::iterator it;
    for (it = l->begin(); it != l->end(); it++)
        size += (*it)->Size() + 4;
    *this << (unsigned short)size;
    for (it = l->begin(); it != l->end(); it++){
        Tlv *tlv = *it;
        *this << tlv->Num() << tlv->Size();
        pack(*tlv, tlv->Size());
    }
    return *this;
}

Tlv::Tlv(unsigned short num, unsigned short size, const char *data)
        : m_nNum(num), m_nSize(size)
{
    m_data = new char[size + 1];
    memcpy(m_data, data, size);
    m_data[size] = 0;
}

Tlv::~Tlv()
{
    delete[] m_data;
}

EXPORT void log_packet(Buffer &buf, bool bOut, unsigned packet_id, const char *add_info)
{
    LogInfo li;
    li.log_level = bOut ? L_PACKET_OUT : L_PACKET_IN;
    li.log_info  = &buf;
    li.packet_id = packet_id;
    li.add_info  = add_info;
    Event e(EventLog, &li);
    e.process();
}

void Buffer::fromBase64(Buffer &from)
{
    unsigned n = 0;
    unsigned tmp2 = 0;
    for (;;) {
        char res[3];
        char c;
        from >> c;
        if (c == 0)
            break;
        char tmp = 0;
        if ((c >= 'A') && (c <= 'Z')) {
            tmp = (char)(c - 'A');
        } else if ((c >= 'a') && (c <= 'z')) {
            tmp = (char)(26 + (c - 'a'));
        } else if ((c >= '0') && (c <= 57)) {
            tmp = (char)(52 + (c - '0'));
        } else if (c == '+') {
            tmp = 62;
        } else if (c == '/') {
            tmp = 63;
        } else if ((c == '\r') || (c == '\n')) {
            continue;
        } else if (c == '=') {
            if (n == 3) {
                res[0] = (char)((tmp2 >> 10) & 0xff);
                res[1] = (char)((tmp2 >> 2) & 0xff);
                pack(res, 2);
            } else if (n == 2) {
                res[0] = (char)((tmp2 >> 4) & 0xff);
                pack(res, 1);
            }
            break;
        }
        tmp2 = ((tmp2 << 6) | (tmp & 0xff));
        n++;
        if (n == 4) {
            res[0] = (char)((tmp2 >> 16) & 0xff);
            res[1] = (char)((tmp2 >> 8) & 0xff);
            res[2] = (char)(tmp2 & 0xff);
            pack(res, 3);
            tmp2 = 0;
            n = 0;
        }
    }
}

static const char alphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

void Buffer::toBase64(Buffer &from)
{
    unsigned char b[3];
    char res[4];
    unsigned tmp;

    while (from.readPos() + 3 < from.size()){
        from.unpack((char*)b, 3);
        tmp = (b[0] << 16) | (b[1] << 8) | b[2];
        res[0] = alphabet[(tmp >> 18) & 0x3F];
        res[1] = alphabet[(tmp >> 12) & 0x3F];
        res[2] = alphabet[(tmp >> 6) & 0x3F];
        res[3] = alphabet[tmp & 0x3F];
        pack(res, 4);
    }

    switch(from.size() - from.readPos()){
    case 2:
        from.unpack((char*)b, 2);
        tmp = (b[0] << 16) | (b[1] << 8);
        res[0] = alphabet[(tmp >> 18) & 0x3F];
        res[1] = alphabet[(tmp >> 12) & 0x3F];
        res[2] = alphabet[(tmp >> 6) & 0x3F];
        res[3] = '=';
        pack(res, 4);
        break;
    case 1:
        from.unpack((char*)b, 1);
        tmp = b[0] << 16;
        res[0] = alphabet[(tmp >> 18) & 0x3F];
        res[1] = alphabet[(tmp >> 12) & 0x3F];
        res[2] = res[3] = '=';
        pack(res, 4);
        break;
    }
}

string Buffer::getSection(bool bSkip)
{
    m_posRead = m_posWrite;
    unsigned posRead = m_posRead;
    char *p = data(m_posRead);
    if (bSkip){
        /* skip until next '[' */
        for (;;){
            for (; m_posRead < m_size; p++, m_posRead++)
                if ((*p == '\n') || (*p == 0))
                    break;
            if (m_posRead >= m_size){
                m_posRead = posRead;
                return "";
            }
            m_posRead++;
            p++;
            if (*p == '[')
                break;
        }
    }
    for (;;){
        /* Search for '[' */
        if (m_posRead >= m_size){
            m_posRead = posRead;
            return "";
        }
        if (*p == '[')
            break;
        for (; m_posRead < m_size; p++, m_posRead++)
            if ((*p == '\n') || (*p == 0))
                break;
        if (m_posRead >= m_size){
            m_posRead = posRead;
            return "";
        }
        m_posRead++;
        p++;
    }
    m_startSection = m_posRead;
    m_posRead++;
    p++;
    string section;
    char *s = p;
    /* Search for ']' and get section name when found */
    for (; m_posRead < m_size; p++, m_posRead++){
        if (*p == ']'){
            *p = 0;
            section = s;
            *p = ']';
        }
        /* no ']' or end of stream */
        if ((*p == '\n') || (*p == 0))
            break;
    }
    if (m_posRead >= m_size){
        m_posRead = posRead;
        return "";
    }
    /* next line with data */
    for (;m_posRead < m_size; p++, m_posRead++){
        if ((*p != '\n') || (*p == 0))
            break;
    }
    m_posWrite = m_posRead;
    /* when current line starts with '[' we have a section
       without any data */
    if ((m_posRead >= m_size) || (*p == '[')) {
        return section;
    }
    /* put m_posWrite to (next section start) - 1 */
    for (; m_posWrite < m_size; p++, m_posWrite++){
        if ((*p == '\r') || (*p == '\n') || (*p == 0)){
            *p = 0;
            if ((m_posWrite + 1 < m_size) && (p[1] == '[')){
                m_posWrite++;
                break;
            }
        }
    }
    if (m_posWrite >= m_size){
        allocate(m_size + 1, 0);
        m_data[m_size] = 0;
    }
    return section;
}

char *Buffer::getLine()
{
    if (readPos() >= writePos())
        return NULL;
    char *res = data(m_posRead);
    /* handle cases when the buffer is not \n-terminated (avoid returning non-null-terminated string) */
    int maxLength = m_size-m_posRead, length = 0;
    while ((length < maxLength) && (res[length] != '\0'))
        ++length;
    if (length == maxLength)
    {
        allocate(m_size + 1, 0);
        m_data[m_size] = 0;
    }
    char *p;
    for (p = res; (m_posRead < m_posWrite) && *p ; p++)
        m_posRead++;
    for (; (m_posRead < m_posWrite) && (*p == 0) ; p++)
        m_posRead++;
    return res;
}


