// analyze field test files dumped from the iPhone // (C)2010,11 Simon Urbanek #import #import #include //--- on-disk data format (header) typedef struct record_header { int ts, lat, lon, dill, res, len; } record_header_t; NSMutableDictionary *curDict = nil; static void traverse(NSDictionary *d, NSString *prefix); static void printValue(id val, NSString *lastKey, NSString *prefix) { if ([val isKindOfClass:[NSDictionary class]]) { traverse(val, prefix); } else if ([val isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) val; int i = 0, n = [a count]; for (; i < n; i++) printValue([a objectAtIndex:i], lastKey, prefix ? [NSString stringWithFormat:@"%@[%d]", prefix, i] : nil); } else { if (curDict && val && lastKey) [curDict setObject:val forKey:lastKey]; if (prefix) printf("%s=%s\n", [prefix UTF8String], [[val description] UTF8String]); } } static void traverse(NSDictionary *d, NSString *prefix) { for (id key in d) { id val = [d objectForKey:key]; printValue(val, key, prefix ? [NSString stringWithFormat:@"%@:%@", prefix, key] : nil); } } static NSDictionary *unfold(NSDictionary *d) { NSDictionary *res = curDict = [[NSMutableDictionary alloc] init]; traverse(d, nil); curDict = nil; return [res autorelease]; } static id dictGet(NSDictionary *d, ...) { va_list ap; va_start(ap, d); id key, val = nil; while ((key = va_arg(ap, id)) && d) { val = [d objectForKey:key]; // NSLog(@"key=%@, val=%@\n", key, val); if (!val) return nil; if ([val isKindOfClass:[NSDictionary class]]) d = val; else d = nil; } va_end(ap); return val; } static void printVals(NSDictionary *d, ...) { va_list ap; va_start(ap, d); id key, val = nil; int i = 0; while ((key = va_arg(ap, id))) { val = [d objectForKey:key]; if (i++) printf("|"); if (val && [val isKindOfClass:[NSString class]]) printf("%s", [val UTF8String]); } va_end(ap); printf("\n"); } int main(int ac, char **av) { int help = 0, dump = 0, index = 0, skip = 0, max = -1; char *fn = 0; int ai = 0; while (++ai < ac) { if (av[ai][0] == '-') { switch (av[ai][1]) { case 'h': help = 1; break; case 'd': dump = 1; break; case 'n': if (++ai < ac) max = atoi(av[ai]); break; case 's': if (++ai < ac) skip = atoi(av[ai]); break; } } else if (!fn) fn = av[ai]; } if (help) { printf("\n Usage: %s [-h] [-d] [-s ] [-n ] \n\n", av[0]); return 0; } if (!fn) { fprintf(stderr, "ERROR: missing file argument (see -h if in doubt)\n"); return 1; } NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSError *err = nil; NSString *fname = [NSString stringWithUTF8String:fn]; NSData *data = [NSData dataWithContentsOfFile:fname options:0 error:&err]; if (!data) { NSLog(@"ERROR: loading %@ failed with %@", fname, err); return 1; } if (max > -1 && skip) max += skip; const char *b = [data bytes], *be = b + [data length]; while (b + sizeof(record_header_t) < be) { record_header_t rh; if (max > -1 && index >= max) break; index++; memcpy(&rh, b, sizeof(record_header_t)); int len = rh.len; if (index > skip) printf("INFO|%d|%d|%d|%d|%d\n", rh.ts, rh.lat, rh.lon, rh.dill, len); b += sizeof(record_header_t); if (b + len > be) { fprintf(stderr, "ERROR: requested %d bytes but only %d available\n", len, (int) (be - b)); return 1; } if (!len) /* zero-length data are ok - we just record location, but decoding doesn't like it to get out here */ continue; NSData *pldata = [NSData dataWithBytesNoCopy:(void*)b length:len freeWhenDone:NO]; b += len; if (index <= skip) continue; CFStringRef errstr = NULL; NSDictionary *pl = (NSDictionary*) CFPropertyListCreateFromXMLData(NULL, (CFDataRef)pldata, kCFPropertyListImmutable, &errstr); if (!pl) { fprintf(stderr, "ERROR: decoding failed with %s\n", [((NSString*)errstr)UTF8String]); return 1; } if (dump) { traverse(pl, @"root"); continue; } // GSM cell info NSDictionary *d, *ci = dictGet(pl, @"GSM Cell Environment", @"\"GSM\"", @"Cell Information", nil); if (ci) { // GSM service cell d = [ci objectForKey:@"Service Cell"]; if (d && (d = unfold(d))) { printf("GSM-SC|%d|", rh.ts); printVals(d, @"LAC", @"CI", @"MNC", @"BSIC", nil); // traverse(sci, @"service cell"); } d = dictGet(ci, @"Neighbour Cells", @"Neighbour Cell", nil); if (d && [d isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) d; for (id o in a) { d = unfold(o); NSString *mnc = (NSString*) [d objectForKey:@"MNC"]; if (mnc && ![mnc isEqualToString:@"Unknown"]) { printf("GSM-NC|%d|", rh.ts); printVals(d, @"LAC", @"CI", @"MNC", @"BSIC", @"Arfcn", @"RxLev", nil); } } //printf("%d neighbor cells\n", (int) [a count]); } } d = dictGet(pl, @"UMTS Cell Environment", @"\"UMTS\"", @"Neighbor Cells", @"Non-Ranked UMTS Set", @"NU", nil); if (d && [d isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) d; for (id o in a) { d = unfold(o); NSString *rscp = (NSString*) [d objectForKey:@"RSCP"]; if (rscp && ![rscp isEqualToString:@"Unknown"]) { printf("UMTS-NU|%d|", rh.ts); printVals(d, @"SC", @"ECN0", @"RSCP", @"DLF", @"RS", nil); } } } d = dictGet(pl, @"UMTS Cell Environment", @"\"UMTS\"", @"Neighbor Cells", @"Non-Ranked GSM Set", @"NG", nil); if (d && [d isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) d; for (id o in a) { d = unfold(o); NSString *rscp = (NSString*) [d objectForKey:@"Bsic"]; if (rscp && ![rscp isEqualToString:@"Unknown"]) { printf("UMTS-NG|%d|", rh.ts); printVals(d, @"Bsic", @"Arfcn", @"Rssi", @"RS", nil); } } } d = dictGet(pl, @"UMTS Cell Environment", @"\"UMTS\"", @"Neighbor Cells", @"Active Set", @"AS", nil); if (d && [d isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) d; for (id o in a) { d = unfold(o); NSString *rscp = (NSString*) [d objectForKey:@"RSCP"]; if (rscp && ![rscp isEqualToString:@"Unknown"]) { printf("UMTS-AS|%d|", rh.ts); printVals(d, @"SC", @"ECN0", @"RSCP", @"DLF", @"RS", nil); } } } d = dictGet(pl, @"UMTS Cell Environment", @"\"UMTS\"", @"Neighbor Cells", @"Virtual Active Set", @"VAS", nil); if (d && [d isKindOfClass:[NSArray class]]) { NSArray *a = (NSArray*) d; for (id o in a) { d = unfold(o); NSString *rscp = (NSString*) [d objectForKey:@"RSCP"]; if (rscp && ![rscp isEqualToString:@"Unknown"]) { printf("UMTS-VAS|%d|", rh.ts); printVals(d, @"SC", @"ECN0", @"RSCP", @"DLF", @"RS", nil); } } } } [pool release]; return 0; }