MYErrorUtils.m
author snej@snej.local
Sun Apr 12 22:00:36 2009 -0700 (2009-04-12)
changeset 25 47d10ac2d04e
parent 23 a910102a1c9d
child 27 256370e8935a
permissions -rw-r--r--
Fixed some incorrect CSSM error code strings. Removed a log call from MYError.
snej@22
     1
//
snej@22
     2
//  MYErrorUtils.m
snej@22
     3
//  MYCrypto
snej@22
     4
//
snej@22
     5
//  Created by Jens Alfke on 2/25/09.
snej@22
     6
//  Copyright 2009 Jens Alfke. All rights reserved.
snej@22
     7
//
snej@22
     8
snej@22
     9
#import "MYErrorUtils.h"
snej@22
    10
#import "Test.h"
snej@22
    11
#import "CollectionUtils.h"
snej@22
    12
#import <Foundation/Foundation.h>
snej@23
    13
#import <Security/SecBase.h>
snej@22
    14
snej@22
    15
snej@22
    16
NSString* const MYErrorDomain = @"MYErrorDomain";
snej@22
    17
snej@22
    18
snej@22
    19
static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
snej@22
    20
{
snej@22
    21
    message = [[NSString alloc] initWithFormat: message arguments: args];
snej@22
    22
    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
snej@25
    23
                                                      message, NSLocalizedDescriptionKey,
snej@25
    24
                                                      nil];
snej@22
    25
    [message release];
snej@22
    26
    return [NSError errorWithDomain: domain
snej@22
    27
                               code: errorCode
snej@22
    28
                           userInfo: userInfo];
snej@22
    29
}
snej@22
    30
snej@22
    31
snej@22
    32
NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
snej@22
    33
{
snej@22
    34
    va_list args;
snej@22
    35
    va_start(args,message);
snej@22
    36
    NSError *error = MYMakeErrorV(errorCode,domain,message,args);
snej@22
    37
    va_end(args);
snej@22
    38
    return error;
snej@22
    39
}
snej@22
    40
snej@22
    41
snej@23
    42
BOOL MYReturnError( NSError **outError,
snej@23
    43
                    int errorCode, NSString *domain, NSString *messageFormat, ... ) 
snej@23
    44
{
snej@23
    45
    if (errorCode) {
snej@23
    46
        if (outError) {
snej@23
    47
            va_list args;
snej@23
    48
            va_start(args,messageFormat);
snej@23
    49
            *outError = MYMakeErrorV(errorCode, domain, messageFormat, args);
snej@23
    50
            va_end(args);
snej@25
    51
            Log(@"MYReturnError: %@",*outError);
snej@25
    52
        } else
snej@25
    53
            Log(@"MYReturnError: %@/%i",domain,errorCode);
snej@23
    54
        return NO;
snej@23
    55
    } else
snej@23
    56
        return YES;
snej@23
    57
}
snej@23
    58
snej@23
    59
snej@22
    60
BOOL MYMiscError( NSError **error, NSString *message, ... )
snej@22
    61
{
snej@22
    62
    if (error) {
snej@22
    63
        va_list args;
snej@22
    64
        va_start(args,message);
snej@22
    65
        *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
snej@22
    66
        va_end(args);
snej@22
    67
    }
snej@22
    68
    return NO;
snej@22
    69
}
snej@22
    70
snej@22
    71
snej@22
    72
static NSString* printableOSType( OSType t ) {
snej@22
    73
    if (t < 0x20202020 || t > 0x7e7e7e7e)
snej@22
    74
        return nil;
snej@22
    75
    union {
snej@22
    76
        OSType ostype;
snej@22
    77
        unsigned char ch[4];
snej@22
    78
    } buf;
snej@22
    79
    buf.ostype = CFSwapInt32HostToBig(t);
snej@22
    80
    for (int i=0; i<4; i++)
snej@22
    81
        if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
snej@22
    82
            return nil;
snej@22
    83
    return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
snej@22
    84
            autorelease];
snej@22
    85
}
snej@22
    86
snej@22
    87
snej@22
    88
static NSString* printableErrorCode( NSInteger code ) {
snej@22
    89
    if (code < -99999)
snej@22
    90
        return $sprintf(@"%u", code);       // CSSM errors are huge unsigned values > 0x80000000
snej@22
    91
    NSString *result = printableOSType((OSType)code);
snej@22
    92
    if (result)
snej@22
    93
        return result;                      // CoreAudio errors are OSTypes (4-char strings)
snej@22
    94
    return $sprintf(@"%i", code);           // Default: OSStatus and errno values are signed
snej@22
    95
}
snej@22
    96
snej@22
    97
static NSString* MYShortErrorDomainName( NSString *domain ) {
snej@22
    98
    if ([domain hasPrefix: @"kCFErrorDomain"])
snej@22
    99
        domain = [domain substringFromIndex: 14];
snej@22
   100
    else {
snej@22
   101
        if ([domain hasSuffix: @"ErrorDomain"])
snej@22
   102
            domain = [domain substringToIndex: domain.length - 11];
snej@22
   103
        if ([domain hasPrefix: @"NS"])
snej@22
   104
            domain = [domain substringFromIndex: 2];
snej@22
   105
    }
snej@22
   106
    return domain;
snej@22
   107
}
snej@22
   108
snej@22
   109
NSString* MYErrorName( NSString *domain, NSInteger code ) {
snej@22
   110
    if (code == 0)
snej@22
   111
        return nil;
snej@22
   112
    NSString *codeStr = printableErrorCode(code);
snej@22
   113
    if (!domain)
snej@22
   114
        return codeStr;
snej@22
   115
    NSString *result = nil;
snej@22
   116
    
snej@22
   117
    if ($equal(domain,NSPOSIXErrorDomain)) {
snej@22
   118
        // Interpret POSIX errors via strerror
snej@22
   119
        // (which unfortunately returns a description, not the name of the constant)
snej@22
   120
        const char *name = strerror(code);
snej@22
   121
        if (name) {
snej@22
   122
            result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
snej@22
   123
            if ([result hasPrefix: @"Unknown error"])
snej@22
   124
                result = nil;
snej@22
   125
        }
snej@22
   126
    } 
snej@23
   127
#if !TARGET_OS_IPHONE || defined(__SEC_TYPES__)
snej@22
   128
    else if ($equal(domain,NSOSStatusErrorDomain)) {
snej@22
   129
        // If it's an OSStatus, check whether CarbonCore knows its name:
snej@22
   130
        const char *name = NULL;
snej@22
   131
#if !TARGET_OS_IPHONE
snej@22
   132
        name = GetMacOSStatusErrorString(code);
snej@22
   133
#endif
snej@22
   134
        if (name && *name)
snej@22
   135
            result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
snej@22
   136
        else {
snej@22
   137
            result = (id) SecCopyErrorMessageString(code,NULL);
snej@22
   138
            if (result) {
snej@22
   139
                [(id)CFMakeCollectable(result) autorelease];
snej@22
   140
                if ([result hasPrefix: @"OSStatus "])
snej@22
   141
                    result = nil; // just a generic message
snej@22
   142
            }
snej@22
   143
        }
snej@22
   144
    }
snej@22
   145
#endif
snej@22
   146
    
snej@22
   147
    if (!result) {
snej@22
   148
        // Look up errors in string files keyed by the domain name:
snej@22
   149
        NSString *table = [@"MYError_" stringByAppendingString: domain];
snej@22
   150
        result = [[NSBundle mainBundle] localizedStringForKey: codeStr value: @"?" table: table];
snej@22
   151
        if ([result isEqualToString: @"?"])
snej@22
   152
            result = nil;
snej@22
   153
    }
snej@22
   154
    
snej@22
   155
    codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
snej@22
   156
    return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
snej@22
   157
}
snej@22
   158
snej@22
   159
snej@22
   160
snej@22
   161
snej@22
   162
@implementation NSError (MYUtils)
snej@22
   163
snej@22
   164
- (NSError*) my_errorByPrependingMessage: (NSString*)message
snej@22
   165
{
snej@22
   166
    if( message.length ) {
snej@22
   167
        NSDictionary *oldUserInfo = self.userInfo;
snej@22
   168
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
snej@22
   169
        if( oldUserInfo )
snej@22
   170
            [userInfo addEntriesFromDictionary: oldUserInfo];
snej@22
   171
        NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
snej@22
   172
        if( desc )
snej@22
   173
            message = $sprintf(@"%@: %@", message, desc);
snej@22
   174
        [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
snej@22
   175
        return [NSError errorWithDomain: self.domain
snej@22
   176
                                   code: self.code
snej@22
   177
                               userInfo: userInfo];
snej@22
   178
    } else
snej@22
   179
        return self;
snej@22
   180
}
snej@22
   181
snej@22
   182
- (NSString*) my_nameOfCode {
snej@22
   183
    return MYErrorName(self.domain, self.code);
snej@22
   184
}
snej@22
   185
snej@22
   186
@end
snej@22
   187
snej@22
   188
snej@22
   189
TestCase(MYErrorUtils) {
snej@22
   190
    CAssertEqual(printableOSType('abcd'), @"abcd");
snej@22
   191
    CAssertEqual(printableOSType('    '), @"    ");
snej@22
   192
    CAssertEqual(printableOSType(0x7e7e7e7e), @"~~~~");
snej@22
   193
    CAssertEqual(printableOSType(0x7e7F7e7e), nil);
snej@22
   194
    CAssertEqual(printableOSType(0x7e0D7e7e), nil);
snej@22
   195
    CAssertEqual(printableOSType(0), nil);
snej@22
   196
    CAssertEqual(printableOSType((OSType)-123456), nil);
snej@22
   197
snej@22
   198
    CAssertEqual(MYErrorName(nil,0),      nil);
snej@22
   199
    CAssertEqual(MYErrorName(nil,12345),  @"12345");
snej@22
   200
    CAssertEqual(MYErrorName(nil,1),      @"1");
snej@22
   201
    CAssertEqual(MYErrorName(nil,-1),     @"-1");
snej@22
   202
    CAssertEqual(MYErrorName(nil,12345),  @"12345");
snej@22
   203
    CAssertEqual(MYErrorName(nil,-12345), @"-12345");
snej@22
   204
    CAssertEqual(MYErrorName(nil,2147549184u), @"2147549184");
snej@22
   205
    
snej@22
   206
    CAssertEqual(MYErrorName(@"foobar",0), nil);
snej@22
   207
    CAssertEqual(MYErrorName(@"foobar",'fmt?'), @"foobar fmt?");
snej@22
   208
    CAssertEqual(MYErrorName(@"foobar",1), @"foobar 1");
snej@22
   209
    CAssertEqual(MYErrorName(@"FoobarErrorDomain",-1), @"Foobar -1");
snej@22
   210
    CAssertEqual(MYErrorName(@"NSFoobarErrorDomain",12345), @"Foobar 12345");
snej@22
   211
snej@22
   212
    NSError *err;
snej@22
   213
    err = [NSError errorWithDomain: NSPOSIXErrorDomain code: EPERM userInfo: nil];
snej@22
   214
    CAssertEqual(err.my_nameOfCode, @"Operation not permitted (POSIX 1)");
snej@22
   215
    err = [NSError errorWithDomain: NSPOSIXErrorDomain code: 12345 userInfo: nil];
snej@22
   216
    CAssertEqual(err.my_nameOfCode, @"POSIX 12345");
snej@22
   217
    
snej@22
   218
#if !TARGET_OS_IPHONE
snej@22
   219
    err = [NSError errorWithDomain: NSOSStatusErrorDomain code: paramErr userInfo: nil];
snej@22
   220
    CAssertEqual(err.my_nameOfCode, @"paramErr (OSStatus -50)");
snej@22
   221
    err = [NSError errorWithDomain: NSOSStatusErrorDomain code: fnfErr userInfo: nil];
snej@22
   222
    CAssertEqual(err.my_nameOfCode, @"fnfErr (OSStatus -43)");
snej@22
   223
    err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25291 userInfo: nil];
snej@22
   224
    CAssertEqual(err.my_nameOfCode, @"errKCNotAvailable / errSecNotAvailable (OSStatus -25291)");
snej@22
   225
    err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25260 userInfo: nil];
snej@22
   226
    CAssertEqual(err.my_nameOfCode, @"Passphrase is required for import/export. (OSStatus -25260)");
snej@22
   227
#endif
snej@22
   228
    err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
snej@22
   229
    CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
snej@22
   230
snej@22
   231
    err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
snej@22
   232
    CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
snej@22
   233
snej@22
   234
    err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
snej@22
   235
    CAssertEqual(err.my_nameOfCode, @"Cocoa 100");
snej@22
   236
}