Plugins/Bonjour/libezv/Private Classes/AWEzvRendezvousData.m
author Evan Schoenberg
Sun Nov 22 09:58:41 2009 -0600 (2009-11-22)
changeset 2930 61a5bc1a37d6
parent 2794 ca3d8c8b4a3d
child 3087 30703336e5b6
permissions -rw-r--r--
* Fix a crash if `kDNSServiceErr_Invalid` is returned by `TXTRecordSetValue` which occured because the `%@` was being used for a C string
* Handle the situation which caused `kDNSServiceErr_Invalid` to be returned by `TXTRecordSetValue` if setting a long status message (≥ 250 characters) by truncating appropriately.

Fixes #11322
     1 /*
     2  * Project:     Libezv
     3  * File:        AWEzvRendezvousData.h
     4  *
     5  * Version:     1.0
     6  * Author:      Andrew Wellington <proton[at]wiretapped.net>
     7  *
     8  * License:
     9  * Copyright (C) 2004-2005 Andrew Wellington.
    10  * All rights reserved.
    11  * 
    12  * Redistribution and use in source and binary forms, with or without
    13  * modification, are permitted provided that the following conditions are met:
    14  * 
    15  * 1. Redistributions of source code must retain the above copyright notice,
    16  * this list of conditions and the following disclaimer.
    17  * 2. Redistributions in binary form must reproduce the above copyright notice,
    18  * this list of conditions and the following disclaimer in the documentation
    19  * and/or other materials provided with the distribution.
    20  * 
    21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
    22  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
    24  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    27  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    29  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
    30  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    31  */
    32  
    33 #import "AWEzvRendezvousData.h"
    34 #import "AWEzvSupportRoutines.h"
    35 #import <AIUtilities/AIStringAdditions.h>
    36 
    37 @implementation AWEzvRendezvousData
    38 
    39 /* subnegotiation that appears at start of rendezvous packet */
    40 /*                             Reserved version? */
    41 NSString	*subn = @"subn\x00\x00\x00\x01";
    42 
    43 /* end of subnegotation. significance of value unknown */
    44 /*                        Reserved unknown       */
    45 NSString	*endn = @"\x00\x00\x00\x00";
    46 
    47 /* initialization, create our dictionary */
    48 -(AWEzvRendezvousData *) init 
    49 {
    50     if ((self = [super init])) {
    51 		keys = [[NSMutableDictionary dictionary] retain];
    52 		serial = 1;
    53 	}
    54 
    55     return self;
    56 }
    57 
    58 /* intialise given an NSData containing an announcement */
    59 -(AWEzvRendezvousData *) initWithData:(NSData *)data {
    60     UInt32	version;	/* the version of the iChat announcement (?) */
    61     UInt32	fieldCount;	/* number of fields in the announcement */
    62     UInt16	fieldLen;	/* length of field being read */
    63     UInt32	i;		/* read index into data buffer */
    64     NSString	*fieldName;	/* name of field */
    65     NSString	*fieldContent;	/* contents of field */
    66     NSData	*tmpData;	/* temporary data */
    67     NSRange	range;		/* range for reading of data */
    68 
    69     /* call the standard initialisation */
    70     self = [self init];
    71     
    72     /* check that the length is ok */
    73     if ([data length] < ([subn length] + 4 + [endn length])) {
    74 	AWEzvLog(@"Invalid rendezvous announcement: length %u", [data length]);
    75 		[self autorelease];
    76 	return nil;
    77     }
    78         
    79     /* check version (?) of iChat announcement */
    80     range.location = 4;
    81     range.length = 4;
    82     [data getBytes:&version range:range];
    83     version = ntohl(version);
    84     if (version != 1) {
    85 	AWEzvLog(@"Invalid rendezvous announcement: incorrect version: %u", version);
    86 		[self autorelease];
    87 	return nil;
    88     }
    89     
    90     /* get serial of announcement */
    91     range.location = 8;
    92     range.length = 4;
    93     [data getBytes:&serial range:range];
    94     serial = ntohl(serial);
    95     
    96     /* get field count of data */
    97     range.location = 16;
    98     range.length = 4;
    99     [data getBytes:&fieldCount range:range];
   100     fieldCount = ntohl(fieldCount);
   101     
   102     /* read fields from data */
   103     for (i = [subn length] + 4 + [endn length] + 4; i < [data length];) {
   104 	int binFlag = 0;
   105 	
   106 	/* read length of field name */
   107 	if ([data length] < i + 2) {
   108 	    AWEzvLog(@"Invalid rendezvous announcement at field name length");
   109 		[self autorelease];
   110 	    return nil;
   111 	}
   112 	range.location = i;
   113 	range.length = 2;
   114 	[data getBytes:&fieldLen range:range];
   115 	fieldLen = ntohs(fieldLen);
   116 	fieldLen = fieldLen & 0x7FFF;
   117         i = i + 2;
   118         
   119 	/* read field data */
   120 	if ([data length] < i + fieldLen) {
   121 	    AWEzvLog(@"Invalid rendezvous announcement at field name");
   122 		[self autorelease];
   123 	    return nil;
   124 	}
   125         tmpData = [NSData dataWithBytes:[data bytes] + i length:fieldLen];
   126 	fieldName = [[[NSString alloc] initWithData:tmpData encoding:NSUTF8StringEncoding] autorelease];
   127 	i = i + fieldLen;
   128 	
   129 	/* read length of field data */
   130 	if ([data length] < i + 2) {
   131 	    AWEzvLog(@"Invalid rendezvous announcement at field data length");
   132 		[self autorelease];
   133 	    return nil;
   134 	}
   135 	range.location = i;
   136 	range.length = 2;
   137 	[data getBytes:&fieldLen range:range];
   138 	fieldLen = ntohs(fieldLen);
   139 	/* most significant bit in fieldLen is a binary data flag */
   140 	if ((fieldLen & 0x7FFF) != fieldLen)
   141 	    binFlag = 1;
   142         fieldLen = fieldLen & 0x7FFF;
   143 	i = i + 2;
   144 	
   145 	/* read field data */
   146 	if ([data length] < i + fieldLen) {
   147 	    AWEzvLog(@"Invalid rendezvous announcement at field data");
   148 		[self autorelease];
   149 	    return nil;
   150 	}
   151         if (!binFlag) {
   152             tmpData = [NSData dataWithBytes:[data bytes] + i length:fieldLen];
   153             fieldContent = [[[NSString alloc] initWithData:tmpData encoding:NSUTF8StringEncoding] autorelease];
   154         } else {
   155 			fieldContent = [[[NSString alloc] initWithBytes:[data bytes] + i length:fieldLen encoding:NSUTF8StringEncoding] autorelease];
   156         }
   157         i = i + fieldLen;
   158 	
   159 	/* save field information */
   160 	[self setField:fieldName content:fieldContent];
   161     }
   162     
   163     /* return initialised object */
   164     return self;
   165 }
   166 
   167 /* intialise rendezvous data with a plist of the data */
   168 -(AWEzvRendezvousData *) initWithPlist:(NSString *)plist {
   169     id		extracted;	/* extracted data from plist */
   170     NSData	*xmlData;	/* XML data in NSData form */
   171     NSString	*error;		/* error from conversion of plist */
   172     NSPropertyListFormat format;/* something we can point at for the format pointer */
   173     
   174     error = [NSString string];
   175     
   176     /* create XML data */
   177     xmlData = [NSData dataWithBytes:[plist UTF8String] length:[plist length]];
   178 
   179     /* extract plist from XML data */
   180     format = NSPropertyListXMLFormat_v1_0;
   181     extracted = [NSPropertyListSerialization
   182 		    propertyListFromData:xmlData
   183 		    mutabilityOption:NSPropertyListImmutable
   184 		    format:&format
   185 		    errorDescription:&error];
   186 
   187     /* check if there was an error in extraction */
   188     if (extracted == nil) {
   189 	AWEzvLog(@"Unable to extract XML into plist");
   190 		[self autorelease];
   191 	return nil;
   192     }
   193     
   194     /* make sure it's an NSData, or reponds to getBytes:range: */
   195     if (![extracted respondsToSelector:@selector(getBytes:range:)]) {
   196 	AWEzvLog(@"Extracted object from XML is not an NSData");
   197 		[self autorelease];
   198 	return nil;  
   199     }
   200 
   201     /* pass it to another initialiser */
   202     return [self initWithData:extracted];
   203 }
   204 
   205 /* initialise object with a dictionary */
   206 - (AWEzvRendezvousData *)initWithDictionary:(NSDictionary *)dictionary 
   207 {
   208     if ((self = [super init])) {
   209 		keys = [dictionary retain];
   210 		serial++;
   211 	}	
   212     
   213     return self;
   214 }
   215 
   216 /* initialise object with an AV TXT record */
   217 - (AWEzvRendezvousData *)initWithAVTxt:(NSString *)txt {
   218     NSArray *attribs;
   219     NSString *key;
   220     
   221     self = [self init];
   222     
   223     attribs = [txt componentsSeparatedByString:@"\001"];
   224 
   225     for (key in attribs) {
   226 	NSRange range;
   227 	
   228 	range = [key rangeOfString:@"="];
   229 	if (range.location != NSNotFound) {
   230 	    [self setField:[key substringToIndex:range.location]
   231 		   content:[key substringFromIndex:range.location+1]];
   232 	}
   233     }
   234     
   235     return self;
   236 }
   237 - (AWEzvRendezvousData *) initWithTXTRecordRef:(const unsigned char *) txtRecord length:(uint16_t)len{
   238 	
   239 	self = [self init];
   240     
   241 	DNSServiceErrorType txtRecordError;
   242 	
   243 	int i, numKeys;
   244 	numKeys = TXTRecordGetCount(len, txtRecord);
   245 	for (i=0; i<numKeys; i++) {
   246 		char key[256];
   247 		uint8_t valLen;
   248 		const void *value;
   249 		
   250 		txtRecordError = TXTRecordGetItemAtIndex (
   251 			/* length */ len,
   252 			/* txtrecord */ txtRecord,
   253 			/* index */ i,
   254 			/* keybuffer length */ sizeof(key), 
   255 		    /* key buffer */ key,
   256 			/* valueLength */ &valLen,
   257 			/* value pointer */ &value );
   258 		if (txtRecordError == kDNSServiceErr_NoError) {
   259 			NSString *keyString = NULL;
   260 			NSString *data = NULL;
   261 			keyString = [NSString stringWithUTF8String: key];
   262 			
   263 			if (value) {
   264 				data = [[[NSString alloc] initWithBytes: value length: valLen encoding: NSUTF8StringEncoding] autorelease];
   265 			}
   266 			
   267 			if (data != NULL && keyString != NULL) {
   268 				[self setField:keyString content:data];
   269 			} else {
   270 				AWEzvLog(@"Creating TXTRecord: No data and No key");
   271 			}
   272 			
   273 			/* AWEzvLog(@"key:%@ value=%@", keyString, data); */
   274 		} else {
   275 			AWEzvLog(@"Error reading txt keys");
   276 		}
   277 		
   278 		
   279 		
   280 	}
   281 		
   282 //	// kind of a hack: munge txtRecord so it's human-readable
   283 //	if ( len > 0) {
   284 //		char	*readableText = (char*) malloc( len);
   285 //		if ( readableText != nil) {
   286 //			ByteCount   index, subStrLen;
   287 //			memcpy( readableText, txtRecord, len);
   288 //			for ( index=0; index < len - 1; index += subStrLen + 1) {
   289 //				subStrLen = readableText[ index];
   290 //				readableText[ index] = '\n';
   291 //			}
   292 //			//NSLog(@"%@\n\n",[NSString stringWithCString:&readableText[1] length:len - 1]);
   293 //			free( readableText);
   294 //		}
   295 //	}
   296 	
   297 	return self;
   298 	
   299 }
   300 
   301 /* deallocate, destroy our dictionary */
   302 - (void)dealloc
   303 {
   304 	[keys release];
   305 	[super dealloc];
   306 }
   307 
   308 /* sets a field in the rendezvous data structures */
   309 -(void) setField:(NSString *)fieldName content:(NSObject *)content {
   310     if (content == nil || fieldName == nil)
   311         return;
   312     
   313     [keys setObject:content forKey:fieldName];
   314     serial++;
   315 }
   316 
   317 /* delete a field in the rendezvous data structure */
   318 -(void) deleteField:(NSString *)fieldName {
   319     if ([keys objectForKey:fieldName] != nil)
   320         [keys removeObjectForKey:fieldName];
   321 }
   322 
   323 /* get a field from the rendezvous data structure */
   324 -(NSString *) getField:(NSString *)fieldName {
   325     return [[[keys objectForKey:fieldName] copy] autorelease];
   326 }
   327 
   328 /* return if a field exists */
   329 -(BOOL) fieldExists:(NSString *)fieldName {
   330     return [keys objectForKey:fieldName] != nil;
   331 }
   332 
   333 /* return the serial number of the data */
   334 -(UInt32) serial {
   335     return serial;
   336 }
   337 
   338 /* return the dictionary */
   339 -(NSDictionary *)dictionary {
   340     return [[keys copy] autorelease];
   341 }
   342 
   343 /*
   344  * Generate data to be placed in the protocolSpecificInformation (TXT record)
   345  * of the rendezvous announcement. This is data shouldn't be used directly, but
   346  * should be used indirectly via dataAsDNSTXT or dataAsPackedPString
   347  */
   348 -(NSString *) data {
   349     NSMutableData   *data;		/* binary representation of rendezvous data */
   350     NSData	    *xmlData;		/* data converted to an XML plist */
   351     NSMutableString *infoData;		/* XML plist as a string */
   352     NSString *key;			/* strings used when manipulating data */
   353     id value;				/* value for data field */
   354     NSString	    *error;		/* error from creation of plist */
   355     UInt32	    keycount;		/* a 32-bit integer, count of keys in data */
   356     UInt16	    fieldlen;		/* a 16-bit integer, length of field being added to data */
   357     UInt16	    fieldlenBE;		/* fieldlen as converted to network byte order */
   358     UInt32      serialBE = htonl(serial); /* serial as converted to network byte order */
   359 
   360     /* allocate NSData to create data in */
   361     data = [[NSMutableData alloc] init];
   362     [data autorelease];
   363     /* add the subnegotiation string */
   364     [data appendBytes:[subn UTF8String] length:[subn length]];
   365     [data appendBytes:&serialBE length:4];
   366     [data appendBytes:[endn UTF8String] length:[endn length]];
   367     /* add a field containing the number of fields for the rest of the data */
   368     keycount = [keys count] + 1; /* +1 for slumming field */
   369     keycount = htonl(keycount);
   370     [data appendBytes:&keycount length:4];
   371 
   372     /* loop through fields to be added and add them to data */
   373 		for (key in keys) {	    
   374         /* add length of field name, then field name */
   375 		const char *field;
   376 		field = [key UTF8String];
   377 		fieldlen = strlen(field);
   378 		fieldlenBE = htons(fieldlen);
   379 		[data appendBytes:&fieldlenBE length:2];
   380 		[data appendBytes:field length:fieldlen];
   381 		
   382         /* add length of field data, then field data */
   383         value = [keys objectForKey:key];
   384         if ([value isKindOfClass: [NSData class]]) {
   385 			field = [value bytes];
   386 			fieldlen = [(NSData *)value length];
   387             fieldlen = fieldlen | ~0x7FFF;
   388 		} else {
   389 			field = [value UTF8String];
   390 			fieldlen = strlen(field);
   391 		}
   392 		fieldlenBE = htons(fieldlen);
   393         [data appendBytes:&fieldlenBE length:2];
   394         if ([value isKindOfClass: [NSData class]]) {
   395 			fieldlen = [(NSData *)value length];
   396 		}
   397 		[data appendBytes:field length:fieldlen];
   398 	}
   399     
   400     /* we're slumming it in iChat-land */
   401     key = @"slumming";
   402     fieldlen = [key length];
   403     fieldlenBE = htons(fieldlen);
   404     [data appendBytes:&fieldlenBE length:2];
   405     [data appendBytes:[key UTF8String] length:[key length]];
   406     value = @"1";
   407     fieldlen = [(NSData *)value length];
   408     fieldlenBE = htons(fieldlen);
   409     [data appendBytes:&fieldlenBE length:2];
   410     [data appendBytes:[value UTF8String] length:[(NSData *)value length]];
   411     
   412     /* create XML plist of data and convert to string */
   413     xmlData = [NSPropertyListSerialization dataFromPropertyList:data
   414     				    format:NSPropertyListXMLFormat_v1_0
   415     				    errorDescription:&error];
   416     infoData = [[NSMutableString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
   417     [infoData autorelease];
   418 	
   419     /* and now we have the rendezvous data to return to the caller, the copy
   420        converts it to immutable */
   421     return [[infoData copy] autorelease];
   422 }
   423 
   424 /* 
   425  * Converts data: to a format appropriate for passing to service registration.
   426  * We add an ASCII 1 character every 255 characters for pascal string separation
   427  */
   428 -(NSString *)dataAsDNSTXT {
   429     NSMutableString	*infoData = [[[self data] mutableCopy] autorelease]; /* data to be done */
   430     unsigned long	i;	/* loop counter */
   431 
   432     /* add the character \001 when we exceed 255 characters, required to allow announcement
   433     to be longer than 255 characters */
   434     for (i = 255; i < [infoData length]; i += 255)
   435     {
   436 	[infoData insertString:@"\001" atIndex:i];
   437     }
   438 
   439     /* return a copy so it is immutable */
   440     return [[infoData copy] autorelease];
   441 }
   442 
   443 /* ichat AV style TXT record */
   444 -(NSString *)avDataAsDNSTXT {
   445     NSMutableString *infoData = [NSMutableString string];
   446     id value;
   447     NSString	    *key;
   448     
   449     [infoData appendString:@"\001txtvers=1"];
   450     [infoData appendString:@"\001version=1"];
   451     
   452     /* enumerate through fields for announcement */    
   453 		for (key in keys) {	    
   454 	[infoData appendString:@"\001"];	
   455 	[infoData appendString:key];
   456 	[infoData appendString:@"="];
   457 	value = [keys objectForKey:key];
   458 	
   459 	if ([value isKindOfClass: [NSData class]]) {
   460 	    /* convert binary to hex */
   461 	    char *hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
   462 	    int i;
   463 	    
   464 	    for (i = 0; i < 20; i++) {
   465 		sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
   466 	    }
   467 	    hexdata[[(NSData *)value length] * 2] = '\0';
   468 	    
   469 	    [infoData appendString:[NSString stringWithUTF8String:hexdata]];
   470 	} else {
   471 	    [infoData appendString:value];
   472 	}
   473     }
   474     
   475     return infoData;
   476 }
   477 
   478 -(TXTRecordRef)dataAsTXTRecordRef {
   479 	//AWEzvLog(@"dataAsTXTRecordRef called");
   480 	TXTRecordRef txtRecord;
   481 	DNSServiceErrorType txtRecordError;
   482 	id value;
   483 	const char *valueToSet;
   484 	uint8_t valueSize;
   485 	TXTRecordCreate(/* TXTRecordRef */ &txtRecord, /* buffer length */ 0, /* buffer */ NULL);
   486 	
   487 	/* Enumerate through keys setting the txtrecordvalue */
   488 	for (NSString *key in [keys keyEnumerator]) {		
   489 		value = [keys objectForKey:key];
   490 		 //AWEzvLog(@"key=%@ value=%@", key, value);
   491 		
   492 		if ([value isKindOfClass: [NSData class]]) {
   493 		    /* convert binary to hex */
   494 		    char *hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
   495 		    int i;
   496             
   497 		    for (i = 0; i < 20; i++) {
   498 			sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
   499 		    }
   500 		    hexdata[[(NSData *)value length] * 2] = '\0';
   501 			valueToSet = [[NSString stringWithUTF8String:hexdata] UTF8String];
   502 			valueSize = strlen(valueToSet);
   503 			free(hexdata);
   504 		} else {
   505 		    valueToSet = [value UTF8String];
   506 			valueSize = strlen(valueToSet);
   507 		}
   508 		
   509 		txtRecordError = TXTRecordSetValue (
   510 			/* TXTRecord */ &txtRecord,
   511 			/* key */ [key UTF8String],
   512 			/* size, may be zero */ valueSize,
   513 			/* value, may be null */ valueToSet);
   514 		
   515 		if ((txtRecordError == kDNSServiceErr_Invalid) &&
   516 			[value isKindOfClass:[NSString class]]) {
   517 			/* kDNSServiceErr_Invalid may be returned if:
   518 			 *	1. Invalid characters were included (per documentation)
   519 			 *	2. The length of the value is >= 250 characters (at least for the msg key)
   520 			 *
   521 			 * So: First, try stripping out any non-ASCII characters, as I'm not sure what might consitute an
   522 			 * "illegal character" in a field which requests UTF8.
   523 			 *
   524 			 * Then, if that still fails, truncate the string to 248 characters (248,249,250 are the ellipsis).
   525 			 */
   526 			valueToSet = [[value dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES] bytes];
   527 			valueSize = strlen(valueToSet);
   528 
   529 			txtRecordError = TXTRecordSetValue (
   530 												/* TXTRecord */ &txtRecord,
   531 												/* key */ [key UTF8String],
   532 												/* size, may be zero */ valueSize,
   533 												/* value, may be null */ valueToSet);
   534 
   535 			if (txtRecordError == kDNSServiceErr_Invalid) {
   536 				valueToSet = [[value stringWithEllipsisByTruncatingToLength:248] UTF8String];
   537 				valueSize = strlen(valueToSet);
   538 				txtRecordError = TXTRecordSetValue (
   539 													/* TXTRecord */ &txtRecord,
   540 													/* key */ [key UTF8String],
   541 													/* size, may be zero */ valueSize,
   542 													/* value, may be null */ valueToSet);				
   543 			}
   544 		}
   545 
   546 		if (txtRecordError != kDNSServiceErr_NoError) {
   547 			if (txtRecordError == kDNSServiceErr_Invalid) {
   548 				AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Value contains illegal characters", key, valueToSet);
   549 				
   550 			} else if (txtRecordError == kDNSServiceErr_NoMemory) {
   551 				AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Exceeded available storage", key, valueToSet);
   552 			} else {
   553 				AWEzvLog(@"Error setting TXTRecord of key=%@ and value=%s: Error is %i", key, valueToSet, txtRecordError);
   554 			}
   555 			
   556 		}
   557 		
   558 	}
   559 	
   560 	return txtRecord;
   561 }
   562 /*
   563  * Converts data: to packed PString format as required by the low level rendezvous
   564  * functions when passing an opaque RData structure
   565  */
   566 -(NSData *) dataAsPackedPString {
   567     NSString		*origdata = [self data];	/* original data */
   568     NSMutableData	*data = [NSMutableData data];	/* modified data to return */
   569     unsigned char	pstring[256];			/* pascal string under construction */
   570     unsigned long	i;				/* loop counter */
   571     
   572     /* initialise pstring */
   573     pstring[0] = 0;
   574     
   575     /* create strings */
   576     for (i = 0; i < [origdata length]; i++) {
   577 	pstring[0]++;
   578 	pstring[pstring[0]] = [origdata characterAtIndex:i];
   579 	if (pstring[0] == 254 || i == [origdata length] - 1) {
   580 	    [data appendBytes:(char *)&pstring length:pstring[0] + 1];
   581 	    pstring[0] = 0;
   582 	}
   583     }
   584     
   585     /* return copy so it is immutable */
   586     return [[data copy] autorelease];
   587 }
   588 
   589 /* ichat AV style TXT record */
   590 -(NSData *)avDataAsPackedPString {
   591     NSMutableString *infoData = [NSMutableString string];
   592     id value;
   593     NSString	    *key;
   594     const char *data;
   595     
   596     [infoData appendString:@"\x09txtvers=1"];
   597     [infoData appendString:@"\x09version=1"];
   598     
   599     /* enumerate through fields for announcement */    
   600 		for (key in keys) {
   601 		/* convert binary to hex */
   602 		char *hexdata;
   603 		int i;
   604 		
   605 		value = [keys objectForKey:key];
   606 		
   607 		if ([value isKindOfClass:[NSData class]]) {
   608 			hexdata = (char *)malloc([(NSData *)value length] * 2 + 1);
   609 			
   610 			for (i = 0; i < 20; i++) {
   611 				sprintf(hexdata + (i*2), "%.2x", ((unsigned char *)[(NSData *)value bytes])[i]);
   612 			}
   613 			hexdata[[(NSData *)value length] * 2] = '\0';
   614 			
   615 			[infoData appendFormat:@"%c", ([(NSData *)value length] * 2 + [key length] + 1)];
   616 			[infoData appendString:key];
   617 			[infoData appendString:@"="];
   618 			[infoData appendString:[NSString stringWithUTF8String:hexdata]];
   619 			free(hexdata);
   620 		} else {
   621 			const char *val = [(NSString *)value UTF8String];
   622 			int len = strlen(val);
   623 			[infoData appendFormat:@"%c", len + [key length] + 1];
   624 			[infoData appendString:key];
   625 			[infoData appendString:@"="];
   626 			[infoData appendString:value];
   627 		}
   628     }
   629     
   630 	data = [infoData UTF8String];
   631 	
   632 	return [NSData dataWithBytes:data length:strlen(data)];
   633 }
   634 
   635 
   636 @end