/*
 * COPYRIGHT:       GPL, see COPYING in the top level directory
 * PROJECT:         ReactOS kernel
 * FILE:            drivers/base/kddll/kddll.c
 * PURPOSE:         Base functions for the kernel debugger.
 * PROGRAMMER:      Timo Kreuzer (timo.kreuzer@reactos.org)
 */

#include "kddll.h"

/* GLOBALS ********************************************************************/

ULONG CurrentPacketId = INITIAL_PACKET_ID | SYNC_PACKET_ID;
ULONG RemotePacketId  = INITIAL_PACKET_ID;


/* PRIVATE FUNCTIONS **********************************************************/

/******************************************************************************
 * \name KdpCalculateChecksum
 * \brief Calculates the checksum for the packet data.
 * \param Buffer Pointer to the packet data.
 * \param Length Length of data in bytes.
 * \return The calculated checksum.
 * \sa http://www.vista-xp.co.uk/forums/technical-reference-library/2540-basics-debugging.html
 */
ULONG
NTAPI
KdpCalculateChecksum(
    IN PVOID Buffer,
    IN ULONG Length)
{
    PUCHAR ByteBuffer = Buffer;
    ULONG Checksum = 0;

    while (Length-- > 0)
    {
        Checksum += (ULONG)*ByteBuffer++;
    }
    return Checksum;
}

VOID
NTAPI
KdpSendControlPacket(
    IN USHORT PacketType,
    IN ULONG PacketId OPTIONAL)
{
    KD_PACKET Packet;

    Packet.PacketLeader = CONTROL_PACKET_LEADER;
    Packet.PacketId = PacketId;
    Packet.ByteCount = 0;
    Packet.Checksum = 0;
    Packet.PacketType = PacketType;

    KdpSendBuffer(&Packet, sizeof(KD_PACKET));
}


/* PUBLIC FUNCTIONS ***********************************************************/

/******************************************************************************
 * \name KdReceivePacket
 * \brief Receive a packet from the KD port.
 * \param [in] PacketType Describes the type of the packet to receive.
 *        This can be one of the PACKET_TYPE_ constants.
 * \param [out] MessageHeader Pointer to a STRING structure for the header.
 * \param [out] MessageData Pointer to a STRING structure for the data.
 * \return KdPacketReceived if successful, KdPacketTimedOut if the receive
 *         timed out, KdPacketNeedsResend to signal that the last packet needs
 *         to be sent again.
 * \note If PacketType is PACKET_TYPE_KD_POLL_BREAKIN, the function doesn't
 *       wait for any data, but returns KdPacketTimedOut instantly if no breakin
 *       packet byte is received.
 * \sa http://www.nynaeve.net/?p=169
 */
KDP_STATUS
NTAPI
KdReceivePacket(
    IN ULONG PacketType,
    OUT PSTRING MessageHeader,
    OUT PSTRING MessageData,
    OUT PULONG DataLength,
    IN OUT PKD_CONTEXT KdContext)
{
    UCHAR Byte = 0;
    KDP_STATUS KdStatus;
    KD_PACKET Packet;
    ULONG Checksum;

    /* Special handling for breakin packet */
    if (PacketType == PACKET_TYPE_KD_POLL_BREAKIN)
    {
        return KdpPollBreakIn();
    }

    for (;;)
    {
        /* Step 1 - Read PacketLeader */
        KdStatus = KdpReceivePacketLeader(&Packet.PacketLeader);
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Check if we got a breakin  */
            if (KdStatus == KDP_PACKET_RESEND)
            {
                KdContext->KdpControlCPending = TRUE;
            }
            return KdStatus;
        }

        /* Step 2 - Read PacketType */
        KdStatus = KdpReceiveBuffer(&Packet.PacketType, sizeof(USHORT));
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Didn't receive a PacketType. */
            return KdStatus;
        }

        /* Check if we got a resend packet */
        if (Packet.PacketLeader == CONTROL_PACKET_LEADER &&
            Packet.PacketType == PACKET_TYPE_KD_RESEND)
        {
            return KDP_PACKET_RESEND;
        }

        /* Step 3 - Read ByteCount */
        KdStatus = KdpReceiveBuffer(&Packet.ByteCount, sizeof(USHORT));
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Didn't receive ByteCount. */
            return KdStatus;
        }

        /* Step 4 - Read PacketId */
        KdStatus = KdpReceiveBuffer(&Packet.PacketId, sizeof(ULONG));
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Didn't receive PacketId. */
            return KdStatus;
        }

/*
        if (Packet.PacketId != ExpectedPacketId)
        {
            // Ask for a resend!
            continue;
        }
*/

        /* Step 5 - Read Checksum */
        KdStatus = KdpReceiveBuffer(&Packet.Checksum, sizeof(ULONG));
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Didn't receive Checksum. */
            return KdStatus;
        }

        /* Step 6 - Handle control packets */
        if (Packet.PacketLeader == CONTROL_PACKET_LEADER)
        {
            switch (Packet.PacketType)
            {
                case PACKET_TYPE_KD_ACKNOWLEDGE:
                    /* Are we waiting for an ACK packet? */
                    if (PacketType == PACKET_TYPE_KD_ACKNOWLEDGE &&
                        Packet.PacketId == (CurrentPacketId & ~SYNC_PACKET_ID))
                    {
                        /* Remote acknowledges the last packet */
                        CurrentPacketId ^= 1;
                        return KDP_PACKET_RECEIVED;
                    }
                    /* That's not what we were waiting for, start over */
                    continue;

                case PACKET_TYPE_KD_RESET:
                    KDDBGPRINT("KdReceivePacket - got PACKET_TYPE_KD_RESET\n");
                    CurrentPacketId = INITIAL_PACKET_ID;
                    RemotePacketId  = INITIAL_PACKET_ID;
                    KdpSendControlPacket(PACKET_TYPE_KD_RESET, 0);
                    /* Fall through */

                case PACKET_TYPE_KD_RESEND:
                    KDDBGPRINT("KdReceivePacket - got PACKET_TYPE_KD_RESEND\n");
                    /* Remote wants us to resend the last packet */
                    return KDP_PACKET_RESEND;

                default:
                    KDDBGPRINT("KdReceivePacket - got unknown control packet\n");
                    /* We got an invalid packet, ignore it and start over */
                    continue;
            }
        }

        /* Did we wait for an ack packet? */
        if (PacketType == PACKET_TYPE_KD_ACKNOWLEDGE)
        {
            /* We received something different */
            KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
            CurrentPacketId ^= 1;
            return KDP_PACKET_RECEIVED;
        }

        /* Get size of the message header */
        MessageHeader->Length = MessageHeader->MaximumLength;

        /* Packet smaller than expected or too big? */
        if (Packet.ByteCount < MessageHeader->Length ||
            Packet.ByteCount > PACKET_MAX_SIZE)
        {
            KDDBGPRINT("KdReceivePacket - too few data (%d) for type %d\n",
                          Packet.ByteCount, MessageHeader->Length);
            MessageHeader->Length = Packet.ByteCount;
            KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
            continue;
        }

        //KDDBGPRINT("KdReceivePacket - got normal PacketType, Buffer = %p\n", MessageHeader->Buffer);

        /* Receive the message header data */
        KdStatus = KdpReceiveBuffer(MessageHeader->Buffer,
                                    MessageHeader->Length);
        if (KdStatus != KDP_PACKET_RECEIVED)
        {
            /* Didn't receive data. Packet needs to be resent. */
            KDDBGPRINT("KdReceivePacket - Didn't receive message header data.\n");
            KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
            continue;
        }

        //KDDBGPRINT("KdReceivePacket - got normal PacketType 3\n");

        /* Calculate checksum for the header data */
        Checksum = KdpCalculateChecksum(MessageHeader->Buffer,
                                        MessageHeader->Length);

        /* Calculate the length of the message data */
        *DataLength = Packet.ByteCount - MessageHeader->Length;

        /* Shall we receive message data? */
        if (MessageData)
        {
            /* Set the length of the message data */
            MessageData->Length = (USHORT)*DataLength;

            /* Do we have data? */
            if (MessageData->Length)
            {
                KDDBGPRINT("KdReceivePacket - got data\n");

                /* Receive the message data */
                KdStatus = KdpReceiveBuffer(MessageData->Buffer,
                                           MessageData->Length);
                if (KdStatus != KDP_PACKET_RECEIVED)
                {
                    /* Didn't receive data. Start over. */
                    KDDBGPRINT("KdReceivePacket - Didn't receive message data.\n");
                    KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
                    continue;
                }

                /* Add cheksum for message data */
                Checksum += KdpCalculateChecksum(MessageData->Buffer,
                                                 MessageData->Length);
            }
        }

        /* We must receive a PACKET_TRAILING_BYTE now */
        KdStatus = KdpReceiveBuffer(&Byte, sizeof(UCHAR));
        if (KdStatus != KDP_PACKET_RECEIVED || Byte != PACKET_TRAILING_BYTE)
        {
            KDDBGPRINT("KdReceivePacket - wrong trailing byte (0x%x), status 0x%x\n", Byte, KdStatus);
            KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
            continue;
        }

        /* Compare checksum */
        if (Packet.Checksum != Checksum)
        {
            KDDBGPRINT("KdReceivePacket - wrong cheksum, got %x, calculated %x\n",
                       Packet.Checksum, Checksum);
            KdpSendControlPacket(PACKET_TYPE_KD_RESEND, 0);
            continue;
        }

        /* Acknowledge the received packet */
        KdpSendControlPacket(PACKET_TYPE_KD_ACKNOWLEDGE, Packet.PacketId);

        /* Check if the received PacketId is ok */
        if (Packet.PacketId != RemotePacketId)
        {
            /* Continue with next packet */
            continue;
        }

        /* Did we get the right packet type? */
        if (PacketType == Packet.PacketType)
        {
            /* Yes, return success */
            //KDDBGPRINT("KdReceivePacket - all ok\n");
            RemotePacketId ^= 1;
            return KDP_PACKET_RECEIVED;
        }

        /* We received something different, ignore it. */
        KDDBGPRINT("KdReceivePacket - wrong PacketType\n");
    }

    return KDP_PACKET_RECEIVED;
}

VOID
NTAPI
KdSendPacket(
    IN ULONG PacketType,
    IN PSTRING MessageHeader,
    IN PSTRING MessageData,
    IN OUT PKD_CONTEXT KdContext)
{
    KD_PACKET Packet;
    KDP_STATUS KdStatus;
    ULONG Retries;

    /* Initialize a KD_PACKET */
    Packet.PacketLeader = PACKET_LEADER;
    Packet.PacketType = (USHORT)PacketType;
    Packet.ByteCount = MessageHeader->Length;
    Packet.Checksum = KdpCalculateChecksum(MessageHeader->Buffer,
                                           MessageHeader->Length);

    /* If we have message data, add it to the packet */
    if (MessageData)
    {
        Packet.ByteCount += MessageData->Length;
        Packet.Checksum += KdpCalculateChecksum(MessageData->Buffer,
                                                MessageData->Length);
    }

    Retries = KdContext->KdpDefaultRetries;

    for (;;)
    {
        /* Set the packet id */
        Packet.PacketId = CurrentPacketId;

        /* Send the packet header to the KD port */
        KdpSendBuffer(&Packet, sizeof(KD_PACKET));

        /* Send the message header */
        KdpSendBuffer(MessageHeader->Buffer, MessageHeader->Length);

        /* If we have message data, also send it */
        if (MessageData)
        {
            KdpSendBuffer(MessageData->Buffer, MessageData->Length);
        }

        /* Finalize with a trailing byte */
        KdpSendByte(PACKET_TRAILING_BYTE);

        /* Wait for acknowledge */
        KdStatus = KdReceivePacket(PACKET_TYPE_KD_ACKNOWLEDGE,
                                   NULL,
                                   NULL,
                                   NULL,
                                   KdContext);

        /* Did we succeed? */
        if (KdStatus == KDP_PACKET_RECEIVED)
        {
            /* Packet received, we can quit the loop */
            CurrentPacketId &= ~SYNC_PACKET_ID;
            Retries = KdContext->KdpDefaultRetries;
            break;
        }
        else if (KdStatus == KDP_PACKET_TIMEOUT)
        {
            /* Timeout, decrement the retry count */
            if (Retries > 0)
                Retries--;

            /*
             * If the retry count reaches zero, bail out
             * for packet types allowed to timeout.
             */
            if (Retries == 0)
            {
                ULONG MessageId = *(PULONG)MessageHeader->Buffer;
                switch (PacketType)
                {
                    case PACKET_TYPE_KD_DEBUG_IO:
                    {
                        if (MessageId != DbgKdPrintStringApi) continue;
                        break;
                    }

                    case PACKET_TYPE_KD_STATE_CHANGE32:
                    case PACKET_TYPE_KD_STATE_CHANGE64:
                    {
                        if (MessageId != DbgKdLoadSymbolsStateChange) continue;
                        break;
                    }

                    case PACKET_TYPE_KD_FILE_IO:
                    {
                        if (MessageId != DbgKdCreateFileApi) continue;
                        break;
                    }
                }

                /* Reset debugger state */
                KD_DEBUGGER_NOT_PRESENT = TRUE;
                SharedUserData->KdDebuggerEnabled &= ~0x00000002;
                CurrentPacketId = INITIAL_PACKET_ID | SYNC_PACKET_ID;
                RemotePacketId  = INITIAL_PACKET_ID;

                return;
            }
        }
        // else (KdStatus == KDP_PACKET_RESEND) /* Resend the packet */

        /* Packet timed out, send it again */
        KDDBGPRINT("KdSendPacket got KdStatus 0x%x\n", KdStatus);
    }
}

/* EOF */