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