summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/iap/Device/iPod.pm386
-rw-r--r--tools/iap/Makefile7
-rw-r--r--tools/iap/README23
-rw-r--r--tools/iap/device-ipod.t74
-rw-r--r--tools/iap/iap-verbose.pl1856
-rw-r--r--tools/iap/ipod-001-general.t133
-rw-r--r--tools/iap/ipod-002-lingo0.t277
-rw-r--r--tools/iap/ipod-003-lingo2.t220
8 files changed, 2976 insertions, 0 deletions
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();