snej@22: // snej@22: // MYErrorUtils.m snej@22: // MYCrypto snej@22: // snej@22: // Created by Jens Alfke on 2/25/09. snej@22: // Copyright 2009 Jens Alfke. All rights reserved. snej@22: // snej@22: snej@22: #import "MYErrorUtils.h" snej@22: #import "Test.h" snej@22: #import "CollectionUtils.h" snej@22: #import jens@27: jens@27: #if USE_SECURITY_API snej@23: #import jens@27: #endif snej@22: snej@22: snej@22: NSString* const MYErrorDomain = @"MYErrorDomain"; snej@22: snej@22: snej@22: static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args ) snej@22: { snej@22: message = [[NSString alloc] initWithFormat: message arguments: args]; snej@22: NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: snej@25: message, NSLocalizedDescriptionKey, snej@25: nil]; snej@22: [message release]; snej@22: return [NSError errorWithDomain: domain snej@22: code: errorCode snej@22: userInfo: userInfo]; snej@22: } snej@22: snej@22: snej@22: NSError *MYError( int errorCode, NSString *domain, NSString *message, ... ) snej@22: { snej@22: va_list args; snej@22: va_start(args,message); snej@22: NSError *error = MYMakeErrorV(errorCode,domain,message,args); snej@22: va_end(args); snej@22: return error; snej@22: } snej@22: snej@22: snej@23: BOOL MYReturnError( NSError **outError, snej@23: int errorCode, NSString *domain, NSString *messageFormat, ... ) snej@23: { snej@23: if (errorCode) { snej@23: if (outError) { snej@23: va_list args; snej@23: va_start(args,messageFormat); snej@23: *outError = MYMakeErrorV(errorCode, domain, messageFormat, args); snej@23: va_end(args); snej@25: Log(@"MYReturnError: %@",*outError); snej@25: } else snej@25: Log(@"MYReturnError: %@/%i",domain,errorCode); snej@23: return NO; snej@23: } else snej@23: return YES; snej@23: } snej@23: snej@23: snej@22: BOOL MYMiscError( NSError **error, NSString *message, ... ) snej@22: { snej@22: if (error) { snej@22: va_list args; snej@22: va_start(args,message); snej@22: *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args); snej@22: va_end(args); snej@22: } snej@22: return NO; snej@22: } snej@22: snej@22: snej@22: static NSString* printableOSType( OSType t ) { snej@22: if (t < 0x20202020 || t > 0x7e7e7e7e) snej@22: return nil; snej@22: union { snej@22: OSType ostype; snej@22: unsigned char ch[4]; snej@22: } buf; snej@22: buf.ostype = CFSwapInt32HostToBig(t); snej@22: for (int i=0; i<4; i++) snej@22: if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E) snej@22: return nil; snej@22: return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding] snej@22: autorelease]; snej@22: } snej@22: snej@22: snej@22: static NSString* printableErrorCode( NSInteger code ) { snej@22: if (code < -99999) snej@22: return $sprintf(@"%u", code); // CSSM errors are huge unsigned values > 0x80000000 snej@22: NSString *result = printableOSType((OSType)code); snej@22: if (result) snej@22: return result; // CoreAudio errors are OSTypes (4-char strings) snej@22: return $sprintf(@"%i", code); // Default: OSStatus and errno values are signed snej@22: } snej@22: snej@22: static NSString* MYShortErrorDomainName( NSString *domain ) { snej@22: if ([domain hasPrefix: @"kCFErrorDomain"]) snej@22: domain = [domain substringFromIndex: 14]; snej@22: else { snej@22: if ([domain hasSuffix: @"ErrorDomain"]) snej@22: domain = [domain substringToIndex: domain.length - 11]; snej@22: if ([domain hasPrefix: @"NS"]) snej@22: domain = [domain substringFromIndex: 2]; snej@22: } snej@22: return domain; snej@22: } snej@22: snej@22: NSString* MYErrorName( NSString *domain, NSInteger code ) { snej@22: if (code == 0) snej@22: return nil; snej@22: NSString *codeStr = printableErrorCode(code); snej@22: if (!domain) snej@22: return codeStr; snej@22: NSString *result = nil; snej@22: snej@22: if ($equal(domain,NSPOSIXErrorDomain)) { snej@22: // Interpret POSIX errors via strerror snej@22: // (which unfortunately returns a description, not the name of the constant) snej@22: const char *name = strerror(code); snej@22: if (name) { snej@22: result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding]; snej@22: if ([result hasPrefix: @"Unknown error"]) snej@22: result = nil; snej@22: } snej@22: } snej@23: #if !TARGET_OS_IPHONE || defined(__SEC_TYPES__) snej@22: else if ($equal(domain,NSOSStatusErrorDomain)) { snej@22: // If it's an OSStatus, check whether CarbonCore knows its name: snej@22: const char *name = NULL; snej@22: #if !TARGET_OS_IPHONE snej@22: name = GetMacOSStatusErrorString(code); snej@22: #endif snej@22: if (name && *name) snej@22: result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding]; snej@22: else { jens@27: #if USE_SECURITY_API snej@22: result = (id) SecCopyErrorMessageString(code,NULL); snej@22: if (result) { jens@29: [NSMakeCollectable(result) autorelease]; snej@22: if ([result hasPrefix: @"OSStatus "]) snej@22: result = nil; // just a generic message snej@22: } jens@27: #endif snej@22: } snej@22: } snej@22: #endif snej@22: snej@22: if (!result) { snej@22: // Look up errors in string files keyed by the domain name: snej@22: NSString *table = [@"MYError_" stringByAppendingString: domain]; snej@22: result = [[NSBundle mainBundle] localizedStringForKey: codeStr value: @"?" table: table]; snej@22: if ([result isEqualToString: @"?"]) snej@22: result = nil; snej@22: } snej@22: snej@22: codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);; snej@22: return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr; snej@22: } snej@22: snej@22: snej@22: snej@22: snej@22: @implementation NSError (MYUtils) snej@22: snej@22: - (NSError*) my_errorByPrependingMessage: (NSString*)message snej@22: { snej@22: if( message.length ) { snej@22: NSDictionary *oldUserInfo = self.userInfo; snej@22: NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; snej@22: if( oldUserInfo ) snej@22: [userInfo addEntriesFromDictionary: oldUserInfo]; snej@22: NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey]; snej@22: if( desc ) snej@22: message = $sprintf(@"%@: %@", message, desc); snej@22: [userInfo setObject: message forKey: NSLocalizedDescriptionKey]; snej@22: return [NSError errorWithDomain: self.domain snej@22: code: self.code snej@22: userInfo: userInfo]; snej@22: } else snej@22: return self; snej@22: } snej@22: snej@22: - (NSString*) my_nameOfCode { snej@22: return MYErrorName(self.domain, self.code); snej@22: } snej@22: snej@22: @end snej@22: snej@22: snej@22: TestCase(MYErrorUtils) { snej@22: CAssertEqual(printableOSType('abcd'), @"abcd"); snej@22: CAssertEqual(printableOSType(' '), @" "); snej@22: CAssertEqual(printableOSType(0x7e7e7e7e), @"~~~~"); snej@22: CAssertEqual(printableOSType(0x7e7F7e7e), nil); snej@22: CAssertEqual(printableOSType(0x7e0D7e7e), nil); snej@22: CAssertEqual(printableOSType(0), nil); snej@22: CAssertEqual(printableOSType((OSType)-123456), nil); snej@22: snej@22: CAssertEqual(MYErrorName(nil,0), nil); snej@22: CAssertEqual(MYErrorName(nil,12345), @"12345"); snej@22: CAssertEqual(MYErrorName(nil,1), @"1"); snej@22: CAssertEqual(MYErrorName(nil,-1), @"-1"); snej@22: CAssertEqual(MYErrorName(nil,12345), @"12345"); snej@22: CAssertEqual(MYErrorName(nil,-12345), @"-12345"); snej@22: CAssertEqual(MYErrorName(nil,2147549184u), @"2147549184"); snej@22: snej@22: CAssertEqual(MYErrorName(@"foobar",0), nil); snej@22: CAssertEqual(MYErrorName(@"foobar",'fmt?'), @"foobar fmt?"); snej@22: CAssertEqual(MYErrorName(@"foobar",1), @"foobar 1"); snej@22: CAssertEqual(MYErrorName(@"FoobarErrorDomain",-1), @"Foobar -1"); snej@22: CAssertEqual(MYErrorName(@"NSFoobarErrorDomain",12345), @"Foobar 12345"); snej@22: snej@22: NSError *err; snej@22: err = [NSError errorWithDomain: NSPOSIXErrorDomain code: EPERM userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"Operation not permitted (POSIX 1)"); snej@22: err = [NSError errorWithDomain: NSPOSIXErrorDomain code: 12345 userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"POSIX 12345"); snej@22: snej@22: #if !TARGET_OS_IPHONE snej@22: err = [NSError errorWithDomain: NSOSStatusErrorDomain code: paramErr userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"paramErr (OSStatus -50)"); snej@22: err = [NSError errorWithDomain: NSOSStatusErrorDomain code: fnfErr userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"fnfErr (OSStatus -43)"); snej@22: err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25291 userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"errKCNotAvailable / errSecNotAvailable (OSStatus -25291)"); snej@22: err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25260 userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"Passphrase is required for import/export. (OSStatus -25260)"); snej@22: #endif snej@22: err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"OSStatus 12345"); snej@22: snej@22: err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)"); snej@22: snej@22: err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil]; snej@22: CAssertEqual(err.my_nameOfCode, @"Cocoa 100"); snej@22: }