NDS WiFi Programming with devkitARM — Part 4
Posted in NintendoDS, Uncategorized, sensors on February 25th, 2010 by Chaos EngineerWe've established the important parts of the PC application, the NdsInterfaceHost, and now comes time to actually delve into the DS programming aspects w/ devkitARM and dswifi. We will here lay out the design of the NintendoDS application, the NdsInterfaceClient. Many of the network and socket concepts we covered during Part 3 of this article will carry over into the DS code. I will try not to cover the same ground. Also note that it is entirely possible to use C++ and iostream type code with devkitARM. I chose to use standard C because it is less verbose, and for the DS platform, it just seems more appropriate to use less abstraction.
The NDS application exhibits much code that is ancillary to the main purpose of the project. Being a platform that is rarely developed for, there is only a small community focusing on NintendoDS development. I figure that any code that exploits the features of the DS or establishes a framework that can be extended and used for new purposes is a good contribution to the community. I won't cover this ancillary code, but be sure to download the project and examine the extra bits like the chat server that will let you telnet into your DS.
The basis of the NdsInterfaceClient is simple. On start, it displays some information about the DS it is running on, and then prompts the user to press B to get a list of wireless access points (APs). The code then calls Wifi_ScanMode(), and enters a loop that constantly polls the number of APs, collects information about each, and displays this information on the screen. The Wifi_ScanMode() call actually tells the dswifi library to poll and maintain an internal list of available APs. You can then query the length of this internal list using Wifi_GetNumAp(), and then get specifics using Wifi_GetAPData(...), passing the index of a particular AP.
For the AP list, I reused some code written by Stephen Stair (the author of the dswifi library). I added some ANSI color for readability, and changed the format for AP data so it offered more info, particularly letting the user know if an AP was WAP protected (and thus unusable by dswifi). One of devkitPro's first feats was to implement a console on the DS. The state of this implementation today is quite elite. The console supports ANSI escape sequences, which allow coloring text and arbitrarily relocating the cursor position. One could create an ASCII game using the console and escape sequences alone. The chat server code shows how to use escape sequences to create a "window" in the console.
Once an AP is selected from the list, the code will determine if the user needs to enter an encryption key. If so, a keyboard appears, and allows the entry of an encryption key. The user can enter either a 40b or 104b encryption key. Based on the length of the entry, the code determines if it is 64 or 128-bit WEP encryption, and attempts a connection to the AP. This is one of the least documented basic steps for using dswifi, and on top of that, there are some curiosities worth mentioning.
WAP encryption is unsupported by the NDS and NDS Lite. Support for WAP was added to the DSi, but it isn't likely we'll see it supported by the dswifi library any time soon, if ever. We are limited to using WEP-secured APs. There are two levels of supported WEP encryption, 64-bit and 128-bit. Here is where a curiosity appears. Both 64b and 128b WEP encryption use a 24b Initialization Vector, which is not included in the length of the encryption key you enter. So the actual length of the entered encryption keys for 64b and 128b WEP encryption are 40b and 104b, respectively. It is fine to refer to these encryption levels as either bit length including the IV, or bit length excluding the IV. Strangely, the dswifi library does both, and defines the following two values: WEPMODE_40BIT and WEPMODE_128BIT. It should be obvious which is which.
So, with the keyboard displayed a user can enter the ASCII representation of a 40b or 104b hex key, which is either a 10 or 26 character long string, respectively. We then need to convert this ASCII representation of a hex value into a 5 or 13 byte actual hex value. To accomplish this, we look at each character in the string and convert it from it's ASCII value to it's hex value. We then pack two of these hex values into a single byte (unsigned char), and append each byte onto our encryption key. Once we have 5 or 13 of these bytes, we pass them to the Wifi_ConnectAP(...) function, and wait for the connection to succeed or fail. This is accomplished by polling Wifi_AssocStatus(), waiting for it to return ASSOCSTATUS_ASSOCIATED, or ASSOCSTATUS_CANNOTCONNECT.
Once we successfully connect to a wireless access point, our DS will be assigned an IP address (we assume the use of DHCP). Now connected to the network, we can then start using functions like gethostbyname(...) to look up DNS entries, or connect(...) to make socket connections to remote hosts. Things are finally getting interesting. To provide a user interface to a variety of functions, we print a simple menu, with each DS button bound to a different function, and wait for a keypress:
if(status == ASSOCSTATUS_ASSOCIATED) while(1) { u32 ip = Wifi_GetIP(); consoleClear(); iprintf("\n"); iprintf("ip: [%i.%i.%i.%i]\n", (ip ) & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); iprintf("--------------------------------\n"); iprintf("Press Y to nslookup host name\n"); iprintf("Press X to connect to interface\n"); iprintf("Press A to listen on socket\n"); iprintf("Press B to break\n\n"); keypressed = 0; while(!(keypressed & (KEY_Y | KEY_X | KEY_A | KEY_B | KEY_L))) { scanKeys(); keypressed = keysDown(); } if(keypressed & KEY_Y) { //execute gethostbyname(...) } //etc... }
Now the code governing the interaction with the Host interface is very similar to the code in the NdsHostInterface application. We synchronously send a register signal, receive the signal port (and data port) back with the register acknowledgement signal from the Host interface. We then use this signal port to construct a listener, and this data port to pipe input data to the Host. This is the main loop of the NdsInterfaceClient:
while(1) { tvTimeout.tv_sec = 0; tvTimeout.tv_usec = 1000; // This will throttle the data xfer rate just a tad... // I've found that anything less than 1000 microseconds will make select return immediately // when called on the DS platform FD_ZERO(&fdsRead); FD_SET(sktServer, &fdsRead); retval = select(1, &fdsRead, NULL, NULL, &tvTimeout); if (retval == -1) iprintf("select() error\n"); else if (retval) { if((sktClient = accept(sktServer, (struct sockaddr *)&adrSignalClient, &iClientSize)) < 0) { iprintf("Failed to accept client connection.\n"); continue; } iprintf("Client connected: %s\n", inet_ntoa(adrSignalClient.sin_addr)); InterfaceSignalHandler(sktClient); } // If there are no signals from the host, and pen is down, send input data on UDP port. if(keysHeld() & KEY_TOUCH) { touchRead(&touchXY); pos = 0x00000000; pos = (touchXY.px | ((int)(touchXY.py) << 16)); iprintf("Sending 0x%x", pos); sendto(sktData, &pos, 4, 0, &adrData, sizeof(adrData)); } scanKeys(); if(keysDown() & KEY_B) break; } // Broke out of main loop, so unregister from Host and shutdown...
That is really the jist of the NdsInterfaceClient. Obviously check out the source code to see how to appropriately construct sockets. In my last article in this series, I'll provide links to the complete source code for both applications. I might take the time to make the NdsInterfaceHost compile on Windows as well as Linux and OSX, but no promises. I'd rather leave the standard Berkeley sockets implementation alone, and let you port it to winsock yourself... or even better let you install a REAL operating system, and run it there. =)

