## January 29, 2005

### Breaking the code

As a follow-up to my previous post, I'd like to describe how the I broke the GCHQ code. All it took was a bit of luck, some detective work and some programming.

The first time I saw the challenge, I skimped over the names to get a feel for it, without any intention of actually trying to solve it. It was pretty obvious to me, though, that the names had been encrypted with some kind of substitution cypher.

And then I saw the tip-off... BZGZD A'GAANZ. That had to be an irish-like name, so the A would be and encrypted O. Consequently, the last name would have to have the form O'xOOxx. Peter O'Toole immediately came to mind, although I didn't initially remember who he was. But the name sounded so familiar. I googled for it and it turned out he was an actor. Then I remembered :), and I was hooked.

I looked at the other names carefully, trying to spot easy ones, like KHP FNWHQBEV. With three distinct letters in the first name, I couldn't think of anything else than "Kim". Kim Basinger came to mind, and it fit nicely.

It was then that I decided to write a program to automate the discovery. I needed the program to take a name as input and match it to each name in the list. The question was how to do this efficiently. I realized that when dealing with substitution cyphers, an unencrypted string has the same form as the encrypted version. For example, PETER could be described as a sequence of characters [a, b, c, d, e] where a = P, b = d = E, c = T, e = R. It can be rewritten as [a, b, c, b, e], or "abcbe". In a sense, this sequence is a canonical form for the string. This (Ruby) code snippet will do the transformation:

def canonicalForm(chars)
map = {}
currentIndex = 64
result = ""
chars.each_byte { |char|
if not Regexp.new("[' ]") =~ char.chr
index = map[char] ||= (currentIndex += 1)
else
index = char
end
result << index
}
return result
end

A string and its encrypted version will both have the same canonical form. For instance, for PETER O'TOOLE and BZGZD A'GAANZ, the canonical form is ABCBD E'CEEFB.

The original version was quite primitive... if you fed it a name it would tell you which, if any, encrypted names matched. I discovered a few names with this approach. At first I thought the actors and actresses were Oscar winners, but I quickly dropped that hypothesis when I encountered "Kirsten Dunst". Given that she performed in Spiderman, I decided to try "Tobey Maguire". Direct hit! So my new theory was that an actor and actress that had performed in the same movie would be paired together. But I had to test it.

That's when I came up with the second version of the program, one that would build the cypher given a string and its encrypted version. For example, if I provided it with TOBEY MAGUIRE and DYFJN WCLEQPJ, it would produce

CF..J.L.Q...W.Y..P.DE...N.

The dots represent unknown substitutions. If I did the same with Kirsten Dunst, I'd get

...HJ...Q.U..X...PIDE.....

If you take a close look at the cyphers, you'll notice that they look awfully similar (the J, Q, P, D, and E are in the same positions). If you merge them, you get

CF.HJ.L.Q.U.WXY..PIDE...N.

Also, you'll note that the merged cypher is very regular and that the letters seem to be, up to a point, in alphabetical order. We can confidently fill in some of the missing spaces, for only one letter seems to fit:

CFGHJKL.Q.UVWXY..PIDE...N.

The last portion is suspiciously close to Spiderman, the name of the movie. That was one of the tell-tale signs that confirmed that the cyphers were unique to each pair, and that they were built from the name of the movie. Completing the cypher with the missing letters from Spiderman we get

CFGHJKL.Q.UVWXY.SPIDERMAN.

That leaves only a few letters left, which, again, we can detemine unequivocally:

CFGHJKLOQTUVWXYZSPIDERMANB

Some names were still missing their pair. I was able to infer a few by applying the cyphers of the still-unpaired names to the remaining encrypted names. For example, the cypher for Sean Connery produced .ONOR...AC..AN when decrypting one of the names. .ONOR could be nothing else than "Honor", so I queried www.imdb.com for all the people named that way. The first entry in the list was "Honor Blackman". Another direct hit!

At this point I was out of luck. I had no idea how to decrypt the remaining names and I had tried with all the actors and actresses I could remember. That's when I decided to investigate if IMDb had a programmatic interface. It did not, but it was possible to download the list of all known actors and actresses in a text file. Oh yeah, baby!

The third incarnation of the program would parse the text files and attempt to decrypt each of the remaining names with each of the names in the file. Surprisingly, some names produced unambiguous translations, but others resulted in dozens of alternatives. And I was not going to try one by one.

During the few minutes I spent thinking about how to best tackle the problem ahead I contemplated a couple of alternatives and I made an interesting discovery: if I picked any name (with Ae and Ad being the encrypted and decrypted versions, respectively) and another name B (with Be and Bd following the same rule as A), such that A and B are encrypted with the same cypher, the concatenation of Ae with Be has the same canonical form as the concatenation of Ad with Bd. If you stop to think about it for a second it, it makes perfect sense. Just consider what happens within a name. The encrypted and decrypted versions of the first name have the same canonical form. If we add the last name, which is written in terms of the standard alphabet in the decrypted version, and in a non-standard alphabet resulting applying the cypher on the standard alphabet, the strings are still isomorphic (i.e., they have the same canonical form). Thus, it follows that if we add a full name to each of the strings, each in their respective alphabet, the result will still be two isomorphic strings.

In the fourth incarnation, the program would pair up all the alternatives for the names that I hadn't been able to decrypt and make sure that their concatenation was isomorphic with the contatenation of the encrypted versions. Amazingly, the program produce one and only one pairing for each name! I was done with the first step.

Next, I had to figure out what were the names of the movies, since I had a hunch that the hidden quotation would be buried somewhere within the cyphers. I could've written a program to extract this information from the IMDb text files, but they are not well structured and I considered that it would take me longer to figure out how to parse them than to query IMDd. It did take some going back and forth in IMDb, but it wasn't that bad. Plus, I had already figured out most of the names, anyway.

Finding the hidden quotation took me a while, though. I tried everything I could possibly think of, to no avail. Then, it simply clicked. I looked at the release dates of the movies and noticed they were all different, and ranged from the 40's up to the present day. That could imply only one thing: they were meant to be sorted! And so I did. I sorted the cyphers based on the release date, in ascending order. And it didn't take me long to realize that the quote was in the diagonal of the matrix formed by the cyphers.

### no that protected, it seems

During a conversation with Diego today, he pointed out that the protected qualifier in Java does not prevent access to classes within the same package.

That was news to me. I always thought protected limited the access to subclasses of the class that contains the protected member.

As Diego points out in his weblog, it seems the Java Language Specification is incomplete. The ambiguity is actually cleared up in section 6.6.1 of the spec:
• A member (class, interface, field, or method) of a reference (class, interface, or array) type or a constructor of a class type is accessible only if the type is accessible and the member or constructor is declared to permit access:
• If the member or constructor is declared public, then access is permitted. All members of interfaces are implicitly public.
• Otherwise, if the member or constructor is declared protected, then access is permitted only when one of the following is true:
• Access to the member or constructor occurs from within the package containing the class in which the protected member or constructor is declared.
• Access is correct as described in ยง6.6.2.

### Amazon Gold Box ... gone

I just realized that Amazon has yanked the Gold Box feature from their site.

In the past two or so years I occasionally opened the gold box in search of a good deal. And never was I offered anything I may have remotely wished for. Items such as power tools, kitchen appliances and jewelry were common.

One would think the gold box would offer deals for items that were in one's wish list or related to stuff bought in in the past. Quite the opposite, items I had never even browsed for showed up.

I say, good riddance.

## January 27, 2005

### Solution to the GCHQ challenge

As promised, here's the solution to the challenge.

The names in the two lists are those of actors and actresses. They are encoded using a simple substitution cypher. There's one cypher for each pair of men and women, built from the name of the movie they performed in together. These are the translations:

 Men QJAMDU WWVDU MICKEY ROONEY KCRVKXHL EUJDXZ HUMPHREY BOGART XCT GSXXJYUQ REX HARRISON JRFJWRI XFCS GREGORY PECK NRIKUSL UIQORMDB CHARLES LAUGHTON QFDX ORNQTP TONY CURTIS UFIV WKXXZY JACK LEMMON HYUE WREEYCS SEAN CONNERY WKNJVWL GSWOXU RICHARD BURTON BZGZD A'GAANZ PETER O'TOOLE VTWMAC UEIIML ALBERT FINNEY ZGRT BPDIIRNN ALEC GUINNESS XIVL VFBD JOHN HURT GMTHYL IKUBGMFPTPSSPM ARNOLD SCHWARZENEGGER OFIIPTYX LYIJ HARRISON FORD RNBTCNP TCOSHNM ANTHONY HOPKINS KEZHQ WSNIEC KEVIN SPACEY LVZD CONKLNFK EWAN MCGREGOR DYFJN WCLEQPJ TOBEY MAGUIRE PZSS TEBBMJ BILL MURRAY

 Women LRNU FHZOHVN JUDY GARLAND MTJXMG EHXJRDT INGRID BERGMAN OSXFSXCZ XBZGCXDUXA MARGARET RUTHERFORD YAERFI KFXBARV AUDREY HEPBURN AIKUSBS EHSMKHNR MARLENE DIETRICH CZNTBXD CFDNFE MARILYN MONROE AQSEWKC XFIWFSYK SHIRLEY MACLAINE ORERC VIUWFNUE HONOR BLACKMAN BQKDVGBOJ OVIQXW ELIZABETH TAYLOR OUGEUDLRZ EZBVJDR KATHARINE HEPBURN TVFAMI WVYVTT LAUREN BACALL TZMMDR WDNCRM CARRIE FISHER CWUIFBLSK HSOGSB SIGOURNEY WEAVER YVTLG UGZVYNHT LINDA HAMILTON RKUUC VHMPUUPT KELLY MCGILLIS ECWHX YCMBXA JODIE FOSTER KHP FNWHQBEV KIM BASINGER DGOFBL AGUCZD NICOLE KIDMAN UQPIDJX HEXID KIRSTEN DUNST CQMBSVDD LNYMICCNI SCARLETT JOHANSSON

And here's how they relate to each other and the name of each movie:

 STRIKE UP THE BAND MICKEY ROONEY JUDY GARLAND CASABLANCA HUMPHREY BOGART INGRID BERGMAN BLITHE SPIRIT REX HARRISON MARGARET RUTHERFORD ROMAN HOLIDAY GREGORY PECK AUDREY HEPBURN WITNESS FOR THE PROSECUTION CHARLES LAUGHTON MARLENE DIETRICH SOME LIKE IT HOT TONY CURTIS MARILYN MONROE THE APARTMENT JACK LEMMON SHIRLEY MACLAINE GOLDFINGER SEAN CONNERY HONOR BLACKMAN WHO'S AFRAID OF VIRGINIA WOOLF RICHARD BURTON ELIZABETH TAYLOR THE LION IN WINTER PETER O'TOOLE KATHARINE HEPBURN MURDER ON THE ORIENT EXPRESS ALBERT FINNEY LAUREN BACALL STAR WARS ALEC GUINNESS CARRIE FISHER ALIEN JOHN HURT SIGOURNEY WEAVER THE TERMINATOR ARNOLD SCHWARZENEGGER LINDA HAMILTON WITNESS HARRISON FORD KELLY MCGILLIS THE SILENCE OF THE LAMBS ANTHONY HOPKINS JODIE FOSTER LA CONFIDENTIAL KEVIN SPACEY KIM BASINGER MOULIN ROUGE EWAN MCGREGOR NICOLE KIDMAN SPIDERMAN TOBEY MAGUIRE KIRSTEN DUNST LOST IN TRANSLATION BILL MURRAY SCARLETT JOHANSSON

The cyphers are created by taking the name of the movie, capitalizing it, removing duplicate letters and completing the string with the missing letters from the alphabet, in alphabetical order. The string is then shift-rotated by a certain number of positions.

For example, if the movie is "The silence of the lambs", we get "THESILNCOFAMB" after the first two steps. We then add the missing letters and obtain "THESILNCOFAMBDGJKPQRUVWXYZ". The cypher is then shift-rotated to produce "RUVWXYZTHESILNCOFAMBDGJKPQ".

You may be wondering how is that used to encrypt the actor name. Simple, replace each letter in the name with the letter that appears in the cypher in the position that corresponds to the original letter's position in the alphabet. For instance, to encrypt an F we look at the 6th position in the cypher (F is the 6th letter of the alphabet) and find a Y.

To find the hidden quotation we first listing all cyphers to form a matrix (26x20). They should be sorted according to the movie release date, in ascending order. The hidden quotation can be read from the diagonal of the matrix, starting top-left corner and going down and to the right. The phrase reads "Here's looking at you, kid", a quote from Casablanca.

 HBANDCFGJLMOQVWXYZSTRIKEUP STRIKE UP THE BAND DEFGHIJKMOPQRTUVWXYZCASBLN CASABLANCA SPRACDFGJKMNOQUVWXYZBLITHE BLITHE SPIRIT YBCEFGJKPQSTUVWXZROMANHLID ROMAN HOLIDAY ITNESFORHPCUABDGJKLMQVXYZW WITNESS FOR THE PROSECUTION ZSOMELIKTHABCDFGJNPQRUVWXY SOME LIKE IT HOT FGIJKLOQSUVWXYZTHEAPRMNBCD THE APARTMENT UVWXYZGOLDFINERABCHJKMPQST GOLDFINGER VGNLBCEJKMPQTUXYZWHOSAFRID WHO'S AFRAID OF VIRGINIA WOOLF UVXYZTHELIONWRABCDFGJKMPQS THE LION IN WINTER VWYZMURDEONTHIXPSABCFGJKLQ MURDER ON THE ORIENT EXPRESS ZSTARWBCDEFGHIJKLMNOPQUVXY STAR WARS OPQRSTUVWXYZALIENBCDFGHJKM ALIEN GJKLPQSUVWXYZTHERMINAOBCDF THE TERMINATOR FGHJKLMOPQRUVXYZWITNESABCD WITNESS RUVWXYZTHESILNCOFAMBDGJKPQ THE SILENCE OF THE LAMBS NFIDETBGHJKMPQRSUVWXYZLACO LA CONFIDENTIAL ZMOULINRGEABCDFHJKPQSTVWXY MOULIN ROUGE CFGHJKLOQTUVWXYZSPIDERMANB SPIDERMAN MPQUVWXYZLOSTINRABCDEFGHJK LOST IN TRANSLATION

Here's a Ruby script to automate the decryption process. You'll also need to get the list of movies and the actors.list and actresses.list files from IMDB's text database.

## January 26, 2005

### Conditional initialization in Ruby

Exploring Ruby further, I came accross the || operator. It behaves like the boolean or operator in other languages (Java, C++) but with a catch: it returns the value of the last evaluated term. Since evaluation is lazy (from left to right), the last evaluated term is either the first non-false (I'll qualify this later on, bear with me) term or the last term in the expression.

More concretely, the expression d = a || b || c will assign the value of a to d if a is non-false, the value of b if a is false and b is non-false, or the value of c otherwise.

A non-false value in Ruby is anything that's neither nil, nor false. Thus, values such as true, 1, 0, "a", [] are all non-false.

The || operator can be combined with the = operator in a self-assignment statement: a ||= expression, which is a shorthand for a = a || expression.

A statement of that kind is essentially a conditional initializer: if a is nil (i.e., not initialized), initialize it with the value of expression. Otherwise, leave it untouched.

The conditional initialization idiom has at least one interesting usage. Before we get to it, it is important to note that in Ruby every statement returns a value, which means that any statement can be used in place of an expression. For example, one could conceivably do:

(if true then 8 end) + 3

Ok, on to the example now...

It is common to have to keep track of lists of items associated with certain key values. The obvious way to implement that is with a map of keys to lists of values. In Java, the code for adding an element to such structure would typically look like this:

{
List list = (List) this.map.get(key);
if (list == null) {
list = new ArrayList();
this.map.put(key, list);
}

}

In Ruby, using the conditional initialization idiom, it could be written more compactly as:

(@map[key] ||= []) << value
end

The @map[key] ||= [] piece performs the conditional initialization of the list associated with key and the (...) << value part adds the value to the existing or newly created list.

Ok, ok... you might be thinking, while the Java syntax is more verbose, is certainly more readable. That may be true, but in that case you could still write:

list = (@map[key] ||= [])
list << value
end

Which is still more compact than in Java but in my opinion does not sacrifice readability.

Pretty neat, eh?

Labels:

## January 25, 2005

### Babbler

Here's a useless cute little program I wrote yesterday, called "Babbler", which creates new text based on how patterns of characters appear in a piece of text used to train the program. It is a Markov generator, which means that it uses the last N (N being the order) produced characters to determine which character comes next.

During training, Babbler analyzes all sequences of 1, 2, ... N characters and records the number of times each shows up in the original text.

To generate a character, it picks one at random from the set of all characters that may follow the sequence of the last N-1 output characters. The random function honors the frequency distribution of the characters in the source text. If the sequence of N-1 characters hasn't been seen in the source text, the program falls back to a N-2 sequence and so on.

Let's see an example. Using the book "The Prince" by Nicolo Machiavelli as training data, here's a fragment generated by an order-1 Babbler:

"gstkln tyeannioi rfa iaeafalrtutficbah tat eri de oesowaitpt neeff p odhi nn .ett borege r susnhsardlreneftgbnwauihn"

This is garbage. Essentially, an order-1 Babbler generates random characters based on their frequency in the source text. It gets better with higher order Babblers:

Order 2: "pom so owins ase is ibend antusirty hothlest r t nnd cthoath we wnt ony as y sizistaul, cos."
Order 3: "sse mays ing per bringet beforgeopers to st yound thus en of wily pir i same of cousembing of the vin to came they caussuccul is much aggrands"
Order 4: "runnibaldo, whers, count, and in had the for of meason thirds heles, markable in ought a luccion of france cound seat to blus, on of his rose they againtate trodus, he prince them will for have thing thould by have he abilitated the prince fro"

Here you can see that words start taking a definite english-like form -- they can be pronounced in english -- even if they are meaningless. Compare this to an order-4 Babbler trained with Don Quijote, by Cervantes (a well-known Spanish writer):

"no la tale pensigida, porque vaya ena gana, sea desto, y, aun llencio que durmino a mayor de su mores quelevas ciel tien del me dar locura razos mucho malano que alma: no hacer punto de me de sea; pensaminea quijo me hartida"

Lastly, here's an order-5 Babbler for The Prince:

"oned again they always or maste; and not. in 1510. thing except down they have saw the duke, and was defend when case helples. fifteen very were admitted by those offerent him outral; and intered himself anothing his notwith him near again"

The input to Babbler is a tokenizer object, which must implement the each() method. This allows for babbling randomly selected characters (what I've shown you so far) or words. Here's an example of an order-4 Babbler that uses entire words as tokens:

"should now take part; but nothing being concluded, Oliverotto da Fermo was sent to propose that if the duke wished to undertake an expedition against Tuscany they were ready; if he did not wish that the fortresses, which he did not recognize any need for doing so, he begged Castruccio to pardon the other members of his family by reason of the wrongs recently inflicted upon them."

If you're interested, here's the source: babbler.rb. It's written in Ruby, but may contain many Javaisms due to the fact I'm still getting used to the language (I started learning Ruby this weekend and this is my version of a "Hello World" -- on steroids)

## January 21, 2005

### Emulating keystrokes in Windows

One of the core functions in Shoot, a voice command program I wrote a couple of years ago, is the ability to emulate key presses programmatically.

There is a fairly straightforward way to do this in Windows 2000/XP, via the SendInput Win32 function. In Windows 98/ME things are somewhat more complicated: SendInput inserts the keystrokes in the Windows event queue, but DirectInput bypasses it and hooks directly into the keyboard driver.

As a result, any keystroke sent via SendInput is completely ignored by DirectInput applications. Pretty much every modern Windows-based game uses DirectInput, the main target for Shoot. The solution is to implement a Virtual Device Driver (VxD) that inserts the keystrokes directly into the keyboard driver buffer, and that is what I ended up doing for Shoot.

Here's how to use the SendInput function from C#. I stripped all the Win98/ME-related code. Not only would it make the code below significantly more complex, but it's probably useless by now. Who's using Win 98, anyway? Not even Microsoft supports it anymore :)

[StructLayout(LayoutKind.Sequential)]
private struct KEYBOARD_INPUT {
public uint type;
public ushort vk;
public ushort scanCode;
public uint flags;
public uint time;
public uint extrainfo;
}

[DllImport("User32.dll")]
private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst=1)] KEYBOARD_INPUT[] input, int structSize);

void press(int scanCode)
{
sendKey(scanCode, true);
}

void release(int scanCode)
{
sendKey(scanCode, false);
}

private void sendKey(int scanCode, bool press)
{
KEYBOARD_INPUT[] input = new KEYBOARD_INPUT[1];
input[0] = new KEYBOARD_INPUT();
input[0].type = INPUT_KEYBOARD;
input[0].flags = KEY_SCANCODE;

if ((scanCode & 0xFF00) == 0xE000) { // extended key?
input[0].flags |= KEY_EXTENDED;
}

if (press) { // press?
input[0].scanCode = (ushort) (scanCode & 0xFF);
}
else { // release?
input[0].scanCode = scanCode;
input[0].flags |= KEY_UP;
}

uint result = SendInput(1, input, Marshal.SizeOf(input[0]));

if (result != 1) {
throw new Exception("Could not send key: " + scanCode);
}
}

### Java tip #1

Working with Swing JTables can be tricky. Working with custom cell editors is even trickier. Here's a tip that may save you some time and frustration.

This is a custom cell editor (based on a text field) that will automatically highlight its contents as soon as it is becomes active and save the user from having to select the text with the mouse or with the all-too-common "home - shift+end" key sequence.

The trick is to select the text after the editor gets focus. Otherwise, the JTextField object will ignore the request.

Here's the code:

private class CustomTableCellEditor
extends DefaultCellEditor
{
public CustomTableCellEditor()
{
// use a JTextField as the actual editor
super(new JTextField());
final JTextField editor = (JTextField) getComponent();

editor.setName("Table.editor");
editor.setBorder(new LineBorder(Color.black));

// listen for focusing events
{
public void focusGained(FocusEvent e)
{
// now is the time to select the text in the JTextField
editor.selectAll();
}

public void focusLost(FocusEvent e) { }
});
}

public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
final JTextField textField = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);

Runnable task = new Runnable() {
public void run()
{
// set the focus on the JTextField
textField.requestFocusInWindow();
}
};

return textField;
}
}

## January 20, 2005

### GCHQ challenge

The GCHQ posted the following challenge a couple of months ago:

What is the connection between the men in the first list and the women in the second list? Which man pairs with which woman? And what is the hidden quotation?

 MEN KCRVKXHL EUJDXZ WKNJVWL GSWOXU HYUE WREEYCS QFDX ORNQTP VTWMAC UEIIML OFIIPTYX LYIJ ZGRT BPDIIRNN XCT GSXXJYUQ RNBTCNP TCOSHNM XIVL VFBD NRIKUSL UIQORMDB UFIV WKXXZY DYFJN WCLEQPJ LVZD CONKLNFK PZSS TEBBMJ BZGZD A'GAANZ JRFJWRI XFCS QJAMDU ZWWVDU GMTHYL IKUBGMFPTPSSPM KEZHQ WSNIEC WOMEN TVFAMI WVYVTT KHP FNWHQBEV MTJXMG EHXJRDT ORERC VIUWFNUE AIKUSBS EHSMKHNR UQPIDJX HEXID TZMMDR WDNCRM ECWHX YCMBXA LRNU FHZOHVN YVTLG UGZVYNHT YAERFI KFXBARV OUGEUDLRZ EZBVJDR CQMBSVDD LNYMICCNI DGOFBL AGUCZD AQSEWKC XFIWFSYK RKUUC VHMPUUPT CZNTBXD CFDNFE OSXFSXCZ XBZGCXDUXA BQKDVGBOJ OVIQXW CWUIFBLSK HSOGSB

I've already solved it, but I'm not going to tell you the answer yet. I'll post it here after the window for submitting the solution to GCHQ has closed.

What are you waiting for, get those brain cells working!

## January 19, 2005

### Garmin iQue 3600a

Garmin has unveiled a new GPS gadget, the iQue 3600a. It's a Palm device with GPS capabilities and a bunch of aviation-related software. It looks great, if you ask me, although it's a little bit pricey at \$1100.

I've been on the lookout for a GPS device lately, and the best candidate so far was the Garmin GPSMAP 296. The iQue has all the functions a regular Palm has, such as adress book and calendar, which, in my opinion tilt the balance against the 296 (plus the fact that the latter costs \$1700).

From the specs I couldn't see anything significant in the 296 that's missing in the iQue. In fact the iQue features a nifty 16-bit color screen (65536 colors) vs. 296's 8-bit (256 colors) screen.

For in-cockpit and outdoors use the 296 may be more robust (better shock resistance, weather-proof, longer battery life), but for regular day-to-day use, the iQue seems like a great contender.

Finally, I'm sure Garmin will make an SDK available for the 3600a soon. It's available for the 3600, and I'd be surprised if it doesn't already support the 3600a, so it may just be a matter of certification. The device runs PalmOS and the iQue 3600 page claims that all GPS-related functions are available to third-party software. Sweeeet!