1/*
2 * Copyright (c) 2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24// ****************************************************************************
25//  cclparser.m
26//  libccl
27//
28//  Created by kevine on 3/1/06.
29//  Copyright 2006-7 Apple, Inc. All rights reserved.
30// *****************************************************************************
31
32// #define SC_SCHEMA_DECLARATION(k,q)     extern NSString * k;      // ??
33#import "cclparser.h"
34
35
36// names that would otherwise cause ambiguity are expanded
37#define kPersonalityExpanded @"CCLNameExpanded"
38#define kPathExpanded @"CCLPathExpanded"
39// localized by BTSA and network preferences
40
41
42@implementation CCLParser
43
44+ (CCLParser*) createCCLParser
45{
46    CCLParser* retVal= [[CCLParser alloc] init];
47
48    return retVal;
49}
50
51/******************************************************************************
52* see cclparser.h for a description of the class variables
53******************************************************************************/
54- (id) init
55{
56    self = [super init];
57    mBundleData = [[NSMutableDictionary alloc] init];
58    mBundlesProcessed = [[NSMutableDictionary alloc] init];
59	mFlatOverrides = [[NSMutableDictionary alloc] init];
60	mTypeFilter = nil;
61
62    return self;
63}
64
65// ****************************************************************************************************
66- (void) dealloc
67{
68	[self setTypeFilter:nil];
69	[mFlatOverrides release];
70	[mBundlesProcessed release];
71    [mBundleData release];
72    [super dealloc];
73
74    return;
75}
76
77- (void)finalize
78{
79    [super finalize];
80}
81
82- (void)setTypeFilter:(NSSet*)desiredConnectTypes
83{
84	[desiredConnectTypes retain];
85	[mTypeFilter release];
86
87	mTypeFilter = desiredConnectTypes;
88}
89
90
91// ****************************************************************************************************
92- (NSMutableDictionary*)buildBaseModelDict:(NSDictionary*)matchEntry
93        path:(NSString*)cclPath personality:(NSString*)personality
94{
95    NSMutableDictionary* rval = NULL;
96    NSMutableDictionary* baseDict = [NSMutableDictionary dictionaryWithCapacity:5];
97    id connectType= [matchEntry objectForKey: (id)kCCLConnectTypeKey];
98    id cclVars= [matchEntry objectForKey: (id)kCCLParametersKey];
99    id gprsCaps = [matchEntry objectForKey: (id)kCCLGPRSCapabilitiesKey];
100
101    if (!baseDict ||
102            ![connectType isKindOfClass: [NSString class]] ||
103            ![cclVars isKindOfClass: [NSDictionary class]])
104        goto finish;
105
106    [baseDict setValue:cclPath forKey:(id)kSCPropNetModemConnectionScript];
107    [baseDict setValue:personality forKey:(id)kSCPropNetModemConnectionPersonality];
108    [baseDict setValue:connectType forKey:(id)kCCLConnectTypeKey];
109    [baseDict setValue:cclVars forKey:(id)kCCLParametersKey];
110
111    if ([connectType isEqualTo:(id)kCCLConnectGPRS]) {
112        if (![gprsCaps isKindOfClass:[NSDictionary class]])
113            goto finish;
114        [baseDict setValue:gprsCaps forKey:(id)kCCLGPRSCapabilitiesKey];
115    }
116
117    rval = baseDict;
118
119finish:
120    return rval;
121}
122
123// ****************************************************************************************************
124- (BOOL) parseMatchEntry:(NSDictionary*)matchEntry path:(NSString*)cclPath personality:(NSString*)personality mergeDict:(NSMutableDictionary*)mergeDict
125{
126    BOOL retVal= NO;
127    NSMutableDictionary *firstModelDict = nil;
128	NSArray *supersedesList;
129
130	NSArray* deviceNameList= [matchEntry objectForKey: (id)kCCLDeviceNamesKey];
131	if(deviceNameList!= NULL && [deviceNameList isKindOfClass: [NSArray class]]) {
132		NSEnumerator* deviceEnum= [deviceNameList objectEnumerator];
133		NSDictionary* curEntry= [deviceEnum nextObject];
134
135		retVal= YES;
136		while(curEntry!= NULL) {
137			BOOL success = NO;
138
139			if([curEntry isKindOfClass: [NSDictionary class]]) {
140				NSString* venName= [curEntry objectForKey: (id)kCCLVendorKey];
141				NSString* modName= [curEntry objectForKey: (id)kCCLModelKey];
142
143				if([venName isKindOfClass: [NSString class]] &&
144						[modName isKindOfClass: [NSString class]]) {
145					NSMutableDictionary *modelDict;
146
147					// Since each UI dict has its own model/vendor keys,
148					// UIs need a separate dictionary for each pair
149					modelDict= [self buildBaseModelDict:matchEntry
150							path:cclPath personality:personality];
151					[modelDict setObject: modName forKey:(id)kCCLModelKey];
152					[modelDict setObject: venName forKey:(id)kCCLVendorKey];
153
154					NSMutableArray* venList= [mergeDict objectForKey: venName];
155					if(venList!= NULL) {
156						[venList addObject: modelDict];
157					} else {
158						venList= [NSMutableArray arrayWithObject: modelDict];
159						[mergeDict setObject: venList forKey: venName];
160					}
161					success = YES;
162
163					// stash for overrides below
164					if (!firstModelDict)
165						firstModelDict = modelDict;
166				}
167			}
168			retVal&= success;
169			curEntry= [deviceEnum nextObject];
170		}
171	}
172
173	// for the overrides, we use firstModelDict captured above
174	if (firstModelDict) {
175		// .ccl bundles implicitly supersede flat scripts of the same basename
176		NSString *oldName = [cclPath lastPathComponent];
177		if ([[oldName pathExtension] isEqual:(id)kCCLFileExtension])
178			oldName = [oldName stringByDeletingPathExtension];
179		// if a bundle has multiple personalities, implicit override -> first
180		if (![mFlatOverrides objectForKey:oldName])
181			[mFlatOverrides setObject:firstModelDict forKey:oldName];
182		/* all personalities one foo.ccl will all implicitly supersede foo
183		else
184			NSLog(@"WARNING: %@/%@ also claims to supersede %@",
185					cclPath, personality, oldName);
186		*/
187
188		// if this personality overrides anything else,
189		// add one description to mFlatOverrides
190		supersedesList = [matchEntry objectForKey:(id)kCCLSupersedesKey];
191		if (supersedesList && [supersedesList isKindOfClass:[NSArray class]]) {
192			NSEnumerator *e = [supersedesList objectEnumerator];
193			id flatscript;
194
195			while ((flatscript = [e nextObject])) {
196				if ([flatscript isKindOfClass:[NSString class]])
197					[mFlatOverrides setObject:firstModelDict forKey:flatscript];
198				else
199					NSLog(@"%@'s Supersedes list: unintelligible object %@",
200							cclPath, flatscript);
201			}
202		}
203	}
204
205    return retVal;
206}
207
208// ****************************************************************************************************
209- (BOOL) mergePersonalityDict: (NSDictionary*) mergeDict
210{
211    NSEnumerator* mergeVenEnum = [mergeDict keyEnumerator];
212    NSString* venName;
213
214    // walk list of vendors in the merge dictionary,
215    // adding to existing lists in mBundleData
216    while((venName = [mergeVenEnum nextObject])) {
217        NSArray* mergeModels = [mergeDict objectForKey:venName];
218        NSMutableArray* existingModels = [mBundleData objectForKey:venName];
219
220        if(existingModels)
221            [existingModels addObjectsFromArray:mergeModels];
222        else
223            [mBundleData setObject:mergeModels forKey:venName];
224    }
225    return YES;
226}
227
228// ****************************************************************************
229- (BOOL)processFlatCCL:(NSString*)path named:(NSString*)name
230{
231    NSMutableDictionary *modelDict;
232    NSMutableArray *otherVendor;
233
234    // someday we could validate or auto-name, but flat CCLs are fading
235    // (mutable for "cleanupMatchingModels")
236    modelDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
237            (id)kCCLConnectDialup, (id)kCCLConnectTypeKey,
238            name, (id)kCCLModelKey,
239            (id)kCCLOtherVendorName, (id)kCCLVendorKey,
240            path, (id)kSCPropNetModemConnectionScript,
241        NULL];
242    if (!modelDict)     return NO;
243
244    // add to existing 'Other' vendor if it exists; else create
245    otherVendor = [mBundleData objectForKey:(id)kCCLOtherVendorName];
246    if (otherVendor) {
247        [otherVendor addObject:modelDict];
248    } else {
249        if(!(otherVendor = [NSMutableArray arrayWithObject:modelDict]))
250            return NO;
251        [mBundleData setObject:otherVendor forKey:(id)kCCLOtherVendorName];
252    }
253
254    return YES;
255}
256
257// ****************************************************************************************************
258- (BOOL) processCCLBundle:(NSString*)cclPath
259{
260    BOOL retVal= NO;
261    NSDictionary* infoDict= [[NSBundle bundleWithPath: cclPath] infoDictionary];
262	NSNumber *verNum;
263
264	// check for verNum since broken plist doesn't lead to nULL infoDict
265    if([infoDict isKindOfClass:[NSDictionary class]] &&
266			(verNum = [infoDict objectForKey:(id)kCCLVersionKey]))
267    {
268		NSString *cfbundleID = [infoDict objectForKey:(id)kCFBundleIdentifierKey];
269		NSString *opath;
270
271		// ignore duplicate bundles
272		// log if the duplication doesn't involve /S/L/Modem Scripts
273		if ((opath = [mBundlesProcessed objectForKey:cfbundleID])) {
274			if (!([opath hasPrefix:@"/System/Library"] ||
275					[cclPath hasPrefix:@"/System/Library"]))
276				NSLog(@"%@ appears to be a duplicate of %@ (id = %@); ignoring",
277						cclPath, opath, cfbundleID);
278			return YES;
279		} else {
280			// add the bundle ID
281			[mBundlesProcessed setObject:cclPath forKey:(id)cfbundleID];
282		}
283
284        if([verNum isKindOfClass: [NSNumber class]] && [verNum isEqualToNumber:
285                [NSNumber numberWithInt: kCCLBundleVersion]])
286        { //Versions match, let the fun continue...
287            NSDictionary* personalityList= [infoDict objectForKey: (id)kCCLPersonalitiesKey];
288            if(personalityList!= NULL && [personalityList isKindOfClass: [NSDictionary class]])
289            {
290                NSMutableDictionary* mergeDict= [NSMutableDictionary dictionaryWithCapacity: [personalityList count]];
291                NSEnumerator* personalityKeyEnum= [personalityList keyEnumerator];
292                NSString* personalityKey= [personalityKeyEnum nextObject];
293
294                retVal= YES;
295                while((personalityKey!= NULL) && retVal)
296                {
297                    NSDictionary* personalityEntry= [personalityList objectForKey: personalityKey];
298                    if([personalityEntry isKindOfClass: [NSDictionary class]]) {
299						BOOL interested;
300
301						if (!mTypeFilter) {
302							interested = YES;
303						} else {
304							NSString *connectType = [personalityEntry objectForKey:(id)kCCLConnectTypeKey];
305							// see if this personality's type matches
306							interested = [mTypeFilter containsObject:connectType];
307						}
308
309						if (interested)
310							retVal= [self parseMatchEntry: personalityEntry path: cclPath personality: personalityKey mergeDict: mergeDict];
311					}
312                    personalityKey= [personalityKeyEnum nextObject];
313                }
314
315                if(retVal)
316                    retVal= [self mergePersonalityDict: mergeDict];
317            } else
318                NSLog(@"skipping %@: trouble extracting personalities",cclPath);
319        } else
320            NSLog(@"skipping %@: incompatible CCL version number: %@",
321                    cclPath, verNum);
322    } else
323        NSLog(@"skipping %@: malformed bundle dictionary", cclPath);
324
325    return retVal;
326}
327
328// ****************************************************************************************************
329- (BOOL) processFolder:(NSString*)folderPath
330{
331    BOOL retVal= YES;
332    NSFileManager* fileMan=  [NSFileManager defaultManager];
333    NSDirectoryEnumerator* folderEnum= [fileMan enumeratorAtPath:folderPath];
334    NSString *curFileName, *displayName;
335
336    while((curFileName = [folderEnum nextObject])) {
337        NSString* filePath;
338        BOOL isDir = NO, exists;
339
340		filePath = [folderPath stringByAppendingPathComponent:curFileName];
341		if (![fileMan fileExistsAtPath:filePath isDirectory:&isDir]) {
342			NSLog(@"Warning: %@ doesn't seem to exist", filePath);
343			continue;
344		}
345
346		// if it's a new-fangled .ccl bundle, process appropriately
347		if (isDir) {
348			if ([[curFileName pathExtension] isEqualToString:(id)kCCLFileExtension]){
349				[folderEnum skipDescendents];   // don't descend into bundle
350				retVal&= [self processCCLBundle: filePath];
351			} else {
352				// ignore .directories
353				if ([[filePath lastPathComponent] hasPrefix:@"."])
354					[folderEnum skipDescendents];
355			}
356        } else {
357			// (note: we always process flat CCLs b/c we don't know their type)
358			// handle as a flat file
359			CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:filePath];
360			LSItemInfoRecord info;
361			OSStatus errn;
362
363			// 4152940 requests invisibility info in NSFileManager
364			errn = LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
365			if (errn) {
366				NSLog(@"skipping %@: error %d getting item info",filePath,errn);
367				continue;
368			}
369
370			// must be visible and flat
371			if ((info.flags & (kLSItemInfoIsPlainFile | kLSItemInfoIsSymlink))
372					&& !(info.flags & kLSItemInfoIsInvisible)) {
373				// use display name of file
374				displayName = [fileMan displayNameAtPath:filePath];
375				retVal&= [self processFlatCCL:filePath named:displayName];
376            }
377        }
378    }
379
380    return retVal;
381}
382
383
384// ****************************************************************************************************
385- (void) cleanupNameWithPersonality: (NSMutableDictionary*) modelDict
386{
387    if([modelDict objectForKey: kPersonalityExpanded]== NULL)
388    {
389        NSString* modelName= [modelDict objectForKey: (id)kCCLModelKey];
390        NSString* modelPersonality= [modelDict objectForKey: (id)kSCPropNetModemConnectionPersonality];
391
392	if (modelName && modelPersonality) {
393	    NSString* newModelName= [NSString stringWithFormat: @"%@, %@", modelName, modelPersonality];
394	    [modelDict setObject: newModelName forKey: (id)kCCLModelKey];
395	    [modelDict setObject: [NSNull null] forKey: kPersonalityExpanded];
396	}
397    }
398}
399
400// ****************************************************************************************************
401- (void) cleanupNameWithCCLName: (NSMutableDictionary*) modelDict
402{
403    if([modelDict objectForKey: kPathExpanded]== NULL)
404    {
405        NSString *modelName = [modelDict objectForKey: (id)kCCLModelKey];
406	NSString  *cScript = [modelDict objectForKey:(id)kSCPropNetModemConnectionScript];
407        NSString *cclName = [cScript lastPathComponent];
408	if ([modelName isEqual:cclName]) {
409	    NSLog(@"a second copy of %@ is installed at %@?", cclName, cScript);
410	    cclName = cScript;
411	}
412
413	if (![modelName isEqual:cclName]) {
414	    NSString* newModelName= [NSString stringWithFormat: @"%@, %@", modelName, cclName];
415	    [modelDict setObject: newModelName forKey: (id)kCCLModelKey];
416	    [modelDict setObject: [NSNull null] forKey: kPathExpanded];
417	}
418    }
419}
420
421// ****************************************************************************************************
422- (bool) cleanupMatchingModels: (NSDictionary*) curModelDict modelArray: (NSArray*) modelArray changeSEL: (SEL) selector
423{
424    bool retVal= NO;
425    NSMutableArray* matchList= [NSMutableArray array];
426    NSString* matchModelName= [curModelDict objectForKey: (id)kCCLModelKey];
427    NSEnumerator* modelEnum= [modelArray objectEnumerator];
428    NSDictionary* curDict= [modelEnum nextObject];
429    while(curDict!= NULL)
430    {
431        if(curDict!= curModelDict)
432        {
433            NSString* curModelName= [curDict objectForKey: (id)kCCLModelKey];
434            if([matchModelName isEqualToString: curModelName])
435            {
436                [matchList addObject: curDict];
437            }
438
439        }
440        curDict= [modelEnum nextObject];
441    }
442    if([matchList count]>0)
443    {
444        [matchList addObject: curModelDict];
445        NSEnumerator* modelEnum= [matchList objectEnumerator];
446        NSMutableDictionary* curDict= [modelEnum nextObject];
447        while(curDict!= NULL)
448        {
449            [self performSelector: selector withObject: curDict];
450            curDict= [modelEnum nextObject];
451        }
452        retVal= YES;
453    }
454    return retVal;
455}
456
457// ****************************************************************************************************
458- (void)cleanupDuplicates
459{
460    NSEnumerator	*keyEnum;
461    NSString		*curVenName, *modName;
462	NSMutableArray	*persList;
463	unsigned		persCount, i;
464
465	// first remove overridden flat CCLs (message -> nil == no-op)
466	// remove from mBundleData/Other any names in mFlatOverrides
467	persList = [mBundleData objectForKey:kCCLOtherVendorName];
468	persCount = [persList count];
469	for (i = 0; i < persCount; i++) {
470		modName = [[persList objectAtIndex:i] objectForKey:(id)kCCLModelKey];
471		if ([mFlatOverrides objectForKey:modName]) {
472			[persList removeObjectAtIndex:i];
473			i--; persCount--;		// since everything slid
474		}
475	}
476	// and if nothing's left, remove the Other vendor entirely
477	if (persList && [persList count] == 0)
478		[mBundleData removeObjectForKey:kCCLOtherVendorName];
479
480	// check each model list against itself (XX use NSOrderedSet?)
481	keyEnum = [mBundleData keyEnumerator];
482    while(curVenName= [keyEnum nextObject]) {
483        NSArray* modelArray= [mBundleData objectForKey: curVenName];
484        NSEnumerator* modelEnum= [modelArray objectEnumerator];
485        NSMutableDictionary* curModelDict;
486        while((curModelDict = [modelEnum nextObject]))
487            if([self cleanupMatchingModels: curModelDict modelArray: modelArray changeSEL: @selector(cleanupNameWithCCLName:)])
488                [self cleanupMatchingModels: curModelDict modelArray: modelArray changeSEL: @selector(cleanupNameWithPersonality:)];
489    }
490}
491
492// ****************************************************************************************************
493- (NSArray*)copyVendorList
494{
495    NSMutableArray *vendors;
496
497    // get mutable copy of all vendors
498    vendors = [[mBundleData allKeys] mutableCopy];
499
500    // remove 'Other'; sort; and append
501    [vendors removeObject:(id)kCCLOtherVendorName];
502    [vendors sortUsingSelector:@selector(caseInsensitiveCompare:)];
503	if ([mBundleData objectForKey:(id)kCCLOtherVendorName])
504		[vendors addObject:(id)kCCLOtherVendorName];
505
506    return vendors;
507}
508
509// ****************************************************************************************************
510- (NSArray*)getModelListForVendor: (NSString*) vendor
511{
512	NSMutableArray *models = [mBundleData objectForKey:vendor];
513	NSSortDescriptor* sortd = [[NSSortDescriptor alloc]
514			initWithKey:(id)kCCLModelKey ascending:TRUE
515			selector:@selector(caseInsensitiveCompare:)];
516
517	// might have sorted it before, but this doesn't happen often and
518	// the sort should be fast if already sorted
519	[models sortUsingDescriptors:[NSArray arrayWithObject:sortd]];
520	[sortd release];
521
522    return models;
523}
524
525// ****************************************************************************************************
526- (void) clearParser
527{
528    [mBundleData removeAllObjects];
529	[mBundlesProcessed removeAllObjects];
530	[mFlatOverrides removeAllObjects];
531}
532
533/*******************************************************************************
534* mergeCCLPersonality:withDeviceConfiguration: takes a "modem" dict that would
535* have come from SystemConfiguration (e.g. containing phone number and such)
536* and sets various keys in a new dictionary based on data in the personality.
537* Some key/value pairs are just copied (connection script and personality
538* within the script bundle) or removed as appropriate, but GPRS APN and CID
539* are expressed as "preferred" (aka "default") in the personality dict while
540* they are hard values in the device dictionary.  If the user has already
541* chosen APN or CID for this personality, we don't copy over the preferred
542* values.  But if the user is choosing the personality for the first time,
543* we do copy the values over.  The UI can then use them to populate the UI.
544*
545* We return a new NSMutableDictionary so that the UI can call us repeatedly
546* with different personalities and the the same deviceConfiguration
547* dictionary (from SysConfig) without any defaults from one personality
548* polluting the UI when the user chooses another personality that perhaps
549* doesn't have any defaults.  Only when the user saves the configuration
550* should we ever see any of the things we inserted feed back to us.
551*******************************************************************************/
552
553- (NSMutableDictionary*)mergeCCLPersonality:(NSDictionary*)personality withDeviceConfiguration:(NSDictionary*)deviceConfiguration;
554{
555    NSMutableDictionary *rval = [deviceConfiguration mutableCopy];
556    NSString *mScript, *pScript, *mPers;
557    NSString *pName, *pType, *pVendor;
558    BOOL newPersonality;
559
560    // see whether the user changed personalities
561    mScript = [rval objectForKey:(id)kSCPropNetModemConnectionScript];
562    pScript = [personality objectForKey:(id)kSCPropNetModemConnectionScript];
563    mPers = [rval objectForKey:(id)kSCPropNetModemConnectionPersonality];
564    pName = [personality objectForKey:(id)kSCPropNetModemConnectionPersonality];
565    newPersonality = (![pScript isEqual:mScript] || ![pName isEqual:mPers]);
566    // XX any other checking of current modem dictionary needed?
567
568    // copy vendor, model, script, personality (only model+script for flat CCL)
569    pVendor = [personality objectForKey:(id)kCCLVendorKey];
570    if (pVendor)
571        [rval setObject:pVendor forKey:(id)kCCLVendorKey];
572    else
573        [rval removeObjectForKey:(id)kCCLVendorKey];
574    [rval setObject:[personality objectForKey:(id)kCCLModelKey]
575            forKey:(id)kCCLModelKey];
576    [rval setObject:pScript forKey:(id)kSCPropNetModemConnectionScript];
577    if (pName)
578        [rval setObject:pName forKey:(id)kSCPropNetModemConnectionPersonality];
579    else
580        [rval removeObjectForKey:(id)kSCPropNetModemConnectionPersonality];
581
582    // check to see if APN or CID need to be populated / updated
583    // - if GPRS and not set; set to preferred
584    // - see below for Preferred CID logic
585    pType = [personality objectForKey:(id)kCCLConnectTypeKey];
586    if ([pType isEqual:(id)kCCLConnectGPRS]) {
587        NSDictionary *cclParms =
588                [personality objectForKey:(id)kCCLParametersKey];
589        NSString *savedAPN =
590                [rval objectForKey:(id)kSCPropNetModemAccessPointName];
591        NSString *preferredAPN =
592                [cclParms objectForKey:(id)kCCLPreferredAPNKey];
593        NSString *savedCID =
594                [rval objectForKey:(id)kSCPropNetModemDeviceContextID];
595        NSNumber *preferredCID =
596                [cclParms objectForKey:(id)kCCLPreferredCIDKey];
597        BOOL safeCIDs = [[[personality objectForKey:(id)kCCLGPRSCapabilitiesKey]
598                            objectForKey:(id)kCCLIndependentCIDs] boolValue];
599        // if !safeCIDs, perhaps we could set the preferred to max[- 1?]?
600        // (we already insert values)
601
602        // A saved CID should be overwritten if changing personalities and
603        // the new personality isn't known to be safe for the old CID
604        // or if there is a new preferred CID for the new personality.
605        // If there is no preferred CID, the old CID should be removed if
606        // CIDs are unsafe.  In other words, leave a CID alone only if
607        // - it was already selected for this personality
608        // - it is known to be safe for this personality && no preferred
609        // unset, no preferred -> leave alone
610        // unset, preferred -> set to preferred
611        // set, !new personality -> leave alone
612        // set, new personality, preferred -> set to preferred
613        // set, new, !preferred, safe -> leave alone
614        // set, new, !preferred, unsafe -> clear
615        if (!savedCID || newPersonality) {
616            if (preferredCID) {
617                [rval setObject:[preferredCID stringValue]
618                        forKey:(id)kSCPropNetModemDeviceContextID];
619            } else if (savedCID && !safeCIDs) {
620                [rval removeObjectForKey:(id)kSCPropNetModemDeviceContextID];
621            }
622        }
623        if (preferredAPN && (!savedAPN || newPersonality))
624            [rval setObject:preferredAPN forKey:(id)kSCPropNetModemAccessPointName];
625    } else {
626        // clear any existing APN/CID
627        [rval removeObjectForKey:(id)kSCPropNetModemAccessPointName];
628        [rval removeObjectForKey:(id)kSCPropNetModemDeviceContextID];
629    }
630
631    return rval;
632}
633
634/*******************************************************************************
635* -upgradeDeviceConfiguration takes a system configuration modem dictionary
636* that doesn't have a device/vendor pair and adds one.  It may or may not
637* do smart things like use keys in the CCL bundles to tell it which
638* personalities are equivalent to old flat scripts.  :)
639*******************************************************************************/
640- (NSMutableDictionary*)upgradeDeviceConfiguration:(NSDictionary*)deviceConf
641{
642    NSMutableDictionary *rval = nil;
643    NSString *vendor, *model, *cScript, *csName, *persName = nil;
644    NSArray *persList;
645	NSEnumerator *e;
646	NSDictionary *pers;
647	BOOL knownModel = NO;
648
649
650    // use the kSCProp constants since these are SC dicts
651    csName = [[deviceConf objectForKey:(id)kSCPropNetModemConnectionScript] lastPathComponent];
652
653    // let's see if there's anything here (we'll validate below)
654    vendor = [deviceConf objectForKey:(id)kSCPropNetModemDeviceVendor];
655    model = [deviceConf objectForKey:(id)kSCPropNetModemDeviceModel];
656
657	if (model && vendor) {
658		persList = [mBundleData objectForKey:vendor];
659		e = [persList objectEnumerator];
660		while ((pers = [e nextObject])) {
661			if ([[pers objectForKey:(id)kCCLModelKey] isEqual:model]) {
662				knownModel = YES;
663				break;
664			}
665		}
666	} else {
667        // maybe overridden (implicitly or otherwise)
668        if ((pers = [mFlatOverrides objectForKey:csName])) {
669            vendor = [pers objectForKey:(id)kCCLVendorKey];
670            model = [pers objectForKey:(id)kCCLModelKey];
671            persName = [pers objectForKey:(id)kSCPropNetModemConnectionPersonality];
672			knownModel = YES;
673        } else {
674            // fall back to trying "other"/<connectionscript>
675            vendor = kCCLOtherVendorName;
676			if ([[csName pathExtension] isEqual:(id)kCCLFileExtension])
677				csName = [csName stringByDeletingPathExtension];
678            model = csName;
679
680			// validate with 'other' vendor list
681			persList = [mBundleData objectForKey:vendor];
682			e = [persList objectEnumerator];
683			while ((pers = [e nextObject])) {
684				if ([[pers objectForKey:(id)kCCLModelKey] isEqual:model]) {
685					knownModel = YES;
686					break;
687				}
688			}
689        }
690    }
691
692	if (knownModel) {
693        // yay; go ahead and create result dictionary
694        rval = [deviceConf mutableCopy];
695        [rval setObject:vendor forKey:(id)kSCPropNetModemDeviceVendor];
696        [rval setObject:model forKey:(id)kSCPropNetModemDeviceModel];
697		cScript = [pers objectForKey:(id)kSCPropNetModemConnectionScript];
698		if (cScript)
699			[rval setObject:cScript forKey:(id)kSCPropNetModemConnectionScript];
700        if (persName)
701            [rval setObject:persName forKey:(id)kSCPropNetModemConnectionPersonality];
702    }
703
704    return rval;        // still nil if the vendor/model didn't work out
705}
706
707@end
708