/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Alan Korr & Nick Robinson
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "iap-core.h"
#include "iap-lingo.h"
#include "kernel.h"
#include "system.h"

/*
 * This macro is meant to be used inside an IAP mode message handler.
 * It is passed the expected minimum length of the message buffer.
 * If the buffer does not have the required lenght an ACK
 * packet with a Bad Parameter error is generated.
 */
#define CHECKLEN(x) do { \
        if (len < (x)) { \
            cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
            return; \
        }} while(0)

/* Check for authenticated state, and return an ACK Not
 * Authenticated on failure.
 */
#define CHECKAUTH do { \
        if (!DEVICE_AUTHENTICATED) { \
            cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
            return; \
        }} while(0)

static void cmd_ack(const unsigned char cmd, const unsigned char status)
{
    IAP_TX_INIT(0x00, 0x02);
    IAP_TX_PUT(status);
    IAP_TX_PUT(cmd);
    iap_send_tx();
}

#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)

static void cmd_pending(const unsigned char cmd, const uint32_t msdelay)
{
    IAP_TX_INIT(0x00, 0x02);
    IAP_TX_PUT(0x06);
    IAP_TX_PUT(cmd);
    IAP_TX_PUT_U32(msdelay);
    iap_send_tx();
}

void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf)
{
    unsigned int cmd = buf[1];

    /* We expect at least two bytes in the buffer, one for the
     * lingo, one for the command
     */
    CHECKLEN(2);

    switch (cmd) {
        /* RequestIdentify (0x00)
         *
         * Sent from the iPod to the device
         */

        /* Identify (0x01)
         * This command is deprecated.
         *
         * It is used by a device to inform the iPod of the devices
         * presence and of the lingo the device supports.
         *
         * Also, it is used to negotiate power for RF transmitters
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x01
         * 0x02: Lingo supported by the device
         *
         * Some RF transmitters use an extended version of this
         * command:
         *
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x01
         * 0x02: Lingo supported by the device, always 0x05 (RF Transmitter)
         * 0x03: Reserved, always 0x00
         * 0x04: Number of valid bits in the following fields
         * 0x05-N: Datafields holding the number of bits specified in 0x04
         *
         * Returns: (none)
         *
         * TODO:
         * BeginHighPower/EndHighPower should be send in the periodic handler,
         * depending on the current play status
         */
        case 0x01:
        {
            unsigned char lingo = buf[2];

            /* This is sufficient even for Lingo 0x05, as we are
             * not actually reading from the extended bits for now
             */
            CHECKLEN(3);

            /* Issuing this command exits any extended interface states
             * and resets authentication
             */
            iap_interface_state_change(IST_STANDARD);
            iap_reset_device(&device);

            switch (lingo) {
                case 0x04:
                {
                    /* A single lingo device negotiating the
                     * extended interface lingo. This causes an interface
                     * state change.
                     */
                    iap_interface_state_change(IST_EXTENDED);
                    break;
                }

                case 0x05:
                {
                    /* FM transmitter sends this: */
                    /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */
                    sleep(HZ/3);
                    /* RF Transmitter: Begin transmission */
                    IAP_TX_INIT(0x05, 0x02);

                    iap_send_tx();
                    break;
                }
            }

            if (lingo < 32) {
                /* All devices that Identify get access to Lingoes 0x00 and 0x02 */
                device.lingoes = BIT_N(0x00) | BIT_N(0x02);

                device.lingoes |= BIT_N(lingo);

                /* Devices that Identify with Lingo 0x04 also gain access
                 * to Lingo 0x03
                 */
                if (lingo == 0x04)
                    device.lingoes |= BIT_N(0x03);
            } else {
                device.lingoes = 0;
            }
            break;
        }

        /* ACK (0x02)
         *
         * Sent from the iPod to the device
         */

        /* RequestRemoteUIMode (0x03)
         *
         * Request the current Extended Interface Mode state
         * This command may be used only if the accessory requests Lingo 0x04
         * during its identification process.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x03
         *
         * Returns on success:
         * ReturnRemoteUIMode
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x04
         * 0x02: Current Extended Interface Mode (zero: false, non-zero: true)
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x03:
        {
            if (!DEVICE_LINGO_SUPPORTED(0x04)) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            IAP_TX_INIT(0x00, 0x04);
            if (interface_state == IST_EXTENDED)
                IAP_TX_PUT(0x01);
            else
                IAP_TX_PUT(0x00);

            iap_send_tx();
            break;
        }

        /* ReturnRemoteUIMode (0x04)
         *
         * Sent from the iPod to the device
         */

        /* EnterRemoteUIMode (0x05)
         *
         * Request Extended Interface Mode
         * This command may be used only if the accessory requests Lingo 0x04
         * during its identification process.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x05
         *
         * Returns on success:
         * IAP_ACK_PENDING
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x05:
        {
            if (!DEVICE_LINGO_SUPPORTED(0x04)) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            cmd_pending(cmd, 1000);
            iap_interface_state_change(IST_EXTENDED);
            cmd_ok(cmd);
            break;
        }

        /* ExitRemoteUIMode (0x06)
         *
         * Leave Extended Interface Mode
         * This command may be used only if the accessory requests Lingo 0x04
         * during its identification process.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x06
         *
         * Returns on success:
         * IAP_ACK_PENDING
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x06:
        {
            if (!DEVICE_LINGO_SUPPORTED(0x04)) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            cmd_pending(cmd, 1000);
            iap_interface_state_change(IST_STANDARD);
            cmd_ok(cmd);
            break;
        }

        /* RequestiPodName (0x07)
         *
         * Retrieves the name of the iPod
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x07
         *
         * Returns:
         * ReturniPodName
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x08
         * 0x02-0xNN: iPod name as NULL-terminated UTF8 string
         */
        case 0x07:
        {
            IAP_TX_INIT(0x00, 0x08);
            IAP_TX_PUT_STRING("ROCKBOX");

            iap_send_tx();
            break;
        }

        /* ReturniPodName (0x08)
         *
         * Sent from the iPod to the device
         */

        /* RequestiPodSoftwareVersion (0x09)
         *
         * Returns the major, minor and revision numbers of the iPod
         * software version. This not any Lingo protocol version.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x09
         *
         * Returns:
         * ReturniPodSoftwareVersion
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0A
         * 0x02: iPod major software version
         * 0x03: iPod minor software version
         * 0x04: iPod revision software version
         */
        case 0x09:
        {
            IAP_TX_INIT(0x00, 0x0A);
            IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR);
            IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR);
            IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV);

            iap_send_tx();
            break;
        }

        /* ReturniPodSoftwareVersion (0x0A)
         *
         * Sent from the iPod to the device
         */

        /* RequestiPodSerialNum (0x0B)
         *
         * Returns the iPod serial number
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0B
         *
         * Returns:
         * ReturniPodSerialNumber
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0C
         * 0x02-0xNN: Serial number as NULL-terminated UTF8 string
         */
        case 0x0B:
        {
            IAP_TX_INIT(0x00, 0x0C);
            IAP_TX_PUT_STRING("0123456789");

            iap_send_tx();
            break;
        }

        /* ReturniPodSerialNum (0x0C)
         *
         * Sent from the iPod to the device
         */

        /* RequestiPodModelNum (0x0D)
         *
         * Returns the model number as a 32bit unsigned integer and
         * as a string.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0D
         *
         * Returns:
         * ReturniPodModelNum
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0E
         * 0x02-0x05: Model number as 32bit integer
         * 0x06-0xNN: Model number as NULL-terminated UTF8 string
         */
        case 0x0D:
        {
            IAP_TX_INIT(0x00, 0x0E);
            IAP_TX_PUT_U32(IAP_IPOD_MODEL);
            IAP_TX_PUT_STRING("ROCKBOX");

            iap_send_tx();
            break;
        }

        /* ReturniPodSerialNum (0x0E)
         *
         * Sent from the iPod to the device
         */

        /* RequestLingoProtocolVersion (0x0F)
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x0F
         * 0x02: Lingo for which to request version information
         *
         * Returns on success:
         * ReturnLingoProtocolVersion
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x10
         * 0x02: Lingo for which version information is returned
         * 0x03: Major protocol version for the given lingo
         * 0x04: Minor protocol version for the given lingo
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x0F:
        {
            unsigned char lingo = buf[2];

            CHECKLEN(3);

            /* Supported lingos and versions are read from the lingo_versions
             * array
             */
            if (LINGO_SUPPORTED(lingo)) {
                IAP_TX_INIT(0x00, 0x10);
                IAP_TX_PUT(lingo);
                IAP_TX_PUT(LINGO_MAJOR(lingo));
                IAP_TX_PUT(LINGO_MINOR(lingo));

                iap_send_tx();
            } else {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
            }
            break;
        }

        /* ReturnLingoProtocolVersion (0x10)
         *
         * Sent from the iPod to the device
         */

        /* IdentifyDeviceLingoes (0x13);
         *
         * Used by a device to inform the iPod of the devices
         * presence and of the lingoes the device supports.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x13
         * 0x02-0x05: Device lingoes spoken
         * 0x06-0x09: Device options
         * 0x0A-0x0D: Device ID. Only important for authentication
         *
         * Returns on success:
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_CMD_FAILED
         */
        case 0x13:
        {
            uint32_t lingoes = get_u32(&buf[2]);
            uint32_t options = get_u32(&buf[6]);
            uint32_t deviceid = get_u32(&buf[0x0A]);
            bool seen_unsupported = false;
            unsigned char i;

            CHECKLEN(14);

            /* Issuing this command exits any extended interface states */
            iap_interface_state_change(IST_STANDARD);

            /* Loop through the lingoes advertised by the device.
             * If it tries to use a lingo we do not support, return
             * a Command Failed ACK.
             */
            for(i=0; i<32; i++) {
                if (lingoes & BIT_N(i)) {
                    /* Bit set by device */
                    if (!LINGO_SUPPORTED(i)) {
                        seen_unsupported = true;
                    }
                }
            }

            /* Bit 0 _must_ be set by the device */
            if (!(lingoes & 1)) {
                seen_unsupported = true;
            }

            /* Specifying a deviceid without requesting authentication is
             * an error
             */
            if (deviceid && !(options & 0x03))
                seen_unsupported = true;

            /* Specifying authentication without a deviceid is an error */
            if (!deviceid && (options & 0x03))
                seen_unsupported = true;

            device.lingoes = 0;
            if (seen_unsupported) {
                cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                break;
            }
            iap_reset_device(&device);
            device.lingoes = lingoes;

            /* Devices using IdentifyDeviceLingoes get power off notifications */
            device.do_power_notify = true;

            /* If a new authentication is requested, start the auth
             * process.
             * The periodic handler will take care of sending out the
             * GetDevAuthenticationInfo packet
             *
             * If no authentication is requested, schedule the start of
             * GetAccessoryInfo
             */
            if (deviceid && (options & 0x03) && !DEVICE_AUTH_RUNNING) {
                device.auth.state = AUST_INIT;
            } else {
                device.accinfo = ACCST_INIT;
            }

            cmd_ok(cmd);

            /* Bit 5: RF Transmitter lingo */
            if (lingoes & (1 << 5))
            {
                /* FM transmitter sends this: */
                /* FF 55 0E 00 13 00 00 00 35 00 00 00 04 00 00 00 00 A6 (??)*/

                /* GetAccessoryInfo */
                unsigned char data2[] = {0x00, 0x27, 0x00};
                iap_send_pkt(data2, sizeof(data2));
                /* RF Transmitter: Begin transmission */
                unsigned char data3[] = {0x05, 0x02};
                iap_send_pkt(data3, sizeof(data3));
            }


#if 0
            /* Bit 7: RF Tuner lingo */
            if (lingoes & (1 << 7))
            {
                /* ipod fm remote sends this: */
                /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */
                radio_present = 1;
                /* GetDevAuthenticationInfo */
                unsigned char data4[] = {0x00, 0x14};
                iap_send_pkt(data4, sizeof(data4));
            }
#endif
            break;
        }

        /* GetDevAuthenticationInfo (0x14)
         *
         * Sent from the iPod to the device
         */

        /* RetDevAuthenticationInfo (0x15)
         *
         * Send certificate information from the device to the iPod.
         * The certificate may come in multiple parts and has
         * to be reassembled.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x15
         * 0x02: Authentication major version
         * 0x03: Authentication minor version
         * 0x04: Certificate current section index
         * 0x05: Certificate maximum section index
         * 0x06-0xNN: Certificate data
         *
         * Returns on success:
         * IAP_ACK_OK for intermediate sections
         * AckDevAuthenticationInfo for the last section
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAMETER
         * AckDevAuthenticationInfo for version mismatches
         *
         */
        case 0x15:
        {
            /* There are two formats of this packet. One with only
             * the version information bytes (for Auth version 1.0)
             * and the long form shown above
             */
            CHECKLEN(4);

            if (!DEVICE_AUTH_RUNNING) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            device.auth.version = (buf[2] << 8) | buf[3];

            /* We support authentication versions 1.0 and 2.0 */
            if (device.auth.version == 0x100) {
                /* If we could really do authentication we'd have to
                 * check the certificate here. Since we can't, just acknowledge
                 * the packet with an "everything OK" AckDevAuthenticationInfo
                 *
                 * Skip GetAccessoryInfo process, this command together with
                 * authentication level 2 were added in iAP release 24, it is
                 * not be supported by devices authenticating at level 1.
                 */
                IAP_TX_INIT(0x00, 0x16);
                IAP_TX_PUT(0x00);

                iap_send_tx();
                device.auth.state = AUST_CERTDONE;
                break;
            }

            if (device.auth.version != 0x200) {
                /* Version mismatches are signalled by AckDevAuthenticationInfo
                 * with the status set to Authentication Information unsupported
                 */
                iap_reset_auth(&(device.auth));

                IAP_TX_INIT(0x00, 0x16);
                IAP_TX_PUT(0x08);

                iap_send_tx();
                break;
            }

            /* There must be at least one byte of certificate data
             * in the packet
             */
            CHECKLEN(7);

            switch (device.auth.state)
            {
                /* This is the first packet. Note the maximum section number
                 * so we can check it later.
                 */
                case AUST_CERTREQ:
                {
                    device.auth.max_section = buf[5];
                    device.auth.state = AUST_CERTBEG;

                    /* Intentional fall-through */
                }
                /* All following packets */
                case AUST_CERTBEG:
                {
                    /* Check if this is the expected section */
                    if (buf[4] != device.auth.next_section) {
                        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                        break;
                    }

                    /* Is this the last section? */
                    if (device.auth.next_section == device.auth.max_section) {
                        /* If we could really do authentication we'd have to
                         * check the certificate here. Since we can't, just acknowledge
                         * the packet with an "everything OK" AckDevAuthenticationInfo
                         *
                         * Also, start GetAccessoryInfo process
                         */
                        IAP_TX_INIT(0x00, 0x16);
                        IAP_TX_PUT(0x00);

                        iap_send_tx();
                        device.auth.state = AUST_CERTDONE;
                        device.accinfo = ACCST_INIT;
                    } else {
                        device.auth.next_section++;
                        cmd_ok(cmd);
                    }
                    break;
                }

                default:
                {
                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                    break;
                }
            }

            break;
        }

        /* AckDevAuthenticationInfo (0x16)
         *
         * Sent from the iPod to the device
         */

        /* GetDevAuthenticationSignature (0x17)
         *
         * Sent from the iPod to the device
         */

        /* RetDevAuthenticationSignature (0x18)
         *
         * Return a calculated signature based on the device certificate
         * and the challenge sent with GetDevAuthenticationSignature
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x17
         * 0x02-0xNN: Certificate data
         *
         * Returns on success:
         * AckDevAuthenticationStatus
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x19
         * 0x02: Status (0x00: OK)
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         *
         * TODO:
         * There is a timeout of 75 seconds between GetDevAuthenticationSignature
         * and RetDevAuthenticationSignature for Auth 2.0. This is currently not
         * checked.
         */
        case 0x18:
        {
            if (device.auth.state != AUST_CHASENT) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            /* Here we could check the signature. Since we can't, just
             * acknowledge and go to authenticated status
             */
            IAP_TX_INIT(0x00, 0x19);
            IAP_TX_PUT(0x00);

            iap_send_tx();
            device.auth.state = AUST_AUTH;
            break;
        }

        /* AckDevAuthenticationStatus (0x19)
         *
         * Sent from the iPod to the device
         */

        /* GetiPodAuthenticationInfo (0x1A)
         *
         * Obtain authentication information from the iPod.
         * This cannot be implemented without posessing an Apple signed
         * certificate and the corresponding private key.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x1A
         *
         * This command requires authentication
         *
         * Returns:
         * IAP_ACK_CMD_FAILED
         */
        case 0x1A:
        {
            CHECKAUTH;

            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
            break;
        }

        /* RetiPodAuthenticationInfo (0x1B)
         *
         * Sent from the iPod to the device
         */

        /* AckiPodAuthenticationInfo (0x1C)
         *
         * Confirm authentication information from the iPod.
         * This cannot be implemented without posessing an Apple signed
         * certificate and the corresponding private key.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x1C
         * 0x02: Authentication state (0x00: OK)
         *
         * This command requires authentication
         *
         * Returns: (none)
         */
        case 0x1C:
        {
            CHECKAUTH;

            break;
        }

        /* GetiPodAuthenticationSignature (0x1D)
         *
         * Send challenge information to the iPod.
         * This cannot be implemented without posessing an Apple signed
         * certificate and the corresponding private key.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x1D
         * 0x02-0x15: Challenge
         *
         * This command requires authentication
         *
         * Returns:
         * IAP_ACK_CMD_FAILED
         */
        case 0x1D:
        {
            CHECKAUTH;

            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
            break;
        }

        /* RetiPodAuthenticationSignature (0x1E)
         *
         * Sent from the iPod to the device
         */

        /* AckiPodAuthenticationStatus (0x1F)
         *
         * Confirm chellenge information from the iPod.
         * This cannot be implemented without posessing an Apple signed
         * certificate and the corresponding private key.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x1C
         * 0x02: Challenge state (0x00: OK)
         *
         * This command requires authentication
         *
         * Returns: (none)
         */
        case 0x1F:
        {
            CHECKAUTH;

            break;
        }

        /* NotifyiPodStateChange (0x23)
         *
         * Sent from the iPod to the device
         */

        /* GetIpodOptions (0x24)
         *
         * Request supported features of the iPod
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x24
         *
         * Retuns:
         * RetiPodOptions
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x25
         * 0x02-0x09: Options as a bitfield
         */
        case 0x24:
        {
            /* There are only two features that can be communicated via this
             * function, video support and the ability to control line-out usage.
             * Rockbox supports neither
             */
            IAP_TX_INIT(0x00, 0x25);
            IAP_TX_PUT_U32(0x00);
            IAP_TX_PUT_U32(0x00);

            iap_send_tx();
            break;
        }

        /* RetiPodOptions (0x25)
         *
         * Sent from the iPod to the device
         */

        /* GetAccessoryInfo (0x27)
         *
         * Sent from the iPod to the device
         */

        /* RetAccessoryInfo (0x28)
         *
         * Send information about the device
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x28
         * 0x02: Accessory info type
         * 0x03-0xNN: Accessory information (depends on 0x02)
         *
         * Returns: (none)
         *
         * TODO: Actually do something with the information received here.
         * Some devices actually expect us to request the data they
         * offer, so completely ignoring this does not work, either.
         */
        case 0x28:
        {
            CHECKLEN(3);

            switch (buf[0x02])
            {
                /* Info capabilities */
                case 0x00:
                {
                    CHECKLEN(7);

                    device.capabilities = get_u32(&buf[0x03]);
                    /* Type 0x00 was already queried, that's where this information comes from */
                    device.capabilities_queried = 0x01;
                    device.capabilities &= ~0x01;
                    break;
                }

                /* For now, ignore all other information */
                default:
                {
                    break;
                }
            }

            /* If there are any unqueried capabilities left, do so */
            if (device.capabilities)
                device.accinfo = ACCST_DATA;

            break;
        }

        /* GetiPodPreferences (0x29)
         *
         * Retrieve information about the current state of the
         * iPod.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x29
         * 0x02: Information class requested
         *
         * This command requires authentication
         *
         * Returns on success:
         * RetiPodPreferences
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x2A
         * 0x02: Information class provided
         * 0x03: Information
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x29:
        {
            CHECKLEN(3);
            CHECKAUTH;

            IAP_TX_INIT(0x00, 0x2A);
            /* The only information really supported is 0x03, Line-out usage.
             * All others are video related
             */
            if (buf[2] == 0x03) {
                IAP_TX_PUT(0x03);
                IAP_TX_PUT(0x01);   /* Line-out enabled */

                iap_send_tx();
            } else {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
            }

            break;
        }

        /* RetiPodPreference (0x2A)
         *
         * Sent from the iPod to the device
         */

        /* SetiPodPreferences (0x2B)
         *
         * Set preferences on the iPod
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: General Lingo, always 0x00
         * 0x01: Command, always 0x29
         * 0x02: Prefecence class requested
         * 0x03: Preference setting
         * 0x04: Restore on exit
         *
         * This command requires authentication
         *
         * Returns on success:
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         * IAP_ACK_CMD_FAILED
         */
        case 0x2B:
        {
            CHECKLEN(5);
            CHECKAUTH;

            /* The only information really supported is 0x03, Line-out usage.
             * All others are video related
             */
            if (buf[2] == 0x03) {
                /* If line-out disabled is requested, reply with IAP_ACK_CMD_FAILED,
                 * otherwise with IAP_ACK_CMD_OK
                 */
                if (buf[3] == 0x00) {
                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                } else {
                    cmd_ok(cmd);
                }
            } else {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
            }

            break;
        }

        /* The default response is IAP_ACK_BAD_PARAM */
        default:
        {
#ifdef LOGF_ENABLE
            logf("iap: Unsupported Mode00 Command");
#else
            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
#endif
            break;
        }
    }
}
