From b170c73f922e3457b923b4e7fcbec794a8885c77 Mon Sep 17 00:00:00 2001 From: Ralf Ertzinger Date: Sat, 22 Jun 2013 10:08:23 +0100 Subject: [PATCH] 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 Reviewed-on: http://gerrit.rockbox.org/533 Reviewed-by: Frank Gevaerts --- apps/SOURCES | 6 +- apps/iap.c | 1110 ---------- apps/iap/iap-core.c | 1392 +++++++++++++ apps/iap/iap-core.h | 250 +++ apps/iap/iap-lingo.h | 23 + apps/iap/iap-lingo0.c | 1035 ++++++++++ apps/iap/iap-lingo2.c | 278 +++ apps/iap/iap-lingo3.c | 1508 ++++++++++++++ apps/iap/iap-lingo4.c | 3153 +++++++++++++++++++++++++++++ apps/misc.c | 8 - firmware/export/iap.h | 4 +- firmware/export/kernel.h | 2 - firmware/target/arm/pp/debug-pp.c | 9 +- tools/iap/Device/iPod.pm | 386 ++++ tools/iap/Makefile | 7 + tools/iap/README | 23 + tools/iap/device-ipod.t | 74 + tools/iap/iap-verbose.pl | 1856 +++++++++++++++++ tools/iap/ipod-001-general.t | 133 ++ tools/iap/ipod-002-lingo0.t | 277 +++ tools/iap/ipod-003-lingo2.t | 220 ++ 21 files changed, 10629 insertions(+), 1125 deletions(-) delete mode 100644 apps/iap.c create mode 100644 apps/iap/iap-core.c create mode 100644 apps/iap/iap-core.h create mode 100644 apps/iap/iap-lingo.h create mode 100644 apps/iap/iap-lingo0.c create mode 100644 apps/iap/iap-lingo2.c create mode 100644 apps/iap/iap-lingo3.c create mode 100644 apps/iap/iap-lingo4.c create mode 100644 tools/iap/Device/iPod.pm create mode 100644 tools/iap/Makefile create mode 100644 tools/iap/README create mode 100644 tools/iap/device-ipod.t create mode 100644 tools/iap/iap-verbose.pl create mode 100644 tools/iap/ipod-001-general.t create mode 100644 tools/iap/ipod-002-lingo0.t create mode 100644 tools/iap/ipod-003-lingo2.t diff --git a/apps/SOURCES b/apps/SOURCES index 8fa1a7ed40..3968666d98 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 6fe0a03281..0000000000 --- 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 -#include -#include -#include - -#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 0000000000..ddcb22853a --- /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 +#include +#include +#include + +#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; istate != 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 0000000000..d06e3c300c --- /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 +#include +#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 0000000000..0c0a9e633d --- /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 0000000000..9e0355cb3f --- /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 0000000000..4fbf730192 --- /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 0000000000..0ed3df118e --- /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 0000000000..fa0196645b --- /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;counterlength; + 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 8dff227bc1..e746c432e6 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 8ee26cbe9f..22f36083d1 100644 --- a/firmware/export/iap.h +++ b/firmware/export/iap.h @@ -22,7 +22,9 @@ #include -#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 76ed96bd14..3cadefdf68 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 f4ba61cd39..2f57e1ef14 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 0000000000..b2d686cce7 --- /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 0000000000..86f3760de6 --- /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 0000000000..dbd050feb4 --- /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 0000000000..607184e331 --- /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 0000000000..ed25de7548 --- /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 0000000000..f2b5451dbc --- /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 0000000000..c3bb676553 --- /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 0000000000..ee0bd6972e --- /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();