
/*
 * Helper functions of various sources for cmd_pkt_cmd_v5.c.
 *
 * Copyright: This file may be distributed under version 2 of the GPL licence.
 *
 * $Id: oldicq_util.c,v 1.4 2007-05-01 21:54:35 kuhlmann Exp $
 */

#include "micq.h"
#include "packet.h"
#include "connection.h"
#include "oldicq_compat.h"
#include "oldicq_client.h"
#include "oldicq_server.h"
#include "oldicq_util.h"
#include "util_ui.h"
#include "util.h"
#include "util_syntax.h"
#include "conv.h"
#include "preferences.h"
#include "contact.h"
#include "im_request.h"
#include "im_response.h"
#include "util_io.h"
#include <assert.h>
#include <errno.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h> /* for htonl, htons */
#endif
#if HAVE_WINSOCK2_H
#include <winsock2.h>
#endif

static UDWORD Gen_Checksum (const Packet *pak);
static UDWORD Scramble_cc (UDWORD cc);
static Packet *Wrinkle (const Packet *pak);

typedef struct { UWORD cmd; const char *name; } jump_cmd_t;
#define jump_el(x) { x, #x },

static jump_cmd_t jump[] = {
    jump_el(CMD_ACK)              jump_el(CMD_SEND_MESSAGE)   jump_el(CMD_TCP_REQUEST)
    jump_el(CMD_LOGIN)            jump_el(CMD_REG_NEW_USER)   jump_el(CMD_CONTACT_LIST)
    jump_el(CMD_SEARCH_UIN)       jump_el(CMD_SEARCH_USER)    jump_el(CMD_KEEP_ALIVE)
    jump_el(CMD_SEND_TEXT_CODE)   jump_el(CMD_ACK_MESSAGES)   jump_el(CMD_LOGIN_1)
    jump_el(CMD_MSG_TO_NEW_USER)  jump_el(CMD_INFO_REQ)       jump_el(CMD_EXT_INFO_REQ)
    jump_el(CMD_CHANGE_PW)        jump_el(CMD_NEW_USER_INFO)  jump_el(CMD_UPDATE_EXT_INFO)
    jump_el(CMD_QUERY_SERVERS)    jump_el(CMD_QUERY_ADDONS)   jump_el(CMD_STATUS_CHANGE)
    jump_el(CMD_NEW_USER_1)       jump_el(CMD_UPDATE_INFO)    jump_el(CMD_AUTH_UPDATE)
    jump_el(CMD_KEEP_ALIVE2)      jump_el(CMD_LOGIN_2)        jump_el(CMD_ADD_TO_LIST)
    jump_el(CMD_RAND_SET)         jump_el(CMD_RAND_SEARCH)    jump_el(CMD_META_USER)
    jump_el(CMD_INVIS_LIST)       jump_el(CMD_VIS_LIST)       jump_el(CMD_UPDATE_LIST)
    { 0, "" }
};

/*
 * Return the name of a command given by number
 */
const char *CmdPktCmdName (UWORD cmd)
{
    char buf[10];
    jump_cmd_t *t;

    for (t = jump; t->cmd; t++)
        if (t->cmd == cmd)
            return t->name;
    snprintf (buf, 9, "0x%04x", cmd);
    buf[9] = '\0';
    return strdup (buf);
}

/*
 * Create a v5 packet and fill in the header.
 */
Packet *PacketCv5 (Connection *conn, UWORD cmd)
{
    Packet *pak = PacketC ();
    BOOL  iss2  = cmd != CMD_KEEP_ALIVE && cmd != CMD_SEND_TEXT_CODE && cmd != CMD_ACK;
    UWORD seq   = cmd != CMD_ACK ? conn->our_seq  : 0;
    UWORD seq2  = iss2 ? conn->our_seq2 : 0;

    assert (pak);
    assert (conn);
    assert (conn->version == 5);

    pak->cmd = cmd;
    pak->ver = conn->version;
    pak->id  = (seq2 << 16) + seq;
    pak->ref = seq;

    PacketWrite2 (pak, conn->version);  /* 5 */
    PacketWrite4 (pak, 0);
    PacketWrite4 (pak, conn->uin);
    PacketWrite4 (pak, conn->our_session);
    PacketWrite2 (pak, cmd);
    PacketWrite2 (pak, seq);
    PacketWrite2 (pak, seq2);
    PacketWrite4 (pak, 0);     /* reserved for check code */

    return pak;
}

/*
 * Finalize a v5 packet and enqueue for sending.
 */
void PacketEnqueuev5 (Packet *pak, Connection *conn)
{
    UDWORD cmd = pak->cmd;
    BOOL iss2 = cmd != CMD_KEEP_ALIVE && cmd != CMD_SEND_TEXT_CODE && cmd != CMD_ACK;

    assert (pak->len > 0x18);
    assert (conn);
    assert (conn->version == 5);

    if (iss2)           conn->our_seq2++;

    PacketSendv5 (pak, conn);

    if (prG->verbose & DEB_PACK5DATA)
    {
        char *f;
        pak->rpos = 0;
        rl_printf ("%s " COLINDENT "%s", s_now, COLCLIENT);
        rl_print  (i18n (1775, "Outgoing packet:"));
        rl_printf (" %04x %08lx:%08lx %04x (%s) @%p%s\n",
                 PacketReadAt2 (pak, CMD_v5_OFF_VER), PacketReadAt4 (pak, CMD_v5_OFF_SESS),
                 PacketReadAt4 (pak, CMD_v5_OFF_SEQ), PacketReadAt2 (pak, CMD_v5_OFF_SEQ2),
                 CmdPktCmdName (PacketReadAt2 (pak, CMD_v5_OFF_CMD)), pak, COLNONE);
        rl_print  (f = PacketDump (pak, "gv5cp", COLDEBUG, COLNONE));
        free (f);
        rl_print  (COLEXDENT "\r");
    }

    if (cmd != CMD_ACK)
    {
        conn->stat_real_pak_sent++;
        conn->our_seq++;

        QueueEnqueueData (conn, QUEUE_UDP_RESEND, pak->id, time (NULL) + 10,
                          pak, 0, NULL, &UDPCallBackResend);
    }
    else
        PacketD (pak);
}

/**
 ** Wrinkling the packet
 **/

/********************************************************
The following data constitutes fair use for compatibility.
*********************************************************/
static const UBYTE table[] = {
    0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48, 0x53, 0x61, 0x4C, 0x59, 0x60, 0x57, 0x5B, 0x3D,
    0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F, 0x6F, 0x67, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39,
    0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69, 0x48, 0x33, 0x31, 0x64, 0x35, 0x5A, 0x4A, 0x42,
    0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C, 0x49, 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48,
    0x33, 0x31, 0x44, 0x65, 0x62, 0x46, 0x48, 0x53, 0x41, 0x07, 0x6C, 0x69, 0x48, 0x33, 0x51, 0x54,
    0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A, 0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36,
    0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x63, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x35, 0x5A,
    0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36, 0x50, 0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35,
    0x5A, 0x4A, 0x62, 0x66, 0x58, 0x3B, 0x4D, 0x66, 0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58, 0x3B,
    0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64,
    0x55, 0x6A, 0x32, 0x3E, 0x44, 0x45, 0x52, 0x6E, 0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C,
    0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F, 0x47, 0x63, 0x59, 0x60, 0x57, 0x5B, 0x3D, 0x3E,
    0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A, 0x52, 0x4E, 0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58,
    0x3B, 0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x67, 0x53, 0x41, 0x25, 0x41,
    0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D, 0x4E, 0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F,
    0x47, 0x43, 0x69, 0x48, 0x33, 0x51, 0x54, 0x5D, 0x6E, 0x3C, 0x31, 0x64, 0x35, 0x5A, 0x00, 0x00,
};

static UDWORD Gen_Checksum (const Packet *pak)
{
    UDWORD cc, cc2;
    UDWORD r1, r2;

    cc = pak->data[8];
    cc <<= 8;
    cc += pak->data[4];
    cc <<= 8;
    cc += pak->data[2];
    cc <<= 8;
    cc += pak->data[6];

    r1 = rand () % (pak->len - 24);
    r1 += 24;
    r2 = rand () & 0xff;

    cc2 = r1;
    cc2 <<= 8;
    cc2 += pak->data[r1];
    cc2 <<= 8;
    cc2 += r2;
    cc2 <<= 8;
    cc2 += table[r2];
    cc2 ^= 0xff00ff;

    return cc ^ cc2;
}

static UDWORD Scramble_cc (UDWORD cc)
{
    UDWORD a[6];

    a[1] = cc & 0x0000001F;
    a[2] = cc & 0x03E003E0;
    a[3] = cc & 0xF8000400;
    a[4] = cc & 0x0000F800;
    a[5] = cc & 0x041F0000;

    a[1] <<= 0x0C;
    a[2] <<= 0x01;
    a[3] >>= 0x0A;
    a[4] <<= 0x10;
    a[5] >>= 0x0F;

    return a[1] + a[2] + a[3] + a[4] + a[5];
}

static Packet *Wrinkle (const Packet *opak)
{
    UDWORD code, pos, checkcode;
    Packet *pak;
    
    pak = PacketClone (opak);

    checkcode = Gen_Checksum (opak);
    code = opak->len * 0x68656c6cL + checkcode;
    PacketWriteAt4 (pak, 20, checkcode);

    for (pos = 10; pos < opak->len; pos += 4)
    {
        PacketWriteAt4 (pak, pos,
            PacketReadAt4 (opak, pos) ^ (code + table[pos & 0xFF]));
    }

    checkcode = Scramble_cc (checkcode);
    PacketWriteAt4 (pak, 20, checkcode);
    pak->len = opak->len;
    return pak;
}

/*
 * Actually send a v5 packet.
 */
void PacketSendv5 (const Packet *pak, Connection *conn)
{
    Packet *cpak;
    
    assert (pak);
    assert (conn);

    DebugH (DEB_PACKET, "---- %p sent", pak);

    cpak = Wrinkle (pak);
    UtilIOSendUDP (conn, cpak);
    PacketD (cpak);
}

/*
 * Callback function that handles UDP resends.
 */
void UDPCallBackResend (Event *event)
{
    Connection *conn = event->conn;
    Packet *pak = event->pak;
    UWORD  cmd;
    UDWORD session;

    assert (pak);

    if (!event->conn)
    {
        EventD (event);
        return;
    }
    ASSERT_SERVER_OLD (conn);

    cmd     = PacketReadAt2 (pak, CMD_v5_OFF_CMD);
    session = PacketReadAt4 (pak, CMD_v5_OFF_SESS);

    event->attempts++;

    if (session != conn->our_session)
    {
        rl_printf (i18n (1856, "Discarded a %04x (%s) packet from old session %08lx (current: %08lx).\n"),
                 cmd, CmdPktSrvName (cmd),
                 session, conn->our_session);
        EventD (event);
    }
    else if (event->attempts <= MAX_RETRY_ATTEMPTS)
    {
        if (prG->verbose & 32)
        {
            rl_printf (i18n (1826, "Resending message %04x (%s) sequence %04lx (attempt #%ld, len %d).\n"),
                     cmd, CmdPktCmdName (cmd),
                     event->seq >> 16, event->attempts, pak->len);
        }
        PacketSendv5 (pak, conn);
        event->due = time (NULL) + 10;
        QueueEnqueue (event);
    }
    else
    {
        if (cmd == CMD_SEND_MESSAGE)
        {
            UWORD  type = PacketReadAt2 (pak, CMD_v5_OFF_PARAM + 4);
            UDWORD tuin = PacketReadAt4 (pak, CMD_v5_OFF_PARAM);
            Contact *cont = ContactUIN (conn, tuin);
            char *data = (char *) &pak->data[CMD_v5_OFF_PARAM + 8];

            rl_print ("\n");
            rl_printf (i18n (1830, "Discarding message to %s after %ld send attempts.  Message content:\n"),
                      cont ? cont->nick : s_sprintf ("%ld", tuin), event->attempts - 1);

            if ((type & ~MSGF_MASS) == MSG_URL)
            {
                char *tmp = strchr (data, '\xFE');
                if (tmp)
                {
                    char *url_desc;
                    const char *url_data;
                    str_s cdata = { NULL, 0, 0 };
                    
                    cdata.txt = data;
                    cdata.len = strlen (data);
                    *tmp++ = 0;
                    url_desc = strdup (ConvFromCont (&cdata, cont));
                    cdata.txt = tmp;
                    cdata.len = strlen (tmp);
                    url_data = ConvFromCont (&cdata, cont);

                    rl_printf (i18n (2128, " Description: %s%s%s\n"), COLMESSAGE, url_desc, COLNONE);
                    rl_printf (i18n (2129, "         URL: %s%s%s\n"), COLMESSAGE, url_data, COLNONE);
                    
                    free (url_desc);
                }
            }
            else if ((type & ~MSGF_MASS) == MSG_NORM)
            {
                str_s cdata = { NULL, 0, 0 };
                cdata.txt = data;
                cdata.len = strlen (data);
                rl_printf ("%s%s%s ", COLMESSAGE, ConvFromCont (&cdata, cont), COLNONE);
            }
        }
        else
        {
            rl_printf (i18n (1825, "Discarded a %04x (%s) packet"), cmd, CmdPktSrvName (cmd));
            if (cmd == CMD_LOGIN || cmd == CMD_KEEP_ALIVE)
            {
                rl_print ("\a");
                rl_print (i18n (1632, "\nConnection unstable. Exiting...."));
                uiG.quit = TRUE;
            }
        }
        rl_print ("\n");
        EventD (event);
    }
}
