I’m still alive…

Yes, I’m still alive and kicking, just off the grid for a while. I was able to implement my previous Objective-C code using the new Obj-C2 @property syntax and a better algorithm, so it is much shorter and works better. However, I still seem to have trouble converting all of the strings I need to from NSData to NSString objects. I don’t have access to my Quad right now, so I haven’t been able to work on it in a while, but that’s the last thing I need to fix before torrent file parsing should work.

In other news, I’ve been reading a lot about computer architecture and design, as well as Unix programming on Mac OS X. Once I get my Quad back and upgraded to Leopard, I should be able to start experimenting.

I’ve also updated my wish list, so family members seeking gift ideas should check it out.

Learning Through Doing

In an effort to teach myself Objective-C and the Cocoa framework, I’ve decided to create a Cocoa BitTorrent client. So far I’m able to parse torrent files (most of the time), hash the file to be downloaded (if it’s already downloaded) and the info dictionary string (big hack), and both scrape and get torrent specific data from trackers. Once this program can actually download a file from a torrent I’ll release the source code under an open source license (likely the Apache license). In the meantime, I’m going to be using whatever resources I can find. That includes IRC channels frequented by various Mac projects (#webkit, #transmission, etc) on irc.freenode.net (which may be how you got here). From time to time I’m going to post some of my code on this blog so I can get feedback from various parties on fixing bugs or implementing features. That’s what this post in about.

Currently, I can parse .torrent files pretty successfully. By turning them into NSDictionary’s I can access their contents very easily. However, I’m not 100% successful yet. Strangely, some torrents, like this one, don’t parse successfully, breaking my later code. It seems as if the contents of the comment and name keys in the file aren’t decoding correctly as UTF8 strings, so the data put into those keys is incorrect. I can’t figure out why that is. I suspect it has something to do with the findBlockRangeAtIndex: method. Here’s the code:

//
//  JSbcoder.m
//
//  Created by Jon Shier on 3/4/05.
//</code>
 
<code>#import "JSbcoder.h"
 
@implementation JSbcoder
 
- (NSDictionary *) decodeTorrentFileData: (NSData *) torrentData {
 
//Need error checking. Check that the data starts with d.
 
const char *torrentFileBytes = [torrentData bytes];
 
[self setDataLength: [torrentData length]];
 
return [NSDictionary dictionaryWithDictionary: [self decodeDictionaryFromBytes: torrentFileBytes withRange: NSMakeRange(1, dataLength - 2)]];
}
 
- (NSDictionary *) convertDictionaryDataToStrings: (NSDictionary *) torrentDictionary {
NSMutableArray *keyArray = [NSMutableArray arrayWithArray: [torrentDictionary allKeys]];
NSMutableArray *valueArray = [NSMutableArray arrayWithArray: [torrentDictionary allValues]];
 
for (int i = 0; i &lt; [keyArray count]; i++) {
[keyArray replaceObjectAtIndex: i withObject: [NSString stringWithUTF8String: [[keyArray objectAtIndex: i] bytes]]];
}
 
for (int i = 0; i &lt; [valueArray count]; i++) {
if([[valueArray objectAtIndex: i] isKindOfClass: [NSData class]] &amp;&amp; ![[keyArray objectAtIndex: i] isEqualToString: @"pieces"]) {
if(IsEmpty([NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]])) NSLog(@"A value is nil, value array is: %@ nil value at index %i which is %@ string of which is %@\n", valueArray, i, [valueArray objectAtIndex: i], [NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]]);
[valueArray replaceObjectAtIndex: i withObject: [NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]]];
} else if([[valueArray objectAtIndex: i] isKindOfClass: [NSDictionary class]]) {
[valueArray replaceObjectAtIndex: i withObject: [self convertDictionaryDataToStrings: [valueArray objectAtIndex: i]]];
}
}
 
return [NSDictionary dictionaryWithObjects: valueArray forKeys: keyArray];
}
 
- (NSNumber *) decodeIntegerFromBytes: (const char*) torrentData withRange: (NSRange) range {
 
return [NSNumber numberWithInt: [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue]];
}
 
- (NSData *) decodeStringAndReturnDataFromBytes: (const char*) torrentData withRange: (NSRange) range {
 
return [[NSData dataWithBytes: torrentData length: dataLength] subdataWithRange: range];
}
 
- (unsigned int) findBlockRangeAtIndex: (unsigned int) index inBytes: (const char *) torrentData {
int j = 0;
int blockLength = 1;
 
//Need error checking
 
while (index + blockLength &lt; dataLength) {
blockLength++;
if (torrentData[index + blockLength] == 'i') {
for (j = 1; j &lt; dataLength; j++) {
if (torrentData[index + j + blockLength] == 'e') break;
}
blockLength += j;
} else if (isdigit(torrentData[index + blockLength])) {
NSRange range;
for (j = 0; j &lt; dataLength; j++) {
if (isdigit(torrentData[j + index + blockLength]) &amp;&amp; torrentData[j + index + 1 + blockLength] == ':') {
range.location = index + blockLength;
range.length = j + 1;
break;
}
}
blockLength += j + 1;
j = [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue];
blockLength += j;
} else if (torrentData[index + blockLength] == 'l') {
blockLength += [self findBlockRangeAtIndex: (index + blockLength) inBytes: torrentData] - 1;
} else if (torrentData[index + blockLength] == 'd') {
blockLength += [self findBlockRangeAtIndex: (index + blockLength) inBytes: torrentData] - 1;
} else if (torrentData[index + blockLength] == 'e') {
break;
}
}
 
return blockLength + 1;
}
 
- (NSArray *) decodeListFromBytes: (const char *) torrentData withRange: (NSRange) range {
 
//Insert error checking
 
return [NSArray arrayWithArray:[self createArrayFromData: torrentData withRange: range]];
}
 
- (NSDictionary *) decodeDictionaryFromBytes: (const char *) torrentData withRange: (NSRange) range {
 
//Need error checking
 
NSArray *methodArray = [NSArray arrayWithArray:[self createArrayFromData: torrentData withRange: range]];
 
NSMutableArray *keyArray = [NSMutableArray array];
NSMutableArray *valueArray = [NSMutableArray array];
for(int i = 0; i &lt; [methodArray count]; i += 2) {
if([[methodArray objectAtIndex: i] isKindOfClass: [NSData class]]) {
if(IsEmpty([NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]])) {
[keyArray addObject: [methodArray objectAtIndex: i]];
} else [keyArray addObject: [NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]]];
} else {
[keyArray addObject: [methodArray objectAtIndex: i]];
}
}
 
for(int i = 1; i &lt; [methodArray count]; i += 2) {
if([[methodArray objectAtIndex: i] isKindOfClass: [NSData class]] ) {
if(IsEmpty([NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]])) {
[valueArray addObject: [methodArray objectAtIndex: i]];
} else [valueArray addObject: [NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]]];
} else {
[valueArray addObject: [methodArray objectAtIndex: i]];
}
}
return [NSDictionary dictionaryWithObjects: valueArray forKeys: keyArray];
}
 
- (void) setDataLength: (unsigned int) torrentFileDataLength {
dataLength = torrentFileDataLength;
}
 
- (NSArray *) createArrayFromData: (const char *) torrentData withRange: (NSRange) range {
NSMutableArray *methodArray = [NSMutableArray array];
 
unsigned int j = 0;
unsigned int k = range.length + range.location;
for (int i = range.location; i &lt;= k; i++) {
if (torrentData[i] == 'i') {
for (j = 1; j &lt; dataLength; j++) {
if (torrentData[i + j] == 'e') break;
}
range.location = i + 1;
range.length = j - 1;
[methodArray addObject: [self decodeIntegerFromBytes: torrentData withRange: range]];
i += j;
} else if (isdigit(torrentData[i])) {
for (j = 0; j &lt; dataLength; j++) {
if (isdigit(torrentData[j + i]) &amp;&amp; torrentData[j + i + 1] == ':') {
range.location = i;
range.length = j + 1;
break;
}
}
i += range.length;
j = [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue];
range.location += range.length + 1;
range.length = j;
[methodArray addObject: [self decodeStringAndReturnDataFromBytes: torrentData withRange: range]];
i += range.length;
} else if (torrentData[i] == 'l') {
range.location = i + 1;
range.length = [self findBlockRangeAtIndex: i inBytes: torrentData] - 2;
[methodArray addObject: [self decodeListFromBytes: torrentData withRange: range]];
i += range.length + 1;
} else if (torrentData[i] == 'd') {
range.location = i + 1;
range.length = [self findBlockRangeAtIndex: i inBytes: torrentData] - 2;
[methodArray addObject: [self decodeDictionaryFromBytes: torrentData withRange: range]];
i += range.length + 1;
}
}
return methodArray;
}
 
@end

I know it’s rough and fragile, but anything you can suggest will help. If you want more info on the BitTorrent specification this is the best resource I could find.