[prev in list] [next in list] [prev in thread] [next in thread] 

List:       rockbox-cvs
Subject:    Updated IAP commands.
From:       gerrit () rockbox ! org
Date:       2013-11-10 17:41:27
Message-ID: 201311101741.rAAHfRsx016717 () giant ! haxx ! se
[Download RAW message or body]

commit b170c73f922e3457b923b4e7fcbec794a8885c77
Author: Ralf Ertzinger <rockbox@camperquake.de>
Date:   Sat Jun 22 10:08:23 2013 +0100

    Updated IAP commands.
    
    Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012.
    They have been condensed into a single patch and some further additions
    by Andy Potter.
    
    Currently includes Authentication V2 support from iPod to Accessory,
    RF/BlueTooth transmitter support, selecting a playlist and selecting a
    track from the current playlist. Does not support uploading Album Art
    or podcasts. Has been tested on the following iPods,
    4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and
    Video 5.5Gen.
    
    Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e
    Co-Authored by: Andy Potter <liveboxandy@gmail.com>
    Reviewed-on: http://gerrit.rockbox.org/533
    Reviewed-by: Frank Gevaerts <frank@gevaerts.be>

diff --git a/apps/SOURCES b/apps/SOURCES
index 8fa1a7e..3968666 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -62,7 +62,11 @@ tagtree.c
 filetree.c
 scrobbler.c
 #ifdef IPOD_ACCESSORY_PROTOCOL
-iap.c
+iap/iap-core.c
+iap/iap-lingo0.c
+iap/iap-lingo2.c
+iap/iap-lingo3.c
+iap/iap-lingo4.c
 #endif
 
 screen_access.c
diff --git a/apps/iap.c b/apps/iap.c
deleted file mode 100644
index 6fe0a03..0000000
--- a/apps/iap.c
+++ /dev/null
@@ -1,1110 +0,0 @@
-/***************************************************************************
- *             __________               __   ___.
- *   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 <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-
-#include "panic.h"
-#include "iap.h"
-#include "button.h"
-#include "config.h"
-#include "cpu.h"
-#include "system.h"
-#include "kernel.h"
-#include "serial.h"
-#include "appevents.h"
-
-#include "playlist.h"
-#include "playback.h"
-#include "audio.h"
-#include "settings.h"
-#include "metadata.h"
-#include "wps.h"
-#include "sound.h"
-#include "action.h"
-#include "powermgmt.h"
-
-#include "tuner.h"
-#include "ipod_remote_tuner.h"
-
-#include "filetree.h"
-#include "dir.h"
-
-static volatile int iap_pollspeed = 0;
-static volatile bool iap_remotetick = true;
-static bool iap_setupflag = false, iap_updateflag = false;
-static int iap_changedctr = 0;
-
-static unsigned long iap_remotebtn = 0;
-static int iap_repeatbtn = 0;
-static bool iap_btnrepeat = false, iap_btnshuffle = false;
-
-static unsigned char serbuf[RX_BUFLEN];
-
-static unsigned char response[TX_BUFLEN];
-
-static char cur_dbrecord[5] = {0};
-
-/* states of the iap de-framing state machine */
-enum fsm_state {
-    ST_SYNC,    /* wait for 0xFF sync byte */
-    ST_SOF,     /* wait for 0x55 start-of-frame byte */
-    ST_LEN,     /* receive length byte (small packet) */
-    ST_LENH,    /* receive length high byte (large packet) */
-    ST_LENL,    /* receive length low byte (large packet) */
-    ST_DATA,    /* receive data */
-    ST_CHECK    /* verify checksum */
-};
-
-static struct state_t {
-    enum fsm_state state;   /* current fsm state */
-    unsigned int len;       /* payload data length */
-    unsigned char *payload; /* payload data pointer */
-    unsigned int check;     /* running checksum over [len,payload,check] */
-    unsigned int count;     /* playload bytes counter */
-} frame_state = {
-    .state = ST_SYNC
-};
-
-static void put_u32(unsigned char *buf, uint32_t data)
-{
-    buf[0] = (data >> 24) & 0xFF;
-    buf[1] = (data >> 16) & 0xFF;
-    buf[2] = (data >>  8) & 0xFF;
-    buf[3] = (data >>  0) & 0xFF;
-}
-
-static uint32_t get_u32(const unsigned char *buf)
-{
-    return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
-}
-
-static void iap_task(void)
-{
-    static int count = 0;
-
-    count += iap_pollspeed;
-    if (count < (500/10)) return;
-
-    /* exec every 500ms if pollspeed == 1 */
-    count = 0;
-    queue_post(&button_queue, SYS_IAP_PERIODIC, 0);
-}
-
-/* called by playback when the next track starts */
-static void iap_track_changed(void *ignored)
-{
-    (void)ignored;
-    iap_changedctr = 1;
-}
-
-void iap_setup(int ratenum)
-{
-    iap_bitrate_set(ratenum);
-    iap_pollspeed = 0;
-    iap_remotetick = true;
-    iap_updateflag = false;
-    iap_changedctr = 0;
-    iap_setupflag = true;
-    iap_remotebtn = BUTTON_NONE;
-    tick_add_task(iap_task);
-    add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed);
-}
-
-void iap_bitrate_set(int ratenum)
-{
-    switch(ratenum)
-    {
-        case 0:
-            serial_bitrate(0);
-            break;
-        case 1:
-            serial_bitrate(9600);
-            break;
-        case 2:
-            serial_bitrate(19200);
-            break;
-        case 3:
-            serial_bitrate(38400);
-            break;
-        case 4:
-            serial_bitrate(57600);
-            break;
-    }
-}
-
-/* Message format:
-   0xff
-   0x55
-   length
-   mode
-   command (2 bytes)
-   parameters (0-n bytes)
-   checksum (length+mode+parameters+checksum == 0)
-*/
-
-void iap_send_pkt(const unsigned char * data, int len)
-{
-    int i, chksum, responselen;
-    
-    if(len > TX_BUFLEN-4) len = TX_BUFLEN-4;
-    responselen = len + 4;
-    
-    response[0] = 0xFF;
-    response[1] = 0x55;
-    
-    chksum = response[2] = len;
-    for(i = 0; i < len; i ++)
-    {
-        chksum += data[i];
-        response[i+3] = data[i];
-    }
-
-    response[i+3] = 0x100 - (chksum & 0xFF);
-    
-    for(i = 0; i < responselen; i ++)
-    {
-        while (!tx_rdy()) ;
-        tx_writec(response[i]);
-    }
-}    
-
-bool iap_getc(unsigned char x)
-{
-    struct state_t *s = &frame_state;
-    
-    /* run state machine to detect and extract a valid frame */
-    switch (s->state) {
-    case ST_SYNC:
-        if (x == 0xFF) {
-            s->state = ST_SOF;
-        }
-        break;
-    case ST_SOF:
-        if (x == 0x55) {
-            /* received a valid sync/SOF pair */
-            s->state = ST_LEN;
-        } else {
-            s->state = ST_SYNC;
-            return iap_getc(x);
-        }
-        break;
-    case ST_LEN:
-        s->check = x;
-        s->count = 0;
-        s->payload = serbuf;
-        if (x == 0) {
-            /* large packet */
-            s->state = ST_LENH;
-        } else {
-            /* small packet */
-            s->len = x;
-            s->state = ST_DATA;
-        }
-        break;
-    case ST_LENH:
-        s->check += x;
-        s->len = x << 8;
-        s->state = ST_LENL;
-        break;
-    case ST_LENL:
-        s->check += x;
-        s->len += x;
-        if ((s->len == 0) || (s->len > RX_BUFLEN)) {
-            /* invalid length */
-            s->state = ST_SYNC;
-            return iap_getc(x);
-        } else {
-            s->state = ST_DATA;
-        }
-        break;
-    case ST_DATA:
-        s->check += x;
-        s->payload[s->count++] = x;
-        if (s->count == s->len) {
-            s->state = ST_CHECK;
-        }
-        break;
-    case ST_CHECK:
-        s->check += x;
-        if ((s->check & 0xFF) == 0) {
-            /* done, received a valid frame */
-            queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0);
-        }
-        s->state = ST_SYNC;
-        break;
-    default:
-        panicf("Unhandled iap state %d", (int) s->state);
-        break;
-    }
-    
-    /* return true while still hunting for the sync and start-of-frame byte */
-    return (s->state == ST_SYNC) || (s->state == ST_SOF);
-}
-
-void iap_periodic(void)
-{
-    if(!iap_setupflag) return;
-    if(!iap_pollspeed) return;
-    
-    /* PlayStatusChangeNotification */
-    unsigned char data[] = {0x04, 0x00, 0x27, 0x04, 0x00, 0x00, 0x00, 0x00};
-    unsigned long time_elapsed = audio_current_track()->elapsed;
-
-    time_elapsed += wps_get_ff_rewind_count();
-
-    data[3] = 0x04; /* playing */
-
-    /* If info has changed, don't flag it right away */
-    if(iap_changedctr && iap_changedctr++ >= iap_pollspeed * 2)
-    {
-        /* track info has changed */
-        iap_changedctr = 0;
-        data[3] = 0x01; /* 0x02 has same effect?  */
-        iap_updateflag = true;
-    }
-
-    put_u32(&data[4], time_elapsed);
-    iap_send_pkt(data, sizeof(data));
-}
-
-static void iap_set_remote_volume(void)
-{
-    unsigned char data[] = {0x03, 0x0D, 0x04, 0x00, 0x00};
-    data[4] = (char)((global_settings.volume+58) * 4);
-    iap_send_pkt(data, sizeof(data));
-}
-
-static void cmd_ok_mode0(unsigned char cmd)
-{
-    unsigned char data[] = {0x00, 0x02, 0x00, 0x00};
-    data[3] = cmd;  /* respond with cmd */
-    iap_send_pkt(data, sizeof(data));
-}
-
-static void iap_handlepkt_mode0(unsigned int len, const unsigned char *buf)
-{
-    (void)len;    /* len currently unused */
-    
-    unsigned int cmd = buf[1];
-    switch (cmd) {
-        /* Identify */
-        case 0x01:
-        {
-            /* FM transmitter sends this: */
-            /* FF 55 06 00 01 05 00 02 01 F1 (mode switch) */
-            if(buf[2] == 0x05)
-            {
-                sleep(HZ/3);
-                /* RF Transmitter: Begin transmission */
-                unsigned char data[] = {0x05, 0x02};
-                iap_send_pkt(data, sizeof(data));
-            }
-            /* FM remote sends this: */
-            /* FF 55 03 00 01 02 FA (1st thing sent) */
-            else if (buf[2] == 0x02)
-            {
-                /* useful only for apple firmware */
-            }
-            break;
-        }
-    
-        /* EnterRemoteUIMode, FM transmitter sends FF 55 02 00 05 F9 */
-        case 0x05:
-        {
-            /* ACK Pending (3000 ms) */
-            unsigned char data[] = {0x00, 0x02, 0x06,
-                                    0x05, 0x00, 0x00, 0x0B, 0xB8};
-            iap_send_pkt(data, sizeof(data));
-            cmd_ok_mode0(cmd);
-            break;
-        }
-
-        /* ExitRemoteUIMode */
-        case 0x06:
-        {
-            audio_stop();
-            cmd_ok_mode0(cmd);
-            break;
-        }
-
-        /* RequestiPodSoftwareVersion, Ipod FM remote sends FF 55 02 00 09 F5 */
-        case 0x09:
-        {
-            /* ReturniPodSoftwareVersion, ipod5G firmware version */
-            unsigned char data[] = {0x00, 0x0A, 0x01, 0x02, 0x01};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        } 
-
-        /* RequestiPodModelNum */
-        case 0x0D:
-        {
-            /* ipod is supposed to work only with 5G and nano 2G */
-            /*{0x00, 0x0E, 0x00, 0x0B, 0x00, 0x05, 0x50, 0x41, 0x31, 0x34, 
-                    0x37, 0x4C, 0x4C, 0x00};    PA147LL (IPOD 5G 60 GO) */
-            /* ReturniPodModelNum */
-            unsigned char data[] = {0x00, 0x0E, 0x00, 0x0B, 0x00, 0x10,
-                                'R', 'O', 'C', 'K', 'B', 'O', 'X', 0x00};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-
-        /* RequestLingoProtocolVersion */
-        case 0x0F:
-        {
-            /* ReturnLingoProtocolVersion */
-            unsigned char data[] = {0x00, 0x10, 0x00, 0x01, 0x05};
-            data[2] = buf[2];
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-
-        /* IdentifyDeviceLingoes */
-        case 0x13:
-        {
-            cmd_ok_mode0(cmd);
-
-            uint32_t lingoes = get_u32(&buf[2]);
-
-            if (lingoes == 0x35)
-            /* 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));
-            }
-            else
-            {
-                /* ipod fm remote sends this: */ 
-                /* FF 55 0E 00 13 00 00 00 8D 00 00 00 0E 00 00 00 03 41 */
-                if (lingoes & (1 << 7)) /* bit 7 = RF tuner lingo */
-                    radio_present = 1;
-                /* GetDevAuthenticationInfo */    
-                unsigned char data4[] = {0x00, 0x14};
-                iap_send_pkt(data4, sizeof(data4));
-            }
-            break;
-        }
-
-        /* RetDevAuthenticationInfo */
-        case 0x15:
-        {
-            /* AckDevAuthenticationInfo */
-            unsigned char data0[] = {0x00, 0x16, 0x00};
-            iap_send_pkt(data0, sizeof(data0));
-            /* GetAccessoryInfo */
-            unsigned char data1[] = {0x00, 0x27, 0x00};
-            iap_send_pkt(data1, sizeof(data1));
-            /* AckDevAuthenticationStatus, mandatory to enable some hardware */
-            unsigned char data2[] = {0x00, 0x19, 0x00};
-            iap_send_pkt(data2, sizeof(data2));
-            if (radio_present == 1)
-            {
-                /* GetTunerCaps */
-                unsigned char data3[] = {0x07, 0x01};
-                iap_send_pkt(data3, sizeof(data3));
-            }
-            iap_set_remote_volume();
-            break;
-        }
-
-        /* RetDevAuthenticationSignature */
-        case 0x18:
-        {
-            /* Isn't used since we don't send the 0x00 0x17 command */
-            break;
-        }
-
-        /* GetIpodOptions */
-        case 0x24:
-        {
-            /* RetIpodOptions (ipod video send this) */
-            unsigned char data[] = {0x00, 0x25, 0x00, 0x00, 0x00,
-                                    0x00, 0x00, 0x00, 0x00, 0x01};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* default response is with cmd ok packet */
-        default:
-        {
-            cmd_ok_mode0(cmd);
-            break;
-        }
-    }
-}
-
-static void iap_handlepkt_mode2(unsigned int len, const unsigned char *buf)
-{
-    if(buf[1] != 0) return;
-    iap_remotebtn = BUTTON_NONE;
-    iap_remotetick = false;
-    
-    if(len >= 3 && buf[2] != 0)
-    {
-        if(buf[2] & 1)
-            iap_remotebtn |= BUTTON_RC_PLAY;
-        if(buf[2] & 2)
-            iap_remotebtn |= BUTTON_RC_VOL_UP;
-        if(buf[2] & 4)
-            iap_remotebtn |= BUTTON_RC_VOL_DOWN;
-        if(buf[2] & 8)
-            iap_remotebtn |= BUTTON_RC_RIGHT;
-        if(buf[2] & 16)
-            iap_remotebtn |= BUTTON_RC_LEFT;
-    }
-    else if(len >= 4 && buf[3] != 0)
-    {
-        if(buf[3] & 1) /* play */
-        {
-            if (audio_status() != AUDIO_STATUS_PLAY)
-            {
-                iap_remotebtn |= BUTTON_RC_PLAY;
-                iap_repeatbtn = 2;
-                iap_remotetick = false;
-                iap_changedctr = 1;
-            }
-        }
-        if(buf[3] & 2) /* pause */
-        {
-            if (audio_status() == AUDIO_STATUS_PLAY)
-            {
-                iap_remotebtn |= BUTTON_RC_PLAY;
-                iap_repeatbtn = 2;
-                iap_remotetick = false;
-                iap_changedctr = 1;
-            }
-        }
-        if((buf[3] & 128) && !iap_btnshuffle) /* shuffle */
-        {
-            iap_btnshuffle = true;
-            if(!global_settings.playlist_shuffle)
-            {
-                global_settings.playlist_shuffle = 1;
-                settings_save();
-                if (audio_status() & AUDIO_STATUS_PLAY)
-                    playlist_randomise(NULL, current_tick, true);
-            }
-            else if(global_settings.playlist_shuffle)
-            {
-                global_settings.playlist_shuffle = 0;
-                settings_save();
-                if (audio_status() & AUDIO_STATUS_PLAY)
-                    playlist_sort(NULL, true);
-            }
-        }
-        else
-            iap_btnshuffle = false;
-    }
-    else if(len >= 5 && buf[4] != 0)
-    {
-        if((buf[4] & 1) && !iap_btnrepeat) /* repeat */
-        {
-            int oldmode = global_settings.repeat_mode;
-            iap_btnrepeat = true;
-        
-            if (oldmode == REPEAT_ONE)
-                    global_settings.repeat_mode = REPEAT_OFF;
-            else if (oldmode == REPEAT_ALL)
-                    global_settings.repeat_mode = REPEAT_ONE;
-            else if (oldmode == REPEAT_OFF)
-                    global_settings.repeat_mode = REPEAT_ALL;
-
-            settings_save();
-            if (audio_status() & AUDIO_STATUS_PLAY)
-            audio_flush_and_reload_tracks();
-        }
-        else
-            iap_btnrepeat = false;
-
-        if(buf[4] & 16) /* ffwd */
-        {
-            iap_remotebtn |= BUTTON_RC_RIGHT;
-        }
-        if(buf[4] & 32) /* frwd */
-        {
-            iap_remotebtn |= BUTTON_RC_LEFT;
-        }
-    }
-}
-
-static void iap_handlepkt_mode3(unsigned int len, const unsigned char *buf)
-{
-    (void)len;    /* len currently unused */
-
-    unsigned int cmd = buf[1];
-    switch (cmd)
-    {
-        /* GetCurrentEQProfileIndex */
-        case 0x01:
-        {
-            /* RetCurrentEQProfileIndex */
-            unsigned char data[] = {0x03, 0x02, 0x00, 0x00, 0x00, 0x00};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-
-        /* SetRemoteEventNotification */
-        case 0x08:
-        {
-            /* ACK */
-            unsigned char data[] = {0x03, 0x00, 0x00, 0x08};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* GetiPodStateInfo */
-        case 0x0C:
-        {
-            /* request ipod volume */
-            if (buf[2] == 0x04)
-            {
-                iap_set_remote_volume();
-            }
-            break;
-        }
-        
-        /* SetiPodStateInfo */
-        case 0x0E:
-        {
-            if (buf[2] == 0x04)
-                global_settings.volume = (-58)+((int)buf[4]+1)/4;
-                sound_set_volume(global_settings.volume);   /* indent BUG? */
-            break;
-        }
-    }
-}
-
-static void cmd_ok_mode4(unsigned int cmd)
-{
-    unsigned char data[] = {0x04, 0x00, 0x01, 0x00, 0x00, 0x00};
-    data[4] = (cmd >> 8) & 0xFF;
-    data[5] = (cmd >> 0) & 0xFF;
-    iap_send_pkt(data, sizeof(data));
-}
-
-static void get_playlist_name(unsigned char *dest,
-                              unsigned long item_offset,
-                              size_t max_length)
-{
-    if (item_offset == 0) return;
-    DIR* dp;
-    struct dirent* playlist_file = NULL;
-
-    dp = opendir(global_settings.playlist_catalog_dir);
-
-    char *extension;
-    unsigned long nbr = 0;
-    while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL))
-    {
-        /*Increment only if there is a playlist extension*/
-        if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){
-            if ((strcmp(extension, ".m3u") == 0 || 
-                strcmp(extension, ".m3u8") == 0))
-                nbr++;
-        }
-    }
-    if (playlist_file != NULL) {
-        strlcpy(dest, playlist_file->d_name, max_length);
-    }
-    closedir(dp);
-}
-
-static void iap_handlepkt_mode4(unsigned int len, const unsigned char *buf)
-{
-    (void)len;    /* len currently unused */
-
-    unsigned int cmd = (buf[1] << 8) | buf[2];
-    switch (cmd)
-    {
-        /* GetAudioBookSpeed */
-        case 0x0009:
-        {
-            /* ReturnAudioBookSpeed */
-            unsigned char data[] = {0x04, 0x00, 0x0A, 0x00};
-            data[3] = iap_updateflag ? 0 : 1;
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* SetAudioBookSpeed */
-        case 0x000B:
-        {
-            iap_updateflag = buf[3] ? 0 : 1;
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* RequestProtocolVersion */
-        case 0x0012:
-        {
-            /* ReturnProtocolVersion */
-            unsigned char data[] = {0x04, 0x00, 0x13, 0x01, 0x0B};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-
-        /* SelectDBRecord */
-        case 0x0017:
-        {
-            memcpy(cur_dbrecord, buf + 3, 5);
-            cmd_ok_mode4(cmd);
-            break;
-        }
-
-        /* GetNumberCategorizedDBRecords */
-        case 0x0018:
-        {
-            /* ReturnNumberCategorizedDBRecords */
-            unsigned char data[] = {0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00};
-            unsigned long num = 0;
-            
-            DIR* dp;
-            unsigned long nbr_total_playlists = 0;
-            struct dirent* playlist_file = NULL;
-            char *extension;
-
-            switch(buf[3]) /* type number */
-            {
-                case 0x01: /* total number of playlists */
-                    dp = opendir(global_settings.playlist_catalog_dir);
-                    while ((playlist_file = readdir(dp)) != NULL)
-                    {
-                    /*Increment only if there is a playlist extension*/
-                        if ((extension=strrchr(playlist_file->d_name, '.'))
-                        != NULL) {
-                            if ((strcmp(extension, ".m3u") == 0 || 
-                                strcmp(extension, ".m3u8") == 0))
-                                nbr_total_playlists++;
-                        }
-                    }
-                    closedir(dp);
-                    /*Add 1 for the main playlist*/
-                    num = nbr_total_playlists + 1;
-                    break;
-                case 0x05: /* total number of songs */
-                    num = 1;
-                    break;
-            }
-            put_u32(&data[3], num);
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* RetrieveCategorizedDatabaseRecords */
-        case 0x001A:
-        {
-            /* ReturnCategorizedDatabaseRecord */
-            unsigned char data[7 + MAX_PATH] = 
-                            {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00,
-                             'R', 'O', 'C', 'K', 'B', 'O', 'X', '\0'};
-            
-            unsigned long item_offset = get_u32(&buf[4]);
-
-            get_playlist_name(data + 7, item_offset, MAX_PATH);
-            /*Remove file extension*/
-            char *dot=NULL;
-            dot = (strrchr(data+7, '.'));
-            if (dot != NULL)
-                *dot = '\0';
-            iap_send_pkt(data, 7 + strlen(data+7) + 1);
-            break;
-        }
-        
-        /* GetPlayStatus */
-        case 0x001C:
-        {
-            /* ReturnPlayStatus */
-            unsigned char data[] = {0x04, 0x00, 0x1D, 0x00, 0x00, 0x00, 
-                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-            struct mp3entry *id3 = audio_current_track();
-            unsigned long time_total = id3->length;
-            unsigned long time_elapsed = id3->elapsed;
-            int status = audio_status();
-            put_u32(&data[3], time_total);
-            put_u32(&data[7], time_elapsed);
-            if (status == AUDIO_STATUS_PLAY)
-                data[11] = 0x01; /* play */
-            else if (status & AUDIO_STATUS_PAUSE)
-                data[11] = 0x02; /* pause */ 
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* GetCurrentPlayingTrackIndex */
-        case 0x001E:
-        {
-            /* ReturnCurrentPlayingTrackIndex */
-            unsigned char data[] = {0x04, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00};
-            long playlist_pos = playlist_next(0);
-            playlist_pos -= playlist_get_first_index(NULL);
-            if(playlist_pos < 0)
-                playlist_pos += playlist_amount();
-            put_u32(&data[3], playlist_pos);
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* GetIndexedPlayingTrackTitle */
-        case 0x0020:
-        /* GetIndexedPlayingTrackArtistName */
-        case 0x0022:
-        /* GetIndexedPlayingTrackAlbumName */
-        case 0x0024:
-        {
-            unsigned char data[70] = {0x04, 0x00, 0xFF};
-            struct mp3entry id3;
-            int fd;
-            size_t len;
-            long tracknum = get_u32(&buf[3]);
-            
-            data[2] = cmd + 1;
-            memcpy(&id3, audio_current_track(), sizeof(id3));
-            tracknum += playlist_get_first_index(NULL);
-            if(tracknum >= playlist_amount())
-                tracknum -= playlist_amount();
-
-            /* If the tracknumber is not the current one,
-               read id3 from disk */
-            if(playlist_next(0) != tracknum)
-            {
-                struct playlist_track_info info;
-                playlist_get_track_info(NULL, tracknum, &info);
-                fd = open(info.filename, O_RDONLY);
-                memset(&id3, 0, sizeof(struct mp3entry));
-                get_metadata(&id3, fd, info.filename);
-                close(fd);
-            }
-
-            /* Return the requested track data */
-            switch(cmd)
-            {
-                case 0x20:
-                    len = strlcpy((char *)&data[3], id3.title, 64);
-                    iap_send_pkt(data, 4+len);
-                    break;
-                case 0x22:
-                    len = strlcpy((char *)&data[3], id3.artist, 64);
-                    iap_send_pkt(data, 4+len);
-                    break;
-                case 0x24:
-                    len = strlcpy((char *)&data[3], id3.album, 64);
-                    iap_send_pkt(data, 4+len);
-                    break;
-            }
-            break;
-        }
-        
-        /* SetPlayStatusChangeNotification */
-        case 0x0026:
-        {
-            iap_pollspeed = buf[3] ? 1 : 0;
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* PlayCurrentSelection */
-        case 0x0028:
-        {
-            switch (cur_dbrecord[0])
-            {
-                case 0x01:
-                {/*Playlist*/
-                    unsigned long item_offset = get_u32(&cur_dbrecord[1]);
-                    
-                    unsigned char selected_playlist
-                    [sizeof(global_settings.playlist_catalog_dir)
-                    + 1
-                    + MAX_PATH] = {0};
-
-                    strcpy(selected_playlist,
-                            global_settings.playlist_catalog_dir);
-                    int len = strlen(selected_playlist);
-                    selected_playlist[len] = '/';
-                    get_playlist_name (selected_playlist + len + 1,
-                                       item_offset,
-                                       MAX_PATH);
-                    ft_play_playlist(selected_playlist,
-                                     global_settings.playlist_catalog_dir,
-                                     strrchr(selected_playlist, '/') + 1);
-                    break;
-                }
-            }
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* PlayControl */
-        case 0x0029:
-        {
-            switch(buf[3])
-            {
-                case 0x01: /* play/pause */
-                    iap_remotebtn = BUTTON_RC_PLAY;
-                    iap_repeatbtn = 2;
-                    iap_remotetick = false;
-                    iap_changedctr = 1;
-                    break;
-                case 0x02: /* stop */
-                    iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT;
-                    iap_repeatbtn = 2;
-                    iap_remotetick = false;
-                    iap_changedctr = 1;
-                    break;
-                case 0x03: /* skip++ */
-                    iap_remotebtn = BUTTON_RC_RIGHT;
-                    iap_repeatbtn = 2;
-                    iap_remotetick = false;
-                    break;
-                case 0x04: /* skip-- */
-                    iap_remotebtn = BUTTON_RC_LEFT;
-                    iap_repeatbtn = 2;
-                    iap_remotetick = false;
-                    break;
-                case 0x05: /* ffwd */
-                    iap_remotebtn = BUTTON_RC_RIGHT;
-                    iap_remotetick = false;
-                    if(iap_pollspeed) iap_pollspeed = 5;
-                    break;
-                case 0x06: /* frwd */
-                    iap_remotebtn = BUTTON_RC_LEFT;
-                    iap_remotetick = false;
-                    if(iap_pollspeed) iap_pollspeed = 5;
-                    break;
-                case 0x07: /* end ffwd/frwd */
-                    iap_remotebtn = BUTTON_NONE;
-                    iap_remotetick = false;
-                    if(iap_pollspeed) iap_pollspeed = 1;
-                    break;
-            }
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* GetShuffle */
-        case 0x002C:
-        {
-            /* ReturnShuffle */
-            unsigned char data[] = {0x04, 0x00, 0x2D, 0x00};
-            data[3] = global_settings.playlist_shuffle ? 1 : 0;
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* SetShuffle */
-        case 0x002E:
-        {
-            if(buf[3] && !global_settings.playlist_shuffle)
-            {
-                global_settings.playlist_shuffle = 1;
-                settings_save();
-                if (audio_status() & AUDIO_STATUS_PLAY)
-                    playlist_randomise(NULL, current_tick, true);
-            }
-            else if(!buf[3] && global_settings.playlist_shuffle)
-            {
-                global_settings.playlist_shuffle = 0;
-                settings_save();
-                if (audio_status() & AUDIO_STATUS_PLAY)
-                    playlist_sort(NULL, true);
-            }
-
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* GetRepeat */
-        case 0x002F:
-        {
-            /* ReturnRepeat */
-            unsigned char data[] = {0x04, 0x00, 0x30, 0x00};
-            if(global_settings.repeat_mode == REPEAT_OFF)
-                data[3] = 0;
-            else if(global_settings.repeat_mode == REPEAT_ONE)
-                data[3] = 1;
-            else
-                data[3] = 2;
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* SetRepeat */
-        case 0x0031:
-        {
-            int oldmode = global_settings.repeat_mode;
-            if (buf[3] == 0)
-                global_settings.repeat_mode = REPEAT_OFF;
-            else if (buf[3] == 1)
-                global_settings.repeat_mode = REPEAT_ONE;
-            else if (buf[3] == 2)
-                global_settings.repeat_mode = REPEAT_ALL;
-
-            if (oldmode != global_settings.repeat_mode)
-            {
-                settings_save();
-                if (audio_status() & AUDIO_STATUS_PLAY)
-                    audio_flush_and_reload_tracks();
-            }
-
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        /* GetMonoDisplayImageLimits */
-        case 0x0033:
-        {
-            /* ReturnMonoDisplayImageLimits */
-            unsigned char data[] = {0x04, 0x00, 0x34,
-                                    LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
-                                    LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
-                                    0x01};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* GetNumPlayingTracks */
-        case 0x0035:
-        {
-            /* ReturnNumPlayingTracks */
-            unsigned char data[] = {0x04, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00};
-            unsigned long playlist_amt = playlist_amount();
-            put_u32(&data[3], playlist_amt);
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* SetCurrentPlayingTrack */
-        case 0x0037:
-        {
-            int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
-            long tracknum = get_u32(&buf[3]);
-            
-            audio_pause();
-            audio_skip(tracknum - playlist_next(0));
-            if (!paused)
-                audio_resume();
-            
-            /* respond with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-        
-        default:
-        {
-            /* default response is with cmd ok packet */
-            cmd_ok_mode4(cmd);
-            break;
-        }
-    }
-}
-
-static void iap_handlepkt_mode7(unsigned int len, const unsigned char *buf)
-{
-    unsigned int cmd = buf[1];
-    switch (cmd)
-    {
-        /* RetTunerCaps */
-        case 0x02:
-        {
-            /* do nothing */
-            
-            /* GetAccessoryInfo */
-            unsigned char data[] = {0x00, 0x27, 0x00};
-            iap_send_pkt(data, sizeof(data));
-            break;
-        }
-        
-        /* RetTunerFreq */
-        case 0x0A:
-            /* fall through */
-        /* TunerSeekDone */
-        case 0x13:
-        {
-            rmt_tuner_freq(len, buf);
-            break;
-        }
-        
-        /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/
-        case 0x21:
-        {
-            rmt_tuner_rds_data(len, buf);
-            break;
-        }
-    }
-}
-
-void iap_handlepkt(void)
-{
-    struct state_t *s = &frame_state;
-
-    if(!iap_setupflag) return;
-
-    /* if we are waiting for a remote button to go out,
-       delay the handling of the new packet */
-    if(!iap_remotetick)
-    {
-        queue_post(&button_queue, SYS_IAP_HANDLEPKT, 0);
-        return;
-    }
-
-    /* handle command by mode */
-    unsigned char mode = s->payload[0];
-    switch (mode) {
-    case 0: iap_handlepkt_mode0(s->len, s->payload); break;
-    case 2: iap_handlepkt_mode2(s->len, s->payload); break;
-    case 3: iap_handlepkt_mode3(s->len, s->payload); break;
-    case 4: iap_handlepkt_mode4(s->len, s->payload); break;
-    case 7: iap_handlepkt_mode7(s->len, s->payload); break;
-    }
-}
-
-int remote_control_rx(void)
-{
-    int btn = iap_remotebtn;
-    if(iap_repeatbtn)
-    {
-        iap_repeatbtn--;
-        if(!iap_repeatbtn)
-        {
-            iap_remotebtn = BUTTON_NONE;
-            iap_remotetick = true;
-        }
-    }
-    else
-        iap_remotetick = true;
-
-    return btn;
-}
-
-const unsigned char *iap_get_serbuf(void)
-{
-    return serbuf;
-}
-
diff --git a/apps/iap/iap-core.c b/apps/iap/iap-core.c
new file mode 100644
index 0000000..ddcb228
--- /dev/null
+++ b/apps/iap/iap-core.c
@@ -0,0 +1,1392 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "panic.h"
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "button.h"
+#include "config.h"
+#include "cpu.h"
+#include "system.h"
+#include "kernel.h"
+#include "thread.h"
+#include "serial.h"
+#include "appevents.h"
+#include "core_alloc.h"
+
+#include "playlist.h"
+#include "playback.h"
+#include "audio.h"
+#include "settings.h"
+#include "metadata.h"
+#include "sound.h"
+#include "action.h"
+#include "powermgmt.h"
+
+#include "tuner.h"
+#include "ipod_remote_tuner.h"
+
+
+/* MS_TO_TICKS converts a milisecond time period into the
+ * corresponding amount of ticks. If the time period cannot
+ * be accurately measured in ticks it will round up.
+ */
+#if (HZ>1000)
+#error "HZ is >1000, please fix MS_TO_TICKS"
+#endif
+#define MS_PER_HZ (1000/HZ)
+#define MS_TO_TICKS(x) (((x)+MS_PER_HZ-1)/MS_PER_HZ)
+/* IAP specifies a timeout of 25ms for traffic from a device to the iPod.
+ * Depending on HZ this cannot be accurately measured. Find out the next
+ * best thing.
+ */
+#define IAP_PKT_TIMEOUT (MS_TO_TICKS(25))
+
+/* Events in the iap_queue */
+#define IAP_EV_TICK         (1)     /* The regular task timeout */
+#define IAP_EV_MSG_RCVD     (2)     /* A complete message has been received from the \
device */ +#define IAP_EV_MALLOC       (3)     /* Allocate memory for the RX/TX \
buffers */ +
+static bool iap_started = false;
+static bool iap_setupflag = false, iap_running = false;
+/* This is set to true if a SYS_POWEROFF message is received,
+ * signalling impending power off
+ */
+static bool iap_shutdown = false;
+static struct timeout iap_task_tmo;
+
+unsigned long iap_remotebtn = 0;
+/* Used to make sure a button press is delivered to the processing
+ * backend. While this is !0, no new incoming messasges are processed.
+ * Counted down by remote_control_rx()
+ */
+int iap_repeatbtn = 0;
+/* Used to time out button down events in case we miss the button up event
+ * from the device somehow.
+ * If a device sends a button down event it's required to repeat that event
+ * every 30 to 100ms as long as the button is pressed, and send an explicit
+ * button up event if the button is released.
+ * In case the button up event is lost any down events will time out after
+ * ~200ms.
+ * iap_periodic() will count down this variable and reset all buttons if
+ * it reaches 0
+ */
+unsigned int iap_timeoutbtn = 0;
+bool iap_btnrepeat = false, iap_btnshuffle = false;
+
+static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)];
+static struct event_queue iap_queue;
+
+/* These are pointer used to manage a dynamically allocated buffer which
+ * will hold both the RX and TX side of things.
+ *
+ * iap_buffer_handle is the handle returned from core_alloc()
+ * iap_buffers points to the start of the complete buffer
+ *
+ * The buffer is partitioned as follows:
+ * - TX_BUFLEN+6 bytes for the TX buffer
+ *   The 6 extra bytes are for the sync byte, the SOP byte, the length indicators
+ *   (3 bytes) and the checksum byte.
+ *   iap_txstart points to the beginning of the TX buffer
+ *   iap_txpayload points to the beginning of the payload portion of the TX buffer
+ *   iap_txnext points to the position where the next byte will be placed
+ *
+ * - RX_BUFLEN+2 bytes for the RX buffer
+ *   The RX buffer can hold multiple packets at once, up to it's
+ *   maximum capacity. Every packet consists of a two byte length
+ *   indicator followed by the actual payload. The length indicator
+ *   is two bytes for every length, even for packets with a length <256
+ *   bytes.
+ *
+ *   Once a packet has been processed from the RX buffer the rest
+ *   of the buffer (and the pointers below) are shifted to the front
+ *   so that the next packet again starts at the beginning of the
+ *   buffer. This happens with interrupts disabled, to prevent
+ *   writing into the buffer during the move.
+ *
+ *   iap_rxstart points to the beginning of the RX buffer
+ *   iap_rxpayload starts to the beginning of the currently recieved
+ *   packet
+ *   iap_rxnext points to the position where the next incoming byte
+ *   will be placed
+ *   iap_rxlen is not a pointer, but an indicator of the free
+ *   space left in the RX buffer.
+ *
+ * The RX buffer is placed behind the TX buffer so that an eventual TX
+ * buffer overflow has some place to spill into where it will not cause
+ * immediate damage. See the comments for IAP_TX_* and iap_send_tx()
+ */
+#define IAP_MALLOC_SIZE (TX_BUFLEN+6+RX_BUFLEN+2)
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_buffer_handle;
+#endif
+static unsigned char* iap_buffers;
+static unsigned char* iap_rxstart;
+static unsigned char* iap_rxpayload;
+static unsigned char* iap_rxnext;
+static uint32_t iap_rxlen;
+static unsigned char* iap_txstart;
+unsigned char* iap_txpayload;
+unsigned char* iap_txnext;
+
+/* The versions of the various Lingoes we support. A major version
+ * of 0 means unsupported
+ */
+unsigned char lingo_versions[32][2] = {
+    {1, 9},     /* General lingo, 0x00 */
+    {0, 0},     /* Microphone lingo, 0x01, unsupported */
+    {1, 2},     /* Simple remote lingo, 0x02 */
+    {1, 5},     /* Display remote lingo, 0x03 */
+    {1, 12},    /* Extended Interface lingo, 0x04 */
+    {1, 1},     /* RF/BT Transmitter lingo, 0x05 */
+    {}          /* All others are unsupported */
+};
+
+/* states of the iap de-framing state machine */
+enum fsm_state {
+    ST_SYNC,    /* wait for 0xFF sync byte */
+    ST_SOF,     /* wait for 0x55 start-of-frame byte */
+    ST_LEN,     /* receive length byte (small packet) */
+    ST_LENH,    /* receive length high byte (large packet) */
+    ST_LENL,    /* receive length low byte (large packet) */
+    ST_DATA,    /* receive data */
+    ST_CHECK    /* verify checksum */
+};
+
+static struct state_t {
+    enum fsm_state state;   /* current fsm state */
+    unsigned int len;       /* payload data length */
+    unsigned int check;     /* running checksum over [len,payload,check] */
+    unsigned int count;     /* playload bytes counter */
+} frame_state = {
+    .state = ST_SYNC
+};
+
+enum interface_state interface_state = IST_STANDARD;
+
+struct device_t device;
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new);
+
+static struct buflib_callbacks iap_buflib_callbacks = {
+    iap_move_callback,
+    NULL
+};
+#endif
+
+static void iap_malloc(void);
+
+void put_u16(unsigned char *buf, const uint16_t data)
+{
+    buf[0] = (data >>  8) & 0xFF;
+    buf[1] = (data >>  0) & 0xFF;
+}
+
+void put_u32(unsigned char *buf, const uint32_t data)
+{
+    buf[0] = (data >> 24) & 0xFF;
+    buf[1] = (data >> 16) & 0xFF;
+    buf[2] = (data >>  8) & 0xFF;
+    buf[3] = (data >>  0) & 0xFF;
+}
+
+uint32_t get_u32(const unsigned char *buf)
+{
+    return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
+uint16_t get_u16(const unsigned char *buf)
+{
+    return (buf[0] << 8) | buf[1];
+}
+
+#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
+/* Convert a buffer into a printable string, perl style
+ * buf contains the data to be converted, len is the length
+ * of the buffer.
+ *
+ * This will convert at most 1024 bytes from buf
+ */
+static char* hexstring(const unsigned char *buf, unsigned int len) {
+    static char hexbuf[4097];
+    unsigned int l;
+    const unsigned char* p;
+    unsigned char* out;
+    unsigned char h[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+                         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+    if (len > 1024) {
+        l = 1024;
+    } else {
+        l = len;
+    }
+    p = buf;
+    out = hexbuf;
+    do {
+            *out++ = h[(*p)>>4];
+            *out++ = h[*p & 0x0F];
+    } while(--l && p++);
+
+    *out = 0x00;
+
+    return hexbuf;
+}
+#endif
+
+
+void iap_tx_strlcpy(const unsigned char *str)
+{
+    ptrdiff_t txfree;
+    int r;
+
+    txfree = TX_BUFLEN - (iap_txnext - iap_txstart);
+    r = strlcpy(iap_txnext, str, txfree);
+
+    if (r < txfree)
+    {
+        /* No truncation occured
+         * Account for the terminating \0
+         */
+        iap_txnext += (r+1);
+    } else {
+        /* Truncation occured, the TX buffer is now full. */
+        iap_txnext = iap_txstart + TX_BUFLEN;
+    }
+}
+
+void iap_reset_auth(struct auth_t* auth)
+{
+    auth->state = AUST_NONE;
+    auth->max_section = 0;
+    auth->next_section = 0;
+}
+
+void iap_reset_device(struct device_t* device)
+{
+    iap_reset_auth(&(device->auth));
+    device->lingoes = 0;
+    device->notifications = 0;
+    device->changed_notifications = 0;
+    device->do_notify = false;
+    device->do_power_notify = false;
+    device->accinfo = ACCST_NONE;
+    device->capabilities = 0;
+    device->capabilities_queried = 0;
+}
+
+static int iap_task(struct timeout *tmo)
+{
+    (void) tmo;
+
+    queue_post(&iap_queue, IAP_EV_TICK, 0);
+    return MS_TO_TICKS(100);
+}
+
+/* This thread is waiting for events posted to iap_queue and calls
+ * the appropriate subroutines in response
+ */
+static void iap_thread(void)
+{
+    struct queue_event ev;
+    while(1) {
+        queue_wait(&iap_queue, &ev);
+        switch (ev.id)
+        {
+            /* Handle the regular 100ms tick used for driving the
+             * authentication state machine and notifications
+             */
+            case IAP_EV_TICK:
+            {
+                iap_periodic();
+                break;
+            }
+
+            /* Handle a newly received message from the device */
+            case IAP_EV_MSG_RCVD:
+            {
+                iap_handlepkt();
+                break;
+            }
+
+            /* Handle memory allocation. This is used only once, during
+             * startup
+             */
+            case IAP_EV_MALLOC:
+            {
+                iap_malloc();
+                break;
+            }
+
+            /* Handle poweroff message */
+            case SYS_POWEROFF:
+            {
+                iap_shutdown = true;
+                break;
+            }
+        }
+    }
+}
+
+/* called by playback when the next track starts */
+static void iap_track_changed(void *ignored)
+{
+    (void)ignored;
+    if ((interface_state == IST_EXTENDED) && device.do_notify) {
+        long playlist_pos = playlist_next(0);
+        playlist_pos -= playlist_get_first_index(NULL);
+        if(playlist_pos < 0)
+            playlist_pos += playlist_amount();
+
+        IAP_TX_INIT4(0x04, 0x0027);
+        IAP_TX_PUT(0x01);
+        IAP_TX_PUT_U32(playlist_pos);
+
+        iap_send_tx();
+        return;
+    }
+}
+
+/* Do general setup of the needed infrastructure.
+ *
+ * Please note that a lot of additional work is done by iap_start()
+ */
+void iap_setup(const int ratenum)
+{
+    iap_bitrate_set(ratenum);
+    iap_remotebtn = BUTTON_NONE;
+    iap_setupflag = true;
+    iap_started = false;
+    iap_running = false;
+}
+
+/* Actually bring up the message queue, message handler thread and
+ * notification timer
+ *
+ * NOTE: This is running in interrupt context
+ */
+static void iap_start(void)
+{
+    unsigned int tid;
+
+    if (iap_started)
+        return;
+
+    iap_reset_device(&device);
+    queue_init(&iap_queue, true);
+    tid = create_thread(iap_thread, thread_stack, sizeof(thread_stack),
+            0, "iap"
+            IF_PRIO(, PRIORITY_SYSTEM)
+            IF_COP(, CPU));
+    if (!tid)
+        panicf("Could not create iap thread");
+    timeout_register(&iap_task_tmo, iap_task, MS_TO_TICKS(100), (intptr_t)NULL);
+    add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, iap_track_changed);
+
+    /* Since we cannot allocate memory while in interrupt context
+     * post a message to our own queue to get that done
+     */
+    queue_post(&iap_queue, IAP_EV_MALLOC, 0);
+    iap_started = true;
+}
+
+static void iap_malloc(void)
+{
+#ifndef IAP_MALLOC_DYNAMIC
+    static unsigned char serbuf[IAP_MALLOC_SIZE];
+#endif
+
+    if (iap_running)
+        return;
+
+#ifdef IAP_MALLOC_DYNAMIC
+    iap_buffer_handle = core_alloc_ex("iap", IAP_MALLOC_SIZE, \
&iap_buflib_callbacks); +    if (iap_buffer_handle < 0)
+        panicf("Could not allocate buffer memory");
+    iap_buffers = core_get_data(iap_buffer_handle);
+#else
+    iap_buffers = serbuf;
+#endif
+    iap_txstart = iap_buffers;
+    iap_txpayload = iap_txstart+5;
+    iap_txnext = iap_txpayload;
+    iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+    iap_rxpayload = iap_rxstart;
+    iap_rxnext = iap_rxpayload;
+    iap_rxlen = RX_BUFLEN+2;
+    iap_running = true;
+}
+
+void iap_bitrate_set(const int ratenum)
+{
+    switch(ratenum)
+    {
+        case 0:
+            serial_bitrate(0);
+            break;
+        case 1:
+            serial_bitrate(9600);
+            break;
+        case 2:
+            serial_bitrate(19200);
+            break;
+        case 3:
+            serial_bitrate(38400);
+            break;
+        case 4:
+            serial_bitrate(57600);
+            break;
+    }
+}
+
+/* Message format:
+   0xff
+   0x55
+   length
+   mode
+   command (2 bytes)
+   parameters (0-n bytes)
+   checksum (length+mode+parameters+checksum == 0)
+*/
+
+/* Send the current content of the TX buffer.
+ * This will check for TX buffer overflow and panic, but it might
+ * be too late by then (although one would have to overflow the complete
+ * RX buffer as well)
+ */
+void iap_send_tx(void)
+{
+    int i, chksum;
+    ptrdiff_t txlen;
+    unsigned char* txstart;
+
+    txlen = iap_txnext - iap_txpayload;
+
+    if (txlen <= 0)
+        return;
+
+    if (txlen > TX_BUFLEN)
+        panicf("IAP: TX buffer overflow");
+
+    if (txlen < 256)
+    {
+        /* Short packet */
+        txstart = iap_txstart+2;
+        *(txstart+2) = txlen;
+        chksum = txlen;
+    } else {
+        /* Long packet */
+        txstart = iap_txstart;
+        *(txstart+2) = 0x00;
+        *(txstart+3) = (txlen >> 8) & 0xFF;
+        *(txstart+4) = (txlen) & 0xFF;
+        chksum = *(txstart+3) + *(txstart+4);
+    }
+    *(txstart) = 0xFF;
+    *(txstart+1) = 0x55;
+
+    for (i=0; i<txlen; i++)
+    {
+        chksum += iap_txpayload[i];
+    }
+    *(iap_txnext) = 0x100 - (chksum & 0xFF);
+
+#ifdef LOGF_ENABLE
+    logf("T: %s", hexstring(txstart+3, (iap_txnext - txstart)-3));
+#endif
+    for (i=0; i <= (iap_txnext - txstart); i++)
+    {
+        while(!tx_rdy()) ;
+        tx_writec(txstart[i]);
+    }
+}
+
+/* This is just a compatibility wrapper around the new TX buffer
+ * infrastructure
+ */
+void iap_send_pkt(const unsigned char * data, const int len)
+{
+    if (!iap_running)
+        return;
+
+    iap_txnext = iap_txpayload;
+    IAP_TX_PUT_DATA(data, len);
+    iap_send_tx();
+}
+
+bool iap_getc(const unsigned char x)
+{
+    struct state_t *s = &frame_state;
+    static long pkt_timeout;
+
+    if (!iap_setupflag)
+        return false;
+
+    /* Check the time since the last packet arrived. */
+    if ((s->state != ST_SYNC) && TIME_AFTER(current_tick, pkt_timeout)) {
+        /* Packet timeouts only make sense while not waiting for the
+         * sync byte */
+         s->state = ST_SYNC;
+         return iap_getc(x);
+    }
+
+
+    /* run state machine to detect and extract a valid frame */
+    switch (s->state) {
+    case ST_SYNC:
+        if (x == 0xFF) {
+            /* The IAP infrastructure is started by the first received sync
+             * byte. It takes a while to spin up, so do not advance the state
+             * machine until it has started.
+             */
+            if (!iap_running)
+            {
+                iap_start();
+                break;
+            }
+            iap_rxnext = iap_rxpayload;
+            s->state = ST_SOF;
+        }
+        break;
+    case ST_SOF:
+        if (x == 0x55) {
+            /* received a valid sync/SOF pair */
+            s->state = ST_LEN;
+        } else {
+            s->state = ST_SYNC;
+            return iap_getc(x);
+        }
+        break;
+    case ST_LEN:
+        s->check = x;
+        s->count = 0;
+        if (x == 0) {
+            /* large packet */
+            s->state = ST_LENH;
+        } else {
+            /* small packet */
+            if (x > (iap_rxlen-2))
+            {
+                /* Packet too long for buffer */
+                s->state = ST_SYNC;
+                break;
+            }
+            s->len = x;
+            s->state = ST_DATA;
+            put_u16(iap_rxnext, s->len);
+            iap_rxnext += 2;
+        }
+        break;
+    case ST_LENH:
+        s->check += x;
+        s->len = x << 8;
+        s->state = ST_LENL;
+        break;
+    case ST_LENL:
+        s->check += x;
+        s->len += x;
+        if ((s->len == 0) || (s->len > (iap_rxlen-2))) {
+            /* invalid length */
+            s->state = ST_SYNC;
+            break;
+        } else {
+            s->state = ST_DATA;
+            put_u16(iap_rxnext, s->len);
+            iap_rxnext += 2;
+        }
+        break;
+    case ST_DATA:
+        s->check += x;
+        *(iap_rxnext++) = x;
+        s->count += 1;
+        if (s->count == s->len) {
+            s->state = ST_CHECK;
+        }
+        break;
+    case ST_CHECK:
+        s->check += x;
+        if ((s->check & 0xFF) == 0) {
+            /* done, received a valid frame */
+            iap_rxpayload = iap_rxnext;
+            queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+        } else {
+            /* Invalid frame */
+        }
+        s->state = ST_SYNC;
+        break;
+    default:
+#ifdef LOGF_ENABLE
+           logf("Unhandled iap state %d", (int) s->state);
+#else
+           panicf("Unhandled iap state %d", (int) s->state);
+#endif
+        break;
+    }
+
+    pkt_timeout = current_tick + IAP_PKT_TIMEOUT;
+
+    /* return true while still hunting for the sync and start-of-frame byte */
+    return (s->state == ST_SYNC) || (s->state == ST_SOF);
+}
+
+void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3)
+{
+    int tracknum;
+    int fd;
+    struct playlist_track_info info;
+
+    tracknum = track;
+
+    tracknum += playlist_get_first_index(NULL);
+    if(tracknum >= playlist_amount())
+        tracknum -= playlist_amount();
+
+    /* If the tracknumber is not the current one,
+       read id3 from disk */
+    if(playlist_next(0) != tracknum)
+    {
+        playlist_get_track_info(NULL, tracknum, &info);
+        fd = open(info.filename, O_RDONLY);
+        memset(id3, 0, sizeof(*id3));
+        get_metadata(id3, fd, info.filename);
+        close(fd);
+    } else {
+        memcpy(id3, audio_current_track(), sizeof(*id3));
+    }
+}
+
+uint32_t iap_get_trackpos(void)
+{
+    struct mp3entry *id3 = audio_current_track();
+
+    return id3->elapsed;
+}
+
+uint32_t iap_get_trackindex(void)
+{
+    struct playlist_info* playlist = playlist_get_current();
+
+    return (playlist->index - playlist->first_index);
+}
+
+void iap_periodic(void)
+{
+    static int count;
+
+    if(!iap_setupflag) return;
+
+    /* Handle pending authentication tasks */
+    switch (device.auth.state)
+    {
+        case AUST_INIT:
+        {
+            /* Send out GetDevAuthenticationInfo */
+            IAP_TX_INIT(0x00, 0x14);
+
+            iap_send_tx();
+            device.auth.state = AUST_CERTREQ;
+            break;
+        }
+
+        case AUST_CERTDONE:
+        {
+            /* Send out GetDevAuthenticationSignature, with
+             * 20 bytes of challenge and a retry counter of 1.
+             * Since we do not really care about the content of the
+             * challenge we just use the first 20 bytes of whatever
+             * is in the RX buffer right now.
+             */
+            IAP_TX_INIT(0x00, 0x17);
+            IAP_TX_PUT_DATA(iap_rxstart, 20);
+            IAP_TX_PUT(0x01);
+
+            iap_send_tx();
+            device.auth.state = AUST_CHASENT;
+            break;
+        }
+
+        default:
+        {
+            break;
+        }
+    }
+
+    /* Time out button down events */
+    if (iap_timeoutbtn)
+        iap_timeoutbtn -= 1;
+
+    if (!iap_timeoutbtn)
+    {
+        iap_remotebtn = BUTTON_NONE;
+        iap_repeatbtn = 0;
+        iap_btnshuffle = false;
+        iap_btnrepeat = false;
+    }
+
+    /* Handle power down messages. */
+    if (iap_shutdown && device.do_power_notify)
+    {
+        /* NotifyiPodStateChange */
+        IAP_TX_INIT(0x00, 0x23);
+        IAP_TX_PUT(0x01);
+
+        iap_send_tx();
+
+        /* No further actions, we're going down */
+        iap_reset_device(&device);
+        return;
+    }
+
+    /* Handle GetAccessoryInfo messages */
+    if (device.accinfo == ACCST_INIT)
+    {
+        /* GetAccessoryInfo */
+        IAP_TX_INIT(0x00, 0x27);
+        IAP_TX_PUT(0x00);
+
+        iap_send_tx();
+        device.accinfo = ACCST_SENT;
+    }
+
+    /* Do not send requests for device information while
+     * an authentication is still running, this seems to
+     * confuse some devices
+     */
+    if (!DEVICE_AUTH_RUNNING && (device.accinfo == ACCST_DATA))
+    {
+        int first_set;
+
+        /* Find the first bit set in the capabilities field,
+         * ignoring those we already asked for
+         */
+        first_set = find_first_set_bit(device.capabilities & \
(~device.capabilities_queried)); +
+        if (first_set != 32)
+        {
+            /* Add bit to queried cababilities */
+            device.capabilities_queried |= BIT_N(first_set);
+
+            switch (first_set)
+            {
+                /* Name */
+                case 0x01:
+                /* Firmware version */
+                case 0x04:
+                /* Hardware version */
+                case 0x05:
+                /* Manufacturer */
+                case 0x06:
+                /* Model number */
+                case 0x07:
+                /* Serial number */
+                case 0x08:
+                /* Maximum payload size */
+                case 0x09:
+                {
+                    IAP_TX_INIT(0x00, 0x27);
+                    IAP_TX_PUT(first_set);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* Minimum supported iPod firmware version */
+                case 0x02:
+                {
+                    IAP_TX_INIT(0x00, 0x27);
+                    IAP_TX_PUT(2);
+                    IAP_TX_PUT_U32(IAP_IPOD_MODEL);
+                    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;
+                }
+
+                /* Minimum supported lingo version. Queries Lingo 0 */
+                case 0x03:
+                {
+                    IAP_TX_INIT(0x00, 0x27);
+                    IAP_TX_PUT(3);
+                    IAP_TX_PUT(0);
+
+                    iap_send_tx();
+                    break;
+                }
+            }
+
+            device.accinfo = ACCST_SENT;
+        }
+    }
+
+    if (!device.do_notify) return;
+    if (device.notifications == 0) return;
+
+    /* Volume change notifications are sent every 100ms */
+    if (device.notifications & (BIT_N(4) | BIT_N(16))) {
+        /* Currently we do not track volume changes, so this is
+         * never sent.
+         *
+         * TODO: Fix volume tracking
+         */
+    }
+
+    /* All other events are sent every 500ms */
+    count += 1;
+    if (count < 5) return;
+
+    count = 0;
+
+    /* RemoteEventNotification */
+
+    /* Mode 04 PlayStatusChangeNotification */
+    /* Are we in Extended Mode */
+    if (interface_state == IST_EXTENDED) {
+        /* Return Track Position */
+        struct mp3entry *id3 = audio_current_track();
+        unsigned long time_elapsed = id3->elapsed;
+        IAP_TX_INIT4(0x04, 0x0027);
+        IAP_TX_PUT(0x04);
+        IAP_TX_PUT_U32(time_elapsed);
+
+        iap_send_tx();
+    }
+
+    /* Track position (ms)  or Track position (s) */
+    if (device.notifications & (BIT_N(0) | BIT_N(15)))
+    {
+        uint32_t t;
+        uint16_t ts;
+        bool changed;
+
+        t = iap_get_trackpos();
+        ts = (t / 1000) & 0xFFFF;
+
+        if ((device.notifications & BIT_N(0)) && (device.trackpos_ms != t))
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x00);
+            IAP_TX_PUT_U32(t);
+            device.changed_notifications |= BIT_N(0);
+            changed = true;
+
+            iap_send_tx();
+        }
+
+        if ((device.notifications & BIT_N(15)) && (device.trackpos_s != ts)) {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x0F);
+            IAP_TX_PUT_U16(ts);
+            device.changed_notifications |= BIT_N(15);
+            changed = true;
+
+            iap_send_tx();
+        }
+
+        if (changed)
+        {
+            device.trackpos_ms = t;
+            device.trackpos_s = ts;
+        }
+    }
+
+    /* Track index */
+    if (device.notifications & BIT_N(1))
+    {
+        uint32_t index;
+
+        index = iap_get_trackindex();
+
+        if (device.track_index != index) {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x01);
+            IAP_TX_PUT_U32(index);
+            device.changed_notifications |= BIT_N(1);
+
+            iap_send_tx();
+
+            device.track_index = index;
+        }
+    }
+
+    /* Chapter index */
+    if (device.notifications & BIT_N(2))
+    {
+        uint32_t index;
+
+        index = iap_get_trackindex();
+
+        if (device.track_index != index)
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x02);
+            IAP_TX_PUT_U32(index);
+            IAP_TX_PUT_U16(0);
+            IAP_TX_PUT_U16(0xFFFF);
+            device.changed_notifications |= BIT_N(2);
+
+            iap_send_tx();
+
+            device.track_index = index;
+        }
+    }
+
+    /* Play status */
+    if (device.notifications & BIT_N(3))
+    {
+        unsigned char play_status;
+
+        play_status = audio_status();
+
+        if (device.play_status != play_status)
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x03);
+            if (play_status & AUDIO_STATUS_PLAY) {
+                /* Playing or paused */
+                if (play_status & AUDIO_STATUS_PAUSE) {
+                    /* Paused */
+                    IAP_TX_PUT(0x02);
+                } else {
+                    /* Playing */
+                    IAP_TX_PUT(0x01);
+                }
+            } else {
+                IAP_TX_PUT(0x00);
+            }
+            device.changed_notifications |= BIT_N(3);
+
+            iap_send_tx();
+
+            device.play_status = play_status;
+        }
+    }
+
+    /* Power/Battery */
+    if (device.notifications & BIT_N(5))
+    {
+        unsigned char power_state;
+        unsigned char battery_l;
+
+        power_state = charger_input_state;
+        battery_l = battery_level();
+
+        if ((device.power_state != power_state) || (device.battery_level != \
battery_l)) +        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x05);
+
+            iap_fill_power_state();
+            device.changed_notifications |= BIT_N(5);
+
+            iap_send_tx();
+
+            device.power_state = power_state;
+            device.battery_level = battery_l;
+        }
+    }
+
+    /* Equalizer state
+     * This is not handled yet.
+     *
+     * TODO: Fix equalizer handling
+     */
+
+    /* Shuffle */
+    if (device.notifications & BIT_N(7))
+    {
+        unsigned char shuffle;
+
+        shuffle = global_settings.playlist_shuffle;
+
+        if (device.shuffle != shuffle)
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x07);
+            IAP_TX_PUT(shuffle?0x01:0x00);
+            device.changed_notifications |= BIT_N(7);
+
+            iap_send_tx();
+
+            device.shuffle = shuffle;
+        }
+    }
+
+    /* Repeat */
+    if (device.notifications & BIT_N(8))
+    {
+        unsigned char repeat;
+
+        repeat = global_settings.repeat_mode;
+
+        if (device.repeat != repeat)
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x08);
+            switch (repeat)
+            {
+                case REPEAT_OFF:
+                {
+                    IAP_TX_PUT(0x00);
+                    break;
+                }
+
+                case REPEAT_ONE:
+                {
+                    IAP_TX_PUT(0x01);
+                    break;
+                }
+
+                case REPEAT_ALL:
+                {
+                    IAP_TX_PUT(0x02);
+                    break;
+                }
+            }
+            device.changed_notifications |= BIT_N(8);
+
+            iap_send_tx();
+
+            device.repeat = repeat;
+        }
+    }
+
+    /* Date/Time */
+    if (device.notifications & BIT_N(9))
+    {
+        struct tm* tm;
+
+        tm = get_time();
+
+        if (memcmp(tm, &(device.datetime), sizeof(struct tm)))
+        {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x09);
+            IAP_TX_PUT_U16(tm->tm_year);
+
+            /* Month */
+            IAP_TX_PUT(tm->tm_mon+1);
+
+            /* Day */
+            IAP_TX_PUT(tm->tm_mday);
+
+            /* Hour */
+            IAP_TX_PUT(tm->tm_hour);
+
+            /* Minute */
+            IAP_TX_PUT(tm->tm_min);
+
+            device.changed_notifications |= BIT_N(9);
+
+            iap_send_tx();
+
+            memcpy(&(device.datetime), tm, sizeof(struct tm));
+        }
+    }
+
+    /* Alarm
+     * This is not supported yet.
+     *
+     * TODO: Fix alarm handling
+     */
+
+    /* Backlight
+     * This is not supported yet.
+     *
+     * TODO: Fix backlight handling
+     */
+
+    /* Hold switch */
+    if (device.notifications & BIT_N(0x0C))
+    {
+        unsigned char hold;
+
+        hold = button_hold();
+        if (device.hold != hold) {
+            IAP_TX_INIT(0x03, 0x09);
+            IAP_TX_PUT(0x0C);
+            IAP_TX_PUT(hold?0x01:0x00);
+
+            device.changed_notifications |= BIT_N(0x0C);
+
+            iap_send_tx();
+
+            device.hold = hold;
+        }
+    }
+
+    /* Sound check
+     * This is not supported yet.
+     *
+     * TODO: Fix sound check handling
+     */
+
+    /* Audiobook check
+     * This is not supported yet.
+     *
+     * TODO: Fix audiobook handling
+     */
+}
+
+/* Change the current interface state.
+ * On a change from IST_EXTENDED to IST_STANDARD, or from IST_STANDARD
+ * to IST_EXTENDED, pause playback, if playing
+ */
+void iap_interface_state_change(const enum interface_state new)
+{
+    if (((interface_state == IST_EXTENDED) && (new == IST_STANDARD)) ||
+        ((interface_state == IST_STANDARD) && (new == IST_EXTENDED))) {
+        if (audio_status() == AUDIO_STATUS_PLAY)
+        {
+            REMOTE_BUTTON(BUTTON_RC_PLAY);
+        }
+    }
+
+    interface_state = new;
+}
+
+static void iap_handlepkt_mode5(const unsigned int len, const unsigned char *buf)
+{
+    (void) len;
+    unsigned int cmd = buf[1];
+    switch (cmd)
+    {
+        /* Sent from iPod Begin Transmission */
+        case 0x02:
+        {
+            /* RF Transmitter: Begin High Power transmission */
+            unsigned char data0[] = {0x05, 0x02};
+            iap_send_pkt(data0, sizeof(data0));
+            break;
+        }
+
+        /* Sent from iPod End High Power Transmission */
+        case 0x03:
+        {
+            /* RF Transmitter: End High Power transmission */
+            unsigned char data1[] = {0x05, 0x03};
+            iap_send_pkt(data1, sizeof(data1));
+            break;
+        }
+        /* Return Version Number ?*/
+        case 0x04:
+        {
+            /* do nothing */
+            break;
+        }
+    }
+}
+
+#if 0
+static void iap_handlepkt_mode7(const unsigned int len, const unsigned char *buf)
+{
+    unsigned int cmd = buf[1];
+    switch (cmd)
+    {
+        /* RetTunerCaps */
+        case 0x02:
+        {
+            /* do nothing */
+
+            /* GetAccessoryInfo */
+            unsigned char data[] = {0x00, 0x27, 0x00};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+
+        /* RetTunerFreq */
+        case 0x0A:
+            /* fall through */
+        /* TunerSeekDone */
+        case 0x13:
+        {
+            rmt_tuner_freq(len, buf);
+            break;
+        }
+
+        /* RdsReadyNotify, RDS station name 0x21 1E 00 + ASCII text*/
+        case 0x21:
+        {
+            rmt_tuner_rds_data(len, buf);
+            break;
+        }
+    }
+}
+#endif
+
+void iap_handlepkt(void)
+{
+    int level;
+    int length;
+
+    if(!iap_setupflag) return;
+
+    /* if we are waiting for a remote button to go out,
+       delay the handling of the new packet */
+    if(iap_repeatbtn)
+    {
+        queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
+        sleep(1);
+        return;
+    }
+
+    /* handle command by mode */
+    length = get_u16(iap_rxstart);
+#ifdef LOGF_ENABLE
+    logf("R: %s", hexstring(iap_rxstart+2, (length)));
+#endif
+
+    unsigned char mode = *(iap_rxstart+2);
+    switch (mode) {
+    case 0: iap_handlepkt_mode0(length, iap_rxstart+2); break;
+    case 2: iap_handlepkt_mode2(length, iap_rxstart+2); break;
+    case 3: iap_handlepkt_mode3(length, iap_rxstart+2); break;
+    case 4: iap_handlepkt_mode4(length, iap_rxstart+2); break;
+    case 5: iap_handlepkt_mode5(length, iap_rxstart+2); break;
+    /* case 7: iap_handlepkt_mode7(length, iap_rxstart+2); break; */
+    }
+
+    /* Remove the handled packet from the RX buffer
+     * This needs to be done with interrupts disabled, to make
+     * sure the buffer and the pointers into it are handled
+     * cleanly
+     */
+    level = disable_irq_save();
+    memmove(iap_rxstart, iap_rxstart+(length+2), (RX_BUFLEN+2)-(length+2));
+    iap_rxnext -= (length+2);
+    iap_rxpayload -= (length+2);
+    iap_rxlen += (length+2);
+    restore_irq(level);
+
+    /* poke the poweroff timer */
+    reset_poweroff_timer();
+}
+
+int remote_control_rx(void)
+{
+    int btn = iap_remotebtn;
+    if(iap_repeatbtn)
+        iap_repeatbtn--;
+
+    return btn;
+}
+
+const unsigned char *iap_get_serbuf(void)
+{
+    return iap_rxstart;
+}
+
+#ifdef IAP_MALLOC_DYNAMIC
+static int iap_move_callback(int handle, void* current, void* new)
+{
+    (void) handle;
+    (void) current;
+
+    iap_txstart = new;
+    iap_txpayload = iap_txstart+5;
+    iap_txnext = iap_txpayload;
+    iap_rxstart = iap_buffers+(TX_BUFLEN+6);
+
+    return BUFLIB_CB_OK;
+}
+#endif
+
+/* Change the shuffle state */
+void iap_shuffle_state(const bool state)
+{
+    /* Set shuffle to enabled */
+    if(state && !global_settings.playlist_shuffle)
+    {
+        global_settings.playlist_shuffle = 1;
+        settings_save();
+        if (audio_status() & AUDIO_STATUS_PLAY)
+            playlist_randomise(NULL, current_tick, true);
+    }
+    /* Set shuffle to disabled */
+    else if(!state && global_settings.playlist_shuffle)
+    {
+        global_settings.playlist_shuffle = 0;
+        settings_save();
+        if (audio_status() & AUDIO_STATUS_PLAY)
+            playlist_sort(NULL, true);
+    }
+}
+
+/* Change the repeat state */
+void iap_repeat_state(const unsigned char state)
+{
+    if (state != global_settings.repeat_mode)
+    {
+        global_settings.repeat_mode = state;
+        settings_save();
+        if (audio_status() & AUDIO_STATUS_PLAY)
+            audio_flush_and_reload_tracks();
+    }
+}
+
+void iap_repeat_next(void)
+{
+    switch (global_settings.repeat_mode)
+    {
+        case REPEAT_OFF:
+        {
+            iap_repeat_state(REPEAT_ALL);
+            break;
+        }
+        case REPEAT_ALL:
+        {
+            iap_repeat_state(REPEAT_ONE);
+            break;
+        }
+        case REPEAT_ONE:
+        {
+            iap_repeat_state(REPEAT_OFF);
+            break;
+        }
+    }
+}
+
+/* This function puts the current power/battery state
+ * into the TX buffer. The buffer is assumed to be initialized
+ */
+void iap_fill_power_state(void)
+{
+    unsigned char power_state;
+    unsigned char battery_l;
+
+    power_state = charger_input_state;
+    battery_l = battery_level();
+
+    if (power_state == NO_CHARGER) {
+        if (battery_l < 30) {
+            IAP_TX_PUT(0x00);
+        } else {
+            IAP_TX_PUT(0x01);
+        }
+        IAP_TX_PUT((char)((battery_l * 255)/100));
+    } else {
+        IAP_TX_PUT(0x04);
+        IAP_TX_PUT(0x00);
+    }
+}
diff --git a/apps/iap/iap-core.h b/apps/iap/iap-core.h
new file mode 100644
index 0000000..d06e3c3
--- /dev/null
+++ b/apps/iap/iap-core.h
@@ -0,0 +1,250 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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.
+ *
+ ****************************************************************************/
+#ifndef _IAP_CORE_H
+#define _IAP_CORE_H
+
+#include <stdint.h>
+#include <string.h>
+#include "timefuncs.h"
+#include "metadata.h"
+#include "playlist.h"
+#include "iap.h"
+
+#define LOGF_ENABLE
+/* #undef LOGF_ENABLE */
+#ifdef LOGF_ENABLE
+  #include "logf.h"
+#endif
+
+/* The Model ID of the iPod we emulate. Currently a 160GB classic */
+#define IAP_IPOD_MODEL (0x00130200U)
+
+/* The firmware version we emulate. Currently 2.0.3 */
+#define IAP_IPOD_FIRMWARE_MAJOR (2)
+#define IAP_IPOD_FIRMWARE_MINOR (0)
+#define IAP_IPOD_FIRMWARE_REV   (3)
+
+/* Status code for IAP ack messages */
+#define IAP_ACK_OK          (0x00)  /* Success */
+#define IAP_ACK_UNKNOWN_DB  (0x01)  /* Unknown Database Category */
+#define IAP_ACK_CMD_FAILED  (0x02)  /* Command failed */
+#define IAP_ACK_NO_RESOURCE (0x03)  /* Out of resources */
+#define IAP_ACK_BAD_PARAM   (0x04)  /* Bad parameter */
+#define IAP_ACK_UNKNOWN_ID  (0x05)  /* Unknown ID */
+#define IAP_ACK_PENDING     (0x06)  /* Command pending */
+#define IAP_ACK_NO_AUTHEN   (0x07)  /* Not authenticated */
+#define IAP_ACK_BAD_AUTHEN  (0x08)  /* Bad authentication version */
+/* 0x09 reserved */
+#define IAP_ACK_CERT_INVAL  (0x0A)  /* Certificate invalid */
+#define IAP_ACK_CERT_PERM   (0x0B)  /* Certificate permissions invalid */
+/* 0x0C-0x10 reserved */
+#define IAP_ACK_RES_INVAL   (0x11)  /* Invalid accessory resistor value */
+
+/* Add a button to the remote button bitfield. Also set iap_repeatbtn=1
+ * to ensure a button press is at least delivered once.
+ */
+#define REMOTE_BUTTON(x) do { \
+        iap_remotebtn |= (x); \
+        iap_timeoutbtn = 3; \
+        iap_repeatbtn = 2; \
+        } while(0)
+
+/* States of the extended command support */
+enum interface_state {
+    IST_STANDARD,    /* General state, support lingo 0x00 commands */
+    IST_EXTENDED,   /* Extended Interface lingo (0x04) negotiated */
+};
+
+/* States of the authentication state machine */
+enum authen_state {
+    AUST_NONE,      /* Initial state, no message sent */
+    AUST_INIT,      /* Remote side has requested authentication */
+    AUST_CERTREQ,   /* Remote certificate requested */
+    AUST_CERTBEG,   /* Certificate is being received */
+    AUST_CERTDONE,  /* Certificate received */
+    AUST_CHASENT,   /* Challenge sent */
+    AUST_CHADONE,   /* Challenge response received */
+    AUST_AUTH,      /* Authentication complete */
+};
+
+/* State of authentication */
+struct auth_t {
+    enum authen_state state;        /* Current state of authentication */
+    unsigned char max_section;      /* The maximum number of certificate sections */
+    unsigned char next_section;     /* The next expected section number */
+};
+
+/* State of GetAccessoryInfo */
+enum accinfo_state {
+    ACCST_NONE,     /* Initial state, no message sent */
+    ACCST_INIT,     /* Send out initial GetAccessoryInfo */
+    ACCST_SENT,     /* Wait for initial RetAccessoryInfo */
+    ACCST_DATA,     /* Query device information, according to capabilities */
+};
+
+/* A struct describing an attached device and it's current
+ * state
+ */
+struct device_t {
+    struct auth_t auth;             /* Authentication state */
+    enum accinfo_state accinfo;     /* Accessory information state */
+    uint32_t lingoes;               /* Negotiated lingoes */
+    uint32_t notifications;         /* Requested notifications. These are just the
+                                     * notifications explicitly requested by the
+                                     * device
+                                     */
+    uint32_t changed_notifications; /* Tracks notifications that changed since the \
last +                                     * call to SetRemoteEventNotification or \
GetRemoteEventStatus +                                     */
+    bool do_notify;                 /* Notifications enabled */
+    bool do_power_notify;           /* Whether to send power change notifications.
+                                     * These are sent automatically to all devices
+                                     * that used IdentifyDeviceLingoes to identify
+                                     * themselves, independent of other \
notifications +                                     */
+
+    uint32_t trackpos_ms;           /* These fields are to save the current state */
+    uint32_t track_index;           /* of various fields so we can send a \
notification */ +    uint32_t chapter_index;         /* if they change */
+    unsigned char play_status;
+    bool mute;
+    unsigned char volume;
+    unsigned char power_state;
+    unsigned char battery_level;
+    uint32_t equalizer_index;
+    unsigned char shuffle;
+    unsigned char repeat;
+    struct tm datetime;
+    unsigned char alarm_state;
+    unsigned char alarm_hour;
+    unsigned char alarm_minute;
+    unsigned char backlight;
+    bool hold;
+    unsigned char soundcheck;
+    unsigned char audiobook;
+    uint16_t trackpos_s;
+    uint32_t capabilities;          /* Capabilities of the device, as returned by \
type 0 +                                     * of GetAccessoryInfo
+                                     */
+    uint32_t capabilities_queried;  /* Capabilities already queried */
+};
+
+extern struct device_t device;
+#define DEVICE_AUTHENTICATED (device.auth.state == AUST_AUTH)
+#define DEVICE_AUTH_RUNNING ((device.auth.state != AUST_NONE) && (device.auth.state \
!= AUST_AUTH)) +#define DEVICE_LINGO_SUPPORTED(x) (device.lingoes & BIT_N((x)&0x1f))
+
+extern unsigned long iap_remotebtn;
+extern unsigned int iap_timeoutbtn;
+extern int iap_repeatbtn;
+
+extern unsigned char* iap_txpayload;
+extern unsigned char* iap_txnext;
+
+/* These are a number of helper macros to manage the dynamic TX buffer content
+ * These macros DO NOT CHECK for buffer overflow. iap_send_tx() will, but
+ * it might be too late at that point. See the current size of TX_BUFLEN
+ */
+
+/* Initialize the TX buffer with a lingo and command ID. This will reset the
+ * data pointer, effectively invalidating unsent information in the TX buffer.
+ * There are two versions of this, one for 1 byte command IDs (all Lingoes except
+ * 0x04) and one for two byte command IDs (Lingo 0x04)
+ */
+#define IAP_TX_INIT(lingo, command) do { \
+        iap_txnext = iap_txpayload; \
+        IAP_TX_PUT((lingo)); \
+        IAP_TX_PUT((command)); \
+        } while (0)
+
+#define IAP_TX_INIT4(lingo, command) do { \
+        iap_txnext = iap_txpayload; \
+        IAP_TX_PUT((lingo)); \
+        IAP_TX_PUT_U16((command)); \
+        } while (0)
+
+/* Put an unsigned char into the TX buffer */
+#define IAP_TX_PUT(data) *(iap_txnext++) = (data)
+
+/* Put a 16bit unsigned quantity into the TX buffer */
+#define IAP_TX_PUT_U16(data) do { \
+        put_u16(iap_txnext, (data)); \
+        iap_txnext += 2; \
+        } while (0)
+
+/* Put a 32bit unsigned quantity into the TX buffer */
+#define IAP_TX_PUT_U32(data) do { \
+        put_u32(iap_txnext, (data)); \
+        iap_txnext += 4; \
+        } while (0)
+
+/* Put an arbitrary amount of data (identified by a char pointer and
+ * a length) into the TX buffer
+ */
+#define IAP_TX_PUT_DATA(data, len) do { \
+        memcpy(iap_txnext, (unsigned char *)(data), (len)); \
+        iap_txnext += (len); \
+        } while(0)
+
+/* Put a NULL terminated string into the TX buffer, including the
+ * NULL byte
+ */
+#define IAP_TX_PUT_STRING(str) IAP_TX_PUT_DATA((str), strlen((str))+1)
+
+/* Put a NULL terminated string into the TX buffer, taking care not to
+ * overflow the buffer. If the string does not fit into the TX buffer
+ * it will be truncated, but always NULL terminated.
+ *
+ * This function is expensive compared to the other IAP_TX_PUT_*
+ * functions
+ */
+#define IAP_TX_PUT_STRLCPY(str) iap_tx_strlcpy(str)
+
+extern unsigned char lingo_versions[32][2];
+#define LINGO_SUPPORTED(x) (LINGO_MAJOR((x)&0x1f) > 0)
+#define LINGO_MAJOR(x) (lingo_versions[(x)&0x1f][0])
+#define LINGO_MINOR(x) (lingo_versions[(x)&0x1f][1])
+
+void put_u16(unsigned char *buf, const uint16_t data);
+void put_u32(unsigned char *buf, const uint32_t data);
+uint32_t get_u32(const unsigned char *buf);
+uint16_t get_u16(const unsigned char *buf);
+void iap_tx_strlcpy(const unsigned char *str);
+
+void iap_reset_auth(struct auth_t* auth);
+void iap_reset_device(struct device_t* device);
+
+void iap_shuffle_state(bool state);
+void iap_repeat_state(unsigned char state);
+void iap_repeat_next(void);
+void iap_fill_power_state(void);
+
+void iap_send_tx(void);
+
+extern enum interface_state interface_state;
+void iap_interface_state_change(const enum interface_state new);
+
+extern bool iap_btnrepeat;
+extern bool iap_btnshuffle;
+
+uint32_t iap_get_trackpos(void);
+uint32_t iap_get_trackindex(void);
+void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3);
+
+#endif
diff --git a/apps/iap/iap-lingo.h b/apps/iap/iap-lingo.h
new file mode 100644
index 0000000..0c0a9e6
--- /dev/null
+++ b/apps/iap/iap-lingo.h
@@ -0,0 +1,23 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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.
+ *
+ ****************************************************************************/
+
+void iap_handlepkt_mode0(const unsigned int len, const unsigned char *buf);
+void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf);
+void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf);
+void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf);
diff --git a/apps/iap/iap-lingo0.c b/apps/iap/iap-lingo0.c
new file mode 100644
index 0000000..9e0355c
--- /dev/null
+++ b/apps/iap/iap-lingo0.c
@@ -0,0 +1,1035 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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;
+            }
+
+            /* We only support version 2.0 */
+            if ((buf[2] != 2) || (buf[3] != 0)) {
+                /* 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;
+        }
+    }
+}
diff --git a/apps/iap/iap-lingo2.c b/apps/iap/iap-lingo2.c
new file mode 100644
index 0000000..4fbf730
--- /dev/null
+++ b/apps/iap/iap-lingo2.c
@@ -0,0 +1,278 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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.
+ *
+ ****************************************************************************/
+
+/* Lingo 0x02, Simple Remote Lingo
+ *
+ * TODO:
+ * - Fix cmd 0x00 handling, there has to be a more elegant way of doing
+ *   this
+ */
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "system.h"
+#include "button.h"
+#include "audio.h"
+#include "settings.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)
+
+static void cmd_ack(const unsigned char cmd, const unsigned char status)
+{
+    IAP_TX_INIT(0x02, 0x01);
+    IAP_TX_PUT(status);
+    IAP_TX_PUT(cmd);
+
+    iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+void iap_handlepkt_mode2(const unsigned int len, const unsigned char *buf)
+{
+    unsigned int cmd = buf[1];
+
+    /* We expect at least three bytes in the buffer, one for the
+     * lingo, one for the command, and one for the first button
+     * state bits.
+     */
+    CHECKLEN(3);
+
+    /* Lingo 0x02 must have been negotiated */
+    if (!DEVICE_LINGO_SUPPORTED(0x02)) {
+        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+        return;
+    }
+
+    switch (cmd)
+    {
+        /* ContextButtonStatus (0x00)
+         *
+         * Transmit button events from the device to the iPod
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+         * 0x01: Command, always 0x00
+         * 0x02: Button states 0:7
+         * 0x03: Button states 8:15 (optional)
+         * 0x04: Button states 16:23 (optional)
+         * 0x05: Button states 24:31 (optional)
+         *
+         * Returns: (none)
+         */
+        case 0x00:
+        {
+            iap_remotebtn = BUTTON_NONE;
+            iap_timeoutbtn = 0;
+
+            if(buf[2] != 0)
+            {
+                if(buf[2] & 1)
+                    REMOTE_BUTTON(BUTTON_RC_PLAY);
+                if(buf[2] & 2)
+                    REMOTE_BUTTON(BUTTON_RC_VOL_UP);
+                if(buf[2] & 4)
+                    REMOTE_BUTTON(BUTTON_RC_VOL_DOWN);
+                if(buf[2] & 8)
+                    REMOTE_BUTTON(BUTTON_RC_RIGHT);
+                if(buf[2] & 16)
+                    REMOTE_BUTTON(BUTTON_RC_LEFT);
+            }
+            else if(len >= 4 && buf[3] != 0)
+            {
+                if(buf[3] & 1) /* play */
+                {
+                    if (audio_status() != AUDIO_STATUS_PLAY)
+                        REMOTE_BUTTON(BUTTON_RC_PLAY);
+                }
+                if(buf[3] & 2) /* pause */
+                {
+                    if (audio_status() == AUDIO_STATUS_PLAY)
+                        REMOTE_BUTTON(BUTTON_RC_PLAY);
+                }
+                if(buf[3] & 128) /* Shuffle */
+                {
+                    if (!iap_btnshuffle)
+                    {
+                        iap_shuffle_state(!global_settings.playlist_shuffle);
+                        iap_btnshuffle = true;
+                    }
+                }
+            }
+            else if(len >= 5 && buf[4] != 0)
+            {
+                if(buf[4] & 1) /* repeat */
+                {
+                    if (!iap_btnrepeat)
+                    {
+                        iap_repeat_next();
+                        iap_btnrepeat = true;
+                    }
+                }
+
+                /* Power off
+                 * Not quite sure how to react to this, but stopping playback
+                 * is a good start.
+                 */
+                if (buf[4] & 0x04)
+                {
+                    if (audio_status() == AUDIO_STATUS_PLAY)
+                        REMOTE_BUTTON(BUTTON_RC_PLAY);
+                }
+
+                if(buf[4] & 16) /* ffwd */
+                    REMOTE_BUTTON(BUTTON_RC_RIGHT);
+                if(buf[4] & 32) /* frwd */
+                    REMOTE_BUTTON(BUTTON_RC_LEFT);
+            }
+
+            break;
+        }
+        /* ACK (0x01)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* ImageButtonStatus (0x02)
+         *
+         * Transmit image button events from the device to the iPod
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+         * 0x01: Command, always 0x02
+         * 0x02: Button states 0:7
+         * 0x03: Button states 8:15 (optional)
+         * 0x04: Button states 16:23 (optional)
+         * 0x05: Button states 24:31 (optional)
+         *
+         * This command requires authentication
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_*
+         */
+        case 0x02:
+        {
+            if (!DEVICE_AUTHENTICATED) {
+                cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+                break;
+            }
+
+            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+            break;
+        }
+
+        /* VideoButtonStatus (0x03)
+         *
+         * Transmit video button events from the device to the iPod
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+         * 0x01: Command, always 0x03
+         * 0x02: Button states 0:7
+         * 0x03: Button states 8:15 (optional)
+         * 0x04: Button states 16:23 (optional)
+         * 0x05: Button states 24:31 (optional)
+         *
+         * This command requires authentication
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_*
+         */
+        case 0x03:
+        {
+            if (!DEVICE_AUTHENTICATED) {
+                cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+                break;
+            }
+
+            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+            break;
+        }
+
+        /* AudioButtonStatus (0x04)
+         *
+         * Transmit audio button events from the device to the iPod
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Simple Remote Lingo, always 0x02
+         * 0x01: Command, always 0x04
+         * 0x02: Button states 0:7
+         * 0x03: Button states 8:15 (optional)
+         * 0x04: Button states 16:23 (optional)
+         * 0x05: Button states 24:31 (optional)
+         *
+         * This command requires authentication
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_*
+         */
+        case 0x04:
+        {
+            unsigned char repeatbuf[6];
+
+            if (!DEVICE_AUTHENTICATED) {
+                cmd_ack(cmd, IAP_ACK_NO_AUTHEN);
+                break;
+            }
+
+            /* This is basically the same command as ContextButtonStatus (0x00),
+             * with the difference that it requires authentication and that
+             * it returns an ACK packet to the device.
+             * So just route it through the handler again, with 0x00 as the
+             * command
+             */
+            memcpy(repeatbuf, buf, 6);
+            repeatbuf[1] = 0x00;
+            iap_handlepkt_mode2((len<6)?len:6, repeatbuf);
+
+            cmd_ok(cmd);
+            break;
+        }
+
+        /* The default response is IAP_ACK_BAD_PARAM */
+        default:
+        {
+#ifdef LOGF_ENABLE
+            logf("iap: Unsupported Mode02 Command");
+#else
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+#endif
+            break;
+        }
+    }
+}
diff --git a/apps/iap/iap-lingo3.c b/apps/iap/iap-lingo3.c
new file mode 100644
index 0000000..0ed3df1
--- /dev/null
+++ b/apps/iap/iap-lingo3.c
@@ -0,0 +1,1508 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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.
+ *
+ ****************************************************************************/
+
+/* Lingo 0x03: Display Remote Lingo
+ *
+ * A bit of a hodgepogde of odds and ends.
+ *
+ * Used to control the equalizer in version 1.00 of the Lingo, but later
+ * grew functions to control album art transfer and check the player
+ * status.
+ *
+ * TODO:
+ * - Actually support multiple equalizer profiles, currently only the
+ *   profile 0 (equalizer disabled) is supported
+ */
+
+#include "iap-core.h"
+#include "iap-lingo.h"
+#include "system.h"
+#include "audio.h"
+#include "powermgmt.h"
+#include "settings.h"
+#include "metadata.h"
+#include "playback.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(0x03, 0x00);
+    IAP_TX_PUT(status);
+    IAP_TX_PUT(cmd);
+
+    iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+void iap_handlepkt_mode3(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
+     * state bits.
+     */
+    CHECKLEN(2);
+
+    /* Lingo 0x03 must have been negotiated */
+    if (!DEVICE_LINGO_SUPPORTED(0x03)) {
+        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+        return;
+    }
+
+    switch (cmd)
+    {
+        /* ACK (0x00)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetCurrentEQProfileIndex (0x01)
+         *
+         * Return the index of the current equalizer profile.
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x01
+         *
+         * Returns:
+         * RetCurrentEQProfileIndex
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x02
+         * 0x02-0x05: Index as an unsigned 32bit integer
+         */
+        case 0x01:
+        {
+            IAP_TX_INIT(0x03, 0x02);
+            IAP_TX_PUT_U32(0x00);
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetCurrentEQProfileIndex (0x02)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* SetCurrentEQProfileIndex (0x03)
+         *
+         * Set the active equalizer profile
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x03
+         * 0x02-0x05: Profile index to activate
+         * 0x06: Whether to restore the previous profile on detach
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_CMD_FAILED
+         *
+         * TODO: Figure out return code for invalid index
+         */
+        case 0x03:
+        {
+            uint32_t index;
+
+            CHECKLEN(7);
+
+            index = get_u32(&buf[2]);
+
+            if (index > 0) {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+
+            /* Currently, we just ignore the command and acknowledge it */
+            cmd_ok(cmd);
+            break;
+        }
+
+        /* GetNumEQProfiles (0x04)
+         *
+         * Get the number of available equalizer profiles
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x04
+         *
+         * Returns:
+         * RetNumEQProfiles
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x05
+         * 0x02-0x05: Number as an unsigned 32bit integer
+         */
+        case 0x04:
+        {
+            IAP_TX_INIT(0x03, 0x05);
+            /* Return one profile (0, the disabled profile) */
+            IAP_TX_PUT_U32(0x01);
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetNumEQProfiles (0x05)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetIndexedEQProfileName (0x06)
+         *
+         * Return the name of the indexed equalizer profile
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x06
+         * 0x02-0x05: Profile index to get the name of
+         *
+         * Returns on success:
+         * RetIndexedEQProfileName
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x06
+         * 0x02-0xNN: Name as an UTF-8 null terminated string
+         *
+         * Returns on failure:
+         * IAP_ACK_BAD_PARAM
+         *
+         * TODO: Figure out return code for out of range index
+         */
+        case 0x06:
+        {
+            uint32_t index;
+
+            CHECKLEN(6);
+
+            index = get_u32(&buf[2]);
+
+            if (index > 0) {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            IAP_TX_INIT(0x03, 0x07);
+            IAP_TX_PUT_STRING("Default");
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetIndexedQUProfileName (0x07)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* SetRemoteEventNotification (0x08)
+         *
+         * Set events the device would like to be notified about
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x08
+         * 0x02-0x05: Event bitmask
+         *
+         * Returns:
+         * IAP_ACK_OK
+         */
+        case 0x08:
+        {
+            struct tm* tm;
+
+            CHECKLEN(6);
+            CHECKAUTH;
+
+            /* Save the current state of the various attributes we track */
+            device.trackpos_ms = iap_get_trackpos();
+            device.track_index = iap_get_trackindex();
+            device.chapter_index = 0;
+            device.play_status = audio_status();
+            /* TODO: Fix this */
+            device.mute = false;
+            device.volume = 0x80;
+            device.power_state = charger_input_state;
+            device.battery_level = battery_level();
+            /* TODO: Fix this */
+            device.equalizer_index = 0;
+            device.shuffle = global_settings.playlist_shuffle;
+            device.repeat = global_settings.repeat_mode;
+            tm = get_time();
+            memcpy(&(device.datetime), tm, sizeof(struct tm));
+            device.alarm_state = 0;
+            device.alarm_hour = 0;
+            device.alarm_minute = 0;
+            /* TODO: Fix this */
+            device.backlight = 0;
+            device.hold = button_hold();
+            device.soundcheck = 0;
+            device.audiobook = 0;
+            device.trackpos_s = (device.trackpos_ms/1000) & 0xFFFF;
+
+            /* Get the notification bits */
+            device.do_notify = false;
+            device.changed_notifications = 0;
+            device.notifications = get_u32(&buf[0x02]);
+            if (device.notifications)
+                device.do_notify = true;
+
+            cmd_ok(cmd);
+            break;
+        }
+
+        /* RemoteEventNotification (0x09)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetRemoteEventStatus (0x0A)
+         *
+         * Request the events changed since the last call to GetREmoteEventStatus
+         * or SetRemoteEventNotification
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0A
+         *
+         * This command requires authentication
+         *
+         * Returns:
+         * RetRemoteEventNotification
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0B
+         * 0x02-0x05: Event status bits
+         */
+        case 0x0A:
+        {
+            CHECKAUTH;
+            IAP_TX_INIT(0x03, 0x0B);
+            IAP_TX_PUT_U32(device.changed_notifications);
+
+            iap_send_tx();
+
+            device.changed_notifications = 0;
+            break;
+        }
+
+        /* RetRemoteEventStatus (0x0B)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetiPodStateInfo (0x0C)
+         *
+         * Request state information from the iPod
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0C
+         * 0x02: Type information
+         *
+         * This command requires authentication
+         *
+         * Returns:
+         * RetiPodStateInfo
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0D
+         * 0x02: Type information
+         * 0x03-0xNN: State information
+         */
+        case 0x0C:
+        {
+            struct mp3entry* id3;
+            struct playlist_info* playlist;
+            int play_status;
+            struct tm* tm;
+
+            CHECKLEN(3);
+            CHECKAUTH;
+
+            IAP_TX_INIT(0x03, 0x0D);
+            IAP_TX_PUT(buf[0x02]);
+
+            switch (buf[0x02])
+            {
+                /* 0x00: Track position
+                 * Data length: 4
+                 */
+                case 0x00:
+                {
+                    id3 = audio_current_track();
+                    IAP_TX_PUT_U32(id3->elapsed);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x01: Track index
+                 * Data length: 4
+                 */
+                case 0x01:
+                {
+                    playlist = playlist_get_current();
+                    IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x02: Chapter information
+                 * Data length: 8
+                 */
+                case 0x02:
+                {
+                    playlist = playlist_get_current();
+                    IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+                    /* Indicate that track does not have chapters */
+                    IAP_TX_PUT_U16(0x0000);
+                    IAP_TX_PUT_U16(0xFFFF);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x03: Play status
+                 * Data length: 1
+                 */
+                case 0x03:
+                {
+                    /* TODO: Handle FF/REW
+                     */
+                    play_status = audio_status();
+                    if (play_status & AUDIO_STATUS_PLAY) {
+                        if (play_status & AUDIO_STATUS_PAUSE) {
+                            IAP_TX_PUT(0x02);
+                        } else {
+                            IAP_TX_PUT(0x01);
+                        }
+                    } else {
+                        IAP_TX_PUT(0x00);
+                    }
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x04: Mute/UI/Volume
+                 * Data length: 2
+                 */
+                case 0x04:
+                {
+                    /* Figuring out what the current volume is
+                     * seems to be tricky.
+                     * TODO: Fix.
+                     */
+
+                    /* Mute status */
+                    IAP_TX_PUT(0x00);
+                    /* Volume */
+                    IAP_TX_PUT(0x80);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x05: Power/Battery
+                 * Data length: 2
+                 */
+                case 0x05:
+                {
+                    iap_fill_power_state();
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x06: Equalizer state
+                 * Data length: 4
+                 */
+                case 0x06:
+                {
+                    /* Currently only one equalizer setting supported, 0 */
+                    IAP_TX_PUT_U32(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x07: Shuffle
+                 * Data length: 1
+                 */
+                case 0x07:
+                {
+                    IAP_TX_PUT(global_settings.playlist_shuffle?0x01:0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x08: Repeat
+                 * Data length: 1
+                 */
+                case 0x08:
+                {
+                    switch (global_settings.repeat_mode)
+                    {
+                        case REPEAT_OFF:
+                        {
+                            IAP_TX_PUT(0x00);
+                            break;
+                        }
+
+                        case REPEAT_ONE:
+                        {
+                            IAP_TX_PUT(0x01);
+                            break;
+                        }
+
+                        case REPEAT_ALL:
+                        {
+                            IAP_TX_PUT(0x02);
+                            break;
+                        }
+                    }
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x09: Data/Time
+                 * Data length: 6
+                 */
+                case 0x09:
+                {
+                    tm = get_time();
+
+                    /* Year */
+                    IAP_TX_PUT_U16(tm->tm_year);
+
+                    /* Month */
+                    IAP_TX_PUT(tm->tm_mon+1);
+
+                    /* Day */
+                    IAP_TX_PUT(tm->tm_mday);
+
+                    /* Hour */
+                    IAP_TX_PUT(tm->tm_hour);
+
+                    /* Minute */
+                    IAP_TX_PUT(tm->tm_min);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0A: Alarm
+                 * Data length: 3
+                 */
+                case 0x0A:
+                {
+                    /* Alarm not supported, always off */
+                    IAP_TX_PUT(0x00);
+                    IAP_TX_PUT(0x00);
+                    IAP_TX_PUT(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0B: Backlight
+                 * Data length: 1
+                 */
+                case 0x0B:
+                {
+                    /* TOOD: Find out how to do this */
+                    IAP_TX_PUT(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0C: Hold switch
+                 * Data length: 1
+                 */
+                case 0x0C:
+                {
+                    IAP_TX_PUT(button_hold()?0x01:0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0D: Sound check
+                 * Data length: 1
+                 */
+                case 0x0D:
+                {
+                    /* TODO: Find out what the hell this is. Default to off */
+                    IAP_TX_PUT(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0E: Audiobook
+                 * Data length: 1
+                 */
+                case 0x0E:
+                {
+                    /* Default to normal */
+                    IAP_TX_PUT(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x0F: Track position in seconds
+                 * Data length: 2
+                 */
+                case 0x0F:
+                {
+                    unsigned int pos;
+
+                    id3 = audio_current_track();
+                    pos = id3->elapsed/1000;
+
+                    IAP_TX_PUT_U16(pos);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x10: Mute/UI/Absolute volume
+                 * Data length: 3
+                 */
+                case 0x10:
+                {
+                    /* TODO: See volume above */
+                    IAP_TX_PUT(0x00);
+                    IAP_TX_PUT(0x80);
+                    IAP_TX_PUT(0x80);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                default:
+                {
+                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    break;
+                }
+            }
+            break;
+        }
+
+        /* RetiPodStateInfo (0x0D)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* SetiPodStateInfo (0x0E)
+         *
+         * Set status information to new values
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0E
+         * 0x02: Type of information to change
+         * 0x03-0xNN: New information
+         *
+         * This command requires authentication
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_CMD_FAILED
+         * IAP_ACK_BAD_PARAM
+         */
+        case 0x0E:
+        {
+            CHECKLEN(3);
+            CHECKAUTH;
+            switch (buf[0x02])
+            {
+                /* Track position (ms)
+                 * Data length: 4
+                 */
+                case 0x00:
+                {
+                    uint32_t pos;
+
+                    CHECKLEN(7);
+                    pos = get_u32(&buf[0x03]);
+                    audio_ff_rewind(pos);
+
+                    cmd_ok(cmd);
+                    break;
+                }
+
+                /* Track index
+                 * Data length: 4
+                 */
+                case 0x01:
+                {
+                    uint32_t index;
+
+                    CHECKLEN(7);
+                    index = get_u32(&buf[0x03]);
+                    audio_skip(index-iap_get_trackindex());
+
+                    cmd_ok(cmd);
+                    break;
+                }
+
+                /* Chapter index
+                 * Data length: 2
+                 */
+                case 0x02:
+                {
+                    /* This is not supported */
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Play status
+                 * Data length: 1
+                 */
+                case 0x03:
+                {
+                    CHECKLEN(4);
+                    switch(buf[0x03])
+                    {
+                        case 0x00:
+                        {
+                            audio_stop();
+                            cmd_ok(cmd);
+                            break;
+                        }
+
+                        case 0x01:
+                        {
+                            audio_resume();
+                            cmd_ok(cmd);
+                            break;
+                        }
+
+                        case 0x02:
+                        {
+                            audio_pause();
+                            cmd_ok(cmd);
+                            break;
+                        }
+
+                        default:
+                        {
+                            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                /* Volume/Mute
+                 * Data length: 2
+                 * TODO: Fix this
+                 */
+                case 0x04:
+                {
+                    CHECKLEN(5);
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Equalizer
+                 * Data length: 5
+                 */
+                case 0x06:
+                {
+                    uint32_t index;
+
+                    CHECKLEN(8);
+                    index = get_u32(&buf[0x03]);
+                    if (index == 0) {
+                        cmd_ok(cmd);
+                    } else {
+                        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    }
+                    break;
+                }
+
+                /* Shuffle
+                 * Data length: 2
+                 */
+                case 0x07:
+                {
+                    CHECKLEN(5);
+
+                    switch(buf[0x03])
+                    {
+                        case 0x00:
+                        {
+                            iap_shuffle_state(false);
+                            cmd_ok(cmd);
+                            break;
+                        }
+                        case 0x01:
+                        case 0x02:
+                        {
+                            iap_shuffle_state(true);
+                            cmd_ok(cmd);
+                            break;
+                        }
+
+                        default:
+                        {
+                            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                /* Repeat
+                 * Data length: 2
+                 */
+                case 0x08:
+                {
+                    CHECKLEN(5);
+
+                    switch(buf[0x03])
+                    {
+                        case 0x00:
+                        {
+                            iap_repeat_state(REPEAT_OFF);
+                            cmd_ok(cmd);
+                            break;
+                        }
+                        case 0x01:
+                        {
+                            iap_repeat_state(REPEAT_ONE);
+                            cmd_ok(cmd);
+                            break;
+                        }
+                        case 0x02:
+                        {
+                            iap_repeat_state(REPEAT_ALL);
+                            cmd_ok(cmd);
+                            break;
+                        }
+                        default:
+                        {
+                            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                /* Date/Time
+                 * Data length: 6
+                 */
+                case 0x09:
+                {
+                    CHECKLEN(9);
+
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Alarm
+                 * Data length: 4
+                 */
+                case 0x0A:
+                {
+                    CHECKLEN(7);
+
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Backlight
+                 * Data length: 2
+                 */
+                case 0x0B:
+                {
+                    CHECKLEN(5);
+
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Sound check
+                 * Data length: 2
+                 */
+                case 0x0D:
+                {
+                    CHECKLEN(5);
+
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Audio book speed
+                 * Data length: 1
+                 */
+                case 0x0E:
+                {
+                    CHECKLEN(4);
+
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                /* Track position (s)
+                 * Data length: 2
+                 */
+                case 0x0F:
+                {
+                    uint16_t pos;
+
+                    CHECKLEN(5);
+                    pos = get_u16(&buf[0x03]);
+                    audio_ff_rewind(1000L * pos);
+
+                    cmd_ok(cmd);
+                    break;
+                }
+
+                /* Volume/Mute/Absolute
+                 * Data length: 4
+                 * TODO: Fix this
+                 */
+                case 0x10:
+                {
+                    CHECKLEN(7);
+                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+                    break;
+                }
+
+                default:
+                {
+                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    break;
+                }
+            }
+
+            break;
+        }
+
+        /* GetPlayStatus (0x0F)
+         *
+         * Request the current play status information
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x0F
+         *
+         * This command requires authentication
+         *
+         * Returns:
+         * RetPlayStatus
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x10
+         * 0x02: Play state
+         * 0x03-0x06: Current track index
+         * 0x07-0x0A: Current track length (ms)
+         * 0x0B-0x0E: Current track position (ms)
+         */
+        case 0x0F:
+        {
+            int play_status;
+            struct mp3entry* id3;
+            struct playlist_info* playlist;
+
+            CHECKAUTH;
+
+            IAP_TX_INIT(0x03, 0x10);
+
+            play_status = audio_status();
+
+            if (play_status & AUDIO_STATUS_PLAY) {
+                /* Playing or paused */
+                if (play_status & AUDIO_STATUS_PAUSE) {
+                    /* Paused */
+                    IAP_TX_PUT(0x02);
+                } else {
+                    /* Playing */
+                    IAP_TX_PUT(0x01);
+                }
+                playlist = playlist_get_current();
+                IAP_TX_PUT_U32(playlist->index - playlist->first_index);
+                id3 = audio_current_track();
+                IAP_TX_PUT_U32(id3->length);
+                IAP_TX_PUT_U32(id3->elapsed);
+            } else {
+                /* Stopped, all values are 0x00 */
+                IAP_TX_PUT(0x00);
+                IAP_TX_PUT_U32(0x00);
+                IAP_TX_PUT_U32(0x00);
+                IAP_TX_PUT_U32(0x00);
+            }
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetPlayStatus (0x10)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* SetCurrentPlayingTrack (0x11)
+         *
+         * Set the current playing track
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x11
+         * 0x02-0x05: Index of track to play
+         *
+         * This command requires authentication
+         *
+         * Returns on success:
+         * IAP_ACK_OK
+         *
+         * Returns on failure:
+         * IAP_ACK_BAD_PARAM
+         */
+        case 0x11:
+        {
+            uint32_t index;
+            uint32_t trackcount;
+
+            CHECKAUTH;
+            CHECKLEN(6);
+
+            index = get_u32(&buf[0x02]);
+            trackcount = playlist_amount();
+
+            if (index >= trackcount)
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            audio_skip(index-iap_get_trackindex());
+            cmd_ok(cmd);
+
+            break;
+        }
+
+        /* GetIndexedPlayingTrackInfo (0x12)
+         *
+         * Request information about a given track
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x12
+         * 0x02: Type of information to retrieve
+         * 0x03-0x06: Track index
+         * 0x07-0x08: Chapter index
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetIndexedPlayingTrackInfo
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x13
+         * 0x02: Type of information returned
+         * 0x03-0xNN: Information
+         */
+        case 0x12:
+        {
+            /* NOTE:
+             *
+             * Retrieving the track information from a track which is not
+             * the currently playing track can take a seriously long time,
+             * in the order of several seconds.
+             *
+             * This most certainly violates the IAP spec, but there's no way
+             * around this for now.
+             */
+            uint32_t track_index;
+            struct playlist_track_info track;
+            struct mp3entry id3;
+
+            CHECKLEN(0x09);
+            CHECKAUTH;
+
+            track_index = get_u32(&buf[0x03]);
+            if (-1 == playlist_get_track_info(NULL, track_index, &track)) {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+
+            IAP_TX_INIT(0x03, 0x13);
+            IAP_TX_PUT(buf[2]);
+            switch (buf[2])
+            {
+                /* 0x00: Track caps/info
+                 * Information length: 10 bytes
+                 */
+                case 0x00:
+                {
+                    iap_get_trackinfo(track_index, &id3);
+                    /* Track capabilities. None of these are supported, yet */
+                    IAP_TX_PUT_U32(0x00);
+
+                    /* Track length in ms */
+                    IAP_TX_PUT_U32(id3.length);
+
+                    /* Chapter count, stays at 0 */
+                    IAP_TX_PUT_U16(0x00);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x01: Chapter time/name
+                 * Information length: 4+variable
+                 */
+                case 0x01:
+                {
+                    /* Chapter length, set at 0 (no chapters) */
+                    IAP_TX_PUT_U32(0x00);
+
+                    /* Chapter name, empty */
+                    IAP_TX_PUT_STRING("");
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x02, Artist name
+                 * Information length: variable
+                 */
+                case 0x02:
+                {
+                    /* Artist name */
+                    iap_get_trackinfo(track_index, &id3);
+                    IAP_TX_PUT_STRLCPY(id3.artist);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x03, Album name
+                 * Information length: variable
+                 */
+                case 0x03:
+                {
+                    /* Album name */
+                    iap_get_trackinfo(track_index, &id3);
+                    IAP_TX_PUT_STRLCPY(id3.album);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x04, Genre name
+                 * Information length: variable
+                 */
+                case 0x04:
+                {
+                    /* Genre name */
+                    iap_get_trackinfo(track_index, &id3);
+                    IAP_TX_PUT_STRLCPY(id3.genre_string);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x05, Track title
+                 * Information length: variable
+                 */
+                case 0x05:
+                {
+                    /* Track title */
+                    iap_get_trackinfo(track_index, &id3);
+                    IAP_TX_PUT_STRLCPY(id3.title);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x06, Composer name
+                 * Information length: variable
+                 */
+                case 0x06:
+                {
+                    /* Track Composer */
+                    iap_get_trackinfo(track_index, &id3);
+                    IAP_TX_PUT_STRLCPY(id3.composer);
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x07, Lyrics
+                 * Information length: variable
+                 */
+                case 0x07:
+                {
+                    /* Packet information bits. All 0 (single packet) */
+                    IAP_TX_PUT(0x00);
+
+                    /* Packet index */
+                    IAP_TX_PUT_U16(0x00);
+
+                    /* Lyrics */
+                    IAP_TX_PUT_STRING("");
+
+                    iap_send_tx();
+                    break;
+                }
+
+                /* 0x08, Artwork count
+                 * Information length: variable
+                 */
+                case 0x08:
+                {
+                    /* No artwork, return packet containing just the type byte */
+                    iap_send_tx();
+                    break;
+                }
+
+                default:
+                {
+                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    break;
+                }
+            }
+
+            break;
+        }
+
+        /* RetIndexedPlayingTrackInfo (0x13)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetNumPlayingTracks (0x14)
+         *
+         * Request the number of tracks in the current playlist
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x14
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetNumPlayingTracks
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x15
+         * 0x02-0xNN: Number of tracks
+         */
+        case 0x14:
+        {
+            CHECKAUTH;
+
+            IAP_TX_INIT(0x03, 0x15);
+            IAP_TX_PUT_U32(playlist_amount());
+
+            iap_send_tx();
+        }
+
+        /* RetNumPlayingTracks (0x15)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetArtworkFormats (0x16)
+         *
+         * Request a list of supported artwork formats
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x16
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetArtworkFormats
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x17
+         * 0x02-0xNN: list of 7 byte format descriptors
+         */
+        case 0x16:
+        {
+            CHECKAUTH;
+
+            /* We return the empty list, meaning no artwork
+             * TODO: Fix to return actual artwork formats
+             */
+            IAP_TX_INIT(0x03, 0x17);
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetArtworkFormats (0x17)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetTrackArtworkData (0x18)
+         *
+         * Request artwork for the given track
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x18
+         * 0x02-0x05: Track index
+         * 0x06-0x07: Format ID
+         * 0x08-0x0B: Track offset in ms
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetTrackArtworkData
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x19
+         * 0x02-0x03: Descriptor index
+         * 0x04: Pixel format code
+         * 0x05-0x06: Image width in pixels
+         * 0x07-0x08: Image height in pixels
+         * 0x09-0x0A: Inset rectangle, top left x
+         * 0x0B-0x0C: Inset rectangle, top left y
+         * 0x0D-0x0E: Inset rectangle, bottom right x
+         * 0x0F-0x10: Inset rectangle, bottom right y
+         * 0x11-0x14: Row size in bytes
+         * 0x15-0xNN: Image data
+         *
+         * If the image data does not fit in a single packet, subsequent
+         * packets omit bytes 0x04-0x14.
+         */
+        case 0x18:
+        {
+            CHECKAUTH;
+            CHECKLEN(0x0C);
+
+            /* No artwork support currently */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+
+        /* RetTrackArtworkFormat (0x19)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetPowerBatteryState (0x1A)
+         *
+         * Request the current power state
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1A
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetPowerBatteryState
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1B
+         * 0x02: Power state
+         * 0x03: Battery state
+         */
+        case 0x1A:
+        {
+            IAP_TX_INIT(0x03, 0x1B);
+
+            iap_fill_power_state();
+            iap_send_tx();
+            break;
+        }
+
+        /* RetPowerBatteryState (0x1B)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* GetSoundCheckState (0x1C)
+         *
+         * Request the current sound check state
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1C
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetSoundCheckState
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1D
+         * 0x02: Sound check state
+         */
+        case 0x1C:
+        {
+            CHECKAUTH;
+
+            IAP_TX_INIT(0x03, 0x1D);
+            IAP_TX_PUT(0x00);       /* Always off */
+
+            iap_send_tx();
+            break;
+        }
+
+        /* RetSoundCheckState (0x1D)
+         *
+         * Sent from the iPod to the device
+         */
+
+        /* SetSoundCheckState (0x1E)
+         *
+         * Set the sound check state
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1E
+         * 0x02: Sound check state
+         * 0x03: Restore on exit
+         *
+         * This command requires authentication.
+         *
+         * Returns on success
+         * IAP_ACK_OK
+         *
+         * Returns on failure
+         * IAP_ACK_CMD_FAILED
+         */
+        case 0x1E:
+        {
+            CHECKAUTH;
+            CHECKLEN(4);
+
+            /* Sound check is not supported right now
+             * TODO: Fix
+             */
+
+            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
+            break;
+        }
+
+        /* GetTrackArtworkTimes (0x1F)
+         *
+         * Request a list of timestamps at which artwork exists in a track
+         *
+         * Packet format (offset in buf[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x1F
+         * 0x02-0x05: Track index
+         * 0x06-0x07: Format ID
+         * 0x08-0x09: Artwork Index
+         * 0x0A-0x0B: Artwork count
+         *
+         * This command requires authentication.
+         *
+         * Returns:
+         * RetTrackArtworkTimes
+         *
+         * Packet format (offset in data[]: Description)
+         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
+         * 0x01: Command, always 0x20
+         * 0x02-0x05: Offset in ms
+         *
+         * Bytes 0x02-0x05 can be repeated multiple times
+         */
+        case 0x1F:
+        {
+            uint32_t index;
+            uint32_t trackcount;
+
+            CHECKAUTH;
+            CHECKLEN(0x0C);
+
+            /* Artwork is currently unsuported, just check for a valid
+             * track index
+             */
+            index = get_u32(&buf[0x02]);
+            trackcount = playlist_amount();
+
+            if (index >= trackcount)
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+
+            /* Send an empty list */
+            IAP_TX_INIT(0x03, 0x20);
+
+            iap_send_tx();
+            break;
+        }
+
+        /* The default response is IAP_ACK_BAD_PARAM */
+        default:
+        {
+#ifdef LOGF_ENABLE
+            logf("iap: Unsupported Mode03 Command");
+#else
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+#endif
+            break;
+        }
+    }
+}
diff --git a/apps/iap/iap-lingo4.c b/apps/iap/iap-lingo4.c
new file mode 100644
index 0000000..fa01966
--- /dev/null
+++ b/apps/iap/iap-lingo4.c
@@ -0,0 +1,3153 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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 "dir.h"
+#include "settings.h"
+#include "filetree.h"
+#include "wps.h"
+#include "playback.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)
+
+/* Used to remember the last Type and Record requested */
+static char cur_dbrecord[5] = {0};
+
+/* Used to remember the total number of filtered database records */
+static unsigned long dbrecordcount = 0;
+
+/* Used to remember the LAST playlist selected */
+static unsigned long last_selected_playlist = 0;
+
+static void cmd_ack(const unsigned int cmd, const unsigned char status)
+{
+    IAP_TX_INIT4(0x04, 0x0001);
+    IAP_TX_PUT(status);
+    IAP_TX_PUT_U16(cmd);
+    iap_send_tx();
+}
+
+#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)
+
+static void get_playlist_name(unsigned char *dest,
+                              unsigned long item_offset,
+                              size_t max_length)
+{
+    if (item_offset == 0) return;
+    DIR* dp;
+    struct dirent* playlist_file = NULL;
+
+    dp = opendir(global_settings.playlist_catalog_dir);
+
+    char *extension;
+    unsigned long nbr = 0;
+    while ((nbr < item_offset) && ((playlist_file = readdir(dp)) != NULL))
+    {
+        /*Increment only if there is a playlist extension*/
+        if ((extension=strrchr(playlist_file->d_name, '.')) != NULL){
+            if ((strcmp(extension, ".m3u") == 0 ||
+                 strcmp(extension, ".m3u8") == 0))
+                nbr++;
+        }
+    }
+    if (playlist_file != NULL) {
+        strlcpy(dest, playlist_file->d_name, max_length);
+    }
+    closedir(dp);
+}
+
+static void seek_to_playlist(unsigned long index)
+{
+                    unsigned char selected_playlist
+                    [sizeof(global_settings.playlist_catalog_dir)
+                    + 1
+                    + MAX_PATH] = {0};
+
+                    strcpy(selected_playlist,
+                            global_settings.playlist_catalog_dir);
+                    int len = strlen(selected_playlist);
+                    selected_playlist[len] = '/';
+                    get_playlist_name (selected_playlist + len + 1,
+                                       index,
+                                       MAX_PATH);
+                    ft_play_playlist(selected_playlist,
+                                     global_settings.playlist_catalog_dir,
+                                     strrchr(selected_playlist, '/') + 1);
+
+}
+ 
+static unsigned long nbr_total_playlists(void)
+{
+    DIR* dp;
+    unsigned long nbr_total_playlists = 0;
+    struct dirent* playlist_file = NULL;
+    char *extension;
+    dp = opendir(global_settings.playlist_catalog_dir);
+    while ((playlist_file = readdir(dp)) != NULL)
+    {
+        /*Increment only if there is a playlist extension*/
+        if ((extension=strrchr(playlist_file->d_name, '.')) != NULL)
+        {
+            if ((strcmp(extension, ".m3u") == 0 ||
+                 strcmp(extension, ".m3u8") == 0))
+            {
+                nbr_total_playlists++;
+            }
+        }
+    }
+    closedir(dp);
+    return nbr_total_playlists;
+}
+
+void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf)
+{
+    unsigned int cmd = (buf[1] << 8) | buf[2];
+    /* Lingo 0x04 commands are at least 3 bytes in length */
+    CHECKLEN(3);
+
+    /* Lingo 0x04 must have been negotiated */
+    if (!DEVICE_LINGO_SUPPORTED(0x04)) {
+#ifdef LOGF_ENABLE
+        logf("iap: Mode04 Not Negotiated");
+#endif
+        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+        return;
+    }
+
+    /* All these commands require extended interface mode */
+    if (interface_state != IST_EXTENDED) {
+#ifdef LOGF_ENABLE
+        logf("iap: Not in Mode04 Extended Mode");
+#endif
+        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+        return;
+    }
+    switch (cmd)
+    {
+        case 0x0001: /* CmdAck. See above cmd_ack() */
+            /* 
+             * The following is the description for the Apple Firmware
+             * The iPod sends this telegram to acknowledge the receipt of a
+             * command and return the command status. The command ID field
+             * indicates the device command for which the response is being
+             * sent. The command status indicates the results of the command
+             * (success or failure).
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x06  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x01  Command ID (bits 7:0)
+             *  6   0xNN  Command result status. Possible values are:
+             *            0x00 = Success (OK)
+             *            0x01 = ERROR: Unknown database category
+             *            0x02 = ERROR: Command failed
+             *            0x03 = ERROR: Out of resources
+             *            0x04 = ERROR: Bad parameter
+             *            0x05 = ERROR: Unknown ID
+             *            0x06 = Reserved
+             *            0x07 = Accessory not authenticated
+             *            0x08 - 0xFF = Reserved
+             *  7  0xNN   The ID of the command being acknowledged (bits 15:8).
+             *  8  0xNN   The ID of the command being acknowledged (bits 7:0).
+             *  9  0xNN   Telegram payload checksum byte
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0002: /* GetCurrentPlayingTrackChapterInfo */
+            /* The following is the description for the Apple Firmware
+             * Requests the chapter information of the currently playing track.
+             * In response, the iPod sends a
+             * Command 0x0003: ReturnCurrentPlayingTrackChapterInfo
+             * telegram to the device.
+             * Note: The returned track index is valid only when there is a
+             * currently playing or paused track.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x02  Command ID (bits 7:0)
+             *  6   0xF7  Telegram payload checksum byte
+             *
+             * We Return that the track does not have chapter information by
+             * returning chapter index -1 (0xFFFFFFFF) and chapter count 0
+             * (0x00000000)
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x03,
+                                    0xFF, 0xFF, 0xFF, 0xFF,
+                                    0x00, 0x00, 0x00, 0x00};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0003: /* ReturnCurrentPlayingTrackChapterInfo. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the chapter information of the currently playing track.
+             * The iPod sends this telegramin response to the
+             * Command 0x0002: GetCurrentPlayingTrackChapterInfo
+             * telegram from the device. The track chapter information includes
+             * the currently playingtrack's chapter index,as well as the
+             * total number of chapters in the track. The track chapter and the
+             * total number of chapters are 32-bit signed integers. The chapter
+             * index of the firstchapter is always 0x00000000. If the track does
+             * not have chapter information, a chapter index of -1(0xFFFFFFFF)
+             * and a chapter count of 0 (0x00000000) are returned.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0B  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x03  Command ID (bits 7:0)
+             *  6   0xNN  Current chapter index (bits 31:24)
+             *  7   0xNN  Current chapter index (bits 23:16)
+             *  8   0xNN  Current chapter index (bits 15:8)
+             *  9   0xNN  Current chapter index (bits 7:0)
+             * 10   0xNN  Chapter count (bits 31:24)
+             * 11   0xNN  Chapter count (bits 23:16)
+             * 12   0xNN  Chapter count (bits 15:8)
+             * 13   0xNN  Chapter count (bits 7:0)
+             * 14   0xNN  Telegram payload checksum byte
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0004: /* SetCurrentPlayingTrackChapter */
+            /* The following is the description for the Apple Firmware
+             *
+             * Sets the currently playing track chapter.You can send the Command
+             * 0x0002: GetCurrentPlayingTrackChapterInfo telegram to get the
+             * chapter count and the index of the currently playing chapter in
+             * the current track. In response to the command
+             * SetCurrentPlayingTrackChapter, the iPod sends an ACK telegram
+             * with the command status.
+             *
+             * Note: This command should be used only when the iPod is in a
+             * playing or paused state. The command fails if the iPod is stopped
+             * or if the track does not contain chapter information.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x04  Command ID (bits 7:0)
+             *  6   0xNN  Chapter index (bits 31:24)
+             *  7   0xNN  Chapter index (bits 23:16)
+             *  8   0xNN  Chapter index (bits 15:8)
+             *  9   0xNN  Chapter index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             * We don't do anything with this as we don't support chapters,
+             * so just return BAD_PARAM
+             */
+        {
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0005: /* GetCurrentPlayingTrackChapterPlayStatus */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the chapter playtime status of the currently playing
+             * track. The status includes the chapter length and the time
+             * elapsed within that chapter. In response to a valid telegram, the
+             * iPod sends a Command 0x0006:
+             * ReturnCurrentPlayingTrackChapterPlayStatus telegram to the
+             * device.
+             *
+             * Note: If the telegram length or chapter index is invalid for
+             * instance, if the track does not contain chapter information the
+             * iPod responds with an ACK telegram including the specific error
+             * status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x05  Command ID (bits 7:0)
+             *  6   0xNN  Currently playingchapter index (bits31:24)
+             *  7   0xNN  Currently playingchapter index (bits23:16)
+             *  8   0xNN  Currently playing chapter index (bits 15:8)
+             *  9   0xNN  Currently playing chapter index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             * The returned data includes 4 bytes for Chapter Length followed
+             * by 4 bytes of elapsed time If there is no currently playing
+             * chapter, length and elapsed are 0
+             * We don't do anything with this as we don't support chapters,
+             * so just return BAD_PARAM
+             */
+        {
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0006: /* ReturnCurrentPlayingTrackChapterPlayStatus. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the play status of the currently playing track chapter.
+             * The iPod sends this telegram in response to the Command 0x0005:
+             * GetCurrentPlayingTrackChapterPlayStatus telegram from the device.
+             * The returned information includes the chapter length and elapsed
+             * time, in milliseconds. If there is no currently playing chapter,
+             * the chapter length and elapsed time are zero.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0B  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x06  Command ID (bits 7:0)
+             *  6   0xNN  Chapter length in milliseconds (bits 31:24)
+             *  7   0xNN  Chapter length in milliseconds (bits 23:16)
+             *  8   0xNN  Chapter length in milliseconds (bits 15:8)
+             *  9   0xNN  Chapter length in milliseconds (bits 7:0)
+             * 10   0xNN  Elapsed time in chapter, in milliseconds (bits 31:24)
+             * 11   0xNN  Elapsed time in chapter, in milliseconds (bits 23:16)
+             * 12   0xNN  Elapsed time in chapter, in milliseconds (bits 15:8)
+             * 13   0xNN  Elapsed time in chapter, in milliseconds (bits 7:0)
+             * 14   0xNN  Telegram payload checksum byte
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0007:  /* GetCurrentPlayingTrackChapterName */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests a chapter name in the currently playing track. In
+             * response to a valid telegram, the iPod sends a Command 0x0008:
+             * ReturnCurrentPlayingTrackChapterName telegram to the device.
+             *
+             * Note: If the received telegram length or track index is invalid
+             * for instance, if the track does not have chapter information or
+             * is not a part of the Audiobook category, the iPod responds with
+             * an ACK telegram including the specific error status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x07  Command ID (bits 7:0)
+             *  6   0xNN  Chapter index (bits 31:24)
+             *  7   0xNN  Chapter index (bits 23:16)
+             *  8   0xNN  Chapter index (bits 15:8)
+             *  9   0xNN  Chapter index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             * We don't do anything with this as we don't support chapters,
+             * so just return BAD_PARAM
+             */
+        {
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0008: /* ReturnCurrentPlayingTrackChapterName. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns a chapter name in the currently playing track. The iPod
+             * sends this telegram in response to a valid Command 0x0007:
+             * GetCurrentPlayingTrackChapterName telegram from the
+             * device. The chapter name is encoded as a null-terminated UTF-8
+             * character array.
+             *
+             * Note: The chapter name string is not limited to 252 characters;
+             * it may be sent in small or large telegram format depending on
+             * the string length. The small telegram format is shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x08  Command ID (bits 7:0)
+             *  6-N 0xNN  Chapter name as UTF-8 character array
+             *(last byte) 0xNN Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0009: /* GetAudioBookSpeed */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the current iPod audiobook speed state. The iPod
+             * responds with the “Command 0x000A: ReturnAudiobookSpeed”
+             * telegram indicating the current audiobook speed.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x09  Command ID (bits 7:0)
+             *  6   0xF0  Telegram payload checksum byte
+             *
+             * ReturnAudioBookSpeed
+             * 0x00 = Normal, 0xFF = Slow, 0x01 = Fast
+             * We always respond with Normal speed
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x0A, 0x00};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x000A: /* ReturnAudioBookSpeed. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the current audiobook speed setting. The iPod sends
+             * this telegram in response to the Command 0x0009:
+             * GetAudiobookSpeed command from the device.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0A  Command ID (bits 7:0)
+             *  6   0xNN  Audiobook speed status code.
+             *            0xFF Slow (-1)
+             *            0x00 Normal
+             *            0x01 Fast (+1)
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x000B: /* SetAudioBookSpeed */
+            /* The following is the description for the Apple Firmware
+             * Sets the speed of audiobook playback. The iPod audiobook speed
+             * states are listed above. This telegram has two modes: one to
+             * set the speed of the currently playing audiobook and a second
+             * to set the audiobook speed for all audiobooks. Byte number 7
+             * is an optional byte; devices should not send this byte if they
+             * want to set the speed only of the currently playing audiobook.
+             * If devices want to set the global audiobook speed setting then
+             * they must use byte number 7. This is the Restore on Exit byte;
+             * a nonzero value restores the original audiobook speed setting
+             * when the accessory is detached. If this byte is zero, the
+             * audiobook speed setting set by the accessory is saved and
+             * persists after the accessory is detached from the iPod. See below
+             * for the telegram format used when including the Restore on Exit
+             * byte.
+             * In response to this command, the iPod sends an ACK telegram with
+             * the command status.
+             *
+             * Note: Accessory developers are encouraged to always use the
+             * Restore on Exit byte with a nonzero value, to restore any of the
+             * user's iPod settings that were modified by the accessory.
+             *
+             * Byte Value Meaning (Cuurent audiobook only
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0B  Command ID (bits 7:0)
+             *  6   0xNN  New audiobook speed code.
+             *  7   0xNN Telegram payload checksum byte
+             *
+             *
+             * SetAudiobookSpeed telegram to set the global audiobook speed
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x05  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0B  Command ID (bits 7:0)
+             *  6   0xNN  Global audiobook speed code.
+             *  7   0xNN  Restore on Exit byte.
+             *            If 1, the original setting is restored on detach;
+             *            if 0, the newsetting persists after accessory detach.
+             *  8   0xNN  Telegram payload checksum byte
+             *
+             *
+             * We don't do anything with this as we don't support chapters,
+             * so just return BAD_PARAM
+             */
+        {
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x000C: /* GetIndexedPlayingTrackInfo */
+            /* The following is the description for the Apple Firmware
+             *
+             * Gets track information for the track at the specified index.
+             * The track info type field specifies the type of information to be
+             * returned, such as song lyrics, podcast name, episode date, and
+             * episode description. In response, the iPod sends the Command
+             * 0x000D: ReturnIndexedPlayingTrackInfo command with the requested
+             * track information. If the information type is invalid or does not
+             * apply to the selected track, the iPod returns an ACK with an
+             * error status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0A  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0C  Command ID (bits 7:0)
+             *  6   0xNN  Track info type. See Below
+             *  7   0xNN  Track index (bits 31:24)
+             *  8   0xNN  Track index (bits 23:16)
+             *  9   0xNN  Track index (bits 15:8)
+             * 10   0xNN  Track index (bits 7:0)
+             * 11   0xNN  Chapter index (bits 15:8)
+             * 12   0xNN  Chapter index (bits 7:0)
+             * (last byte)0xNN Telegram payload checksum byte
+             *
+             * Track Info Types: Return Data
+             * 0x00 Track Capabilities. 10byte data
+             * 0x01 Podcast Name        UTF-8 String
+             * 0x02 Track Release Date  7Byte Data All 0 if invalid
+             * 0x03 Track Description   UTF-8 String
+             * 0x04 Track Song Lyrics   UTF-8 String
+             * 0x05 Track Genre         UTF-8 String
+             * 0x06 Track Composer      UTF-8 String
+             * 0x07 Track Artwork Count 2byte formatID and 2byte count of images
+             * 0x08-0xff Reserved
+             *
+             * Track capabilities
+             * 0x00-0x03
+             *      Bit0 is Audiobook
+             *      Bit1 has chapters
+             *      Bit2 has album art
+             *      Bit3 has lyrics
+             *      Bit4 is podcast episode
+             *      Bit5 has Release Date
+             *      Bit6 has Decription
+             *      Bit7 Contains Video
+             *      Bit8 Play as Video
+             *      Bit9+ Reserved
+             * 0x04-0x07 Total Track Length in ms
+             * 0x08-0x09 Chapter Count
+             *
+             * Track Release Date Encoding
+             * 0x00 Seconds 0-59
+             * 0x01 Minutes 0-59
+             * 0x02 Hours 0-23
+             * 0x03 Day Of Month 1-31
+             * 0x04 Month 1-12
+             * 0x05 Year Bits 15:8 2006 = 2006AD
+             * 0x06 Year Bits 7:0
+             * 0x07 Weekday 0-6 where 0=Sunday 6=Saturday
+             *
+             *
+             */
+        {
+            if (len < (10))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            struct mp3entry *id3 = audio_current_track();
+
+            switch(buf[3])
+            {
+                case 0x01: /* Podcast Not Supported */
+                case 0x04: /* Lyrics Not Supported */
+                case 0x03: /* Description */
+                case 0x05: /* Genre */
+                case 0x06: /* Composer */
+                {
+                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    break;
+                }
+                default:
+                {
+                    IAP_TX_INIT4(0x04, 0x000D);
+                    IAP_TX_PUT(buf[3]);
+                    switch(buf[3])
+                    {
+                        case 0x00:
+                        {
+                            /* Track Capabilities 10Bytes Data */
+                            IAP_TX_PUT_U32(0);   /* Track Capabilities. */
+                                                 /* Currently None Supported*/
+                            IAP_TX_PUT_U32(id3->length); /* Track Length */
+                            IAP_TX_PUT_U16(0x00);        /* Chapter Count */
+                            break;
+                        }
+                        case 0x02:
+                        {
+                            /* Track Release Date 7 Bytes Data
+                             * Currently only returns a fixed value,
+                             * Sunday 1st Feb 2011 3Hr 4Min 5Secs
+                             */
+                            IAP_TX_PUT(5);               /* Seconds 0-59 */
+                            IAP_TX_PUT(4);               /* Minutes 0-59 */
+                            IAP_TX_PUT(3);               /* Hours 0-23 */
+                            IAP_TX_PUT(1);               /* Day Of Month 1-31 */
+                            IAP_TX_PUT(2);               /* Month 1-12 */
+                            IAP_TX_PUT_U16(2011);        /* Year */
+                            IAP_TX_PUT(0);               /* Day 0=Sunday */
+                            break;
+                        }
+                         case 0x07:
+                        {
+                            /* Track Artwork Count */
+                            /* Currently not supported */
+                            IAP_TX_PUT_U16(0x00);        /* Format ID */
+                            IAP_TX_PUT_U16(0x00);        /* Image Count */
+                            break;
+                        }
+                    }
+                    iap_send_tx();
+                    break;
+                }
+            }
+            break;
+        }
+        case 0x000D: /* ReturnIndexedPlayingTrackInfo. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the requested track information type and data. The iPod
+             * sends this command in response to the Command 0x000C:
+             * GetIndexedPlayingTrackInfo command.
+             * Data returned as strings are encoded as null-terminated UTF-8
+             * character arrays.
+             * If the track information string does not exist, a null UTF-8
+             * string is returned. If the track has no release date, then the
+             * returned release date has all bytes zeros. Track song lyrics and
+             * the track description are sent in a large or small telegram
+             * format with an incrementing packet index field, spanning
+             * multiple packets if needed.
+             *
+             * Below is the packet format for the
+             * ReturnIndexedPlayingTrackInfo telegram sent in response to a
+             * request for information types 0x00 to 0x02.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0D  Command ID (bits 7:0)
+             *  6   0xNN  Track info type. See
+             *  7-N 0xNN  Track information. The data format is specific
+             *            to the track info type.
+             *  NN  0xNN  Telegram payload checksum byte
+             *
+             * Below is the large packet format for the
+             * ReturnIndexedPlayingTrackInfo telegram sent in response to a
+             * request for information types 0x03 to 0x04. The small telegram
+             * format is not shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte
+             *  1   0x55  Start of telegram
+             *  2   0x00  Telegram payload marker (large format)
+             *  3   0xNN  Large telegram payload length (bits 15:8)
+             *  4   0xNN  Large telegram payload length (bits 7:0)
+             *  5   0x04  Lingo ID (Extended Interface lingo)
+             *  6   0x00  Command ID (bits 15:8)
+             *  7   0x0D  Command ID (bits 7:0)
+             *  8   0xNN  Track info type.
+             *  9   0xNN  Packet information bits. If set,
+             *            these bits have the following meanings:
+             *            Bit 0: Indicates that there are multiple packets.
+             *            Bit 1: This is the last packet. Applicable only if
+             *                   bit 0 is set.
+             *            Bit 31:2 Reserved
+             * 10   0xNN  Packet Index (bits 15:8)
+             * 11   0xNN  Packet Index (bits 7:0)
+             * 12-N 0xNN  Track information as a UTF-8 string.
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             * Track info types and return data
+             * Info Type Code                          Data Format
+             * 0x00 Track Capabilities and Information 10-byte data.
+             * 0x01 PodcastName                        UTF-8 string
+             * 0x02 Track Release Date                 7-byte data.
+             * 0x03 Track Description                  UTF-8 string
+             * 0x04 Track Song Lyrics                  UTF-8 string
+             * 0x05 TrackGenre                         UTF-8 string
+             * 0x06 Track Composer                     UTF-8 string
+             * 0x07 Track Artwork Count                Artwork count data. The
+             *      artwork count is a sequence of 4-byte records; each record
+             *      consists of a 2-byte format ID value followed by a 2-byte
+             *      count of images in that format for this track. For more
+             *      information about formatID and chapter index values, see
+             *      commands 0x000E-0x0011 and 0x002A-0x002B.
+             * 0x08-0xFF Reserved
+             *
+             * Track Capabilities and Information encoding
+             * Byte      Code
+             * 0x00-0x03 Track Capability bits. If set, these bits have the
+             *           following meanings:
+             *           Bit 0: Track is audiobook
+             *           Bit 1: Track has chapters
+             *           Bit 2: Track has album artwork
+             *           Bit 3: Track has song lyrics
+             *           Bit 4: Track is a podcast episode
+             *           Bit 5: Track has release date
+             *           Bit 6: Track has description
+             *           Bit 7: Track contains video (a video podcast, music
+             *                  video, movie, or TV show)
+             *           Bit 8: Track is currently queued to play as a video
+             *           Bit 31:9: Reserved
+             * 0x04      Total track length, in milliseconds (bits 31:24)
+             * 0x05      Total track length, in milliseconds (bits 23:16)
+             * 0x06      Total track length, in milliseconds (bits 15:8)
+             * 0x07      Total track length, in milliseconds (bits 7:0)
+             * 0x08      Chapter count (bits 15:8)
+             * 0x09      Chapter count (bits 7:0)
+             *
+             * Track Release Date encoding
+             * Byte Code
+             * 0x00 Seconds (0-59)
+             * 0x01 Minutes (0-59)
+             * 0x02 Hours (0-23)
+             * 0x03 Day of themonth(1-31)
+             * 0x04 Month (1-12)
+             * 0x05 Year (bits 15:8). For example, 2006 signifies the year 2006
+             * 0x06 Year (bits 7:0)
+             * 0x07 Weekday (0-6, where 0 = Sunday and 6 = Saturday)
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x000E: /* GetArtworkFormats */
+            /* The following is the description for the Apple Firmware
+             *
+             * The device sends this command to obtain the list of supported
+             * artwork formats on the iPod. No parameters are sent.
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0E  Command ID (bits 7:0)
+             *  6   0xEB  Checksum/
+             *
+             * Returned Artwork Formats are a 7byte record.
+             * formatID:2
+             * pixelFormat:1
+             * width:2
+             * height:2
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x0F,
+                                    0x04, 0x04, /* FormatID   */
+                                    0x02,       /* PixelFormat*/
+                                    0x00, 0x64, /* 100 pixels */
+                                    0x00, 0x64, /* 100 pixels */
+                                    0x04, 0x05, /* FormatID   */
+                                    0x02,       /* PixelFormat*/
+                                    0x00, 0xC8, /* 200 pixels */
+                                    0x00, 0xC8  /* 200 pixels */
+                                    };
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x000F: /* RetArtworkFormats. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * The iPod sends this command to the device, giving it the list
+             * of supported artwork formats. Each format is described in a
+             * 7-byte record (formatID:2, pixelFormat:1, width:2, height:2).
+             * The formatID is used when sending GetTrackArtworkTimes.
+             * The device may return zero records.
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x0F  Command ID (bits 7:0)
+             *  NN  0xNN  formatID (15:8) iPod-assigned value for this format
+             *  NN  0xNN  formatID (7:0)
+             *  NN  0xNN  pixelFormat. Same as from SetDisplayImage
+             *  NN  0xNN  imageWidth(15:8).Number of pixels widefor eachimage.
+             *  NN  0xNN  imageWidth (7:0)
+             *  NN  0xNN  imageHeight (15:8). Number of pixels high for each
+             *  NN  0xNN  imageHeight (7:0).  image
+             *  Previous 7 bytes may be repeated NN times
+             *  NN  0xNN  Checksum
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0010: /* GetTrackArtworkData */
+            /* The following is the description for the Apple Firmware
+             * The device sends this command to the iPod to request data for a
+             * given trackIndex, formatID, and artworkIndex. The time offset
+             * from track start is the value returned by GetTrackArtworkTimes
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0D  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x10  Command ID (bits 7:0)
+             *  6   0xNN  trackIndex (31:24).
+             *  7   0xNN  trackIndex(23:16)
+             *  8   0xNN  trackIndex (15:8)
+             *  9   0xNN  trackIndex (7:0)
+             * 10   0xNN  formatID (15:8)
+             * 11   0xNN  formatID (7:0)
+             * 12   0xNN  time offset from track start, in ms (31:24)
+             * 13   0xNN  time offset from track start, in ms (23:16)
+             * 14   0xNN  time offset from track start, in ms (15:8)
+             * 15   0xNN  time offset from track start, in ms (7:0)
+             * 16   0xNN  Checksum
+             *
+             * Returned data is
+             * DescriptorTelegramIndex:        2
+             * pixelformatcode:                1
+             * ImageWidthPixels:               2
+             * ImageHeightPixels:              2
+             * InsetRectangleTopLeftX:         2
+             * InsetRectangleTopLeftY:         2
+             * InsetRectangleBotRightX:        2
+             * InsetRectangleBotRightY:        2
+             * RowSizeInBytes:                 4
+             * ImagePixelData:VariableLength
+             * Subsequent packets omit bytes 8 - 24
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x11,
+                                    0x00, 0x00, /* DescriptorIndex */
+                                    0x00,       /* PixelFormat */
+                                    0x00, 0x00, /* ImageWidthPixels*/
+                                    0x00, 0x00, /* ImageHeightPixels*/
+                                    0x00, 0x00, /* InsetRectangleTopLeftX*/
+                                    0x00, 0x00, /* InsetRectangleTopLeftY*/
+                                    0x00, 0x00, /* InsetRectangleBotRightX*/
+                                    0x00, 0x00, /* InsetRectangleBotRoghtY*/
+                                    0x00, 0x00, 0x00, 0x00,/* RowSize*/
+                                    0x00        /* ImagePixelData Var Length*/
+                                   };
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0011: /* RetTrackArtworkData. See Abobe */
+            /* The following is the description for the Apple Firmware
+             *
+             * The iPod sends the requested artwork to the accessory. Multiple
+             * RetTrackArtworkData commands may be necessary to transfer all
+             * the data because it will be too much to fit into a single packet.
+             * This command uses nearly the same format as the SetDisplayImage
+             * command (command 0x0032). The only difference is the addition
+             * of 2 coordinates; they define an inset rectangle that describes
+             * any padding that may have been added to the image. The
+             * coordinates consist of two x,y pairs. Each x or y value is 2
+             * bytes, so the total size of the coordinate set is 8 bytes.
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x11  Command ID (bits 7:0)
+             *  6   0x00  Descriptor telegram index (15:8).
+             *            These fields uniquely identify each packet in the
+             *            RetTrackArtworkData transaction. The first telegram
+             *            is the descriptor telegram, which always starts with
+             *            an index of 0x0000.
+             *  7   0x00  Descriptor telegram index (7:0)
+             *  8   0xNN  Display pixel format code.
+             *  9   0xNN  Imagewidth in pixels (15:8)
+             * 10   0xNN  Image width in pixels (7:0)
+             * 11   0xNN  Image height in pixels (15:8)
+             * 12   0xNN  Image height in pixels (7:0)
+             * 13   0xNN  Inset rectangle, top-left point, x value (15:8)
+             * 14   0xNN  Inset rectangle, top-left point, x value (7:0)
+             * 15   0xNN  Inset rectangle, top-left point, y value (15:8)
+             * 16   0xNN  Inset rectangle, top-left point, y value (7:0)
+             * 17   0xNN  Inset rectangle,bottom-rightpoint,xvalue(15:8)
+             * 18   0xNN  Inset rectangle,bottom-rightpoint, x value(7:0)
+             * 19   0xNN  Inset rectangle,bottom-rightpoint,y value(15:8)
+             * 20   0xNN  Inset rectangle, bottom-right point, y value(7:0)
+             * 21   0xNN  Rowsize in bytes (31:24)
+             * 22   0xNN  Rowsize in bytes (23:16)
+             * 23   0xNN  Row size in bytes (15:8)
+             * 24   0xNN  Row size in bytes (7:0)
+             * 25-NN 0xNN Image pixel data (variable length)
+             * NN   0xNN  Checksum
+             *
+             * In subsequent packets in the sequence, bytes 8 through 24 are
+             * omitted.
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0012: /* RequestProtocolVersion */
+            /* The following is the description for the Apple Firmware
+             *
+             * This command is deprecated.
+             *
+             * Requests the version of the running protocol from the iPod.
+             * The iPod responds with a Command 0x0013:ReturnProtocolVersion
+             * command.
+             *
+             * Note: This command requests the Extended Interface protocol
+             * version, not the iPod software version.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x12  Command ID (bits 7:0)
+             *  6   0xE7  Telegram payload checksum byte
+             *
+             */
+        {
+            IAP_TX_INIT4(0x04, 0x0013);
+            IAP_TX_PUT(LINGO_MAJOR(0x04));
+            IAP_TX_PUT(LINGO_MINOR(0x04));
+            iap_send_tx();
+            break;
+        }
+        case 0x0013: /* ReturnProtocolVersion. See Above */
+            /* The following is the description for the Apple Firmware
+             * This command is deprecated.
+             * Sent from the iPod to the device
+             * Returns the iPod Extended Interface protocol version number.
+             * The iPod sends this command in response to the Command 0x0012:
+             * RequestProtocolVersion command from the device. The major
+             * version number specifies the protocol version digits to the left
+             * of the decimal point; the minor version number specifies the
+             * digits to the right of the decimal point. For example, a major
+             * version number of 0x01 and a minor version number of 0x08
+             * represents an Extended Interface protocol version of 1.08. This
+             * protocol information is also available through the General lingo
+             * (lingo 0x00) command RequestLingoProtocolVersion when passing
+             * lingo 0x04 as the lingo parameter.
+             *
+             * Note: This command returns the Extended Interface protocol
+             * version, not the iPod software version.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x05  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x13  Command ID (bits 7:0)
+             *  6   0xNN  Protocol major version number
+             *  7   0xNN  Protocol minor version number
+             *  8   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0014: /* RequestiPodName */
+            /* The following is the description for the Apple Firmware
+             * This command is deprecated.
+             * Retrieves the name of the iPod
+             *
+             * Returns the name of the user's iPod or 'iPod' if the iPod name
+             * is undefined. This allows the iPod name to be shown in the
+             * human-machineinterface(HMI) of the interfacingbody. The iPod
+             * responds with the Command 0x0015: ReturniPodName command
+             * containing the iPod name text string.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x14  Command ID (bits 7:0)
+             *  6   0xE5  Telegram payload checksum byte
+             *
+             * We return ROCKBOX, should this be definable?
+             * Should it be Volume Name?
+             */
+        {
+            IAP_TX_INIT4(0x04, 0x0015);
+            IAP_TX_PUT_STRING("ROCKBOX");
+            iap_send_tx();
+            break;
+        }
+        case 0x0015: /* ReturniPodName. See Above */
+            /* The following is the description for the Apple Firmware
+             * This command is deprecated.
+             * Sent from the iPod to the device
+             *
+             * The iPod sends this command in response to the Command 0x0014:
+             * RequestiPodName telegram from the device. The iPod name is
+             * encoded as a null-terminated UTF-8 character array. The iPod
+             * name string is not limited to 252 characters; it may be sent
+             * in small or large telegram format. The small telegram format
+             * is shown.
+             *
+             * Note: Starting with version 1.07 of the Extended Interface lingo,
+             * the ReturniPodName command on Windows-formatted iPods returns the
+             * iTunes name of the iPod instead of the Windows volume name.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x15  Command ID (bits 7:0)
+             *  6-N 0xNN  iPod name as UTF-8 character array
+             *  NN  0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0016: /* ResetDBSelection */
+            /* The following is the description for the Apple Firmware
+             *
+             * Resets the current database selection to an empty state and
+             * invalidates the category entry count that is, sets the count
+             * to 0, for all categories except the playlist category. This is
+             * analogous to pressing the Menu button repeatedly to get to the
+             * topmost iPod HMI menu. Any previously selected database items
+             * are deselected. The command has no effect on the playback engine
+             * In response, the iPod sends an ACK telegram with the command
+             * status. Once the accessory has reset the database selection,
+             * it must initialize the category count before it can select
+             * database records. Please refer to Command 0x0018:
+             * GetNumberCategorizedDBRecords and Command 0x0017:
+             * SelectDBRecord for details.
+             *
+             * Note: Starting with protocol version 1.07, the ResetDBSelection
+             * command clears the sort order.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x16  Command ID (bits 7:0)
+             *  6   0xE3  Telegram payload checksum byte
+             *
+             *
+             * Reset the DB Record Type
+             * Hierarchy is as follows
+             * All        = 0
+             * Playlist   = 1
+             * Artist     = 2
+             * Album      = 3
+             * Genre      = 4
+             * Tracks     = 5
+             * Composers  = 6
+             * Audiobooks = 7
+             * Podcasts   = 8
+             *
+             */
+        {
+            cur_dbrecord[0] = 0;
+            put_u32(&cur_dbrecord[1],0);
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0017: /* SelectDBRecord */
+            /* The following is the description for the Apple Firmware
+             * Selects one or more records in the Database Engine, based on a
+             * category relative index. For example, selecting category two
+             * (artist) and record index one results in a list of selected
+             * tracks (or database records) from the second artist in the
+             * artist list.
+             * Selections are additive and limited by the category hierarchy;
+             * Subsequent selections are made based on the subset of records
+             * resulting from the previous selections and not from the entire
+             * database.
+             * Note that the selection of a single record automatically passes
+             * it to the Playback Engine and starts its playback. Record indices
+             * consist of a 32-bit signed integer. To select database records
+             * with a specific sort order, use
+             * Command 0x0038: SelectSortDBRecord
+             * SelectDBRecord should be called only once a category count has
+             * been initialized through a call to Command 0x0018:
+             * GetNumberCategorizedDBRecords. Without a valid category count,
+             * the SelectDBRecord call cannot select a database record and will
+             * fail with a command failed ACK. Accessories that make use of
+             * Command 0x0016: ResetDBSelection must always initialize the
+             * category count before selecting a new database record using
+             * SelectDBRecord.
+             * Accessories should pay close attention to the ACK returned by the
+             * SelectDBRecord command. Ignoring errors may cause unexpected
+             * behavior.
+             * To undo a database selection, send the SelectDBRecord telegram
+             * with the current category selected in theDatabase Engine and a
+             * record index of -1 (0xFFFFFFFF). This has the same effect as
+             * pressing the iPod Menu button once and moves the database
+             * selection up to the next highest menu level. For example, if a
+             * device selected artist number three and then album number one,
+             * it could use the SelectDBRecord(Album, -1) telegram to return
+             * to the database selection of artist number three. If multiple
+             * database selections have been made, devices can use any of the
+             * previously used categories to return to the next highest database
+             * selection. If the category used in one of these SelectDBRecord
+             * telegrams has not been used in a previous database selection
+             * then the command is treated as a no-op.
+             * Sending a SelectDBRecord telegram with the Track category and a
+             * record index of -1 is invalid, because the previous database
+             * selection made with the Track category and a valid index passes
+             * the database selection to the Playback Engine.
+             * Sending a SelectDBRecord(Track, -1) telegram returns a parameter
+             * error. The iPod also returns a bad parameter error ACK when
+             * devices send the SelectDBRecord telegram with an invalid category
+             * type, or with the Track category and an index greater than the
+             * total number of tracks available on the iPod.
+             *
+             * Note: Selecting a podcast always selects from the main podcast
+             * library regardless of the current category context of the iPod.
+             *
+             * To immediately go to the topmost iPod menu level and reset all
+             * database selections, send the ResetDBSelection telegram to the
+             * iPod.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x08  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x17  Command ID (bits 7:0)
+             *  6   0xNN  Database category type. See
+             *  7   0xNN  Database record index (bits 31:24)
+             *  8   0xNN  Database record index (bits 23:16)
+             *  9   0xNN  Database record index (bits 15:8)
+             * 10   0xNN  Database record index (bits 7:0)
+             * 11   0xNN  Telegram payload checksum byte
+             *
+             * The valid database categories are listed below
+             *
+             * Category Code Protocol version
+             * Reserved 0x00 N/A
+             * Playlist 0x01 1.00
+             * Artist   0x02 1.00
+             * Album    0x03 1.00
+             * Genre    0x04 1.00
+             * Track    0x05 1.00
+             * Composer 0x06 1.00
+             * Audiobook0x07 1.06
+             * Podcast  0x08 1.08
+             * Reserved 0x09+ N/A
+             *
+             * cur_dbrecord[0] is the record type
+             * cur_dbrecord[1-4] is the u32 of the record number requested
+             * which might be a playlist or a track number depending on
+             * the value of cur_dbrecord[0]
+             */
+        {
+            memcpy(cur_dbrecord, buf + 3, 5);
+
+            int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+            uint32_t index;
+            uint32_t trackcount;
+            index = get_u32(&cur_dbrecord[1]);
+            trackcount = playlist_amount();
+            if ((cur_dbrecord[0] == 5) && (index > trackcount))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            if ((cur_dbrecord[0] == 1) && (index > (nbr_total_playlists() + 1)))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            audio_pause();
+            switch (cur_dbrecord[0])
+            {
+                case 0x01: /* Playlist*/
+                {
+                    if (index != 0x00) /* 0x00 is the On-The-Go Playlist and
+                                          we do nothing with it  */
+                    {
+                        last_selected_playlist = index;
+                        audio_skip(-iap_get_trackindex());
+                        seek_to_playlist(last_selected_playlist);
+                    }
+                    break;
+                }
+                case 0x02: /* Artist   */
+                case 0x03: /* Album    */
+                case 0x04: /* Genre    */
+                case 0x05: /* Track    */
+                case 0x06: /* Composer */
+                {
+                    audio_skip(index - playlist_next(0));
+                    break;
+                }
+                default:
+               {
+                    /* We don't do anything with the other selections.
+                     * YET.
+                     */
+                    break;
+               }
+            }
+            if (!paused)
+                audio_resume();
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0018: /* GetNumberCategorizedDBRecords */
+            /* The following is the description for the Apple Firmware
+             *
+             * Retrieves the number of records in a particular database
+             * category.
+             * For example, a device can get the number of artists or albums
+             * present in the database. The category types are described above.
+             * The iPod responds with a Command 0x0019:
+             * ReturnNumberCategorizedDBRecords telegram indicating the number
+             * of records present for this category.
+             * GetNumberCategorizedDBRecords must be called to initialize the
+             * category count before selecting a database record using Command
+             * 0x0017: SelectDBRecord or Command 0x0038: SelectSortDBRecord
+             * commands. A category’s record count can change based on the prior
+             * categories selected and the database hierarchy. The accessory
+             * is expected to call GetNumberCategorizedDBRecords in order to
+             * get the valid range of category entries before selecting a
+             * record in that category.
+             *
+             * Note: The record count returned by this command depends on the
+             * database state before this command is sent. If the database has
+             * been reset using Command 0x0016: ResetDBSelection this command
+             * returns the total number of records for a given category.
+             * However, if this command is sent after one or more categories
+             * are selected, the record count is the subset of records that are
+             * members of all the categories selected prior to this command.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x18  Command ID (bits 7:0)
+             *  6   0xNN  Database category type. See above
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             * This is the actual number of available records for that category
+             * some head units (Alpine CDE-103BT) use this command before
+             * requesting records and then hang if not enough records are
+             * returned.
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x19,
+                                    0x00, 0x00, 0x00, 0x00};
+            switch(buf[3]) /* type number */
+            {
+                case 0x01: /* total number of playlists */
+                    dbrecordcount = nbr_total_playlists() + 1;
+                    break;
+                case 0x05: /* total number of Tracks */
+                case 0x02: /* total number of Artists */
+                case 0x03: /* total number of Albums */
+                           /* We don't sort on the above but some Head Units
+                            * require at least one to exist so we just return
+                            * the number of tracks in the playlist. */
+                    dbrecordcount = playlist_amount();
+                    break;
+                case 0x04: /* total number of Genres */
+                case 0x06: /* total number of Composers */
+                case 0x07: /* total number of AudioBooks */
+                case 0x08: /* total number of Podcasts */
+                           /* We don't support the above so just return that
+                              there are none available. */
+                    dbrecordcount = 0;
+                    break;
+            }
+            put_u32(&data[3], dbrecordcount);
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0019: /* ReturnNumberCategorizedDBRecords. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the number of database records matching the specified
+             * database category. The iPod sends this telegram in response to
+             * the Command 0x0018: GetNumberCategorizedDBRecords telegram from
+             * the device. Individual records can then be extracted by sending
+             * Command 0x001A: RetrieveCategorizedDatabaseRecords to the iPod.
+             * If no matching database records are found, a record count of
+             * zero is returned. Category types are described above.
+             * After selecting the podcast category, the number of artist,
+             * album, composer, genre, and audiobook records is always zero.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x19  Command ID (bits 7:0)
+             *  6   0xNN  Database record count (bits 31:24)
+             *  7   0xNN  Database record count (bits 23:16)
+             *  8   0xNN  Database record count (bits 15:8)
+             *  9   0xNN  Database record count (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x001A: /* RetrieveCategorizedDatabaseRecords */
+            /* The following is the description for the Apple Firmware
+             *
+             * Retrieves one or more database records from the iPod,
+             * typically based on the results from the Command 0x0018:
+             * GetNumberCategorizedDBRecords query. The database
+             * category types are described above. This telegram
+             * specifies the starting record index and the number of
+             * records to retrieve (the record count). This allows a device
+             * to retrieve an individual record or the entire set of records
+             * for a category. The record start index and record count consist
+             * of 32-bit signed integers. To retrieve all records from a given
+             * starting record index, set the record count to -1 (0xFFFFFFFF).
+             * The iPod responds to this telegram with a separate Command
+             * 0x001B: ReturnCategorizedDatabaseRecord telegram FOR EACH record
+             * matching the specified criteria (category and record index
+             * range).
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0C  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1A  Command ID (bits 7:0)
+             *  6   0xNN  Database category type. See above
+             *  7   0xNN  Database record start index (bits 31:24)
+             *  8   0xNN  Database record start index (bits 23:16)
+             *  9   0xNN  Database record start index (bits 15:8)
+             * 10   0xNN  Database record start index (bits 7:0)
+             * 11   0xNN  Database record read count (bits 31:24)
+             * 12   0xNN  Database record read count (bits 23:16)
+             * 13   0xNN  Database record read count (bits 15:8)
+             * 14   0xNN  Database record read count (bits 7:0)
+             * 15   0xNN  Telegram payload checksum byte
+
+             * The returned data
+             * contains information for a single database record. The iPod sends
+             * one or more of these telegrams in response to the Command 0x001A:
+             * RetrieveCategorizedDatabaseRecords telegram from the device. The
+             * category record index is included to allow the device to
+             * determine which record has been sent. The record data is sent as
+             * a null-terminated UTF-8 encoded data array.
+             */
+        {
+            unsigned char data[7 + MAX_PATH] =
+                            {0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x00,
+                             'O','n','-','T','h','e','-','G','o','\0'};
+            struct playlist_track_info track;
+            struct mp3entry id3;
+
+            unsigned long start_index = get_u32(&buf[4]);
+            unsigned long read_count = get_u32(&buf[8]);
+            unsigned long counter = 0;
+            unsigned int number_of_playlists = nbr_total_playlists();
+            uint32_t trackcount;
+            trackcount = playlist_amount();
+            size_t len;
+
+            if ((buf[3] == 0x05) && ((start_index + read_count ) > trackcount))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            if ((buf[3] == 0x01) && ((start_index + read_count) > \
(number_of_playlists + 1))) +            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            for (counter=0;counter<read_count;counter++)
+            {
+                switch(buf[3]) /* type number */
+                {
+                    case 0x01: /* Playlists */
+                       get_playlist_name(data +7,start_index+counter, MAX_PATH);
+                        /*Remove file extension*/
+                       char *dot=NULL;
+                       dot = (strrchr(data+7, '.'));
+                       if (dot != NULL)
+                           *dot = '\0';
+                       break;
+                    case 0x05: /* Tracks   */
+                    case 0x02: /* Artists  */
+                    case 0x03: /* Albums   */
+                    case 0x04: /* Genre    */ 
+                    case 0x06: /* Composer */
+                        playlist_get_track_info(NULL, start_index + counter,
+                                                &track);
+                        iap_get_trackinfo(start_index + counter, &id3);
+                        switch(buf[3])
+                        {
+                            case 0x05:
+                                len = strlcpy((char *)&data[7], id3.title,64);
+                                break;
+                            case 0x02:
+                                len = strlcpy((char *)&data[7], id3.artist,64);
+                                break;
+                            case 0x03:
+                                len = strlcpy((char *)&data[7], id3.album,64);
+                                break;
+                            case 0x04:
+                            case 0x06:
+                                len = strlcpy((char *)&data[7], "Not Supported",14);
+                                break;
+                        }
+                        break;
+                }
+                put_u32(&data[3], start_index+counter);
+                iap_send_pkt(data, 7 + strlen(data+7) + 1);
+                yield();
+            }
+            break;
+        }
+        case 0x001B: /* ReturnCategorizedDatabaseRecord. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Contains information for a single database record. The iPod sends
+             * ONE OR MORE of these telegrams in response to the Command 0x001A:
+             * RetrieveCategorizedDatabaseRecords telegram from the device. The
+             * category record index is included to allow the device to
+             * determine which record has been sent. The record data is sent
+             * as a null-terminated UTF-8 encoded data array.
+             *
+             * Note: The database record string is not limited to 252 characters
+             * it may be sent in small or large telegram format, depending on
+             * the record size. The small telegram format is shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1B  Command ID (bits 7:0)
+             *  6   0xNN  Database record category index (bits 31:24)
+             *  7   0xNN  Database record category index (bits 23:16)
+             *  8   0xNN  Database record category index (bits 15:8)
+             *  9   0xNN  Database record category index (bits 7:0)
+             * 10-N 0xNN  Database record as a UTF-8 character array.
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x001C: /* GetPlayStatus */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the current iPod playback status, allowing the
+             * device to display feedback to the user. In response, the
+             * iPod sends a Command 0x001D: ReturnPlayStatus telegram
+             * with the current playback status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1C  Command ID (bits 7:0)
+             *  6   0xDD  Telegram payload checksum byte
+             *
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x1D,
+                                    0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0x00, 0x00,
+                                    0x00};
+            struct mp3entry *id3 = audio_current_track();
+            unsigned long time_total = id3->length;
+            unsigned long time_elapsed = id3->elapsed;
+            int status = audio_status();
+            put_u32(&data[3], time_total);
+            put_u32(&data[7], time_elapsed);
+            if (status == AUDIO_STATUS_PLAY)
+                data[11] = 0x01; /* play */
+            else if (status & AUDIO_STATUS_PAUSE)
+                data[11] = 0x02; /* pause */
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x001D: /* ReturnPlayStatus. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the current iPod playback status. The iPod sends this
+             * telegram in response to the Command 0x001C: GetPlayStatus
+             * telegram from the device. The information returned includes the
+             * current track length, track position, and player state.
+             *
+             * Note: The track length and track position fields are valid only
+             * if the player state is Playing or Paused. For other player
+             * states, these fields should be ignored.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0C  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1D  Command ID (bits 7:0)
+             *  6   0xNN  Track length in milliseconds (bits 31:24)
+             *  7   0xNN  Track length in milliseconds (bits 23:16)
+             *  8   0xNN  Track length in milliseconds (bits 15:8)
+             *  9   0xNN  Track length in milliseconds (bits 7:0)
+             * 10   0xNN  Track position in milliseconds (bits 31:24)
+             * 11   0xNN  Track position in milliseconds (bits 23:16)
+             * 12   0xNN  Track position in milliseconds (bits 15:8)
+             * 13   0xNN  Track position in milliseconds (bits 7:0)
+             * 14   0xNN  Player state. Possible values are:
+             *            0x00 = Stopped
+             *            0x01 = Playing
+             *            0x02 = Paused
+             *            0x03 - 0xFE = Reserved
+             *            0xFF = Error
+             * 15   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x001E: /* GetCurrentPlayingTrackIndex */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the playback engine index of the currently playing
+             * track. In response, the iPod sends a Command 0x001F:
+             * ReturnCurrentPlayingTrackIndex telegram to the device.
+             *
+             * Note: The track index returned is valid only if there is
+             * currently a track playing or paused.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1E  Command ID (bits 7:0)
+             *  6   0xDB  Telegram payload checksum byte
+             *
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x1F,
+                                    0xFF, 0xFF, 0xFF, 0xFF};
+            long playlist_pos = playlist_next(0);
+            int status = audio_status();
+            playlist_pos -= playlist_get_first_index(NULL);
+            if(playlist_pos < 0)
+                playlist_pos += playlist_amount();
+            if ((status == AUDIO_STATUS_PLAY) || (status & AUDIO_STATUS_PAUSE))
+                put_u32(&data[3], playlist_pos);
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x001F: /* ReturnCurrentPlayingTrackIndex. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the playback engine index of the current playing track in
+             * response to the Command 0x001E: GetCurrentPlayingTrackIndex
+             * telegram from the device. The track index is a 32-bit signed
+             * integer.
+             * If there is no track currently playing or paused, an index of -1
+             * (0xFFFFFFFF) is returned.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x1F  Command ID (bits 7:0)
+             *  6   0xNN  Playback track index (bits 31:24)
+             *  7   0xNN  Playback track index (bits 23:16)
+             *  8   0xNN  Playback track index (bits 15:8)
+             *  9   0xNN  Playback track index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0020: /* GetIndexedPlayingTrackTitle. See 0x0024 below */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the title name of the indexed playing track from the
+             * iPod. In response to a valid telegram, the iPod sends a
+             * Command 0x0021: ReturnIndexedPlayingTrackTitle telegram to the
+             * device.
+             *
+             * Note: If the telegram length or playing track index is invalid,
+             * the iPod responds with an ACK telegram including the specific
+             * error status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x20  Command ID (bits 7:0)
+             *  6   0xNN  Playback track index (bits 31:24)
+             *  7   0xNN  Playback track index (bits 23:16)
+             *  8   0xNN  Playback track index (bits 15:8)
+             *  9   0xNN  Playback track index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        case 0x0021: /* ReturnIndexedPlayingTrackTitle. See 0x0024 Below */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the title of the indexed playing track in response to
+             * a valid Command 0x0020 GetIndexedPlayingTrackTitle telegram from
+             * the device. The track title is encoded as a null-terminated UTF-8
+             * character array.
+             *
+             * Note: The track title string is not limited to 252 characters;
+             * it may be sent in small or large telegram format, depending on
+             * the string length. The small telegram format is shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x21  Command ID (bits 7:0)
+             *  6-N 0xNN  Track title as a UTF-8 character array
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             */
+        case 0x0022: /* GetIndexedPlayingTrackArtistName. See 0x0024 Below */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the name of the artist of the indexed playing track
+             * In response to a valid telegram, the iPod sends a
+             * Command 0x0023: ReturnIndexedPlayingTrackArtistName telegram to
+             * the device.
+             *
+             * Note: If the telegram length or playing track index is invalid,
+             * the iPod responds with an ACK telegram including the specific
+             * error status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x22  Command ID (bits 7:0)
+             *  6   0xNN  Playback track index (bits 31:24)
+             *  7   0xNN  Playback track index (bits 23:16)
+             *  8   0xNN  Playback track index (bits 15:8)
+             *  9   0xNN  Playback track index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        case 0x0023: /* ReturnIndexedPlayingTrackArtistName. See 0x0024 Below */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the artist name of the indexed playing track in response
+             * to a valid Command 0x0022 GetIndexedPlayingTrackArtistName
+             * telegram from the device. The track artist name is encoded as a
+             * null-terminated UTF-8 character array.
+             *
+             * Note: The artist name string is not limited to 252 characters;
+             * it may be sent in small or large telegram format, depending on
+             * the string length. The small telegram format is shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x23  Command ID (bits 7:0)
+             *  6-N 0xNN  Track Artist as a UTF-8 character array
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             */
+        case 0x0024: /* GetIndexedPlayingTrackAlbumName AND
+                      * GetIndexedPlayingTrackTitle AND
+                      * AND GetIndexedPlayingTrackArtistName. */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the album name of the indexed playing track
+             * In response to a valid telegram, the iPod sends a
+             * Command 0x0025: ReturnIndexedPlayingTrackAlbumName telegram to
+             * the device.
+             *
+             * Note: If the telegram length or playing track index is invalid,
+             * the iPod responds with an ACK telegram including the specific
+             * error status.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x24  Command ID (bits 7:0)
+             *  6   0xNN  Playback track index (bits 31:24)
+             *  7   0xNN  Playback track index (bits 23:16)
+             *  8   0xNN  Playback track index (bits 15:8)
+             *  9   0xNN  Playback track index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            unsigned char data[70] = {0x04, 0x00, 0xFF};
+            struct mp3entry id3;
+            int fd;
+            size_t len;
+            long tracknum = get_u32(&buf[3]);
+
+            data[2] = cmd + 1;
+            memcpy(&id3, audio_current_track(), sizeof(id3));
+            tracknum += playlist_get_first_index(NULL);
+            if(tracknum >= playlist_amount())
+                tracknum -= playlist_amount();
+            /* If the tracknumber is not the current one,
+               read id3 from disk */
+            if(playlist_next(0) != tracknum)
+            {
+                struct playlist_track_info info;
+                playlist_get_track_info(NULL, tracknum, &info);
+                fd = open(info.filename, O_RDONLY);
+                memset(&id3, 0, sizeof(struct mp3entry));
+                get_metadata(&id3, fd, info.filename);
+                close(fd);
+            }
+            /* Return the requested track data */
+            switch(cmd)
+            {
+                case 0x20:
+                    len = strlcpy((char *)&data[3], id3.title, 64);
+                    iap_send_pkt(data, 4+len);
+                    break;
+                case 0x22:
+                    len = strlcpy((char *)&data[3], id3.artist, 64);
+                    iap_send_pkt(data, 4+len);
+                    break;
+                case 0x24:
+                    len = strlcpy((char *)&data[3], id3.album, 64);
+                    iap_send_pkt(data, 4+len);
+                    break;
+            }
+            break;
+        }
+        case 0x0025: /* ReturnIndexedPlayingTrackAlbumName. See 0x0024 Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the album name of the indexed playing track in response
+             * to a valid Command 0x0024 GetIndexedPlayingTrackAlbumName
+             * telegram from the device. The track artist name is encoded as a
+             * null-terminated UTF-8 character array.
+             *
+             * Note: The album name string is not limited to 252 characters;
+             * it may be sent in small or large telegram format, depending on
+             * the string length. The small telegram format is shown.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x25  Command ID (bits 7:0)
+             *  6-N 0xNN  Track Album as a UTF-8 character array
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0026: /* SetPlayStatusChangeNotification */
+            /* The following is the description for the Apple Firmware
+             * Sets the state of play status change notifications from the iPod
+             * to the device. Notification of play status changes can be
+             * globally enabled or disabled. If notifications are enabled, the
+             * iPod sends a Command 0x0027: PlayStatusChangeNotification
+             * telegram to the device each time the play status changes, until
+             * the device sends this telegram again with the disable
+             * notification option. In response, the iPod sends an ACK telegram
+             * indicating the status of the SetPlayStatusChangeNotification
+             * command.
+             *
+             * Byte Value Meaning
+             *  0     0xFF  Sync byte (required only for UART serial)
+             *  1     0x55  Start of telegram
+             *  2     0x04  Telegram payload length
+             *  3     0x04  Lingo ID: Extended Interface lingo
+             *  4     0x00  Command ID (bits 15:8)
+             *  5     0x26  Command ID (bits 7:0)
+             *  6     0xNN  The state of play status change notifications.
+             *            Possible values are:
+             *              0x00 = Disable all change notification
+             *              0x01 = Enable all change notification
+             *              0x02 - 0xFF = Reserved
+             *  7     0xNN   Telegram payload checksum byte
+             */
+        {
+            device.do_notify = buf[3] ? true : false;
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0027: /* PlayStatusChangeNotification */
+        /* This response is handled by iap_track_changed() and iap_periodic()
+         * within iap-core.c
+         * The following is the description for the Apple Firmware
+         *
+         * The iPod sends this telegram to the device when the iPod play status
+         * changes, if the device has previously enabled notifications using
+         * Command 0x0026: SetPlayStatusChangeNotification . This telegram
+         * contains details about the new play status. Notification telegrams
+         * for changes in track position occur approximately every 500
+         * milliseconds while the iPod is playing. Notification telegrams are
+         * sent from the iPod until the device sends the
+         * SetPlayStatusChangeNotification telegram with the option to disable
+         * all notifications.
+         * Some notifications include additional data about the new play status.
+         *
+         * PlayStatusChangeNotification telegram: notifications 0x00, 0x02, 0x03
+         * Byte Value Meaning
+         *  0   0xFF  Sync byte (required only for UART serial)
+         *  1   0x55  Start of telegram
+         *  2   0x04  Telegram payload length
+         *  3   0x04  Lingo ID: Extended Interface lingo
+         *  4   0x00  Command ID (bits 15:8)
+         *  5   0x27  Command ID (bits 7:0)
+         *  6   0xNN  New play status. See Below.
+         *  7   0xNN  Telegram payload checksum byte
+         *
+         *             Play Status Change          Code Extended Status Data (if
+         *                                              any)
+         *             Playback stopped            0x00 None
+         *             Playback track changed      0x01 New track record index
+         *                                             (32 bits)
+         *             Playback forward seek stop  0x02 None
+         *             Playback backward seek stop 0x03 None
+         *             Playback track position     0x04 New track position in
+         *                                              milliseconds (32 bits)
+         *             Playback chapter changed    0x05 New chapter index (32
+         *                                              bits)
+         *             Reserved                    0x06 - 0xFF N/A
+         *
+         * Playback track changed (code 0x01)
+         * Byte Value Meaning
+         *  0   0xFF  Sync byte (required only for UART serial)
+         *  1   0x55  Start of telegram
+         *  2   0x08  Telegram payload length
+         *  3   0x04  Lingo ID: Extended Interface lingo
+         *  4   0x00  Command ID (bits 15:8)
+         *  5   0x27  Command ID (bits 7:0)
+         *  6   0x01  New status: Playback track changed
+         *  7   0xNN  New playback track index (bits 31:24)
+         *  8   0xNN  New playback track index (bits 23:16)
+         *  9   0xNN  New playback track index (bits 15:8)
+         * 10   0xNN  New playback track index (bits 7:0)
+         * 11   0xNN  Telegram payload checksum byte
+         *
+         * Playback track position changed (code 0x04)
+         * Byte Value Meaning
+         *  0   0xFF  Sync byte (required only for UART serial)
+         *  1   0x55  Start of telegram
+         *  2   0x08  Telegram payload length
+         *  3   0x04  Lingo ID: Extended Interface lingo
+         *  4   0x00  Command ID (bits 15:8)
+         *  5   0x27  Command ID (bits 7:0)
+         *  6   0x04  New status: Playback track position changed
+         *  7   0xNN  New track position in milliseconds (bits 31:24)
+         *  8   0xNN  New track position in milliseconds (bits 23:16)
+         *  9   0xNN  New track position in milliseconds (bits 15:8)
+         * 10   0xNN  New track position in milliseconds (bits 7:0)
+         * 11   0xNN  Telegram payload checksum byte
+         *
+         * Playback chapter changed (code 0x05)
+         * Byte Value Meaning
+         *  0   0xFF  Sync byte (required only for UART serial)
+         *  1   0x55  Start of telegram
+         *  2   0x08  Telegram payload length
+         *  3   0x04  Lingo ID: Extended Interface lingo
+         *  4   0x00  Command ID (bits 15:8)
+         *  5   0x27  Command ID (bits 7:0)
+         *  6   0x05  New status: Playback chapter changed
+         *  7   0xNN  New track chapter index (bits 31:24)
+         *  8   0xNN  New track chapter index (bits 23:16)
+         *  9   0xNN  New track chapter index (bits 15:8)
+         * 10   0xNN  New track chapter index (bits 7:0)
+         * 11   0xNN  Telegram payload checksum byte
+         *
+         */
+        case 0x0028: /* PlayCurrentSelection */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests playback of the currently selected track or list of
+             * tracks. The currently selected tracks are placed in the Now
+             * Playing playlist, where they are optionally shuffled (if the
+             * shuffle feature is enabled). Finally, the specified track record
+             * index is passed to the player to play. Note that if the track
+             * index is -1(0xFFFFFFFF), the first track after the shuffle is
+             * complete is played first. If a track index of n is sent, the nth
+             * track of the selected tracks is played first, regardless of
+             * where it is located in the Now Playing playlist after the shuffle
+             * is performed (assuming that shuffle is on). In response, the iPod
+             * sends an ACK telegram indicating the status of the command.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x28  Command ID (bits 7:0)
+             *  6   0xNN  Selection track record index (bits 31:24)
+             *  7   0xNN  Selection track record index (bits 23:16)
+             *  8   0xNN  Selection track record index (bits 15:8)
+             *  9   0xNN  Selection track record index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+            uint32_t index;
+            uint32_t trackcount;
+            index = get_u32(&buf[3]);
+            trackcount = playlist_amount();
+            if (index >= trackcount)
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            audio_pause();
+            if(global_settings.playlist_shuffle)
+            {
+                playlist_randomise(NULL, current_tick, true);
+            }
+            else
+	          {
+	              playlist_sort(NULL, true);
+            }
+            audio_skip(index - playlist_next(0));
+            if (!paused)
+                audio_resume();
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0029: /* PlayControl */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Sets the new play state of the iPod. The play control command
+             * codes are shown below. In response, the iPod sends an ACK
+             * telegram indicating the status of the command.
+             *
+             * Note: If a remote device requests the Next or Previous Chapter
+             * for a track that does not contain chapters, the iPod returns a
+             * command failed ACK.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x29  Command ID (bits 7:0)
+             *  6   0xNN  Play control command code.
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             * Play Control Command Code Protocol version
+             * Reserved             0x00 N/A
+             * Toggle Play/Pause    0x01 1.00
+             * Stop                 0x02 1.00
+             * Next Track           0x03 1.00
+             * Previous Track       0x04 1.00
+             * StartFF              0x05 1.00
+             * StartRew             0x06 1.00
+             * EndFFRew             0x07 1.00
+             * NextChapter          0x08 1.06
+             * Previous Chapter     0x09 1.06
+             * Reserved             0x0A - 0xFF
+             *
+             */
+            switch(buf[3])
+            {
+                case 0x01: /* play/pause */
+                    iap_remotebtn = BUTTON_RC_PLAY;
+                    iap_repeatbtn = 2;
+                    break;
+                case 0x02: /* stop */
+                    iap_remotebtn = BUTTON_RC_PLAY|BUTTON_REPEAT;
+                    iap_repeatbtn = 2;
+                    break;
+                case 0x03: /* skip++ */
+                    iap_remotebtn = BUTTON_RC_RIGHT;
+                    iap_repeatbtn = 2;
+                    break;
+                case 0x04: /* skip-- */
+                    iap_remotebtn = BUTTON_RC_LEFT;
+                    iap_repeatbtn = 2;
+                    break;
+                case 0x05: /* ffwd */
+                    iap_remotebtn = BUTTON_RC_RIGHT;
+                    break;
+                case 0x06: /* frwd */
+                    iap_remotebtn = BUTTON_RC_LEFT;
+                    break;
+                case 0x07: /* end ffwd/frwd */
+                    iap_remotebtn = BUTTON_NONE;
+                    break;
+            }
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x002A: /* GetTrackArtworkTimes */
+            /* The following is the description for the Apple Firmware
+             *
+             * The device sends this command to the iPod to request the list of
+             * artwork time locations for a track. A 4-byte track Index
+             * specifies which track from the Playback Engine is to be selected.
+             * A 2-byte formatID indicates which type of artwork is desired.
+             * The 2-byte artworkIndex specifies at which index to begin
+             * searching for artwork. A value of 0 indicates that the iPod
+             * should start with the first available artwork.
+             * The 2-byte artworkCount specifies the maximum number of times
+             * (artwork locations) to be returned. A value of -1 (0xFFFF)
+             * indicates that there is no preferred limit. Note that podcasts
+             * may have a large number of associated images.
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x0D  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2A  Command ID (bits 7:0)
+             *  6   0xNN  trackIndex(31:24)
+             *  7   0xNN  trackIndex(23:16)
+             *  8   0xNN  trackIndex (15:8)
+             *  9   0xNN  trackIndex (7:0)
+             * 10   0xNN  formatID (15:8)
+             * 11   0xNN  formatID (7:0)
+             * 12   0xNN  artworkIndex (15:8)
+             * 13   0xNN  artworkIndex (7:0)
+             * 14   0xNN  artworkCount (15:8)
+             * 15   0xNN  artworkCount (7:0)
+             * 16   0xNN  Checksum
+             *
+             */
+        {
+            unsigned char data[] = {0x04, 0x00, 0x2B,
+                                    0x00, 0x00, 0x00, 0x00};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x002B: /* ReturnTrackArtworkTimes. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * The iPod sends this command to the device to return the list of
+             * artwork times for a given track. The iPod returns zero or more
+             * 4-byte times, one for each piece of artwork associated with the
+             * track and format specified by GetTrackArtworkTimes.
+             * The number of records returned will be no greater than the number
+             * specified in the GetTrackArtworkTimes command. It may however, be
+             * less than requested. This can happen if there are fewer pieces of
+             * artwork available than were requested, or if the iPod is unable
+             * to  place the full number in a single packet. Check the number of
+             * records returned against the results of
+             * RetIndexedPlayingTrackInfo with infoType 7 to ensure that all
+             * artwork has been received.
+             *
+             * Byte Value Comment
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Length of packet
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2B  Command ID (bits 7:0)
+             *  6   0xNN  time offset from track start in ms (31:24)
+             *  7   0xNN  time offset from track start in ms (23:16)
+             *  8   0xNN  time offset from track start in ms (15:8)
+             *  9   0xNN  time offset from track start in ms (7:0)
+             *  Preceding 4 bytes may be repeated NN times
+             * NN   0xNN  Checksum
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x002C: /* GetShuffle */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the current state of the iPod shuffle setting. The iPod
+             * responds with the Command0x002D: ReturnShuffle telegram.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2C  Command ID (bits 7:0)
+             *  6   0xCD  Telegram payload checksum byte
+             *
+             */
+            unsigned char data[] = {0x04, 0x00, 0x2D,
+                                    0x00};
+            data[3] = global_settings.playlist_shuffle ? 1 : 0;
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x002D: /* ReturnShuffle. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the current state of the shuffle setting. The iPod sends
+             * this telegram in response to the Command 0x002C: GetShuffle
+             * telegram from the device.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2D  Command ID (bits 7:0)
+             *  6   0xNN  Shuffle mode. See Below.
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             * Possible values of the shufflemode.
+             * Value Meaning
+             * 0x00  Shuffle off
+             * 0x01  Shuffle tracks
+             * 0x02  Shuffle albums
+             * 0x03 – 0xFF Reserved
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x002E: /* SetShuffle */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Sets the iPod shuffle mode. The iPod shuffle modes are listed
+             * below. In response, the iPod sends an ACK telegram with the
+             * command status.
+             * This telegram has an optional byte, byte 0x07, called the
+             * RestoreonExit byte. This byte can be used to restore the
+             * original shuffle setting in use when the accessory was attached
+             * to the iPod. A non zero value restores the original shuffle
+             * setting of the iPod when the accessory is detached. If this byte
+             * is zero, the shuffle setting set by the accessory overwrites the
+             * original setting and persists after the accessory is detached
+             * from the iPod.
+             * Accessory engineers should note that the shuffle mode affects
+             * items only in the playback engine. The shuffle setting does not
+             * affect the order of tracks in the database engine, so calling
+             * Command 0x001A: RetrieveCategorizedDatabaseRecords on a database
+             * selection with the shuffle mode set returns a list of unshuffled
+             * tracks. To get the shuffled playlist, an accessory must query the
+             * playback engine by calling Command 0x0020
+             * GetIndexedPlayingTrackTitle.
+             * Shuffling tracks does not affect the track index, just the track
+             * at that index. If an unshuffled track at playback index 1 is
+             * shuffled, a new track is placed into index 1. The playback
+             * indexes themselves are not shuffled.
+             * When shuffle mode is enabled, tracks that are marked 'skip when
+             * shuffling' are filtered from the database selection. This affects
+             * all audiobooks and all tracks that the user has marked in iTunes.
+             * It also affects all podcasts unless their default 'skip when
+             * shuffling' markings have been deliberately removed. To apply the
+             * filter to the playback engine, the accessory should send
+             * Command 0x0017: SelectDBRecord or
+             * Command 0x0028: PlayCurrentSelection after enabling shuffle mode.
+             *
+             * Note: Accessory developers are encouraged to always use the
+             * Restore on Exit byte with a nonzero value to restore any settings
+             * modified by the accessory upon detach.
+             *
+             * SetShuffle telegram with Restore on Exit byte
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x05  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2E  Command ID (bits 7:0)
+             *  6   0xNN  New shuffle mode. See above .
+             *  7   0xNN  Restore on Exit byte. If 1, the orig setting is
+             *            restored on detach; if 0, the newsetting persists
+             *            after accessory detach.
+             *  8   0xNN  Telegram payload checksum byte
+             *
+             * SetShuffle setting persistent after the accessory detach.
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2E  Command ID (bits 7:0)
+             *  6   0xNN  New shuffle mode. See above.
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             */
+            if(buf[3] && !global_settings.playlist_shuffle)
+            {
+                global_settings.playlist_shuffle = 1;
+                settings_save();
+                if (audio_status() & AUDIO_STATUS_PLAY)
+                    playlist_randomise(NULL, current_tick, true);
+            }
+            else if(!buf[3] && global_settings.playlist_shuffle)
+            {
+                global_settings.playlist_shuffle = 0;
+                settings_save();
+                if (audio_status() & AUDIO_STATUS_PLAY)
+                    playlist_sort(NULL, true);
+            }
+
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x002F: /* GetRepeat */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the track repeat state of the iPod. In response, the
+             * iPod sends a Command 0x0030: ReturnRepeat  telegram
+             * to the device.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x2F  Command ID (bits 7:0)
+             *  6   0xCA  Telegram payload checksum byte
+             *
+             */
+            unsigned char data[] = {0x04, 0x00, 0x30, 0x00};
+            if(global_settings.repeat_mode == REPEAT_OFF)
+                data[3] = 0;
+            else if(global_settings.repeat_mode == REPEAT_ONE)
+                data[3] = 1;
+            else
+                data[3] = 2;
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0030: /* ReturnRepeat. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the current iPod track repeat state to the device.
+             * The iPod sends this telegram in response to the Command
+             * 0x002F:GetRepeat command.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x30  Command ID (bits 7:0)
+             *  6   0xNN  Repeat state. See Below.
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             * Repeat state values
+             * Value Meaning
+             *  0x00 Repeat off
+             *  0x01 Repeat one track
+             *  0x02 Repeat all tracks
+             *  0x03 - 0xFF Reserved
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0031: /* SetRepeat */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Sets the repeat state of the iPod. The iPod track repeat modes
+             * are listed above. In response, the iPod sends an ACK telegram
+             * with the command status.
+             *
+             * This telegram has an optional byte, byte 0x07, called the
+             * RestoreonExitbyte. This byte can be used to restore the original
+             * repeat setting in use when the accessory was attached to the
+             * iPod. A nonzero value restores the original repeat setting of
+             * the iPod when the accessory is detached. If this byte is zero,
+             * the repeat setting set by the accessory overwrites the original
+             * setting and persists after the accessory is detached from the
+             * iPod.
+             *
+             * Note: Accessory developers are encouraged to always use the
+             * Restore on Exit byte with a nonzero value to restore any
+             * settings modified by the accessory upon detach.
+             *
+             * SetRepeat telegram with Restore on Exit byte
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x05  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x31  Command ID (bits 7:0)
+             *  6   0xNN  New repeat state. See above.
+             *  7   0xNN  Restore on Exit byte. If 1, the original setting is
+             *            restored on detach; if 0, the newsetting persists
+             *            after accessory detach.
+             *  8   0xNN  Telegram payload checksum byte
+             *
+             * SetRepeat setting persistent after the accessory detach.
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x31  Command ID (bits 7:0)
+             *  6   0xNN  New repeat state. See above.
+             *  7   0xNN  Telegram payload checksum byte
+             *
+             */
+            int oldmode = global_settings.repeat_mode;
+            if (buf[3] == 0)
+                global_settings.repeat_mode = REPEAT_OFF;
+            else if (buf[3] == 1)
+                global_settings.repeat_mode = REPEAT_ONE;
+            else if (buf[3] == 2)
+                global_settings.repeat_mode = REPEAT_ALL;
+
+            if (oldmode != global_settings.repeat_mode)
+            {
+                settings_save();
+                if (audio_status() & AUDIO_STATUS_PLAY)
+                    audio_flush_and_reload_tracks();
+            }
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0032: /* SetDisplayImage */
+        {
+            /* The following is the description for the Apple Firmware
+             * This sets a bitmap image
+             * that is displayed on the iPod display when connected to the
+             * device. It replaces the default checkmark bitmap image that is
+             * displayed when iPod is connected to an external device
+             *
+             * Sets a bitmap image that is shown on the iPod display when it is
+             * connected to the device. The intent is to allow third party
+             * branding when the iPod is communicating with an external device.
+             * An image downloaded using this mechanism replaces the default
+             * checkmark bitmap image that is displayed when iPod is connected
+             * to an external device. The new bitmap is retained in RAM for as
+             * long as the iPod remains powered. After a system reset or deep
+             * sleep state, the new bitmap is lost and the checkmark image
+             * restored as the default when the iPod next enters Extended
+             * Interface mode after power-up.
+             * Before setting a monochrome display image, the device can send
+             * the Command 0x0033: GetMonoDisplayImageLimits telegram to obtain
+             * the current iPod display width, height and pixel format.The
+             * monochrome display information returned in the Command 0x0034:
+             * ReturnMonoDisplayImageLimits telegram can be useful to the
+             * device in deciding which type of display image format is suitable
+             * for downloading to the iPod.
+             * On iPods withcolor displays, devices can send the Command 0x0039:
+             * GetColorDisplayImageLimits telegram to obtain the iPod color
+             * display width,height,and pixel formats. The color display
+             * information is returned in the Command 0x003A:
+             * ReturnColorDisplayImageLimits” telegram.
+             * To set a display image, the device must successfully send
+             * SetDisplayImage descriptor and data telegrams to the iPod. The
+             * SetDisplayImage descriptor telegram (telegram index 0x0000) must
+             * be sent first, as it gives the iPod a description of the image
+             * to be downloaded. This telegram is shown in below. The image
+             * descriptor telegram includes image pixel format, image width and
+             * height, and display row size (stride) in bytes. Optionally, the
+             * descriptor telegram may also contain the beginning data of the
+             * display image, as long as it remains within the maximum length
+             * limits of the telegram.
+             * Following the descriptor telegram, the SetDisplayImage data
+             * telegrams (telegram index 0x0001 - 0xNNNN) should be sent using
+             * sequential telegram indices until the entire image has been sent
+             * to the iPod.
+             *
+             * Note: The SetDisplayImage telegram payload length is limited to
+             * 500 bytes. This telegram length limit and the size and format of
+             * the display image generally determine the minimum number of
+             * telegrams that are required to set a display image.
+             *
+             * Note: Starting with the second generation iPod nano with version
+             * 1.1.2 firmware, the use of the SetDisplayImage command is
+             * limited to once every 15 seconds over USB transport. The iPod
+             * classic and iPod 3G nano apply this restriction to both USB and
+             * UART transports. Calls made to SetDisplayImage more frequently
+             * than every 15 seconds will return a successful ACK command, but
+             * the bitmap will not be displayed on the iPod’s screen. Hence use
+             * of the SetDisplayImage command should be limited to drawing one
+             * bitmap image per accessory connect. The iPod touch will accept
+             * the SetDisplayImage command but will not draw it on the iPod’s
+             * screen.
+             *
+             * Below shows the format of a descriptor telegram. This example
+             * assumes the display image descriptor data exceeds the small
+             * telegram payload capacity; a large telegram format is shown.
+             *
+             * SetDisplayImage descriptor telegram (telegram index = 0x0000)
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x00  Telegram payload marker (large format)
+             *  3   0xNN  Large telegram payload length (bits 15:8)
+             *  4   0xNN  Large telegram payload length (bits 7:0)
+             *  5   0x04  Lingo ID: Extended Interface lingo
+             *  6   0x00  Command ID (bits 15:8)
+             *  7   0x32  Command ID (bits 7:0)
+             *  8   0x00  Descriptor telegram index (bits 15:8). These fields
+             *            uniquely identify each packet in the SetDisplayImage
+             *            transaction. The first telegram is the Descriptor
+             *            telegram and always starts with an index of 0x0000.
+             *  9   0x00  Descriptor telegram index (bits 7:0)
+             * 10   0xNN  Display pixel format code. See Below.
+             * 11   0xNN  Imagewidth in pixels (bits 15:8). The number of
+             *            pixels, from left to right, per row.
+             * 12   0xNN  Image width in pixels (bits 7:0)
+             * 13   0xNN  Image height in pixels (bits 15:8). The number of
+             *            rows, from top to bottom, in the image.
+             * 14   0xNN  Image height in pixels (bits 7:0)
+             * 15   0xNN  Row size (stride) in bytes (bits 31:24). The number of
+             *            bytes representing one row of pixels. Each row is
+             *            zero-padded to end on a 32-bit boundary. The
+             *            cumulative size, in bytes, of the image data,
+             *            transferred across all telegrams in this transaction
+             *            is effectively (Row Size * Image Height).
+             * 16   0xNN  Row size (stride) in bytes (bits 23:16)
+             * 17   0xNN  Row size (stride) in bytes (bits 15:8)
+             * 18   0xNN  Row size (stride) in bytes (bits 7:0)
+             * 19–N 0xNN  Display image pixel data
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             * SetDisplayImage data telegram (telegram index = 0x0001 - 0xNNNN)
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x00  Telegram payload marker (large format)
+             *  3   0xNN  Large telegram payload length (bits 15:8)
+             *  4   0xNN  Large telegram payload length (bits 7:0)
+             *  5   0x04  Lingo ID: Extended Interface lingo
+             *  6   0x00  Command ID (bits 15:8)
+             *  7   0x32  Command ID (bits 7:0)
+             *  8   0xNN  Descriptor telegram index (bits 15:8). These fields
+             *            uniquely identify each packet in the SetDisplayImage
+             *            transaction. The first telegram is the descriptor
+             *            telegram, shown in Table 6-68 (page 97). The
+             *            remaining n-1 telegrams are simply data telegrams,
+             *            where n is determined by the size of the image.
+             *  9   0xNN  Descriptor telegram index (bits 7:0)
+             * 10–N 0xNN  Display image pixel data
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             * Note: A known issue causes SetDisplayImage data telegram
+             * lengths less than 11 bytes to return a bad parameter error (0x04)
+             * ACK on 3G iPods.
+             *
+             * The iPod display is oriented as a rectangular grid of pixels. In
+             * the horizontal direction (x-coordinate), the pixel columns are
+             * numbered, left to right, from 0 to Cmax. In the vertical
+             * direction (y-coordinate), the pixel rows are numbered, top to
+             * bottom, from 0 to Rmax. Therefore, an (x,y) coordinate of (0,0)
+             * represents the upper-leftmost pixel on the display and
+             * (Cmax,Rmax) represents the lower-rightmost pixel on the display.
+             * A portion  of the iPod display pixel layout is shown below, where
+             * x is the column number, y is the row number, and (Cmax,Rmax)
+             * represents the maximum row and column numbers.
+             *
+             * Pixel layout
+             * x
+             * y 0,0 1,0 2,0 3,0 4,0 5,0 6,0 7,0 - Cmax,0
+             *   0,1 1,1 2,1 3,1 4,1 5,1 6,1 7,1 - Cmax,1
+             *   0,2 1,2 2,2 3,2 4,2 5,2 6,2 7,2 - Cmax,2
+             *   0,3 1,3 2,3 3,3 4,3 5,3 6,3 7,3 - Cmax,3
+             *   0,4 1,4 2,4 3,4 4,4 5,4 6,4 7,4 - Cmax,4
+             *   0,5 1,5 2,5 3,5 4,5 5,5 6,5 7,5 - Cmax,5
+             *   0,6 1,6 2,6 3,6 4,6 5,6 6,6 7,6 - Cmax,6
+             *   0,7 1,7 2,7 3,7 4,7 5,7 6,7 7,7 - Cmax,7
+             *    "   "   "   "   "   "   "   "     " "
+             *   0,  1,  2,  3,  4,  5,  6,  7,  - Cmax,
+             *   RmaxRmaxRmaxRmaxRmaxRmaxRmaxRmax  Rmax
+             *
+             * Display pixel format codes
+             * Display pixel format                 Code Protocol version
+             * Reserved                             0x00 N/A
+             * Monochrome, 2 bits per pixel         0x01 1.01
+             * RGB 565 color, little-endian, 16 bpp 0x02 1.09
+             * RGB 565 color, big-endian, 16 bpp    0x03 1.09
+             * Reserved                             0x04-0xFF N/A
+             *
+             * iPods with color screens support all three image formats. All
+             * other iPods support only display pixel format 0x01 (monochrome,
+             * 2 bpp).
+             *
+             * Display Pixel Format 0x01
+             * Display pixel format 0x01 (monochrome, 2 bits per pixel) is the
+             * pixel format supported by all iPods. Each pixel consists of 2
+             * bits that control the pixel intensity. The pixel intensities and
+             * associated binary codes are listed below.
+             *
+             * 2 bpp monochrome pixel intensities
+             * Pixel Intensity           Binary Code
+             * Pixel off (not visible)   00b
+             * Pixel on 25% (light grey) 01b
+             * Pixel on 50% (dark grey)  10b
+             * Pixel on 100% (black)     11b
+             *
+             * Each byte of image data contains four packed pixels. The pixel
+             * ordering within bytes and the byte ordering within 32 bits is
+             * shown below.
+             *
+             * Image Data Byte 0x0000
+             * Pixel0 Pixel1 Pixel2 Pixel3
+             *  7 6    5 4    3 2    1 0
+             *
+             * Image Data Byte 0x0001
+             * Pixel4 Pixel5 Pixel6 Pixel7
+             *  7 6    5 4    3 2    1 0
+             *
+             * Image Data Byte 0x0002
+             * Pixel8 Pixel9 Pixel10 Pixel11
+             *  7 6    5 4    3 2    1 0
+             *
+             * Image Data Byte 0x0003
+             * Pixel12 Pixel13 Pixel14 Pixel15
+             *  7 6      5 4    3 2     1 0
+             *
+             * Image Data Byte 0xNNNN
+             * PixelN PixelN+1 PixelN+2 PixelN+3
+             *  7 6     5 4     3 2      1 0
+             *
+             * Display Pixel Formats 0x02 and 0x03
+             * Display pixel format 0x02 (RGB 565, little-endian) and display
+             * pixel format 0x03 (RGB 565, big-endian) are available for use
+             * in all iPods with color screens. Each pixel consists of 16 bits
+             * that control the pixel intensity for the colors red, green, and
+             * blue.
+             * It takes two bytes to represent a single pixel. Red is
+             * represented by 5 bits, green is represented by 6 bits, and blue
+             * by the final 5 bits. A 32-bit sequence represents 2 pixels. The
+             * pixel ordering within bytes and the byte ordering within 32 bits
+             * for display format 0x02 (RGB 565, little-endian) is shown below.
+             *
+             * Image Data Byte 0x0000
+             * Pixel 0, lower 3 bits of green Pixel 0, all 5 bits of blue
+             * 7 6 5                                   4 3 2 1 0
+             * Image Data Byte 0x0001
+             * Pixel 0, all 5 bits of red     Pixel 0,upper 3 bits of green
+             * 7 6 5 4 3                               2 1 0
+             *
+             * Image Data Byte 0x0002
+             * Pixel 1, lower 3 bits of green Pixel 1, all 5 bits of blue
+             * 7 6 5                                   4 3 2 1 0
+             *
+             * Image Data Byte 0x0003
+             * Pixel 1, all 5 bits of red     Pixel 1, upper 3 bits of green
+             * 7 6 5 4 3                               2 1 0
+             *
+             * The format for display pixel format 0x03 (RGB 565, big-endian, 16
+             * bpp) is almost identical, with the exception that bytes 0 and 1
+             * are swapped and bytes 2 and 3 are swapped.
+             *
+             */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0033: /* GetMonoDisplayImageLimits */
+
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the limiting characteristics of the monochrome image
+             * that can be sent to the iPod for display while it is connected
+             * to the device. It can be used to determine the display pixel
+             * format and maximum width and height of a monochrome image to be
+             * set using the Command 0x0032: SetDisplayImage telegram. In
+             * response, the iPod sends a Command 0x0034:
+             * ReturnMonoDisplayImageLimits telegram to the device with the
+             * requested display information. The GetMonoDisplayImageLimits
+             * command is supported by iPods with either monochrome or color
+             * displays. To obtain color display image limits, use Command
+             * 0x0039: GetColorDisplayImageLimits.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x33  Command ID (bits 7:0)
+             *  6   0xC6  Telegram payload checksum byte
+             *
+             */
+            unsigned char data[] = {0x04, 0x00, 0x34,
+                                    LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
+                                    LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
+                                    0x01};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0034: /* ReturnMonoDisplayImageLimits. See Above*/
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the limiting characteristics of the monochrome image that
+             * can be sent to the iPod for display while it is connected to the
+             * device. The iPod sends this telegram in response to the Command
+             * 0x0033: GetMonoDisplayImageLimits telegram. Monochrome display
+             * characteristics include maximum image width and height and the
+             * display pixel format.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x34  Command ID (bits 7:0)
+             *  6   0xNN  Maximum image width in pixels (bits 15:8)
+             *  7   0xNN  Maximum image width in pixels (bits 7:0)
+             *  8   0xNN  Maximum image height in pixels (bits 15:8)
+             *  9   0xNN  Maximumimage height in pixels (bits 7:0)
+             * 10   0xNN  Display pixel format (see Table 6-70 (page 99)).
+             * 11   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0035: /* GetNumPlayingTracks */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the number of tracks in the list of tracks queued to
+             * play on the iPod. In response, the iPod sends a Command 0x0036:
+             * ReturnNumPlayingTracks telegram with the count of tracks queued
+             * to play.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x35  Command ID (bits 7:0)
+             *  6   0xC4  Telegram payload checksum byte
+             *
+             */
+            unsigned char data[] = {0x04, 0x00, 0x36,
+                                    0x00, 0x00, 0x00, 0x00};
+            unsigned long playlist_amt = playlist_amount();
+            put_u32(&data[3], playlist_amt);
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x0036: /* ReturnNumPlayingTracks. See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the number of tracks in the actual list of tracks queued
+             * to play, including the currently playing track (if any). The
+             * iPod sends this telegram in response to the Command 0x0035:
+             * GetNumPlayingTracks telegram.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x36  Command ID (bits 7:0)
+             *  6   0xNN  Number of tracks playing(bits 31:24)
+             *  7   0xNN  Number of tracks playing(bits 23:16)
+             *  8   0xNN  Number of tracks playing (bits 15:8)
+             *  9   0xNN  Number of tracks playing (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x0037: /* SetCurrentPlayingTrack */
+            /* The following is the description for the Apple Firmware
+             *
+             * Sets the index of the track to play in the Now Playing playlist
+             * on the iPod. The index that is specified here is obtained by
+             * sending the Command 0x0035: GetNumPlayingTracks and Command
+             * 0x001E: GetCurrentPlayingTrackIndex telegrams to obtain the
+             * number of playing tracks and the current playing track index,
+             * respectively. In response, the iPod sends an ACK telegram
+             * indicating the status of the command.
+             *
+             * Note: The behavior of this command has changed. Before the
+             * 2G nano, if this command was sent with the current playing track
+             * index the iPod would pause playback momentarily and then resume.
+             * Starting with the 2G nano, the iPod restarts playback of the
+             * current track from the beginning. Older iPods will not be
+             * updated to the new behavior.
+             *
+             * Note: This command is usable only when the iPod is in a playing
+             * or paused state. If the iPod is stopped, this command fails.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x07  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x37  Command ID (bits 7:0)
+             *  6   0xNN  New current playing track index (bits 31:24)
+             *  7   0xNN  New current playing track index (bits 23:16)
+             *  8   0xNN  New current playing track index (bits 15:8)
+             *  9   0xNN  New current playing track index (bits 7:0)
+             * 10   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+            long tracknum = get_u32(&buf[3]);
+
+            audio_pause();
+            audio_skip(tracknum - playlist_next(0));
+            if (!paused)
+                audio_resume();
+
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0038: /* SelectSortDBRecord */
+            /* The following is the description for the Apple Firmware
+             *
+             * Selects one or more records in the iPod database, based on a
+             * category-relative index. For example, selecting category 2
+             * (Artist), record index 1, and sort order 3 (Album) results in a
+             * list of selected tracks (records) from the second artist in the
+             * artist list, sorted by album name. Selections are additive and
+             * limited by the category hierarchy. Subsequent selections are
+             * made based on the subset of records resulting from previous
+             * selections and not from the entire database. The database
+             * category types are shown above. The sort order options and codes
+             * are shown below.
+             *
+             * SelectSortDBRecord telegram
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x09  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x38  Command ID (bits 7:0)
+             *  6   0xNN  Database category type.
+             *  7   0xNN  Category record index (bits 31:24)
+             *  8   0xNN  Category record index (bits 23:16)
+             *  9   0xNN  Category record index (bits 15:8)
+             * 10   0xNN  Category record index (bits 7:0)
+             * 11   0xNN  Database sort type.
+             * 12   0xNN  Telegram payload checksum byte
+             *
+             * Database sort order options
+             * Sort Order Code Protocol version
+             * Sort by genre         0x00 1.00
+             * Sort by artist        0x01 1.00
+             * Sort by composer      0x02 1.00
+             * Sort by album         0x03 1.00
+             * Sort by name          0x04 1.00
+             * Sort by playlist      0x05 1.00
+             * Sort by release date  0x06 1.08
+             * Reserved              0x07 - 0xFE N/A
+             * Use default sort type 0xFF 1.00
+             *
+             * The default order of song and audiobook tracks on the iPod is
+             * alphabetical by artist, then alphabetical by that artist's
+             * albums, then ordered according to the order of the tracks on the
+             * album.
+             * For podcasts, the default order of episode tracks is reverse
+             * chronological. That is, the newest ones are first,then
+             * alphabetical by podcast name.
+             * The SelectSortDBRecord command can be used to sort all the song
+             * and audiobook tracks on the iPod alphabetically as follows:
+             * 1. Command 0x0016: ResetDBSelection
+             * 2. Command 0x0018: GetNumberCategorizedDBRecords for the Playlist
+             *                    category.
+             * 3. SelectSortDBRecord based on the Playlist category, using a
+             *                    record index of 0 to select the All Tracks
+             *                    playlist and the sort by name (0x04) sort
+             *                    order.
+             * 4. GetNumberCategorizedDBRecords for the Track category.
+             * 5. Command 0x001A :RetrieveCategorizedDatabaseRecords based on
+             *                    the Track category, using a start index of 0
+             *                    and an end index of the number of records
+             *                    returned by the call to
+             *                    GetNumberCategorizedDBRecords in step 4.
+             *
+             * The sort order of artist names ignores certain articles such
+             * that the artist “The Doors” is sorted under the letter ‘D’ and
+             * not ‘T’; this matches the behavior of iTunes. The sort order is
+             * different depending on the language setting used in the iPod.
+             * The list of ignored articles may change in the future without
+             * notice.
+             * The SelectDBRecord command may also be used to select a database
+             * record with the default sort order.
+             *
+             * Note: The sort order field is ignored for the Audiobook category.
+             * Audiobooks are automatically sorted by track title. The sort
+             * order for podcast tracks defaults to release date, with the
+             * newest track coming first.
+             *
+             * Selects one or more records in the iPod database, based on a
+             * category-relative index. This appears to be hardcoded hierarchy
+             * decided by Apple that the external devices follow.
+             * This is as follows for all except podcasts,
+             *
+             * All (highest level),
+             * Playlist,
+             * Genre or Media Kind,
+             * Artist or Composer,
+             * Album,
+             * Track or Audiobook (lowest)
+             *
+             * for Podcasts, the order is
+             *
+             * All (highest),
+             * Podcast,
+             * Episode
+             * Track (lowest)
+             *
+             * Categories are
+             *
+             * 0x00 Reserved
+             * 0x01 Playlist
+             * 0x02 Artist
+             * 0x03 Album
+             * 0x04 Genre
+             * 0x05 Track
+             * 0x06 Composer
+             * 0x07 Audiobook
+             * 0x08 Podcast
+             * 0x09 - 0xff Reserved
+             *
+             * Sort Order optiona and codes are
+             *
+             * 0x00 Sort by Genre
+             * 0x01 Sort by Artist
+             * 0x02 Sort by Composer
+             * 0x03 Sort by Album
+             * 0x04 Sort by Name (Song Title)
+             * 0x05 Sort by Playlist
+             * 0x06 Sort by Release Date
+             * 0x07 - 0xfe Reserved
+             * 0xff Use default Sort Type
+             *
+             * Packet format (offset in data[]: Description)
+             * 0x00: Lingo ID: Extended Interface Protocol Lingo, always 0x04
+             * 0x01-0x02: Command, always 0x0038
+             * 0x03: Database Category Type
+             * 0x04-0x07:  Category Record Index
+             * 0x08 Database Sort Type
+             *
+             * On Rockbox, if the recordtype is playlist, we load the selected
+             * playlist and start playing from the first track.
+             * If the recordtype is track, we play that track from the current
+             * playlist.
+             * On anything else we just play the current track from the current
+             * playlist.
+             * cur_dbrecord[0] is the recordtype
+             * cur_dbrecord[1-4] is the u32 of the record number requested
+             * which might be a playlist or a track number depending on
+             * the value of cur_dbrecord[0]
+             */
+        {
+            memcpy(cur_dbrecord, buf + 3, 5);
+
+            int paused = (is_wps_fading() || (audio_status() & AUDIO_STATUS_PAUSE));
+            unsigned int number_of_playlists = nbr_total_playlists();
+            uint32_t index;
+            uint32_t trackcount;
+            index = get_u32(&cur_dbrecord[1]);
+            trackcount = playlist_amount();
+            if ((cur_dbrecord[0] == 0x05) && (index > trackcount))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            if ((cur_dbrecord[0] == 0x01) && (index > (number_of_playlists + 1)))
+            {
+                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                break;
+            }
+            switch (cur_dbrecord[0])
+            {
+                case 0x01: /* Playlist*/
+                {
+                    if (index != 0x00) /* 0x00 is the On-The-Go Playlist and
+                                          we do nothing with it  */
+                    {
+                        audio_pause();
+                        audio_skip(-iap_get_trackindex());
+                        playlist_sort(NULL, true);
+                        last_selected_playlist = index;
+                        seek_to_playlist(last_selected_playlist);
+                    }
+                    break;
+                }
+                case 0x02: /* Artist   Do Nothing  */
+                case 0x03: /* Album    Do Nothing  */
+                case 0x04: /* Genre    Do Nothing  */
+                case 0x06: /* Composer Do Nothing  */
+                    break;
+                case 0x05: /* Track*/
+                {
+                    audio_pause();
+                    audio_skip(-iap_get_trackindex());
+                    playlist_sort(NULL, true);
+                    audio_skip(index - playlist_next(0));
+                    break;
+                }
+            }
+            if (!paused)
+                audio_resume();
+            /* respond with cmd ok packet */
+            cmd_ok(cmd);
+            break;
+        }
+        case 0x0039: /* GetColorDisplayImageLimits */
+            /* The following is the description for the Apple Firmware
+             *
+             * Requests the limiting characteristics of the color image that
+             * can be sent to the iPod for display while it is connected to
+             * the device. It can be used to determine the display pixel format
+             * and maximum width and height of a color image to be set using
+             * the Command 0x0032: SetDisplayImage telegram. In response, the
+             * iPod sends a Command 0x003A: ReturnColorDisplayImageLimits
+             * telegram to the device with the requested display information.
+             * This command is supported only by iPods with color displays.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x03  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x39  Command ID (bits 7:0)
+             *  6   0xC0  Telegram payload checksum byte
+             *
+             */
+        {
+            /* Set the same as the ReturnMonoDisplayImageLimits */
+            unsigned char data[] = {0x04, 0x00, 0x3A,
+                                    LCD_WIDTH >> 8, LCD_WIDTH & 0xff,
+                                    LCD_HEIGHT >> 8, LCD_HEIGHT & 0xff,
+                                    0x01};
+            iap_send_pkt(data, sizeof(data));
+            break;
+        }
+        case 0x003A: /* ReturnColorDisplayImageLimits See Above */
+            /* The following is the description for the Apple Firmware
+             *
+             * Returns the limiting characteristics of the color image that can
+             * be sent to the iPod for display while it is connected to the
+             * device. The iPod sends this telegram in response to the Command
+             * 0x0039: GetColorDisplayImageLimits telegram. Display
+             * characteristics include maximum image width and height and the
+             * display pixel format.
+             *
+             * Note: If the iPod supports multiple display image formats, a five
+             * byte block of additional image width, height, and pixel format
+             * information is appended to the payload for each supported display
+             * format. The list of supported color display image formats
+             * returned by the iPod may change in future software versions.
+             * Devices must be able to parse a variable length list of supported
+             * color display formats to search for compatible formats.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0xNN  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x3A  Command ID (bits 7:0)
+             *  6   0xNN  Maximum image width in pixels (bits 15:8)
+             *  7   0xNN  Maximum image width in pixels (bits 7:0)
+             *  8   0xNN  Maximum image height in pixels (bits 15:8)
+             *  9   0xNN  Maximum image height in pixels (bits 7:0)
+             * 10   0xNN  Display pixel format (see Table 6-70 (page 99)).
+             * 11-N 0xNN  Optional display image width, height, and pixel format
+             *            for the second to nth supported display formats,
+             *            if present.
+             * NN   0xNN  Telegram payload checksum byte
+             *
+             */
+        {
+            /* We should NEVER receive this command so ERROR if we do */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+        case 0x003B: /* ResetDBSelectionHierarchy */
+        {
+            /* The following is the description for the Apple Firmware
+             *
+             * This command carries a single byte in its payload (byte 6). A
+             * hierarchy selection value of 0x01 means that the accessory wants
+             * to navigate the music hierarchy; a hierarchy selection value of
+             * 0x02 means that the accessory wants to navigate the video
+             * hierarchy.
+             *
+             * Byte Value Meaning
+             *  0   0xFF  Sync byte (required only for UART serial)
+             *  1   0x55  Start of telegram
+             *  2   0x04  Telegram payload length
+             *  3   0x04  Lingo ID: Extended Interface lingo
+             *  4   0x00  Command ID (bits 15:8)
+             *  5   0x3B  Command ID (bits 7:0)
+             *  6   0x01 or 0x02 Hierarchy selection
+             *  7   0xNN Telegram payload checksum byte
+             *
+             * Note: The iPod will return an error if a device attempts to
+             * enable an unsupported hierarchy, such as a video hierarchy on an
+             * iPod  model that does not support video.
+             */
+            dbrecordcount = 0;
+            cur_dbrecord[0] = 0;
+            put_u32(&cur_dbrecord[1],0);
+            switch (buf[3])
+            {
+                case 0x01: /* Music */
+                {
+                    cmd_ok(cmd);
+                    break;
+                }
+                default: /* Anything else */
+                {
+                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+                    break;
+                }
+            }
+        }
+        default:
+        {
+#ifdef LOGF_ENABLE
+            logf("iap: Unsupported Mode04 Command");
+#endif
+            /* The default response is IAP_ACK_BAD_PARAM */
+            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
+            break;
+        }
+    }
+}
diff --git a/apps/misc.c b/apps/misc.c
index 8dff227..e746c43 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -607,14 +607,6 @@ long default_event_handler_ex(long event, void (*callback)(void \
*), void *parame  unplug_change(false);
             return SYS_PHONE_UNPLUGGED;
 #endif
-#ifdef IPOD_ACCESSORY_PROTOCOL
-        case SYS_IAP_PERIODIC:
-            iap_periodic();
-            return SYS_IAP_PERIODIC;
-        case SYS_IAP_HANDLEPKT:
-            iap_handlepkt();
-            return SYS_IAP_HANDLEPKT;
-#endif
 #if CONFIG_PLATFORM & (PLATFORM_ANDROID|PLATFORM_MAEMO)
         /* stop playback if we receive a call */
         case SYS_CALL_INCOMING:
diff --git a/firmware/export/iap.h b/firmware/export/iap.h
index 8ee26cb..22f3608 100644
--- a/firmware/export/iap.h
+++ b/firmware/export/iap.h
@@ -22,7 +22,9 @@
 
 #include <stdbool.h>
 
-#define RX_BUFLEN 512
+/* This is just the payload size, without sync, length and checksum */
+#define RX_BUFLEN (64*1024)
+/* This is the entire frame length, sync, length, payload and checksum */
 #define TX_BUFLEN 128
 
 extern bool iap_getc(unsigned char x);
diff --git a/firmware/export/kernel.h b/firmware/export/kernel.h
index 76ed96b..3cadefd 100644
--- a/firmware/export/kernel.h
+++ b/firmware/export/kernel.h
@@ -79,8 +79,6 @@
 #define SYS_REMOTE_PLUGGED        MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 4)
 #define SYS_REMOTE_UNPLUGGED      MAKE_SYS_EVENT(SYS_EVENT_CLS_PLUG, 5)
 #define SYS_CAR_ADAPTER_RESUME    MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 0)
-#define SYS_IAP_PERIODIC          MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 1)
-#define SYS_IAP_HANDLEPKT         MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 2)
 #define SYS_CALL_INCOMING         MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 3)
 #define SYS_CALL_HUNG_UP          MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 4)
 #define SYS_VOLUME_CHANGED        MAKE_SYS_EVENT(SYS_EVENT_CLS_MISC, 5)
diff --git a/firmware/target/arm/pp/debug-pp.c b/firmware/target/arm/pp/debug-pp.c
index f4ba61c..2f57e1e 100644
--- a/firmware/target/arm/pp/debug-pp.c
+++ b/firmware/target/arm/pp/debug-pp.c
@@ -155,9 +155,12 @@ bool dbg_ports(void)
 
 #if defined(IPOD_ACCESSORY_PROTOCOL)
         const unsigned char *serbuf = iap_get_serbuf();
-        lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x", 
-         serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5],
-         serbuf[6], serbuf[7]);
+        if (serbuf)
+        {
+            lcd_putsf(0, line++, "IAP: %02x %02x %02x %02x %02x %02x %02x %02x",
+             serbuf[0], serbuf[1], serbuf[2], serbuf[3], serbuf[4], serbuf[5],
+             serbuf[6], serbuf[7]);
+        }
 #endif
 
 #if defined(IRIVER_H10) || defined(IRIVER_H10_5GB)
diff --git a/tools/iap/Device/iPod.pm b/tools/iap/Device/iPod.pm
new file mode 100644
index 0000000..b2d686c
--- /dev/null
+++ b/tools/iap/Device/iPod.pm
@@ -0,0 +1,386 @@
+package Device::iPod;
+
+use Device::SerialPort;
+use POSIX qw(isgraph);
+use strict;
+
+sub new {
+    my $class = shift;
+    my $port = shift;
+    my $self = {};
+    my $s;
+
+    $self->{-serial} = undef;
+    $self->{-inbuf} = '';
+    $self->{-error} = undef;
+    $self->{-baudrate} = 57600;
+    $self->{-debug} = 0;
+
+    return bless($self, $class);
+}
+
+sub open {
+    my $self = shift;
+    my $port = shift;
+
+    $self->{-serial} = new Device::SerialPort($port);
+    unless(defined($self->{-serial})) {
+        $self->{-error} = $!;
+        return undef;
+    }
+
+    $self->{-serial}->parity('none');
+    $self->{-serial}->databits(8);
+    $self->{-serial}->stopbits(1);
+    $self->{-serial}->handshake('none');
+    return $self->baudrate($self->{-baudrate});
+}
+
+sub baudrate {
+    my $self = shift;
+    my $baudrate = shift;
+
+    if ($baudrate < 1) {
+        $self->{-error} = "Invalid baudrate";
+        return undef;
+    }
+
+    $self->{-baudrate} = $baudrate;
+    if (defined($self->{-serial})) {
+        $self->{-serial}->baudrate($baudrate);
+    }
+
+    return 1;
+}
+
+sub sendmsg {
+    my $self = shift;
+    my $lingo = shift;
+    my $command = shift;
+    my $data = shift || '';
+
+    return $self->_nosetup() unless(defined($self->{-serial}));
+
+    if (($lingo < 0) || ($lingo > 255)) {
+        $self->{-error} = 'Invalid lingo';
+        return undef;
+    }
+
+    if ($command < 0) {
+        $self->{-error} = 'Invalid command';
+        return undef;
+    }
+
+    if ($lingo == 4) {
+        if ($command > 0xffff) {
+            $self->{-error} = 'Invalid command';
+            return undef;
+        }
+        return $self->_send($self->_frame_cmd(pack("Cn", $lingo, $command) . \
$data)); +    } else {
+        if ($command > 0xff) {
+            $self->{-error} = 'Invalid command';
+            return undef;
+        }
+        return $self->_send($self->_frame_cmd(pack("CC", $lingo, $command) . \
$data)); +    }
+}
+
+sub sendraw {
+    my $self = shift;
+    my $data = shift;
+
+    return $self->_nosetup() unless(defined($self->{-serial}));
+
+    return $self->_send($data);
+}
+
+sub recvmsg {
+    my $self = shift;
+    my $m;
+    my @m;
+
+    return $self->_nosetup() unless(defined($self->{-serial}));
+
+    $m = $self->_fillbuf();
+    unless(defined($m)) {
+        # Error was set by lower levels
+        return wantarray?():undef;
+    }
+
+    printf("Fetched %s\n", $self->_hexstring($m)) if $self->{-debug};
+
+    @m = $self->_unframe_cmd($m);
+
+    unless(@m) {
+        return undef;
+    }
+
+    if (wantarray()) {
+        return @m;
+    } else {
+        return {-lingo => $m[0], -cmd => $m[1], -payload => $m[2]};
+    }
+}
+
+sub emptyrecv {
+    my $self = shift;
+    my $m;
+
+    while ($m = $self->_fillbuf()) {
+        printf("Discarded %s\n", $self->_hexstring($m)) if (defined($m) && \
$self->{-debug}); +    }
+}
+
+sub error {
+    my $self = shift;
+
+    return $self->{-error};
+}
+
+sub _nosetup {
+    my $self = shift;
+
+    $self->{-error} = 'Serial port not setup';
+    return undef;
+}
+
+sub _frame_cmd {
+    my $self = shift;
+    my $data = shift;
+    my $l = length($data);
+    my $csum;
+
+    if ($l > 0xffff) {
+        $self->{-error} = 'Command too long';
+        return undef;
+    }
+
+    if ($l > 255) {
+        $data = pack("Cn", 0, length($data)) . $data;
+    } else {
+        $data = pack("C", length($data)) . $data;
+    }
+
+    foreach (unpack("C" x length($data), $data)) {
+        $csum += $_;
+    }
+    $csum &= 0xFF;
+    $csum = 0x100 - $csum;
+
+    return "\xFF\x55" . $data . pack("C", $csum);
+}
+
+sub _unframe_cmd {
+    my $self = shift;
+    my $data = shift;
+    my $payload = '';
+    my ($count, $length, $csum);
+    my $state = 0;
+    my $c;
+    my ($lingo, $cmd);
+
+    return () unless(defined($data));
+
+    foreach $c (unpack("C" x length($data), $data)) {
+        if ($state == 0) {
+            # Wait for sync
+            next unless($c == 255);
+            $state = 1;
+        } elsif ($state == 1) {
+            # Wait for sop
+            next unless($c == 85);
+            $state = 2;
+        } elsif ($state == 2) {
+            # Length (short frame)
+            $csum = $c;
+            if ($c == 0) {
+                # Large frame
+                $state = 3;
+            } else {
+                $state = 5;
+            }
+            $length = $c;
+            $count = 0;
+            next;
+        } elsif ($state == 3) {
+            # Large frame, hi
+            $csum += $c;
+            $length = ($c << 8);
+            $state = 4;
+            next;
+        } elsif ($state == 4) {
+            # Large frame, lo
+            $csum += $c;
+            $length |= $c;
+            if ($length == 0) {
+                $self->{-error} = 'Length is 0';
+                return ();
+            }
+            $state = 5;
+            next;
+        } elsif ($state == 5) {
+            # Data bytes
+            $csum += $c;
+            $payload .= chr($c);
+            $count += 1;
+            if ($count == $length) {
+                $state = 6;
+            }
+        } elsif ($state == 6) {
+            # Checksum byte
+            $csum += $c;
+            if (($csum & 0xFF) != 0) {
+                $self->{-error} = 'Invalid checksum';
+                return ();
+            }
+            $state = 7;
+            last;
+        } else {
+            $self->{-error} = 'Invalid state';
+            return ();
+        }
+    }
+
+    # If we get here, we either have data or not. Check.
+    if ($state != 7) {
+        $self->{-error} = 'Could not unframe data';
+        return ();
+    }
+
+    $lingo = unpack("C", $payload);
+    if ($lingo == 4) {
+        return unpack("Cna*", $payload);
+    } else {
+        return unpack("CCa*", $payload);
+    }
+}
+
+sub _send {
+    my $self = shift;
+    my $data = shift;
+    my $l = length($data);
+    my $c;
+
+    printf("Sending %s\n", $self->_hexstring($data)) if $self->{-debug};
+
+    $c = $self->{-serial}->write($data);
+    unless(defined($c)) {
+        $self->{-error} = 'write failed';
+        return undef;
+    }
+
+    if ($c != $l) {
+        $self->{-error} = 'incomplete write';
+        return undef;
+    }
+
+    return 1;
+}
+
+sub _fillbuf {
+    my $self = shift;
+    my $timeout = shift || 2;
+    my $to;
+
+    # Read from the port until we have a complete message in the buffer,
+    # or until we haven't read any new data for $timeout seconds, whatever
+    # comes first.
+
+    $to = $timeout;
+
+    while(!$self->_message_in_buffer() && $to > 0) {
+        my ($c, $s) = $self->{-serial}->read(255);
+        if ($c == 0) {
+            # No data read
+            select(undef, undef, undef, 0.1);
+            $to -= 0.1;
+        } else {
+            $self->{-inbuf} .= $s;
+            $to = $timeout;
+        }
+    }
+    if ($self->_message_in_buffer()) {
+        # There is a complete message in the buffer
+        return $self->_message();
+    } else {
+        # Timeout occured
+        $self->{-error} = 'Timeout reading from port';
+        return undef;
+    }
+}
+
+sub _message_in_buffer {
+    my $self = shift;
+    my $sp = 0;
+    my $i;
+
+    $i = index($self->{-inbuf}, "\xFF\x55", $sp);
+    while ($i != -1) {
+        my $header;
+        my $len;
+        my $large = 0;
+
+
+        $header = substr($self->{-inbuf}, $i, 3);
+        if (length($header) != 3) {
+            # Runt frame
+            return ();
+        }
+        $len = unpack("x2C", $header);
+        if ($len == 0) {
+            # Possible large frame
+            $header = substr($self->{-inbuf}, $i, 5);
+            if (length($header) != 5) {
+                # Runt frame
+                return ();
+            }
+            $large = 1;
+            $len = unpack("x3n", $header);
+        }
+
+        # Add framing, checksum and length
+        $len = $len+3+($large?3:1);
+
+        if (length($self->{-inbuf}) < ($i+$len)) {
+            # Buffer too short to hold rest of frame. Try again.
+            $sp = $i+1;
+            $i = index($self->{-inbuf}, "\xFF\x55", $sp);
+        } else {
+            return ($i, $len);
+        }
+    }
+
+    # No complete message found
+    return ();
+}
+
+
+sub _message {
+    my $self = shift;
+    my $start;
+    my $len;
+    my $m;
+
+    # Return the first complete message in the buffer, removing the message
+    # and everything before it from the buffer.
+    ($start, $len) = $self->_message_in_buffer();
+    unless(defined($start)) {
+        $self->{-error} = 'No complete message in buffer';
+        return undef;
+    }
+    $m = substr($self->{-inbuf}, $start, $len);
+    $self->{-inbuf} = substr($self->{-inbuf}, $start+$len);
+
+    return $m;
+}
+
+sub _hexstring {
+    my $self = shift;
+    my $s = shift;
+
+    return join("", map { (($_ == 0x20) || \
isgraph(chr($_)))?chr($_):sprintf("\\x%02x", $_) } +            unpack("C" x \
length($s), $s)); +}
+
+1;
diff --git a/tools/iap/Makefile b/tools/iap/Makefile
new file mode 100644
index 0000000..86f3760
--- /dev/null
+++ b/tools/iap/Makefile
@@ -0,0 +1,7 @@
+default: test
+
+test:
+	perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV' ipod*.t
+
+moduletest:
+	perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV' device-ipod.t
diff --git a/tools/iap/README b/tools/iap/README
new file mode 100644
index 0000000..dbd050f
--- /dev/null
+++ b/tools/iap/README
@@ -0,0 +1,23 @@
+These are perl test scripts for validating the IAP implementation.
+Also included is a perl class for talking to an iPod via the serial
+port. You will probably need Linux to use this.
+
+Run "make moduletest" to test the perl module itself. This will not
+require any serial connection, or even an iPod, for that matter.
+
+Run "make test" to run the iPod communication tests themselves.
+
+In order to test make sure
+
+- the iPod is connected to a serial port
+- the test scripts assume that this port is /dev/ttyUSB0. Change
+  as neccessary
+
+Sometimes, tests will time out instead of giving the desired result.
+As long as the timeouts are not reproducable this is usually not a
+problem. The serial port is known to be unreliable, and devices will
+retransmit. This happens even with the OF.
+
+The tests were designed against an iPod Touch 2G as a reference device.
+Some older iPods fail some of the test, even with the OF, because of
+behaviour changes in later firmware releases by Apple.
diff --git a/tools/iap/device-ipod.t b/tools/iap/device-ipod.t
new file mode 100644
index 0000000..607184e
--- /dev/null
+++ b/tools/iap/device-ipod.t
@@ -0,0 +1,74 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+# Frame a short command
+$m = $ipod->_frame_cmd("\x00\x02\x00\x06");
+ok(defined($m) && ($m eq "\xFF\x55\x04\x00\x02\x00\x06\xF4"), "Framed command \
valid"); +
+# Frame a long command
+$m = $ipod->_frame_cmd("\x00" x 1024);
+ok(defined($m) && ($m eq "\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . "\xFC"), "Long \
framed command valid"); +
+# Frame an overly long command
+$m = $ipod->_frame_cmd("\x00" x 65537);
+ok(!defined($m) && ($ipod->error() =~ 'Command too long'), "Overly long command \
failed"); +
+# Unframe a short command
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x04\x00\x02\x00\x06\xF4");
+ok(defined($l) && ($l == 0x00) && ($c == 0x02) && ($p eq "\x00\x06"), "Unframed \
short command"); +
+# Unframe a long command
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x04\x00" . ("\x00" x 1024) . \
"\xFC"); +ok(defined($l) && ($l == 0x00) && ($c == 0x00) && ($p eq "\x00" x 1022), \
"Unframed long command"); +
+# Frame without sync byte
+($l, $c, $p) = $ipod->_unframe_cmd("\x00");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without sync \
byte failed"); +
+# Frame without SOP byte
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Frame without SOP \
byte failed"); +
+# Frame with length 0
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x00\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Length is 0/), "Frame with length 0 failed");
+
+# Too short frame
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Could not unframe data/), "Too short frame \
failed"); +
+# Invalid checksum
+($l, $c, $p) = $ipod->_unframe_cmd("\xFF\x55\x03\x00\x00\x00\x00");
+ok(!defined($l) && ($ipod->error() =~ /Invalid checksum/), "Invalid checksum \
failed"); +
+# Find a message in a string
+$ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4";
+($c, $l) = $ipod->_message_in_buffer();
+ok(defined($l) && ($c == 6) && ($l == 8), "Found message in buffer");
+
+# Return message from a string
+$ipod->{-inbuf} = "\x00\xFF\x55\x00\xFF\xFF\xFF\x55\x04\x00\x02\x00\x06\xF4\x00";
+$m = $ipod->_message();
+ok(defined($m) && ($ipod->{-inbuf} eq "\x00"), "Retrieved message from buffer");
+
+# Return two messages from buffer
+$ipod->{-inbuf} = "\xffU\x04\x00\x02\x00\x13\xe7\xffU\x02\x00\x14\xea";
+$m = $ipod->_message();
+subtest "First message" => sub {
+    ok(defined($m), "Message received");
+    is($m, "\xffU\x04\x00\x02\x00\x13\xe7");
+};
+$m = $ipod->_message();
+subtest "Second message" => sub {
+    ok(defined($m), "Message received");
+    is($m, "\xffU\x02\x00\x14\xea");
+};
diff --git a/tools/iap/iap-verbose.pl b/tools/iap/iap-verbose.pl
new file mode 100644
index 0000000..ed25de7
--- /dev/null
+++ b/tools/iap/iap-verbose.pl
@@ -0,0 +1,1856 @@
+#!/usr/bin/perl -w
+
+package iap::decode;
+
+use Device::iPod;
+use Data::Dumper;
+use strict;
+
+sub new {
+    my $class = shift;
+    my $self = {-state => {}};
+
+    return bless($self, $class);
+}
+
+sub display {
+    my $self = shift;
+    my $lingo = shift;
+    my $command = shift;
+    my $data = shift;
+    my $name;
+    my $handler;
+    my $r;
+
+
+    $name = sprintf("_h_%02x_%04x", $lingo, $command);
+    $handler = $self->can($name);
+    if ($handler) {
+        unless(exists($self->{-state}->{$name})) {
+            $self->{-state}->{$name} = {};
+        }
+        $r = $handler->($self, $data, $self->{-state}->{$name});
+    } else {
+        $r = $self->generic($lingo, $command, $data);
+    }
+
+    printf("\n");
+    return $r;
+}
+
+sub generic {
+    my $self = shift;
+    my $lingo = shift;
+    my $command = shift;
+    my $data = shift;
+
+    printf("Unknown command\n");
+    printf(" Lingo:     0x%02x\n", $lingo);
+    printf(" Command    0x%04x\n", $command);
+    printf(" Data:      %s\n", Device::iPod->_hexstring($data));
+
+    exit(1);
+
+    return 1;
+}
+
+sub _h_00_0001 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $lingo = shift;
+
+    $lingo = unpack("C", $data);
+
+    printf("Identify (0x00, 0x01) D->I\n");
+    printf(" Lingo:               0x%02x\n", $lingo);
+
+    return 1;
+}
+
+sub _h_00_0002 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+    my $cmd;
+    my $delay;
+
+    ($res, $cmd, $delay) = unpack("CCN", $data);
+
+    printf("ACK (0x00, 0x02) I->D\n");
+    printf(" Acknowledged command: 0x%02x\n", $cmd);
+    printf(" Status:               %s (%d)\n",
+        ("Success",
+         "ERROR: Unknown Database Category",
+         "ERROR: Command Failed",
+         "ERROR: Out Of Resource",
+         "ERROR: Bad Parameter",
+         "ERROR: Unknown ID",
+         "Command Pending",
+         "ERROR: Not Authenticated",
+         "ERROR: Bad Authentication Version",
+         "ERROR: Accessory Power Mode Request Failed",
+         "ERROR: Certificate Invalid",
+         "ERROR: Certificate permissions invalid",
+         "ERROR: File is in use",
+         "ERROR: Invalid file handle",
+         "ERROR: Directory not empty",
+         "ERROR: Operation timed out",
+         "ERROR: Command unavailable in this iPod mode",
+         "ERROR: Invalid accessory resistor ID",
+         "Reserved",
+         "Reserved",
+         "Reserved",
+         "ERROR: Maximum number of accessory connections already reached")[$res], \
$res); +    if ($res == 6) {
+        $delay = unpack("xxN", $data);
+        printf(" Delay:                %d ms\n", $delay);
+    }
+
+    return 1;
+}
+
+sub _h_00_0005 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("EnterRemoteUIMode (0x00, 0x05) D->I\n");
+
+    return 1;
+}
+
+sub _h_00_000d {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("RequestiPodModelNum (0x00, 0x0D) D->I\n");
+
+    return 1;
+}
+
+sub _h_00_000e {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($modelnum, $name);
+
+    ($modelnum, $name) = unpack("NZ*", $data);
+
+    printf("ReturniPodModelNum (0x00, 0x0E) I->D\n");
+    printf(" Model number:          %08x\n", $modelnum);
+    printf(" Model name:            %s\n", $name);
+
+    return 1;
+}
+
+sub _h_00_000f {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $lingo;
+
+    $lingo = unpack("C", $data);
+
+    printf("RequestLingoProtocolVersion (0x00, 0x0F) D->I\n");
+    printf(" Lingo:               0x%02x\n", $lingo);
+
+    return 1;
+}
+
+sub _h_00_0010 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($lingo, $maj, $min);
+
+    ($lingo, $maj, $min) = unpack("CCC", $data);
+
+    printf("ReturnLingoProtocolVersion (0x00, 0x10) I->D\n");
+    printf(" Lingo:               0x%02x\n", $lingo);
+    printf(" Version:             %d.%02d\n", $maj, $min);
+
+    return 1;
+}
+
+
+sub _h_00_0013 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my @lingolist;
+    my ($lingoes, $options, $devid);
+
+    ($lingoes, $options, $devid) = unpack("N3", $data);
+
+    foreach (0..31) {
+        push(@lingolist, $_) if ($lingoes & (1 << $_));
+    }
+
+    printf("IdentifyDeviceLingoes (0x00, 0x13) D->I\n");
+    printf(" Supported lingoes:    %s\n", join(", ", @lingolist));
+    printf(" Options:\n");
+    printf("  Authentication:      %s\n", ("None", "Defer (1.0)", "Immediate (2.0)", \
"Reserved")[$options & 0x03]); +    printf("  Power:               %s\n", ("Low", \
"Intermittent high", "Reserved", "Constant high")[($options & 0x0C) >> 2]); +    \
printf(" Device ID:            0x%08x\n", $devid); +
+    delete($self->{-state}->{'_h_00_0015'});
+
+    return 1;
+}
+
+sub _h_00_0014 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetDevAuthenticationInfo (0x00, 0x14) I->D\n");
+
+    return 1;
+}
+
+sub _h_00_0015 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($amaj, $amin, $curidx, $maxidx, $certdata);
+
+    $state->{-curidx} = -1 unless(exists($state->{-curidx}));
+    $state->{-maxidx} = -1 unless(exists($state->{-maxidx}));
+    $state->{-cert} = '' unless(exists($state->{-cert}));
+
+    ($amaj, $amin, $curidx, $maxidx, $certdata) = unpack("CCCCa*", $data);
+
+    printf("RetDevAuthenticationInfo (0x00, 0x15) D->I\n");
+    printf(" Authentication version: %d.%d\n", $amaj, $amin);
+    printf(" Segment:                %d of %d\n", $curidx, $maxidx);
+
+    if ($curidx-1 != $state->{-curidx}) {
+        printf("  WARNING! Out of order segment\n");
+        return 0;
+    }
+
+    if (($maxidx != $state->{-maxidx}) && ($state->{-maxidx} != -1)) {
+        printf("  WARNING! maxidx changed midstream\n");
+        return 0;
+    }
+
+    if ($curidx > $maxidx) {
+        printf("  WARNING! Too many segments\n");
+        return 0;
+    }
+
+    $state->{-curidx} = $curidx;
+    $state->{-maxidx} = $maxidx;
+    $state->{-cert} .= $certdata;
+
+    if ($curidx == $maxidx) {
+        printf(" Certificate:            %s\n", \
Device::iPod->_hexstring($state->{-cert})); +    }
+
+    return 1;
+}
+
+sub _h_00_0016 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+
+    $res = unpack("C", $data);
+
+    printf("AckDevAuthenticationInfo (0x00, 0x16) I->D\n");
+    printf(" Result:         ");
+    if ($res == 0x00) {
+        printf("Authentication information supported\n");
+    } elsif ($res == 0x08) {
+        printf("Authentication information unpported\n");
+    } elsif ($res == 0x0A) {
+        printf("Certificate invalid\n");
+    } elsif ($res == 0x0B) {
+        printf("Certificate permissions are invalid\n");
+    } else {
+        printf("Unknown result 0x%02x\n", $res);
+    }
+
+    return 1;
+}
+
+sub _h_00_0017 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($challenge, $retry);
+
+    printf("GetDevAuthenticationSignature (0x00, 0x17) I->D\n");
+
+    if (length($data) == 17) {
+        ($challenge, $retry) = unpack("a16C", $data);
+    } elsif (length($data) == 21) {
+        ($challenge, $retry) = unpack("a20C", $data);
+    } else {
+        printf(" WARNING! Unsupported data length: %d\n", length($data));
+        return 0;
+    }
+
+    printf(" Challenge:              %s (%d bytes)\n", \
Device::iPod->_hexstring($challenge), length($challenge)); +    printf(" Retry \
counter:          %d\n", $retry); +
+    return 1;
+}
+
+sub _h_00_0018 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $reply;
+
+    printf("RetDevAuthenticationSignature (0x00, 0x18) D->I\n");
+    printf(" Data:                  %s\n", Device::iPod->_hexstring($data));
+
+    return 1;
+}
+
+sub _h_00_0019 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+
+    $res = unpack("C", $data);
+
+    printf("AckiPodAuthenticationInfo (0x00, 0x19) I->D\n");
+    printf(" Status:           %s (%d)\n", (
+        "OK")[$res], $res);
+
+    return 1;
+}
+
+sub _h_00_0024 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetiPodOptions (0x00, 0x24) D->I\n");
+
+    return 1;
+}
+
+sub _h_00_0025 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($ophi, $oplo);
+
+    ($ophi, $oplo) = unpack("NN", $data);
+
+    printf("RetiPodOptions (0x00, 0x25) I->D\n");
+    printf(" Options:\n");
+    printf("  iPod supports SetiPodPreferences\n") if ($oplo & 0x02);
+    printf("  iPod supports video\n") if ($oplo & 0x01);
+
+    return 1;
+}
+
+
+sub _h_00_0027 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $acctype;
+    my $accparam;
+
+    $acctype = unpack("C", $data);
+
+    printf("GetAccessoryInfo (0x00, 0x27) I->D\n");
+    printf(" Accessory Info Type:    %s (%d)\n", (
+        "Info capabilities",
+        "Name",
+        "Minimum supported iPod firmware version",
+        "Minimum supported lingo version",
+        "Firmware version",
+        "Hardware version",
+        "Manufacturer",
+        "Model Number",
+        "Serial Number",
+        "Maximum payload size")[$acctype], $acctype);
+    if ($acctype == 0x02) {
+        my ($modelid, $maj, $min, $rev);
+
+        ($modelid, $maj, $min, $rev) = unpack("xNCCC", $data);
+        printf(" Model ID:             0x%04x\n", $modelid);
+        printf(" iPod Firmware:        %d.%d.%d\n", $maj, $min, $rev);
+    } elsif ($acctype == 0x03) {
+        my $lingo;
+
+        $lingo = unpack("xC", $data);
+        printf(" Lingo:                0x%02x\n", $lingo);
+    }
+
+    return 1;
+}
+
+sub _h_00_0028 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $acctype;
+    my $accparam;
+
+    $acctype = unpack("C", $data);
+
+    printf("RetAccessoryInfo (0x00, 0x28) D->I\n");
+    printf(" Accessory Info Type:      %s (%d)\n", (
+        "Info capabilities",
+        "Name",
+        "Minimum supported iPod firmware version",
+        "Minimum supported lingo version",
+        "Firmware version",
+        "Hardware version",
+        "Manufacturer",
+        "Model Number",
+        "Serial Number",
+        "Maximum payload size")[$acctype], $acctype);
+
+    if ($acctype == 0x00) {
+        $accparam = unpack("xN", $data);
+        printf("  Accessory Info capabilities\n") if ($accparam & 0x01);
+        printf("  Accessory name\n") if ($accparam & 0x02);
+        printf("  Accessory minimum supported iPod firmware\n") if ($accparam & \
0x04); +        printf("  Accessory minimum supported lingo version\n") if ($accparam \
& 0x08); +        printf("  Accessory firmware version\n") if ($accparam & 0x10);
+        printf("  Accessory hardware version\n") if ($accparam & 0x20);
+        printf("  Accessory manufacturer\n") if ($accparam & 0x40);
+        printf("  Accessory model number\n") if ($accparam & 0x80);
+        printf("  Accessory serial number\n") if ($accparam & 0x100);
+        printf("  Accessory incoming max packet size\n") if ($accparam & 0x200);
+    }
+
+    if ($acctype ~~ [0x01, 0x06, 0x07, 0x08]) {
+        $accparam = unpack("xZ*", $data);
+        printf(" Data:                     %s\n", $accparam);
+    }
+
+    if ($acctype == 0x02) {
+        $accparam = [ unpack("xNCCC", $data) ];
+        printf(" Model ID:                 %08x\n", $accparam->[0]);
+        printf(" Firmware version:         %d.%02d.%02d\n", $accparam->[1], \
$accparam->[2], $accparam->[3]); +    }
+
+    if ($acctype == 0x03) {
+        $accparam = [ unpack("xCCC", $data) ];
+        printf(" Lingo:                    %02x\n", $accparam->[0]);
+        printf(" Version:                  %d.%02d\n", $accparam->[1], \
$accparam->[2]); +    }
+
+    if ($acctype ~~ [0x04, 0x05]) {
+        $accparam = [ unpack("xCCC", $data) ];
+        printf(" Version:                  %d.%02d.%02d\n", @{$accparam});
+    }
+
+    return 1;
+}
+
+sub _h_00_0029 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $class;
+
+    $class = unpack("C", $data);
+
+    printf("GetiPodPreferences (0x00, 0x29) D->I\n");
+    printf(" Class:                 %s (%d)\n", (
+        "Video out setting",
+        "Screen configuration",
+        "Video signal format",
+        "Line Out usage",
+        "(Reserved)",
+        "(Reserved)",
+        "(Reserved)",
+        "(Reserved)",
+        "Video out connection",
+        "Closed captioning",
+        "Video aspect ratio",
+        "(Reserved)",
+        "Subtitles",
+        "Video alternate audio channel")[$class], $class);
+
+    return 1;
+}
+
+sub _h_00_002b {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($class, $setting);
+
+    ($class, $setting) = unpack("CC", $data);
+
+    printf("SetiPodPreferences (0x00, 0x2B) D->I\n");
+    printf(" Class:             %s (%d)\n", (
+        "Video out setting",
+        "Screen configuration",
+        "Video signal format",
+        "Line out usage",
+        "Reserved",
+        "Reserved",
+        "Reserved",
+        "Reserved",
+        "Video-out connection",
+        "Closed captioning",
+        "Video monitor aspect ratio",
+        "Reserved",
+        "Subtitles",
+        "Video alternate audio channel")[$class], $class);
+    printf(" Setting:           %s (%d)\n", (
+        ["Off",
+         "On",
+         "Ask user"],
+        ["Fill screen",
+         "Fit to screen edge"],
+        ["NTSC",
+         "PAL"],
+        ["Not used",
+         "Used"],
+        [],
+        [],
+        [],
+        [],
+        ["None",
+         "Composite",
+         "S-video",
+         "Component"],
+        ["Off",
+         "On"],
+        ["4:3",
+         "16:9"],
+        [],
+        ["Off",
+         "On"],
+        ["Off",
+         "On"])[$class][$setting], $setting);
+
+    return 1;
+}
+
+sub _h_00_0038 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $transid;
+
+    $transid = unpack("n", $data);
+
+    printf("StartIDPS (0x00, 0x38) D->I\n");
+    printf(" TransID:          %d\n", $transid);
+
+    return 1;
+}
+
+sub _h_00_003b {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($transid, $status);
+
+    ($transid, $status) = unpack("nC", $data);
+
+    printf("EndIDPS (0x00, 0x3B) D->I\n");
+    printf(" TransID:          %d\n", $transid);
+    printf(" Action:           %s (%d)\n", (
+        "Finished",
+        "Reset")[$status], $status);
+
+    return 1;
+}
+
+sub _h_00_004b {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $lingo;
+
+    $lingo = unpack("C", $data);
+
+    printf("GetiPodOptionsForLingo (0x00, 0x4B) D->I\n");
+    printf(" Lingo:               0x%02x\n", $lingo);
+
+    return 1;
+}
+
+sub _h_02_0000 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my @keys;
+
+    @keys = unpack("CCCC", $data);
+
+    printf("ContextButtonStatus (0x02, 0x00) D->I\n");
+    printf(" Buttons:\n");
+    printf("  Play/Pause\n") if ($keys[0] & 0x01);
+    printf("  Volume Up\n") if ($keys[0] & 0x02);
+    printf("  Volume Down\n") if ($keys[0] & 0x04);
+    printf("  Next Track\n") if ($keys[0] & 0x08);
+    printf("  Previous Track\n") if ($keys[0] & 0x10);
+    printf("  Next Album\n") if ($keys[0] & 0x20);
+    printf("  Previous Album\n") if ($keys[0] & 0x40);
+    printf("  Stop\n") if ($keys[0] & 0x80);
+
+    if (exists($keys[1])) {
+        printf("  Play/Resume\n") if ($keys[1] & 0x01);
+        printf("  Pause\n") if ($keys[1] & 0x02);
+        printf("  Mute toggle\n") if ($keys[1] & 0x04);
+        printf("  Next Chapter\n") if ($keys[1] & 0x08);
+        printf("  Previous Chapter\n") if ($keys[1] & 0x10);
+        printf("  Next Playlist\n") if ($keys[1] & 0x20);
+        printf("  Previous Playlist\n") if ($keys[1] & 0x40);
+        printf("  Shuffle Setting Advance\n") if ($keys[1] & 0x80);
+    }
+
+    if (exists($keys[2])) {
+        printf("  Repeat Setting Advance\n") if ($keys[2] & 0x01);
+        printf("  Power On\n") if ($keys[2] & 0x02);
+        printf("  Power Off\n") if ($keys[2] & 0x04);
+        printf("  Backlight for 30 seconds\n") if ($keys[2] & 0x08);
+        printf("  Begin FF\n") if ($keys[2] & 0x10);
+        printf("  Begin REW\n") if ($keys[2] & 0x20);
+        printf("  Menu\n") if ($keys[2] & 0x40);
+        printf("  Select\n") if ($keys[2] & 0x80);
+    }
+
+    if (exists($keys[3])) {
+        printf("  Up Arrow\n") if ($keys[3] & 0x01);
+        printf("  Down Arrow\n") if ($keys[3] & 0x02);
+        printf("  Backlight off\n") if ($keys[3] & 0x04);
+    }
+
+    return 1;
+}
+
+sub _h_02_0001 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+    my $cmd;
+
+    ($res, $cmd) = unpack("CC", $data);
+
+    printf("ACK (0x02, 0x01) I->D\n");
+    printf(" Acknowledged command: 0x%02x\n", $cmd);
+    printf(" Status:               %s (%d)\n",
+        ("Success",
+         "ERROR: Unknown Database Category",
+         "ERROR: Command Failed",
+         "ERROR: Out Of Resource",
+         "ERROR: Bad Parameter",
+         "ERROR: Unknown ID",
+         "Command Pending",
+         "ERROR: Not Authenticated",
+         "ERROR: Bad Authentication Version",
+         "ERROR: Accessory Power Mode Request Failed",
+         "ERROR: Certificate Invalid",
+         "ERROR: Certificate permissions invalid",
+         "ERROR: File is in use",
+         "ERROR: Invalid file handle",
+         "ERROR: Directory not empty",
+         "ERROR: Operation timed out",
+         "ERROR: Command unavailable in this iPod mode",
+         "ERROR: Invalid accessory resistor ID",
+         "Reserved",
+         "Reserved",
+         "Reserved",
+         "ERROR: Maximum number of accessory connections already reached")[$res], \
$res); +
+    return 1;
+}
+
+sub _h_03_0000 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+    my $cmd;
+
+    ($res, $cmd) = unpack("CC", $data);
+
+    printf("ACK (0x03, 0x00) I->D\n");
+    printf(" Acknowledged command: 0x%02x\n", $cmd);
+    printf(" Status:               %s (%d)\n",
+        ("Success",
+         "ERROR: Unknown Database Category",
+         "ERROR: Command Failed",
+         "ERROR: Out Of Resource",
+         "ERROR: Bad Parameter",
+         "ERROR: Unknown ID",
+         "Command Pending",
+         "ERROR: Not Authenticated",
+         "ERROR: Bad Authentication Version",
+         "ERROR: Accessory Power Mode Request Failed",
+         "ERROR: Certificate Invalid",
+         "ERROR: Certificate permissions invalid",
+         "ERROR: File is in use",
+         "ERROR: Invalid file handle",
+         "ERROR: Directory not empty",
+         "ERROR: Operation timed out",
+         "ERROR: Command unavailable in this iPod mode",
+         "ERROR: Invalid accessory resistor ID",
+         "Reserved",
+         "Reserved",
+         "Reserved",
+         "ERROR: Maximum number of accessory connections already reached")[$res], \
$res); +
+    return 1;
+}
+
+sub _h_03_0008 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $events;;
+
+    $events = unpack("N", $data);
+
+    printf("SetRemoteEventsNotification (0x03, 0x08) D->I\n");
+    printf(" Events:\n");
+    printf("  Track position in ms\n") if ($events & 0x00000001);
+    printf("  Track playback index\n") if ($events & 0x00000002);
+    printf("  Chapter index\n") if ($events & 0x00000004);
+    printf("  Play status\n") if ($events & 0x00000008);
+    printf("  Mute/UI volume\n") if ($events & 0x00000010);
+    printf("  Power/Battery\n") if ($events & 0x00000020);
+    printf("  Equalizer setting\n") if ($events & 0x00000040);
+    printf("  Shuffle setting\n") if ($events & 0x00000080);
+    printf("  Repeat setting\n") if ($events & 0x00000100);
+    printf("  Date and time setting\n") if ($events & 0x00000200);
+    printf("  Alarm setting\n") if ($events & 0x00000400);
+    printf("  Backlight state\n") if ($events & 0x00000800);
+    printf("  Hold switch state\n") if ($events & 0x00001000);
+    printf("  Sound check state\n") if ($events & 0x00002000);
+    printf("  Audiobook speed\n") if ($events & 0x00004000);
+    printf("  Track position in s\n") if ($events & 0x00008000);
+    printf("  Mute/UI/Absolute volume\n") if ($events & 0x00010000);
+
+    return 1;
+}
+
+sub _h_03_0009 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $event;
+    my $eventdata;
+
+    $event = unpack("C", $data);
+
+    printf("RemoteEventNotification (0x03, 0x09) I->D\n");
+    printf(" Event:                %s (%d)\n", (
+        "Track position in ms",
+        "Track playback index",
+        "Chapter index",
+        "Play status",
+        "Mute/UI volume",
+        "Power/Battery",
+        "Equalizer setting",
+        "Shuffle setting",
+        "Repeat setting",
+        "Date and time setting",
+        "Alarm setting",
+        "Backlight state",
+        "Hold switch state",
+        "Sound check state",
+        "Audiobook speed",
+        "Track position in s",
+        "Mute/UI/Absolute volume")[$event], $event);
+
+    if ($event == 0x00) {
+        $eventdata = unpack("xN", $data);
+        printf("  Position:               %d ms\n", $eventdata);
+    } elsif ($event == 0x01) {
+        $eventdata = unpack("xN", $data);
+        printf("  Track:                  %d\n", $eventdata);
+    } elsif ($event == 0x02) {
+        $eventdata = [ unpack("xNnn", $data) ];
+        printf("  Track:                  %d\n", $eventdata->[0]);
+        printf("  Chapter count:          %d\n", $eventdata->[1]);
+        printf("  Chapter index:          %d\n", $eventdata->[2]);
+    } elsif ($event == 0x03) {
+        $eventdata = unpack("xC", $data);
+        printf("  Status:                 %s (%d)\n", (
+            "Stopped",
+            "Playing",
+            "Paused",
+            "FF",
+            "REW",
+            "End FF/REW")[$eventdata], $eventdata);
+    } elsif ($event == 0x04) {
+        $eventdata = [ unpack("xCC") ];
+        printf("  Mute:                   %s\n", $eventdata->[0]?"Off":"On");
+        printf("  Volume:                 %d\n", $eventdata->[1]);
+    } elsif ($event == 0x0F) {
+        $eventdata = unpack("xn", $data);
+        printf("  Position:               %d s\n", $eventdata);
+    }
+
+    return 1;
+}
+
+sub _h_03_000c {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $info;
+
+    $info = unpack("C", $data);
+
+    printf("GetiPodStateInfo (0x03, 0x0C) D->I\n");
+    printf(" Info to get:           %s (%d)\n", (
+        "Track time in ms",
+        "Track playback index",
+        "Chapter information",
+        "Play status",
+        "Mute and volume information",
+        "Power and battery status",
+        "Equalizer setting",
+        "Shuffle setting",
+        "Repeat setting",
+        "Date and time",
+        "Alarm state and time",
+        "Backlight state",
+        "Hold switch state",
+        "Audiobook speed",
+        "Track time in seconds",
+        "Mute/UI/Absolute volume")[$info], $info);
+
+    return 1;
+}
+
+sub _h_03_000d {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $type;
+    my $info;
+
+    $type = unpack("C", $data);
+
+    printf("RetiPodStateInfo (0x03, 0x0E) D->I\n");
+
+    if ($type == 0x00) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track position\n");
+        printf(" Position:         %d ms\n", $info);
+    } elsif ($type == 0x01) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track index\n");
+        printf(" Index:            %d\n", $info);
+    } elsif ($type == 0x02) {
+        $info = [ unpack("xNnn", $data) ];
+        printf(" Type:             Chapter Information\n");
+        printf(" Playing Track:    %d\n", $info->[0]);
+        printf(" Chapter count:    %d\n", $info->[1]);
+        printf(" Chapter index:    %d\n", $info->[2]);
+    } elsif ($type == 0x03) {
+        $info = unpack("xC", $data);
+        printf(" Type:             Play status\n");
+        printf(" Status:           %s (%d)\n", (
+            "Stopped",
+            "Playing",
+            "Paused",
+            "FF",
+            "REW",
+            "End FF/REW")[$info], $info);
+    } elsif ($type == 0x04) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Mute/Volume\n");
+        printf(" Mute State:       %s\n", $info->[0]?"On":"Off");
+        printf(" Volume level:     %d\n", $info->[1]);
+    } elsif ($type == 0x05) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Battery Information\n");
+        printf(" Power state:      %s (%d)\n", (
+            "Internal, low power (<30%)",
+            "Internal",
+            "External battery pack, no charging",
+            "External power, no charging",
+            "External power, charging",
+            "External power, charged")[$info->[0]], $info->[0]);
+        printf(" Battery level:    %d%%\n", $info->[1]*100/255);
+    } elsif ($type == 0x06) {
+        $info = [unpack("xN", $data)];
+        printf(" Type:             Equalizer\n");
+        printf(" Index:            %d\n", $info->[0]);
+    } elsif ($type == 0x07) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Shuffle\n");
+        printf(" Shuffle State:    %s\n", $info->[0]?"On":"Off");
+    } elsif ($type == 0x08) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Repeat\n");
+        printf(" Repeat State:     %s\n", $info->[0]?"On":"Off");
+    } elsif ($type == 0x09) {
+        $info = [unpack("xnCCCC", $data)];
+        printf(" Type:             Date\n");
+        printf(" Date:             %02d.%02d.%04d %02d:%02d\n", $info->[2], \
$info->[1], $info->[0], $info->[3], $info->[4]); +    } elsif ($type == 0x0A) {
+        $info = [unpack("xCCC", $data)];
+        printf(" Type:             Alarm\n");
+        printf(" Alarm State:      %s\n", $info->[0]?"On":"Off");
+        printf(" Time:             %02d:%02d\n", $info->[1], $info->[2]);
+    } elsif ($type == 0x0B) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Backlight\n");
+        printf(" Backlight State:  %s\n", $info->[0]?"On":"Off");
+    } elsif ($type == 0x0C) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Hold switch\n");
+        printf(" Switch State:     %s\n", $info->[0]?"On":"Off");
+    } elsif ($type == 0x0D) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Sound check\n");
+        printf(" Sound check:      %s\n", $info->[0]?"On":"Off");
+    } elsif ($type == 0x0E) {
+        $info = [unpack("xC", $data)];
+        printf(" Type:             Audiobook speed\n");
+        printf(" Speed:            %s\n", \
$info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved");
 +    } elsif ($type == 0x0F) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track position\n");
+        printf(" Position:         %d s\n", $info);
+    } elsif ($type == 0x10) {
+        $info = [unpack("xCCC", $data)];
+        printf(" Type:             Mute/UI/Absolute volume\n");
+        printf(" Mute State:       %s\n", $info->[0]?"On":"Off");
+        printf(" UI Volume level:  %d\n", $info->[1]);
+        printf(" Absolute Volume:  %d\n", $info->[2]);
+    } else {
+        printf(" Reserved\n");
+    }
+
+    return 1;
+}
+
+
+sub _h_03_000e {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $type;
+    my $info;
+
+    $type = unpack("C", $data);
+
+    printf("SetiPodStateInfo (0x03, 0x0E) D->I\n");
+
+    if ($type == 0x00) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track position\n");
+        printf(" Position:         %d ms\n", $info);
+    } elsif ($type == 0x01) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track index\n");
+        printf(" Index:            %d\n", $info);
+    } elsif ($type == 0x02) {
+        $info = unpack("xn", $data);
+        printf(" Type:             Chapter index\n");
+        printf(" Index:            %d\n", $info);
+    } elsif ($type == 0x03) {
+        $info = unpack("xC", $data);
+        printf(" Type:             Play status\n");
+        printf(" Status:           %s (%d)\n", (
+            "Stopped",
+            "Playing",
+            "Paused",
+            "FF",
+            "REW",
+            "End FF/REW")[$info], $info);
+    } elsif ($type == 0x04) {
+        $info = [unpack("xCCC", $data)];
+        printf(" Type:             Mute/Volume\n");
+        printf(" Mute State:       %s\n", $info->[0]?"On":"Off");
+        printf(" Volume level:     %d\n", $info->[1]);
+        printf(" Restore on exit:  %s\n", $info->[2]?"Yes":"No");
+    } elsif ($type == 0x06) {
+        $info = [unpack("xNC", $data)];
+        printf(" Type:             Equalizer\n");
+        printf(" Index:            %d\n", $info->[0]);
+        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x07) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Shuffle\n");
+        printf(" Shuffle State:    %s\n", $info->[0]?"On":"Off");
+        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x08) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Repeat\n");
+        printf(" Repeat State:     %s\n", $info->[0]?"On":"Off");
+        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x09) {
+        $info = [unpack("xnCCCC", $data)];
+        printf(" Type:             Date\n");
+        printf(" Date:             %02d.%02d.%04d %02d:%02d\n", $info->[2], \
$info->[1], $info->[0], $info->[3], $info->[4]); +    } elsif ($type == 0x0A) {
+        $info = [unpack("xCCCC", $data)];
+        printf(" Type:             Alarm\n");
+        printf(" Alarm State:      %s\n", $info->[0]?"On":"Off");
+        printf(" Time:             %02d:%02d\n", $info->[1], $info->[2]);
+        printf(" Restore on exit:  %s\n", $info->[3]?"Yes":"No");
+    } elsif ($type == 0x0B) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Backlight\n");
+        printf(" Backlight State:  %s\n", $info->[0]?"On":"Off");
+        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x0D) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Sound check\n");
+        printf(" Sound check:      %s\n", $info->[0]?"On":"Off");
+        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x0E) {
+        $info = [unpack("xCC", $data)];
+        printf(" Type:             Audiobook speed\n");
+        printf(" Speed:            %s\n", \
$info->[0]==0x00?"Normal":$info->[0]==0x01?"Faster":$info->[0]==0xFF?"Slower":"Reserved");
 +        printf(" Restore on exit:  %s\n", $info->[1]?"Yes":"No");
+    } elsif ($type == 0x0F) {
+        $info = unpack("xN", $data);
+        printf(" Type:             Track position\n");
+        printf(" Position:         %d s\n", $info);
+    } elsif ($type == 0x10) {
+        $info = [unpack("xCCCC", $data)];
+        printf(" Type:             Mute/UI/Absolute volume\n");
+        printf(" Mute State:       %s\n", $info->[0]?"On":"Off");
+        printf(" UI Volume level:  %d\n", $info->[1]);
+        printf(" Absolute Volume:  %d\n", $info->[2]);
+        printf(" Restore on exit:  %s\n", $info->[3]?"Yes":"No");
+    } else {
+        printf(" Reserved\n");
+    }
+
+    return 1;
+}
+
+sub _h_03_000f {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    print("GetPlayStatus (0x03, 0x0F) D->I\n");
+
+    return 1;
+}
+
+sub _h_03_0010 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($status, $idx, $len, $pos);
+
+    ($status, $idx, $len, $pos) = unpack("CNNN", $data);
+
+    printf("RetPlayStatus (0x03, 0x10) I->D\n");
+    printf(" Status:          %s (%d)\n", (
+        "Stopped",
+        "Playing",
+        "Paused")[$status], $status);
+    if ($status != 0x00) {
+        printf(" Track index:       %d\n", $idx);
+        printf(" Track length:      %d\n", $len);
+        printf(" Track position:    %d\n", $pos);
+    }
+
+    return 1;
+}
+
+sub _h_03_0012 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($info, $tidx, $cidx);
+
+    ($info, $tidx, $cidx) = unpack("CNn", $data);
+
+    printf("GetIndexedPlayingTrackInfo (0x03, 0x12) D->I\n");
+    printf(" Requested info:         %s (%d)\n", (
+            "Track caps/info",
+            "Chapter time/name",
+            "Artist name",
+            "Album name",
+            "Genre name",
+            "Track title",
+            "Composer name",
+            "Lyrics",
+            "Artwork count")[$info], $info);
+    printf(" Track index:            %d\n", $tidx);
+    printf(" Chapter index:          %d\n", $cidx);
+
+    return 1;
+}
+
+sub _h_03_0013 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $info;
+
+    $info = unpack("C", $data);
+    printf("RetIndexedPlayingTrackInfo (0x03, 0x13) I->D\n");
+    printf(" Returned info:         %s (%d)\n", (
+            "Track caps/info",
+            "Chapter time/name",
+            "Artist name",
+            "Album name",
+            "Genre name",
+            "Track title",
+            "Composer name",
+            "Lyrics",
+            "Artwork count")[$info], $info);
+    if ($info == 0x00) {
+        my ($caps, $len, $chap) = unpack("xNNn", $data);
+        printf("  Track is audiobook\n") if ($caps & 0x01);
+        printf("  Track has chapters\n") if ($caps & 0x02);
+        printf("  Track has artwork\n") if ($caps & 0x04);
+        printf("  Track contains video\n") if ($caps & 0x80);
+        printf("  Track queued as video\n") if ($caps & 0x100);
+
+        printf("  Track length:        %d ms\n", $len);
+        printf("  Track chapters:      %d\n", $chap);
+    } elsif ($info == 0x01) {
+        my ($len, $name) = unpack("xNZ*");
+        printf("  Chapter time:        %d ms\n", $len);
+        printf("  Chapter name:        %s\n", $name);
+    } elsif ($info >= 0x02 && $info <= 0x06) {
+        my $name = unpack("xZ*", $data);
+        printf("  Name/Title:          %s\n", $name)
+    } elsif ($info == 0x07) {
+        my ($info, $index, $data) = unpack("xCnZ*");
+        printf("  Part of multiple packets\n") if ($info & 0x01);
+        printf("  Is last packet\n") if ($info & 0x02);
+        printf("  Packet index:        %d\n", $index);
+        printf("  Data:                %s\n", $data);
+    } elsif ($info == 0x08) {
+
+    }
+
+    return 1;
+}
+
+sub _h_04_0001 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $res;
+    my $cmd;
+
+    ($res, $cmd) = unpack("Cn", $data);
+
+    printf("ACK (0x04, 0x0001) I->D\n");
+    printf(" Acknowledged command: 0x%02x\n", $cmd);
+    printf(" Status:               %s (%d)\n",
+        ("Success",
+         "ERROR: Unknown Database Category",
+         "ERROR: Command Failed",
+         "ERROR: Out Of Resource",
+         "ERROR: Bad Parameter",
+         "ERROR: Unknown ID",
+         "Reserved",
+         "ERROR: Not Authenticated")[$res], $res);
+
+    return 1;
+}
+
+sub _h_04_000c {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($info, $track, $chapter);
+
+    ($info, $track, $chapter) = unpack("CNn", $data);
+
+    printf("GetIndexedPlayingTrackInfo (0x04, 0x000C) D->I\n");
+    printf(" Track:                 %d\n", $track);
+    printf(" Chapter:               %d\n", $chapter);
+    printf(" Info requested:        %s (%d)\n", (
+        "Capabilities and information",
+        "Podcast name",
+        "Track release date",
+        "Track description",
+        "Track song lyrics",
+        "Track genre",
+        "Track Composer",
+        "Tracn Artwork count")[$info], $info);
+
+    return 1;
+}
+
+sub _h_04_000d {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $info;
+
+    $info = unpack("C", $data);
+
+    printf("ReturnIndexedPlayingTrackInfo (0x04, 0x000D) I->D\n");
+    if ($info == 0x00) {
+        my ($capability, $length, $chapter);
+
+        ($capability, $length, $chapter) = unpack("xNNn", $data);
+        printf(" Capabilities:\n");
+        printf("  Is audiobook\n") if ($capability & 0x00000001);
+        printf("  Has chapters\n") if ($capability & 0x00000002);
+        printf("  Has album artwork\n") if ($capability & 0x00000004);
+        printf("  Has song lyrics\n") if ($capability & 0x00000008);
+        printf("  Is a podcast episode\n") if ($capability & 0x00000010);
+        printf("  Has release date\n") if ($capability & 0x00000020);
+        printf("  Has description\n") if ($capability & 0x00000040);
+        printf("  Contains video\n") if ($capability & 0x00000080);
+        printf("  Queued to play as video\n") if ($capability & 0x00000100);
+
+        printf(" Length:              %d ms\n", $length);
+        printf(" Chapters:            %d\n", $chapter);
+    } else {
+        printf(" WARNING: Unknown info\n");
+        return 1;
+    }
+
+    return 1;
+}
+
+sub _h_04_0012 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("RequestProtocolVersion (0x04, 0x0012) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_0013 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($maj, $min);
+
+    ($maj, $min) = unpack("CC", $data);
+
+    printf("ReturnProtocolVersion (0x04, 0x0013) I->D\n");
+    printf(" Lingo 0x04 version:         %d.%02d\n", $maj, $min);
+
+    return 1;
+}
+
+sub _h_04_0016 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("ResetDBSelection (0x04, 0x0016) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_0018 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $category;
+
+    $category = unpack("C", $data);
+
+    printf("GetNumberCategorizedDBRecords (0x04, 0x0018) D->I\n");
+    printf(" Category:              %s (%d)\n", (
+        "Reserved",
+        "Playlist",
+        "Artist",
+        "Album",
+        "Genre",
+        "Track",
+        "Composer",
+        "Audiobook",
+        "Podcast",
+        "Nested Playlist")[$category], $category);
+
+    return 1;
+}
+
+sub _h_04_0019 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $count;
+
+    $count = unpack("N", $data);
+
+    printf("ReturnNumberCategorizedDBRecords (0x04, 0x0019) I->D\n");
+    printf(" Count:                 %d\n", $count);
+
+    return 1;
+}
+
+sub _h_04_001c {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetPlayStatus (0x04, 0x001C) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_001d {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($len, $pos, $s);
+
+    ($len, $pos, $s) = unpack("NNC", $data);
+
+    printf("ReturnPlayStatus (0x04, 0x001D) I->D\n");
+    printf(" Song length:             %d ms\n", $len);
+    printf(" Song position:           %d ms\n", $pos);
+    printf(" Player state:            ");
+    if ($s == 0x00) {
+        printf("Stopped\n");
+    } elsif ($s == 0x01) {
+        printf("Playing\n");
+    } elsif ($s == 0x02) {
+        printf("Paused\n");
+    } elsif ($s == 0xFF) {
+        printf("Error\n");
+    } else {
+        printf("Reserved\n");
+    }
+
+    return 1;
+}
+
+sub _h_04_001e {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetCurrentPlayingTrackIndex (0x04, 0x001E) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_001f {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $num;
+
+    $num = unpack("N", $data);
+
+    printf("ReturnCurrentPlayingTrackIndex (0x04, 0x001F) I->D\n");
+    printf(" Index:                  %d\n", $num);
+
+    return 1;
+}
+
+sub _h_04_0020 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $track;
+
+    $track = unpack("N", $data);
+
+    printf("GetIndexedPlayingTrackTitle (0x04, 0x0020) D->I\n");
+    printf(" Track:                 %d\n", $track);
+
+    return 1;
+}
+
+sub _h_04_0021 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $title;
+
+    $title = unpack("Z*", $data);
+
+    printf("ReturnIndexedPlayingTrackTitle (0x04, 0x0021) I->D\n");
+    printf(" Title:                 %s\n", $title);
+
+    return 1;
+}
+
+sub _h_04_0022 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $track;
+
+    $track = unpack("N", $data);
+
+    printf("GetIndexedPlayingTrackArtistName (0x04, 0x0022) D->I\n");
+    printf(" Track:                 %d\n", $track);
+
+    return 1;
+}
+
+sub _h_04_0023 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $artist;
+
+    $artist = unpack("Z*", $data);
+
+    printf("ReturnIndexedPlayingTrackArtistName (0x04, 0x0023) I->D\n");
+    printf(" Artist:                %s\n", $artist);
+
+    return 1;
+}
+
+sub _h_04_0024 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $track;
+
+    $track = unpack("N", $data);
+
+    printf("GetIndexedPlayingTrackAlbumName (0x04, 0x0024) D->I\n");
+    printf(" Track:                 %d\n", $track);
+
+    return 1;
+}
+
+sub _h_04_0025 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $title;
+
+    $title = unpack("Z*", $data);
+
+    printf("ReturnIndexedPlayingTrackAlbumName (0x04, 0x0025) I->D\n");
+    printf(" Album:                 %s\n", $title);
+
+    return 1;
+}
+
+sub _h_04_0026 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $notification;
+
+    if (length($data) == 1) {
+        $notification = unpack("C", $data);
+    } elsif (length($data) == 4) {
+        $notification = unpack("N", $data);
+    }
+
+    printf("SetPlayStatusChangeNotification (0x04, 0x0026) D->I\n");
+
+    if (length($data) == 1) {
+        printf(" Events for:            %s (%d)\n", (
+            "Disable all",
+            "Basic play state, track index, track time position, FFW/REW seek stop, \
and chapter index changes")[$notification], $notification); +    } elsif \
(length($data) == 4) { +        printf(" Events for:\n");
+        printf("  Basic play state changes\n") if ($notification & 0x00000001);
+        printf("  Extended play state changes\n") if ($notification & 0x00000002);
+        printf("  Track index\n") if ($notification & 0x00000004);
+        printf("  Track time offset (ms)\n") if ($notification & 0x00000008);
+        printf("  Track time offset (s)\n") if ($notification & 0x00000010);
+        printf("  Chapter index\n") if ($notification & 0x00000020);
+        printf("  Chapter time offset (ms)\n") if ($notification & 0x00000040);
+        printf("  Chapter time offset (s)\n") if ($notification & 0x00000080);
+        printf("  Track unique identifier\n") if ($notification & 0x00000100);
+        printf("  Track media tyoe\n") if ($notification & 0x00000200);
+        printf("  Track lyrics\n") if ($notification & 0x00000400);
+    } else {
+        printf(" WARNING: Unknown length for state\n");
+        return 0;
+    }
+
+    return 1;
+}
+
+sub _h_04_0027 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $info;
+
+    $info = unpack("C", $data);
+
+    printf("PlayStatusChangeNotification (0x04, 0x0029) I->D\n");
+    printf(" Status:\n");
+    if ($info == 0x00) {
+        printf("  Playback stopped\n");
+    } elsif ($info == 0x01) {
+        my $index = unpack("xN", $data);
+
+        printf("  Track index:             %d\n", $index);
+    } elsif ($info == 0x02) {
+        printf("  Playback FF seek stop\n");
+    } elsif ($info == 0x03) {
+        printf("  Playback REW seek stop\n");
+    } elsif ($info == 0x04) {
+        my $offset = unpack("xN", $data);
+
+        printf("  Track time offset:       %d ms\n", $offset);
+    } elsif ($info == 0x05) {
+        my $index = unpack("xN", $data);
+
+        printf("  Chapter index:           %d\n", $index);
+    } elsif ($info == 0x06) {
+        my $status = unpack("xC", $data);
+
+        printf("  Playback status extended: %s (%d)\n", (
+            "Reserved",
+            "Reserved",
+            "Stopped",
+            "Reserved",
+            "Reserved",
+            "FF seek started",
+            "REW seek started",
+            "FF/REW seek stopped",
+            "Reserved",
+            "Reserved",
+            "Playing",
+            "Paused")[$status], $status);
+    } elsif ($info == 0x07) {
+        my $offset = unpack("xN", $data);
+
+        printf("  Track time offset:       %d s\n", $offset);
+    } elsif ($info == 0x08) {
+        my $offset = unpack("xN", $data);
+
+        printf("  Chapter time offset      %d ms\n", $offset);
+    } elsif ($info == 0x09) {
+        my $offset = unpack("xN", $data);
+
+        printf("  Chapter time offset      %d s\n", $offset);
+    } elsif ($info == 0x0A) {
+        my ($uidhi, $uidlo) = unpack("xNN", $data);
+
+        printf("  Track UID:               %08x%08x\n", $uidhi, $uidlo);
+    } elsif ($info == 0x0B) {
+        my $mode = unpack("xC", $data);
+
+        printf("  Track mode: %s (%d)\n", (
+            "Audio track",
+            "Video track")[$mode], $mode);
+    } elsif ($info == 0x0C) {
+        printf("  Track lyrics ready\n");
+    } else {
+        printf("  Reserved\n");
+    }
+
+    return 1;
+}
+
+sub _h_04_0029 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $control;
+
+    $control = unpack("C", $data);
+
+    printf("PlayControl (0x04, 0x0029) D->I\n");
+    printf(" Command:             %s (%d)\n", (
+        "Reserved",
+        "Toggle Play/Pause",
+        "Stop",
+        "Next track",
+        "Previous track",
+        "Start FF",
+        "Start Rev",
+        "Stop FF/Rev",
+        "Next",
+        "Previous",
+        "Play",
+        "Pause",
+        "Next chapter",
+        "Previous chapter")[$control], $control);
+
+    return 1;
+}
+
+sub _h_04_002c {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetShuffle (0x04, 0x002C) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_002d {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $mode;
+
+    $mode = unpack("C", $data);
+
+    printf("ReturnShuffle (0x04, 0x002D) I->D\n");
+    printf(" Mode:                %s (%d)\n", (
+        "Off",
+        "Tracks",
+        "Albums")[$mode], $mode);
+
+    return 1;
+}
+
+sub _h_04_002f {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetRepeat (0x04, 0x002F) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_0030 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $mode;
+
+    $mode = unpack("C", $data);
+
+    printf("ReturnRepeat (0x04, 0x0030) I->D\n");
+    printf(" Mode:                %s (%d)\n", (
+        "Off",
+        "One Track",
+        "All Tracks")[$mode], $mode);
+
+    return 1;
+}
+
+sub _h_04_0032 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($index, $format, $width, $height, $stride, $imagedata);
+
+    $state->{-index} = -1 unless(exists($state->{-index}));
+    $state->{-image} = '' unless(exists($state->{-image}));
+
+    ($index, $format, $width, $height, $stride, $imagedata) = unpack("nCnnNa*", \
$data); +
+    printf("SetDisplayImage (0x04, 0x0032) D->I\n");
+    if ($index == 0) {
+        printf(" Width:               %d\n", $width);
+        printf(" Height:              %d\n", $height);
+        printf(" Stride:              %d\n", $stride);
+        printf(" Format:              %s (%d)\n", (
+            "Reserved",
+            "Monochrome, 2 bps",
+            "RGB 565, little endian",
+            "RGB 565, big endian")[$format], $format);
+
+        $state->{-imagelength} = $height * $stride;
+    } else {
+        ($index, $imagedata) = unpack("na*", $data);
+    }
+
+    if ($index-1 != $state->{-index}) {
+        printf(" WARNING! Out of order segment\n");
+        return 0;
+    }
+
+    $state->{-index} = $index;
+    $state->{-image} .= $imagedata;
+
+    if (length($state->{-image}) >= $state->{-imagelength}) {
+        printf(" Image data:           %s\n", \
Device::iPod->_hexstring($state->{-image})); +    }
+
+    return 1;
+}
+
+sub _h_04_0033 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetMonoDisplayImageLimits (0x04, 0x0033) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_0034 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my ($width, $height, $format);
+
+    ($width, $height, $format) = unpack("nnC", $data);
+
+    printf("ReturnMonoDisplayImageLimits (0x04, 0x0034) I->D\n");
+    printf(" Width:                  %d\n", $width);
+    printf(" Height:                 %d\n", $height);
+    printf(" Format:                 %s (%d)\n", (
+        "Reserved",
+        "Monochrome, 2 bps",
+        "RGB 565, little endian",
+        "RGB 565, big endian")[$format], $format);
+
+    return 1;
+}
+
+sub _h_04_0035 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("GetNumPlayingTracks (0x04, 0x0035) D->I\n");
+
+    return 1;
+}
+
+sub _h_04_0036 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $num;
+
+    $num = unpack("N", $data);
+
+    printf("ReturnNumPlayingTracks (0x04, 0x0036) I->D\n");
+    printf(" Number:                 %d\n", $num);
+
+    return 1;
+}
+
+sub _h_04_0037 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $num;
+
+    $num = unpack("N", $data);
+
+    printf("SetCurrentPlayingTrack (0x04, 0x0037) D->I\n");
+    printf(" Track:                 %d\n", $num);
+
+    return 1;
+}
+
+sub _h_07_0005 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $control;
+
+    $control = unpack("C", $data);
+
+    printf("SetTunerCtrl (0x07, 0x05) I->D\n");
+    printf(" Options:\n");
+    printf("  Power %s\n", ($control & 0x01)?"on":"off");
+    printf("  Status change notifications %s\n", ($control & 0x02)?"on":"off");
+    printf("  Raw mode %s\n", ($control & 0x04)?"on":"off");
+}
+
+sub _h_07_0020 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+    my $options;
+
+    $options = unpack("N", $data);
+
+    printf("SetRDSNotifyMask (0x07, 0x20) I->D\n");
+    printf(" Options:\n");
+    printf("  Radiotext\n") if ($options & 0x00000010);
+    printf("  Program Service Name\n") if ($options & 0x40000000);
+    printf("  Reserved\n") if ($options & 0xBFFFFFEF);
+
+    return 1;
+}
+
+sub _h_07_0024 {
+    my $self = shift;
+    my $data = shift;
+    my $state = shift;
+
+    printf("Reserved command (0x07, 0x24) I->D\n");
+
+    return 1;
+}
+
+
+package main;
+
+use Device::iPod;
+use Getopt::Long;
+use strict;
+
+my $decoder;
+my $device;
+my $unpacker;
+my $line;
+
+sub unpack_hexstring {
+    my $line = shift;
+    my $m;
+    my @m;
+
+    $line =~ s/(..)/chr(hex($1))/ge;
+    $device->{-inbuf} = $line;
+
+    $m = $device->_message();
+    next unless defined($m);
+    @m = $device->_unframe_cmd($m);
+    unless(@m) {
+        printf("Line %d: Error decoding frame: %s\n", $., $device->error());
+        return ();
+    }
+
+    return @m;
+}
+
+sub unpack_iaplog {
+    my $line = shift;
+    my @m;
+
+    unless ($line =~ /^(?:\[\d+\] )?[RT]::? /) {
+        printf("Skipped: %s\n", $line);
+        return ();
+    }
+
+    $line =~ s/^(?:\[\d+\] )?[RT]::? //;
+    $line =~ s/\\x(..)/chr(hex($1))/ge;
+    $line =~ s/\\\\/\\/g;
+
+    @m = unpack("CCa*", $line);
+    if ($m[0] == 0x04) {
+        @m = unpack("Cna*", $line);
+    }
+
+    return @m;
+}
+
+
+$decoder = iap::decode->new();
+$device = Device::iPod->new();
+$unpacker = \&unpack_iaplog;
+
+GetOptions("hexstring" => sub {$unpacker = \&unpack_hexstring});
+
+while ($line = <>) {
+    my @m;
+
+    chomp($line);
+
+    @m = $unpacker->($line);
+    next unless (@m);
+
+    printf("Line %d: ", $.);
+    $decoder->display(@m);
+}
diff --git a/tools/iap/ipod-001-general.t b/tools/iap/ipod-001-general.t
new file mode 100644
index 0000000..f2b5451
--- /dev/null
+++ b/tools/iap/ipod-001-general.t
@@ -0,0 +1,133 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a command with wrong checksum
+# We expect no response (Timeout)
+$m = $ipod->sendraw("\xff\x55\x02\x00\x03\x00");
+ok($m == 1, "Broken checksum sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "No response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a too short command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x00, 0x13, "");
+ok($m == 1, "Short command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined lingo
+# We expect a timeout
+$m = $ipod->sendmsg(0x1F, 0x00);
+ok($m == 1, "Undefined lingo sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "No response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x80\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x80000011, options=0x00, deviceid=0x00) \
sent"); +($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0xFF)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\xFF");
+ok($m == 1, "Identify(lingo=0xFF) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "No response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x10, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+# IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00)
+# We expect an ACK Command Failed message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x00, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Command Failed" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x02\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0xFF)
+# We expect an ACK Bad Parameter message as response
+$m = $ipod->sendmsg(0x00, 0x0F, "\xFF");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0xFF) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\x0F", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
diff --git a/tools/iap/ipod-002-lingo0.t b/tools/iap/ipod-002-lingo0.t
new file mode 100644
index 0000000..c3bb676
--- /dev/null
+++ b/tools/iap/ipod-002-lingo0.t
@@ -0,0 +1,277 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\x03", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# EnterRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x05);
+ok($m == 1, "EnterRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\x05", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ExitRemoteUIMode
+# We expect an ACK Bad Parameter as response, as we have not
+# negotiated lingo 0x04
+$m = $ipod->sendmsg(0x00, 0x06);
+ok($m == 1, "ExitRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\x06", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodName
+# We expect a ReturniPodName packet
+$m = $ipod->sendmsg(0x00, 0x07);
+ok($m == 1, "RequestiPodName sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodName" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x08, "Response command");
+    like($p, "/^[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodSoftwareVersion
+# We expect a ReturniPodSoftwareVersion packet
+$m = $ipod->sendmsg(0x00, 0x09);
+ok($m == 1, "RequestiPodSoftwareVersion sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodSoftwareVersion" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x0A, "Response command");
+    like($p, "/^...\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodSerialNumber
+# We expect a ReturniPodSerialNumber packet
+$m = $ipod->sendmsg(0x00, 0x0B);
+ok($m == 1, "RequestiPodSerialNumber sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodSerialNumber" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x0C, "Response command");
+    like($p, "/^[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestiPodModelNum
+# We expect a ReturniPodModelNum packet
+$m = $ipod->sendmsg(0x00, 0x0D);
+ok($m == 1, "RequestiPodModelNum sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturniPodModelNum" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x0E, "Response command");
+    like($p, "/^....[^\\x00]*\\x00\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0x00)
+# We expect a ReturnLingoProtocolVersion packet
+$m = $ipod->sendmsg(0x00, 0x0F, "\x00");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnLingoProtocolVersion" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x10, "Response command");
+    like($p, "/^\\x00..\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x11, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying standard mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x04, "Response command");
+    is($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# EnterRemoteUIMode
+# We expect an ACK Pending packet, followed by an ACK OK packet
+$m = $ipod->sendmsg(0x00, 0x05);
+ok($m == 1, "EnterRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Pending" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    like($p, "/^\\x06\\x05/", "Response payload");
+};
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x05", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying extended mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x04, "Response command");
+    isnt($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ExitRemoteUIMode
+# We expect an ACK Pending packet, followed by an ACK OK packet
+$m = $ipod->sendmsg(0x00, 0x06);
+ok($m == 1, "ExitRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Pending" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    like($p, "/^\\x06\\x06/", "Response payload");
+};
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x06", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestRemoteUIMode
+# We expect an ReturnRemoteUIMode packet specifying standard mode
+$m = $ipod->sendmsg(0x00, 0x03);
+ok($m == 1, "RequestRemoteUIMode sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnRemoteUIMode" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x04, "Response command");
+    is($p, "\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x00, 0xFF);
+ok($m == 1, "Undefined command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x04\xFF", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
diff --git a/tools/iap/ipod-003-lingo2.t b/tools/iap/ipod-003-lingo2.t
new file mode 100644
index 0000000..ee0bd69
--- /dev/null
+++ b/tools/iap/ipod-003-lingo2.t
@@ -0,0 +1,220 @@
+use Test::More qw( no_plan );
+use strict;
+
+BEGIN { use_ok('Device::iPod'); }
+require_ok('Device::iPod');
+
+my $ipod = Device::iPod->new();
+my $m;
+my ($l, $c, $p);
+
+isa_ok($ipod, 'Device::iPod');
+
+$ipod->{-debug} = 1;
+$ipod->open("/dev/ttyUSB0");
+
+$m = $ipod->sendraw("\xFF" x 16); # Wake up and sync
+ok($m == 1, "Wakeup sent");
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x01, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect an ACK Bad Parameter message as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x04\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0x00)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\x00");
+ok($m == 1, "Identify(lingo=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "No response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "Response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Identify(lingo=0x02)
+# We expect no response (timeout)
+$m = $ipod->sendmsg(0x00, 0x01, "\x02");
+ok($m == 1, "Identify(lingo=0x02) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "No response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "Response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00)
+# We expect an ACK OK message as response
+$m = $ipod->sendmsg(0x00, 0x13, "\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00");
+ok($m == 1, "IdentifyDeviceLingoes(lingos=0x05, options=0x00, deviceid=0x00) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK OK" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x02, "Response command");
+    is($p, "\x00\x13", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# RequestLingoProtocolVersion(lingo=0x02)
+# We expect a ReturnLingoProtocolVersion packet
+$m = $ipod->sendmsg(0x00, 0x0F, "\x02");
+ok($m == 1, "RequestLingoProtocolVersion(lingo=0x02) sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ReturnLingoProtocolVersion" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x00, "Response lingo");
+    is($c, 0x10, "Response command");
+    like($p, "/^\\x02..\$/", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send an undefined command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x02, 0xFF);
+ok($m == 1, "Undefined command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x04\xFF", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ContextButtonStatus(0x00)
+# We expect a timeout as response
+$m = $ipod->sendmsg(0x02, 0x00, "\x00");
+ok($m == 1, "ContextButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "Timeout" => sub {
+    ok(!defined($l), "Response received");
+    like($ipod->error(), '/Timeout/', "Timeout reading response");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# Send a too short command
+# We expect an ACK Bad Parameter as response
+$m = $ipod->sendmsg(0x02, 0x00, "");
+ok($m == 1, "Short command sent");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Bad Parameter" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x04\x00", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# ImageButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x02, "\x00");
+ok($m == 1, "ImageButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x07\x02", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# VideoButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x03, "\x00");
+ok($m == 1, "VideoButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x07\x03", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();
+
+# AudioButtonStatus(0x00)
+# We expect an ACK Not Authenticated as response
+$m = $ipod->sendmsg(0x02, 0x04, "\x00");
+ok($m == 1, "AudioButtonStatus(0x00)");
+($l, $c, $p) = $ipod->recvmsg();
+subtest "ACK Not Authenticated" => sub {
+    ok(defined($l), "Response received");
+    is($l, 0x02, "Response lingo");
+    is($c, 0x01, "Response command");
+    is($p, "\x07\x04", "Response payload");
+};
+
+# Empty the buffer
+$ipod->emptyrecv();



_______________________________________________
rockbox-cvs mailing list
rockbox-cvs@cool.haxx.se
http://cool.haxx.se/cgi-bin/mailman/listinfo/rockbox-cvs


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic