Updated IAP commands.

Originally written and uploaded by Lalufu (Ralf Ertzinger) in Feb 2012.
They have been condensed into a single patch and some further additions
by Andy Potter.

Currently includes Authentication V2 support from iPod to Accessory,
RF/BlueTooth transmitter support, selecting a playlist and selecting a
track from the current playlist. Does not support uploading Album Art
or podcasts. Has been tested on the following iPods,
4th Gen Grayscale, 4th Gen Color/Photo, Mini 2nd Gen, Nano 1st Gen and
Video 5.5Gen.

Change-Id: Ie8fc098361844132f0228ecbe3c48da948726f5e
Co-Authored by: Andy Potter <liveboxandy@gmail.com>
Reviewed-on: http://gerrit.rockbox.org/533
Reviewed-by: Frank Gevaerts <frank@gevaerts.be>
This commit is contained in:
Ralf Ertzinger 2013-06-22 10:08:23 +01:00 committed by Frank Gevaerts
parent 500b137308
commit b170c73f92
21 changed files with 10629 additions and 1125 deletions

View file

@ -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

1110
apps/iap.c

File diff suppressed because it is too large Load diff

1392
apps/iap/iap-core.c Normal file

File diff suppressed because it is too large Load diff

250
apps/iap/iap-core.h Normal file
View file

@ -0,0 +1,250 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Alan Korr & Nick Robinson
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _IAP_CORE_H
#define _IAP_CORE_H
#include <stdint.h>
#include <string.h>
#include "timefuncs.h"
#include "metadata.h"
#include "playlist.h"
#include "iap.h"
#define LOGF_ENABLE
/* #undef LOGF_ENABLE */
#ifdef LOGF_ENABLE
#include "logf.h"
#endif
/* The Model ID of the iPod we emulate. Currently a 160GB classic */
#define IAP_IPOD_MODEL (0x00130200U)
/* The firmware version we emulate. Currently 2.0.3 */
#define IAP_IPOD_FIRMWARE_MAJOR (2)
#define IAP_IPOD_FIRMWARE_MINOR (0)
#define IAP_IPOD_FIRMWARE_REV (3)
/* Status code for IAP ack messages */
#define IAP_ACK_OK (0x00) /* Success */
#define IAP_ACK_UNKNOWN_DB (0x01) /* Unknown Database Category */
#define IAP_ACK_CMD_FAILED (0x02) /* Command failed */
#define IAP_ACK_NO_RESOURCE (0x03) /* Out of resources */
#define IAP_ACK_BAD_PARAM (0x04) /* Bad parameter */
#define IAP_ACK_UNKNOWN_ID (0x05) /* Unknown ID */
#define IAP_ACK_PENDING (0x06) /* Command pending */
#define IAP_ACK_NO_AUTHEN (0x07) /* Not authenticated */
#define IAP_ACK_BAD_AUTHEN (0x08) /* Bad authentication version */
/* 0x09 reserved */
#define IAP_ACK_CERT_INVAL (0x0A) /* Certificate invalid */
#define IAP_ACK_CERT_PERM (0x0B) /* Certificate permissions invalid */
/* 0x0C-0x10 reserved */
#define IAP_ACK_RES_INVAL (0x11) /* Invalid accessory resistor value */
/* Add a button to the remote button bitfield. Also set iap_repeatbtn=1
* to ensure a button press is at least delivered once.
*/
#define REMOTE_BUTTON(x) do { \
iap_remotebtn |= (x); \
iap_timeoutbtn = 3; \
iap_repeatbtn = 2; \
} while(0)
/* States of the extended command support */
enum interface_state {
IST_STANDARD, /* General state, support lingo 0x00 commands */
IST_EXTENDED, /* Extended Interface lingo (0x04) negotiated */
};
/* States of the authentication state machine */
enum authen_state {
AUST_NONE, /* Initial state, no message sent */
AUST_INIT, /* Remote side has requested authentication */
AUST_CERTREQ, /* Remote certificate requested */
AUST_CERTBEG, /* Certificate is being received */
AUST_CERTDONE, /* Certificate received */
AUST_CHASENT, /* Challenge sent */
AUST_CHADONE, /* Challenge response received */
AUST_AUTH, /* Authentication complete */
};
/* State of authentication */
struct auth_t {
enum authen_state state; /* Current state of authentication */
unsigned char max_section; /* The maximum number of certificate sections */
unsigned char next_section; /* The next expected section number */
};
/* State of GetAccessoryInfo */
enum accinfo_state {
ACCST_NONE, /* Initial state, no message sent */
ACCST_INIT, /* Send out initial GetAccessoryInfo */
ACCST_SENT, /* Wait for initial RetAccessoryInfo */
ACCST_DATA, /* Query device information, according to capabilities */
};
/* A struct describing an attached device and it's current
* state
*/
struct device_t {
struct auth_t auth; /* Authentication state */
enum accinfo_state accinfo; /* Accessory information state */
uint32_t lingoes; /* Negotiated lingoes */
uint32_t notifications; /* Requested notifications. These are just the
* notifications explicitly requested by the
* device
*/
uint32_t changed_notifications; /* Tracks notifications that changed since the last
* call to SetRemoteEventNotification or GetRemoteEventStatus
*/
bool do_notify; /* Notifications enabled */
bool do_power_notify; /* Whether to send power change notifications.
* These are sent automatically to all devices
* that used IdentifyDeviceLingoes to identify
* themselves, independent of other notifications
*/
uint32_t trackpos_ms; /* These fields are to save the current state */
uint32_t track_index; /* of various fields so we can send a notification */
uint32_t chapter_index; /* if they change */
unsigned char play_status;
bool mute;
unsigned char volume;
unsigned char power_state;
unsigned char battery_level;
uint32_t equalizer_index;
unsigned char shuffle;
unsigned char repeat;
struct tm datetime;
unsigned char alarm_state;
unsigned char alarm_hour;
unsigned char alarm_minute;
unsigned char backlight;
bool hold;
unsigned char soundcheck;
unsigned char audiobook;
uint16_t trackpos_s;
uint32_t capabilities; /* Capabilities of the device, as returned by type 0
* of GetAccessoryInfo
*/
uint32_t capabilities_queried; /* Capabilities already queried */
};
extern struct device_t device;
#define DEVICE_AUTHENTICATED (device.auth.state == AUST_AUTH)
#define DEVICE_AUTH_RUNNING ((device.auth.state != AUST_NONE) && (device.auth.state != AUST_AUTH))
#define DEVICE_LINGO_SUPPORTED(x) (device.lingoes & BIT_N((x)&0x1f))
extern unsigned long iap_remotebtn;
extern unsigned int iap_timeoutbtn;
extern int iap_repeatbtn;
extern unsigned char* iap_txpayload;
extern unsigned char* iap_txnext;
/* These are a number of helper macros to manage the dynamic TX buffer content
* These macros DO NOT CHECK for buffer overflow. iap_send_tx() will, but
* it might be too late at that point. See the current size of TX_BUFLEN
*/
/* Initialize the TX buffer with a lingo and command ID. This will reset the
* data pointer, effectively invalidating unsent information in the TX buffer.
* There are two versions of this, one for 1 byte command IDs (all Lingoes except
* 0x04) and one for two byte command IDs (Lingo 0x04)
*/
#define IAP_TX_INIT(lingo, command) do { \
iap_txnext = iap_txpayload; \
IAP_TX_PUT((lingo)); \
IAP_TX_PUT((command)); \
} while (0)
#define IAP_TX_INIT4(lingo, command) do { \
iap_txnext = iap_txpayload; \
IAP_TX_PUT((lingo)); \
IAP_TX_PUT_U16((command)); \
} while (0)
/* Put an unsigned char into the TX buffer */
#define IAP_TX_PUT(data) *(iap_txnext++) = (data)
/* Put a 16bit unsigned quantity into the TX buffer */
#define IAP_TX_PUT_U16(data) do { \
put_u16(iap_txnext, (data)); \
iap_txnext += 2; \
} while (0)
/* Put a 32bit unsigned quantity into the TX buffer */
#define IAP_TX_PUT_U32(data) do { \
put_u32(iap_txnext, (data)); \
iap_txnext += 4; \
} while (0)
/* Put an arbitrary amount of data (identified by a char pointer and
* a length) into the TX buffer
*/
#define IAP_TX_PUT_DATA(data, len) do { \
memcpy(iap_txnext, (unsigned char *)(data), (len)); \
iap_txnext += (len); \
} while(0)
/* Put a NULL terminated string into the TX buffer, including the
* NULL byte
*/
#define IAP_TX_PUT_STRING(str) IAP_TX_PUT_DATA((str), strlen((str))+1)
/* Put a NULL terminated string into the TX buffer, taking care not to
* overflow the buffer. If the string does not fit into the TX buffer
* it will be truncated, but always NULL terminated.
*
* This function is expensive compared to the other IAP_TX_PUT_*
* functions
*/
#define IAP_TX_PUT_STRLCPY(str) iap_tx_strlcpy(str)
extern unsigned char lingo_versions[32][2];
#define LINGO_SUPPORTED(x) (LINGO_MAJOR((x)&0x1f) > 0)
#define LINGO_MAJOR(x) (lingo_versions[(x)&0x1f][0])
#define LINGO_MINOR(x) (lingo_versions[(x)&0x1f][1])
void put_u16(unsigned char *buf, const uint16_t data);
void put_u32(unsigned char *buf, const uint32_t data);
uint32_t get_u32(const unsigned char *buf);
uint16_t get_u16(const unsigned char *buf);
void iap_tx_strlcpy(const unsigned char *str);
void iap_reset_auth(struct auth_t* auth);
void iap_reset_device(struct device_t* device);
void iap_shuffle_state(bool state);
void iap_repeat_state(unsigned char state);
void iap_repeat_next(void);
void iap_fill_power_state(void);
void iap_send_tx(void);
extern enum interface_state interface_state;
void iap_interface_state_change(const enum interface_state new);
extern bool iap_btnrepeat;
extern bool iap_btnshuffle;
uint32_t iap_get_trackpos(void);
uint32_t iap_get_trackindex(void);
void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3);
#endif

23
apps/iap/iap-lingo.h Normal file
View file

@ -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);

1035
apps/iap/iap-lingo0.c Normal file

File diff suppressed because it is too large Load diff

278
apps/iap/iap-lingo2.c Normal file
View file

@ -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;
}
}
}

1508
apps/iap/iap-lingo3.c Normal file

File diff suppressed because it is too large Load diff

3153
apps/iap/iap-lingo4.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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:

View file

@ -22,7 +22,9 @@
#include <stdbool.h>
#define RX_BUFLEN 512
/* This is just the payload size, without sync, length and checksum */
#define RX_BUFLEN (64*1024)
/* This is the entire frame length, sync, length, payload and checksum */
#define TX_BUFLEN 128
extern bool iap_getc(unsigned char x);

View file

@ -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)

View file

@ -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)

386
tools/iap/Device/iPod.pm Normal file
View file

@ -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;

7
tools/iap/Makefile Normal file
View file

@ -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

23
tools/iap/README Normal file
View file

@ -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.

74
tools/iap/device-ipod.t Normal file
View file

@ -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");
};

1856
tools/iap/iap-verbose.pl Normal file

File diff suppressed because it is too large Load diff

View file

@ -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();

277
tools/iap/ipod-002-lingo0.t Normal file
View file

@ -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();

220
tools/iap/ipod-003-lingo2.t Normal file
View file

@ -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();