ReplicaFile.m   [plain text]


/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#import <unistd.h>
#import <openssl/md5.h>
#import <sys/stat.h>
#import "ReplicaFile.h"
#import "PSUtilitiesDefs.h"

void pwsf_AppendReplicaStatus( ReplicaFile *inReplicaFile, CFDictionaryRef inDict, CFMutableDictionaryRef inOutDict );

void pwsf_CalcServerUniqueID( const char *inRSAPublicKey, char *outHexHash )
{
	MD5_CTX ctx;
	unsigned char pubKeyHash[MD5_DIGEST_LENGTH];
	
	if ( inRSAPublicKey == NULL ) {
		if ( outHexHash != NULL )
			bzero( outHexHash, MD5_DIGEST_LENGTH );
		return;
	}
	
	MD5_Init( &ctx );
	MD5_Update( &ctx, (unsigned char *)inRSAPublicKey, strlen(inRSAPublicKey) );
	MD5_Final( pubKeyHash, &ctx );
	
	outHexHash[0] = 0;
	ConvertBinaryToHex( pubKeyHash, MD5_DIGEST_LENGTH, outHexHash );
}


//----------------------------------------------------------------------------------------------------
//	pwsf_ReplicaFilePath
//----------------------------------------------------------------------------------------------------

char *pwsf_ReplicaFilePath( void )
{
	const char *altPathPrefix = getenv("PWSAltPathPrefix");
	if ( altPathPrefix != NULL )
	{
		char path[PATH_MAX];
		sprintf( path, "%s/%s", altPathPrefix, kPWReplicaFile );
		return strdup( path );
	}
	else
	{
		return strdup( kPWReplicaFile );
	}
	
	return NULL;
}


//----------------------------------------------------------------------------------------------------
//	pwsf_GetStatusForReplicas
//----------------------------------------------------------------------------------------------------

CFDictionaryRef pwsf_GetStatusForReplicas( void )
{
	ReplicaFile *replicaFile = [[ReplicaFile alloc] init];
	CFDictionaryRef repDict;
	CFMutableDictionaryRef outputDict;
	unsigned long repIndex, repCount;
	
	repCount = [replicaFile replicaCount];
	outputDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	if ( outputDict == NULL )
		return NULL;
	
	// parent
	repDict = (CFMutableDictionaryRef)[replicaFile getParent];
	if ( repDict == NULL ) {
		CFRelease( outputDict );
		return NULL;
	}
	pwsf_AppendReplicaStatus( replicaFile, repDict, outputDict );
	
	// replicas
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		repDict = [replicaFile getReplica:repIndex];
		if ( repDict == NULL ) {
			CFRelease( outputDict );
			return NULL;
		}
		
		pwsf_AppendReplicaStatus( replicaFile, repDict, outputDict );
	}
	
	return (CFDictionaryRef)outputDict;
}


//----------------------------------------------------------------------------------------------------
//	pwsf_AppendReplicaStatus
//----------------------------------------------------------------------------------------------------

void pwsf_AppendReplicaStatus( ReplicaFile *inReplicaFile, CFDictionaryRef inDict, CFMutableDictionaryRef inOutDict )
{
	CFArrayRef ipArray;
	CFStringRef nameString;
	CFStringRef valueString = NULL;
	ReplicaStatus replicaStatus;
	CFIndex index, count;
	
	ipArray = [inReplicaFile getIPAddressesFromDict:inDict];
	if ( ipArray == NULL )
		return;
	
	replicaStatus = [inReplicaFile getReplicaStatus:inDict];
	if ( replicaStatus == kReplicaActive )
	{
		// not so fast, make sure it's syncing too
		CFDateRef lastSyncDate = NULL;
		CFDateRef lastFailedDate = NULL;
		
		bool hasLastSyncDate = ( CFDictionaryGetValueIfPresent( inDict, CFSTR(kPWReplicaSyncDateKey), (const void **)&lastSyncDate ) && CFGetTypeID(lastSyncDate) == CFDateGetTypeID() );
		bool hasFailedSyncDate = ( CFDictionaryGetValueIfPresent( inDict, CFSTR(kPWReplicaSyncAttemptKey), (const void **)&lastFailedDate ) && CFGetTypeID(lastFailedDate) == CFDateGetTypeID() );
		if ( hasFailedSyncDate && hasLastSyncDate )
		{
			if ( CFDateCompare(lastFailedDate, lastSyncDate, NULL) == kCFCompareGreaterThan )
			{
				// last sync was not successful but previous sessions were successful
				valueString = CFStringCreateWithCString( kCFAllocatorDefault, "Warning: The most recent replication failed.", kCFStringEncodingUTF8 );
			}
		}
		else
		if ( hasFailedSyncDate && (!hasLastSyncDate) )
		{
			// has failed all attempts
			valueString = CFStringCreateWithCString( kCFAllocatorDefault, "Warning: Replication has failed all attempts.", kCFStringEncodingUTF8 );
		}
		else if ( (!hasFailedSyncDate) && (!hasLastSyncDate) )
		{
			// has not completed a session yet
			valueString = CFStringCreateWithCString( kCFAllocatorDefault, "Warning: Replication has not completed yet.", kCFStringEncodingUTF8 );
		}
	}
	
	// if no special string, report status.
	if ( valueString == NULL )
		valueString = pwsf_GetReplicaStatusString( replicaStatus );
	
	count = CFArrayGetCount( ipArray );
	for ( index = 0; index < count; index++ )
	{
		nameString = (CFStringRef) CFArrayGetValueAtIndex( ipArray, index );
		if ( nameString == NULL )
			continue;
		
		CFDictionaryAddValue( inOutDict, nameString, valueString );
	}
	
	// add DNS if we have it
	if ( CFDictionaryGetValueIfPresent( inDict, CFSTR(kPWReplicaDNSKey), (const void **)&nameString ) )
		CFDictionaryAddValue( inOutDict, nameString, valueString );
	
	// clean up
	CFRelease( valueString );
	CFRelease( ipArray );
}


//----------------------------------------------------------------------------------------------------
//	Class: ReplicaFile
//----------------------------------------------------------------------------------------------------

@implementation ReplicaFile

-(id)init
{
	char *replicaFilePath = pwsf_ReplicaFilePath();
	const char *pathToUse = replicaFilePath ? replicaFilePath : kPWReplicaFile;
	
	if ( (self = [super init]) != nil ) {
		[self loadXMLData:pathToUse];
		if ( replicaFilePath != NULL )
			free( replicaFilePath );
		
		[self initCommon];
	}
	
	return self;
}

-(id)initWithPList:(CFDictionaryRef)xmlPList
{
	CFDataRef xmlData = NULL;
	CFStringRef errorString = NULL;
	
	if ( (self = [super init]) != nil )
	{
		mReplicaDict = NULL;
		
		xmlData = CFPropertyListCreateXMLData( kCFAllocatorDefault, (CFPropertyListRef)xmlPList );
		if ( xmlData != NULL )
		{
			mReplicaDict = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData( kCFAllocatorDefault, xmlData,
												kCFPropertyListMutableContainersAndLeaves, &errorString );
			CFRelease( xmlData );
		}
		
		[self initCommon];
	}
	
	return self;
}

-(id)initWithContentsOfFile:(const char *)filePath
{
	if ( (self = [super init]) != nil ) {
		[self loadXMLData:filePath];
		[self initCommon];
	}
	
	return self;
}

-(id)initWithXMLStr:(const char *)xmlStr
{
	CFDataRef xmlData;
	CFStringRef errorString;

	if ( (self = [super init]) != nil ) {
		[self lock];

		if ( xmlStr != NULL ) {
			xmlData = CFDataCreate( kCFAllocatorDefault, (const unsigned char *)xmlStr, strlen(xmlStr) );
			if ( xmlData != NULL )  {
				mReplicaDict = (CFMutableDictionaryRef) CFPropertyListCreateFromXMLData( kCFAllocatorDefault, xmlData,
								kCFPropertyListMutableContainersAndLeaves, &errorString );
				CFRelease( xmlData );
			}
		}
				
		[self initCommon];
		[self unlock];
	}
	
	return self;	
}


-(void)initCommon
{
	mRunningAsParent = YES;
	
	if ( mReplicaDict == NULL ) {
		// make a new replication dictionary
		mReplicaDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
						&kCFTypeDictionaryValueCallBacks );
	}
	
	[self lock];
	if ( [self isOldFormat] )
		[self updateFormat];
	[self unlock];
}


-free
{
	[self lock];
	if ( mReplicaDict != NULL )
		CFRelease( mReplicaDict );
	if ( mSelfName != NULL )
		CFRelease( mSelfName );
	if ( mFlatReplicaArray != NULL )
		CFRelease( mFlatReplicaArray );
	[self unlock];
	
	return [super free];
}


-(PWSReplicaEntry *)snapshotOfReplicasForServer:(CFDictionaryRef)serverDict;
{
	CFDictionaryRef parentDict = NULL;
	CFMutableDictionaryRef replicaDict = NULL;
	CFArrayRef serverDictReplicas = NULL;
	CFIndex repIndex = 0;
	CFIndex repCount = 0;
	CFIndex snapshotTotal = 0;
	PWSReplicaEntry *entArray = NULL;
	int index = 0;
	
	if ( serverDict == NULL )
		return NULL;
	
	[self lock];
	
	// include parent
	parentDict = [self getParentOfReplica:serverDict];
	if ( parentDict != NULL )
	{
		snapshotTotal++;
	
		// include peers
		serverDictReplicas = CFDictionaryGetValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
		if ( serverDictReplicas != NULL )
			snapshotTotal += CFArrayGetCount( serverDictReplicas );
	}
	
	// include children
	serverDictReplicas = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaReplicaKey) );
	if ( serverDictReplicas != NULL )
		snapshotTotal += CFArrayGetCount( serverDictReplicas );
	
	// allocate a flat array with a zero element on the end
	entArray = (PWSReplicaEntry *) calloc( sizeof(PWSReplicaEntry), snapshotTotal + 1 );
	if ( entArray == NULL )
		return NULL;
	
	// parent first
	if ( parentDict != NULL )
		[self serverStruct:&(entArray[index++]) forServerDict:parentDict];
	
	// children second
	// we want one-level, not subtree, so fetch the array instead
	// of calling replicaCount.
	serverDictReplicas = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaReplicaKey) );
	if ( serverDictReplicas != NULL )
	{
		repCount = CFArrayGetCount( serverDictReplicas );
		for ( repIndex = 0; repIndex < repCount; repIndex++ )
		{
			replicaDict = (CFMutableDictionaryRef) CFArrayGetValueAtIndex( serverDictReplicas, repIndex );
			if ( replicaDict != NULL )
				[self serverStruct:&(entArray[index++]) forServerDict:replicaDict];
		}
	}

	// peers third
	// we want one-level, not subtree, so fetch the array instead
	// of calling replicaCount.
	if ( parentDict != NULL )
	{
		serverDictReplicas = CFDictionaryGetValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
		if ( serverDictReplicas != NULL )
		{
			repCount = CFArrayGetCount( serverDictReplicas );
			for ( repIndex = 0; repIndex < repCount; repIndex++ )
			{
				replicaDict = (CFMutableDictionaryRef) CFArrayGetValueAtIndex( serverDictReplicas, repIndex );
				if ( replicaDict != NULL && replicaDict != serverDict ) {
					[self serverStruct:&(entArray[index]) forServerDict:replicaDict];
					entArray[index++].peer = 1;
				}
			}
		}
	}
	
	[self unlock];
	
	return entArray;
}


-(void)serverStruct:(PWSReplicaEntry *)sEnt forServerDict:(CFDictionaryRef)serverDict
{
	CFMutableArrayRef ipArray = NULL;
	CFStringRef aString = NULL;
	CFDateRef aDateRef = NULL;
	CFNumberRef aNumberRef = NULL;
	SInt64 aNumber = 0;
	CFIndex ipIndex, ipCount;
	struct tm aTimeStruct;
	char aStr[128];
	
	// get ip address list
	ipArray = [self getIPAddressesFromDict:serverDict];
	if ( ipArray == NULL )
		return;
	
	ipCount = CFArrayGetCount( ipArray );
	for ( ipIndex = 0; ipIndex < ipCount; ipIndex++ )
	{
		aString = CFArrayGetValueAtIndex( ipArray, ipIndex );
		if ( aString != NULL &&
			 CFStringGetCString(aString, aStr, sizeof(aStr), kCFStringEncodingUTF8) )
		{
			strlcpy(sEnt->ip[sEnt->ipCount], aStr, sizeof(sEnt->ip[0]));
			(sEnt->ipCount)++;
		}
	}
	
	aString = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaDNSKey) );
	if ( aString != NULL &&
		 CFStringGetCString(aString, aStr, sizeof(aStr), kCFStringEncodingUTF8) )
	{
		strlcpy(sEnt->dns, aStr, sizeof(sEnt->dns));
	}
	
	aString = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaNameKey) );
	if ( aString != NULL &&
		 CFStringGetCString(aString, aStr, sizeof(aStr), kCFStringEncodingUTF8) )
	{
		strlcpy(sEnt->name, aStr, sizeof(sEnt->name));
	}
	
	sEnt->syncPolicy = [self getReplicaSyncPolicy:serverDict];
	sEnt->status = [self getReplicaStatus:serverDict];
	
	[self getIDRangeStart:&sEnt->idRangeBegin end:&sEnt->idRangeEnd forReplica:serverDict];
	
	aNumberRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaSyncTIDKey) );
	if ( aNumberRef != NULL && CFNumberGetValue(aNumberRef, kCFNumberSInt64Type, &aNumber) )
		sEnt->lastSyncTID = aNumber;
	
	aDateRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaSyncDateKey) );
	if ( pwsf_ConvertCFDateToBSDTime(aDateRef, &aTimeStruct) )
		sEnt->lastSyncDate = timegm(&aTimeStruct);
	
	aDateRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaSyncAttemptKey) );
	if ( pwsf_ConvertCFDateToBSDTime(aDateRef, &aTimeStruct) )
		sEnt->lastSyncFailedAttempt = timegm(&aTimeStruct);
	
	aDateRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaIncompletePullKey) );
	if ( pwsf_ConvertCFDateToBSDTime(aDateRef, &aTimeStruct) )
		sEnt->pullIncompleteDate = timegm(&aTimeStruct);
	
	aDateRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaPullDeferred) );
	if ( pwsf_ConvertCFDateToBSDTime(aDateRef, &aTimeStruct) )
		sEnt->pullDeferredDate = timegm(&aTimeStruct);
	
	aDateRef = CFDictionaryGetValue( serverDict, CFSTR(kPWReplicaEntryModDateKey) );
	if ( pwsf_ConvertCFDateToBSDTime(aDateRef, &aTimeStruct) )
		sEnt->entryModDate = timegm(&aTimeStruct);
		
	CFRelease( ipArray );
}

	
-(ReplicaChangeStatus)mergeReplicaList:(ReplicaFile *)inOtherList
{
	CFStringRef id1 = NULL;
	CFStringRef id2 = NULL;
	ReplicaChangeStatus changeStatus = kReplicaChangeNone;
	
	if ( mReplicaDict == nil || inOtherList == nil || [inOtherList replicaDict] == nil )
		return NO;
	
	[self lock];
	if ( CFDictionaryGetValueIfPresent(mReplicaDict, CFSTR(kPWReplicaIDKey), (const void **)&id1) &&
		 CFDictionaryGetValueIfPresent([inOtherList replicaDict], CFSTR(kPWReplicaIDKey), (const void **)&id2) &&
		 CFStringCompare(id1, id2, 0) != kCFCompareEqualTo )
	{
		[self unlock];
		return NO;
	}
	
	[self mergeReplicaListDecommissionedList:inOtherList changeStatus:&changeStatus];
	[self mergeReplicaListParentRecords:inOtherList changeStatus:&changeStatus];
	[self mergeReplicaListReplicas:inOtherList changeStatus:&changeStatus];
	[self mergeReplicaListLegacyTigerReplicaList:inOtherList changeStatus:&changeStatus];
	
	[self unlock];
	
	return changeStatus;
}


-(void)mergeReplicaListDecommissionedList:(ReplicaFile *)inOtherList changeStatus:(ReplicaChangeStatus *)inOutChangeStatus
{
	CFIndex index = 0;
	CFIndex repCount = 0;
	CFMutableArrayRef decomArray1 = NULL;
	CFStringRef replicaNameString = NULL;
	char replicaName[256] = {0,};
	
	// merge decommissioned lists
	decomArray1 = [inOtherList getArrayForKey:CFSTR(kPWReplicaDecommissionedListKey)];
	if ( decomArray1 != NULL )
	{
		repCount = CFArrayGetCount( decomArray1 );
		for ( index = 0; index < repCount; index++ )
		{
			replicaNameString = (CFStringRef) CFArrayGetValueAtIndex( decomArray1, index );
			if ( replicaNameString != NULL )
				if ( CFStringGetCString(replicaNameString, replicaName, sizeof(replicaName), kCFStringEncodingUTF8) )
					if ( [self decommissionReplica:replicaNameString] )
						*inOutChangeStatus |= kReplicaChangeGeneral;
		}
	}
}


-(void)mergeReplicaListParentRecords:(ReplicaFile *)inOtherList changeStatus:(ReplicaChangeStatus *)inOutChangeStatus
{
	CFMutableDictionaryRef otherRepDict = NULL;
	CFMutableDictionaryRef ourRepDict = NULL;
	
	// merge parent records
	otherRepDict = (CFMutableDictionaryRef)[inOtherList getParent];
	if ( otherRepDict != NULL )
	{
		ourRepDict = (CFMutableDictionaryRef)[self getParent];
		if ( ourRepDict == nil )
		{
			[self setParentWithDict:otherRepDict];
			*inOutChangeStatus |= kReplicaChangeGeneral;
		}
		else
		{
			if ( [self needsMergeFrom:otherRepDict to:ourRepDict] )
			{
				if ( [self mergeReplicaValuesFrom:otherRepDict to:ourRepDict parent:YES] )
					*inOutChangeStatus |= kReplicaChangeInterface;
				*inOutChangeStatus |= kReplicaChangeGeneral;
			}
		}
	}
}


-(void)mergeReplicaListReplicas:(ReplicaFile *)inOtherList changeStatus:(ReplicaChangeStatus *)inOutChangeStatus
{
	CFIndex index = 0;
	CFIndex repCount = 0;
	CFMutableDictionaryRef otherRepDict = NULL;
	CFMutableDictionaryRef ourRepDict = NULL;
	CFStringRef replicaNameString = NULL;

	repCount = [inOtherList replicaCount];
	for ( index = 0; index < repCount; index++ )
	{
		otherRepDict = (CFMutableDictionaryRef)[inOtherList getReplica:index];
		if ( otherRepDict == nil )
			continue;
		
		replicaNameString = [inOtherList getNameOfReplica:otherRepDict];
		ourRepDict = [self getReplicaByName:replicaNameString];
		if ( ourRepDict == NULL )
		{
			// add
			if ( [self replicaIsNotDecommissioned:replicaNameString] && (! [self replicaHasBeenPromotedToMaster:otherRepDict]) )
			{
				CFStringRef parentNameString = NULL;
				CFMutableDictionaryRef ourRelativeParentDict = NULL;
				CFMutableDictionaryRef otherParentDict = [inOtherList getParentOfReplica:otherRepDict];
				if ( otherParentDict != NULL ) {
					parentNameString = CFDictionaryGetValue( otherParentDict, CFSTR(kPWReplicaNameKey) );
					if ( parentNameString != NULL )
						ourRelativeParentDict = [self getReplicaByName:parentNameString];
				}
				if ( ourRelativeParentDict == NULL )
					ourRelativeParentDict = (CFMutableDictionaryRef)[self getParent];
				
				[self addReplica:otherRepDict withParent:ourRelativeParentDict];
				*inOutChangeStatus |= kReplicaChangeGeneral;
			}
		}
		else
		{
			// update
			if ( [self needsMergeFrom:otherRepDict to:ourRepDict] )
			{
				if ( [self mergeReplicaValuesFrom:otherRepDict to:ourRepDict parent:NO] )
					*inOutChangeStatus |= kReplicaChangeInterface;
				*inOutChangeStatus |= kReplicaChangeGeneral;
			}
		}
	}
}


-(void)mergeReplicaListLegacyTigerReplicaList:(ReplicaFile *)inOtherList changeStatus:(ReplicaChangeStatus *)inOutChangeStatus
{
	CFIndex index = 0;
	CFIndex index2 = 0;
	CFIndex repCount = 0;
	CFIndex repCount2 = 0;
	CFMutableArrayRef tigerReplicaArrayOurs = NULL;
	CFMutableArrayRef tigerReplicaArrayTheirs = NULL;
	CFMutableDictionaryRef otherRepDict = NULL;
	CFMutableDictionaryRef ourRepDict = NULL;
	CFStringRef replicaNameString = NULL;
	CFStringRef replicaNameString2 = NULL;

	// merge legacy Tiger replicas
	tigerReplicaArrayOurs = [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)];
	tigerReplicaArrayTheirs = [inOtherList getArrayForKey:CFSTR(kPWReplicaReplicaKey)];
	if ( tigerReplicaArrayOurs != NULL && tigerReplicaArrayTheirs != NULL )
	{
		repCount = CFArrayGetCount( tigerReplicaArrayTheirs );
		for ( index = 0; index < repCount; index++ )
		{
			otherRepDict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex( tigerReplicaArrayTheirs, index );
			replicaNameString = [inOtherList getNameOfReplica:otherRepDict];
			
			// need to get the Tiger copy
			repCount2 = CFArrayGetCount( tigerReplicaArrayOurs );
			for ( index2 = 0; index2 < repCount2; index2++ )
			{
				ourRepDict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex( tigerReplicaArrayOurs, index2 );
				if ( ourRepDict != NULL )
					replicaNameString2 = CFDictionaryGetValue( ourRepDict, CFSTR(kPWReplicaNameKey) );
				if ( replicaNameString2 != NULL && CFStringCompare(replicaNameString, replicaNameString2, 0) == kCFCompareEqualTo )
					break;
				
				ourRepDict = NULL;
			}
			
			if ( ourRepDict == NULL )
			{
				// add
				if ( [self replicaIsNotDecommissioned:replicaNameString] && (! [self replicaHasBeenPromotedToMaster:otherRepDict]) )
				{
					CFArrayAppendValue( tigerReplicaArrayOurs, otherRepDict );
					*inOutChangeStatus |= kReplicaChangeGeneral;
				}
			}
			else
			{
				// update
				if ( [self needsMergeFrom:otherRepDict to:ourRepDict] )
				{
					if ( [self mergeReplicaValuesFrom:otherRepDict to:ourRepDict parent:NO] )
						*inOutChangeStatus |= kReplicaChangeInterface;
					*inOutChangeStatus |= kReplicaChangeGeneral;
				}
			}
		}
	}
}


// Returns: YES if an interface change is made
-(BOOL)mergeReplicaValuesFrom:(CFMutableDictionaryRef)dict1 to:(CFMutableDictionaryRef)dict2 parent:(BOOL)isParent;
{
	CFStringRef idRangeBegin;
	CFStringRef idRangeEnd;
	CFTypeRef ip = NULL;
	CFStringRef dns = NULL;
	CFStringRef replicaPolicy = NULL;
	CFDateRef modDate = NULL;
	BOOL ipChange = NO;
	
	if ( dict1 == NULL || dict2 == NULL )
		return NO;
	
	// get 'em
	idRangeBegin = (CFStringRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaIDRangeBeginKey) );
	idRangeEnd = (CFStringRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaIDRangeEndKey) );
	if ( isParent )
		replicaPolicy = (CFStringRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaPolicyKey) );
	
	if ( !isParent || !mRunningAsParent )
	{
		ip = (CFTypeRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaIPKey) );
		dns = (CFStringRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaDNSKey) );
	}
	modDate = (CFDateRef) CFDictionaryGetValue( dict1, CFSTR(kPWReplicaEntryModDateKey) );
	
	// set 'em
	if ( idRangeBegin != NULL )
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaIDRangeBeginKey), idRangeBegin );
	if ( idRangeEnd != NULL )
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaIDRangeEndKey), idRangeEnd );
	if ( ip != NULL ) {
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaIPKey), ip );
		ipChange = YES;
	}
	if ( dns != NULL )
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaDNSKey), dns );
	if ( replicaPolicy != NULL )
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaPolicyKey), replicaPolicy );
	if ( modDate != NULL )
		CFDictionarySetValue( dict2, CFSTR(kPWReplicaEntryModDateKey), modDate );
	
	// note that we don't update the lastSyncDate key because we want to keep *our* last sync date
	
	return ipChange;
}


-(BOOL)needsMergeFrom:(CFMutableDictionaryRef)dict1 to:(CFMutableDictionaryRef)dict2
{
	CFDateRef sinceNeverReferenceDate = NULL;
	CFDateRef lastSyncDate = NULL;
	CFDateRef targetLastSyncDate = NULL;
	bool needsMerge = false;
	
	if ( dict1 == NULL || dict2 == NULL )
		return false;
	
	sinceNeverReferenceDate = CFDateCreate( kCFAllocatorDefault, 0 );
	
	// get the last sync dates
	if ( CFDictionaryGetValueIfPresent( dict1, CFSTR(kPWReplicaEntryModDateKey), (const void **)&lastSyncDate ) )
	{
		if ( CFGetTypeID(lastSyncDate) != CFDateGetTypeID() )
			lastSyncDate = sinceNeverReferenceDate;
	}
	else
	{
		lastSyncDate = sinceNeverReferenceDate;
	}
				
	if ( CFDictionaryGetValueIfPresent( dict2, CFSTR(kPWReplicaEntryModDateKey), (const void **)&targetLastSyncDate ) )
	{
		if ( CFGetTypeID(targetLastSyncDate) != CFDateGetTypeID() )
		{
			// this is the dict we're keeping so fix it
			CFDictionaryRemoveValue( dict2, CFSTR(kPWReplicaEntryModDateKey) );
			targetLastSyncDate = sinceNeverReferenceDate;
		}
	}
	else
	{
		targetLastSyncDate = sinceNeverReferenceDate;
	}
	
	needsMerge = ( targetLastSyncDate != NULL && lastSyncDate != NULL &&
			 CFDateCompare( targetLastSyncDate, lastSyncDate, NULL ) == kCFCompareLessThan );
	
	if ( sinceNeverReferenceDate != NULL )
		CFRelease( sinceNeverReferenceDate );
		
	return needsMerge;
}


-(CFMutableDictionaryRef)getParentOfReplica:(CFDictionaryRef)replicaDict
{
	CFArrayRef replicaArray = NULL;
	CFMutableDictionaryRef parentDict = (CFMutableDictionaryRef)[self getParent];
	CFMutableDictionaryRef curReplicaDict = NULL;
	CFStringRef targetNameString = NULL;
	CFIndex index, repCount;
	
	if ( replicaDict == NULL || replicaDict == parentDict )
		return NULL;
	
	targetNameString = CFDictionaryGetValue( replicaDict, CFSTR(kPWReplicaNameKey) );
	if ( targetNameString == NULL )
		return NULL;
	
	// check top-level parent
	replicaArray = CFDictionaryGetValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
	if ( [self array:replicaArray containsReplicaWithName:targetNameString] )
		return parentDict;
	
	// dive
	repCount = [self replicaCount];
	for ( index = 0; index < repCount; index++ )
	{
		curReplicaDict = (CFMutableDictionaryRef)[self getReplica:index];
		if ( curReplicaDict != NULL )
		{
			replicaArray = CFDictionaryGetValue( curReplicaDict, CFSTR(kPWReplicaReplicaKey) );
			if ( [self array:replicaArray containsReplicaWithName:targetNameString] )
				return curReplicaDict;
		}
	}
	
	return NULL;
}


-(BOOL)array:(CFArrayRef)replicaArray containsReplicaWithName:(CFStringRef)targetNameString
{
	CFMutableDictionaryRef arrayItem = NULL;
	CFStringRef replicaNameString = NULL;
	CFIndex index, repCount;
		
	if ( replicaArray != NULL )
	{
		repCount = CFArrayGetCount( replicaArray );
		for ( index = 0; index < repCount; index++ )
		{
			arrayItem = (CFMutableDictionaryRef) CFArrayGetValueAtIndex( replicaArray, index );
			if ( arrayItem != NULL )
			{
				replicaNameString = CFDictionaryGetValue( arrayItem, CFSTR(kPWReplicaNameKey) );
				if ( CFStringCompare(replicaNameString, targetNameString, 0) == kCFCompareEqualTo )
				{
					// found it
					return YES;
				}
			}
		}
	}
	
	return NO;
}


// traps for overrides
-(void)lock
{
}

-(void)unlock
{
}


// top level
-(ReplicaPolicy)getReplicaPolicy
{
	ReplicaPolicy result = kReplicaNone;
	char statusStr[256];
	
	if ( mReplicaDict == NULL )
		return kReplicaNone;
	
	// key is "Status"
	if ( ![self getCStringFromDictionary:mReplicaDict forKey:CFSTR("Status") maxLen:sizeof(statusStr) result:statusStr] )
		return kReplicaNone;
	
	if ( strcasecmp( statusStr, kPWReplicaStatusAllow ) == 0 )
		result = kReplicaAllowAll;
	else
	if ( strcasecmp( statusStr, kPWReplicaStatusUseACL ) == 0 )
		result = kReplicaUseACL;
	
	return result;
}


-(void)setReplicaPolicy:(ReplicaPolicy)inPolicy
{
	char *valueStr = NULL;
	CFStringRef valString;
	
	if ( mReplicaDict == NULL )
		return;
	
	switch( inPolicy )
	{
		case kReplicaNone:
			valueStr = "None";
			break;
			
		case kReplicaAllowAll:
			valueStr = kPWReplicaStatusAllow;
			break;
			
		case kReplicaUseACL:
			valueStr = kPWReplicaStatusUseACL;
			break;
	}
	
	if ( valueStr != NULL )
	{
		valString = CFStringCreateWithCString( kCFAllocatorDefault, valueStr, kCFStringEncodingUTF8 );
		if ( valString != NULL )
		{
			CFDictionarySetValue( mReplicaDict, CFSTR("Status"), valString );
			CFRelease( valString );
		}
	}
}


-(void)emptyFlatReplicaArray
{
	[self lock];
	if ( mFlatReplicaArray != NULL )
		CFArrayRemoveAllValues( mFlatReplicaArray );
	else
		mFlatReplicaArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
	[self unlock];
}


-(unsigned long)replicaCount
{
	[self emptyFlatReplicaArray];
	
	return [self replicaCount:[self getParent]];
}


-(unsigned long)replicaCount:(CFDictionaryRef)inReplicaDict
{
	CFArrayRef replicaArray = NULL;
	CFDictionaryRef arrayItem = NULL;
	CFIndex index, repCount;
	unsigned long result = 0;
	
	if ( inReplicaDict == NULL )
		return 0;
	
	replicaArray = CFDictionaryGetValue( inReplicaDict, CFSTR(kPWReplicaReplicaKey) );
	if ( replicaArray != NULL )
	{
		repCount = CFArrayGetCount( replicaArray );
		for ( index = 0; index < repCount; index++ )
		{
			arrayItem = CFArrayGetValueAtIndex( replicaArray, index );
			if ( arrayItem != NULL )
			{
				CFArrayAppendValue( mFlatReplicaArray, arrayItem );
				result++;
				
				// add the children
				result += [self replicaCount:arrayItem];
			}
		}
	}
	
	return result;
}


-(CFDictionaryRef)getReplica:(unsigned long)index
{
	CFDictionaryRef result = NULL;
	
	[self lock];
	if ( mFlatReplicaArray != NULL )
		result = (CFDictionaryRef)CFArrayGetValueAtIndex( mFlatReplicaArray, index );
	[self unlock];
	
	return result;
}


-(BOOL)isActive
{
	if ( CFDictionaryContainsKey(mReplicaDict, CFSTR("Decommission")) )
		return NO;
	
	return YES;
}


-(CFStringRef)getUniqueID
{
	CFStringRef idString = NULL;
	
	if ( mReplicaDict == NULL )
		return NULL;
	
	if ( ! CFDictionaryGetValueIfPresent( mReplicaDict, CFSTR(kPWReplicaIDKey), (const void **)&idString ) )
		return NULL;
	
	if ( CFGetTypeID(idString) != CFStringGetTypeID() )
		return NULL;
	
	return CFRetain( idString );
}


-(CFStringRef)currentServerForLDAP
{
	CFStringRef serverString = NULL;
	
	if ( mReplicaDict == NULL )
		return NULL;
	
	if ( ! CFDictionaryGetValueIfPresent( mReplicaDict, CFSTR(kPWReplicaCurrentServerForLDAPKey), (const void **)&serverString ) )
		return NULL;
	
	if ( CFGetTypeID(serverString) != CFStringGetTypeID() )
		return NULL;
	
	CFRetain( serverString );
	return serverString;
}


-(CFDictionaryRef)getParent
{
	CFDictionaryRef parentDict = NULL;

	[self lock];
	if ( mReplicaDict != NULL )
	{
		if ( CFDictionaryGetValueIfPresent( mReplicaDict, CFSTR(kPWReplicaParentKey), (const void **)&parentDict ) )
		{
			if ( CFGetTypeID(parentDict) != CFDictionaryGetTypeID() )
				parentDict = NULL;
		}
	}
	[self unlock];
	
	return parentDict;
}


-(CFStringRef)xmlString
{
	CFStringRef returnString = NULL;
	CFDataRef xmlData = NULL;
	const UInt8 *sourcePtr;
	long length;
	
	[self lock];
	if ( mReplicaDict != NULL )
	{
		xmlData = CFPropertyListCreateXMLData( kCFAllocatorDefault, (CFPropertyListRef)mReplicaDict );
		if ( xmlData != NULL )
		{
			sourcePtr = CFDataGetBytePtr( xmlData );
			length = CFDataGetLength( xmlData );
			if ( sourcePtr != NULL && length > 0 )
				returnString = CFStringCreateWithBytes( kCFAllocatorDefault, sourcePtr, length, kCFStringEncodingUTF8, NO );
			
			CFRelease( xmlData );
		}
	}
	[self unlock];
	
	return returnString;
}


-(BOOL)fileHasChanged
{
	BOOL refresh = NO;
	struct timespec modDate;
	char *replicaFilePath = pwsf_ReplicaFilePath();
	const char *pathToUse = replicaFilePath ? replicaFilePath : kPWReplicaFile;
	
	refresh = ( [self statReplicaFile:pathToUse andGetModDate:&modDate] != 0 );
	if ( replicaFilePath != NULL )
		free( replicaFilePath );
	
	if ( !refresh && modDate.tv_sec > mReplicaFileModDate.tv_sec )
		refresh = YES;
		
	if ( !refresh && modDate.tv_sec == mReplicaFileModDate.tv_sec && modDate.tv_nsec > mReplicaFileModDate.tv_nsec )
		refresh = YES;
	
	return refresh;
}


-(void)refreshIfNeeded
{
	BOOL refresh = NO;
	CFMutableDictionaryRef lastPropertyList = mReplicaDict;
	
	refresh = [self fileHasChanged];
	
	if ( refresh && mDirty )
	{
		// need to resolve conflict between file and memory copies
		// SaveXMLData handles merging
		[self saveXMLData];
	}
	else
	{
		if ( refresh )
		{
			char *replicaFilePath = pwsf_ReplicaFilePath();
			const char *pathToUse = replicaFilePath ? replicaFilePath : kPWReplicaFile;

			if ( [self loadXMLData:pathToUse] == 0 )
			{
				if ( lastPropertyList != NULL )
					CFRelease( lastPropertyList );
			}
			else
			{
				mReplicaDict = lastPropertyList;
			}
			
			if ( replicaFilePath != NULL )
				free( replicaFilePath );
		}
		else
		if ( mDirty )
			[self saveXMLData];
	}
}


-(CFStringRef)calcServerUniqueID:(const char *)inRSAPublicKey
{
	char pubKeyHashHex[MD5_DIGEST_LENGTH*2 + 1];
	
	pwsf_CalcServerUniqueID( inRSAPublicKey, pubKeyHashHex );	
	return CFStringCreateWithCString( kCFAllocatorDefault, pubKeyHashHex, kCFStringEncodingUTF8 );
}


-(void)addServerUniqueID:(const char *)inRSAPublicKey
{
	CFStringRef idString;
	
	if ( inRSAPublicKey == NULL || mReplicaDict == NULL )
		return;
	
	// the ID never changes, so return if present
	if ( CFDictionaryContainsKey( mReplicaDict, CFSTR(kPWReplicaIDKey) ) )
		return;
		
	idString = [self calcServerUniqueID:inRSAPublicKey];
	if ( idString != NULL )
	{
		CFDictionaryAddValue( mReplicaDict, CFSTR(kPWReplicaIDKey), idString );
		mDirty = YES;
	}
}


-(void)setParentWithIP:(const char *)inIPStr andDNS:(const char *)inDNSStr
{
	CFMutableDictionaryRef parentData;
	CFStringRef ipString;
	CFStringRef dnsString = NULL;
	
	if ( inIPStr == NULL )
		return;
	
	parentData = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	if ( parentData != NULL )
	{
		ipString = CFStringCreateWithCString( kCFAllocatorDefault, inIPStr, kCFStringEncodingUTF8 );
		if ( ipString != NULL ) {
			CFDictionaryAddValue( parentData, CFSTR(kPWReplicaIPKey), ipString );
			CFRelease( ipString );
			ipString = NULL;
		}
		
		if ( inDNSStr != NULL )
		{
			dnsString = CFStringCreateWithCString( kCFAllocatorDefault, inDNSStr, kCFStringEncodingUTF8 );
			if ( dnsString != NULL ) {
				CFDictionaryAddValue( parentData, CFSTR(kPWReplicaDNSKey), dnsString );
				CFRelease( dnsString );
				dnsString = NULL;
			}
		}
		
		[self setParentWithDict:parentData];
	}
}


-(void)setParentWithDict:(CFDictionaryRef)inParentData
{
	if ( inParentData == NULL )
		return;
	
	CFDictionarySetValue( mReplicaDict, CFSTR(kPWReplicaParentKey), inParentData );
	
	// need to have the parent inserted before calling AllocateIDRange()
	[self allocateIDRangeOfSize:500 forReplica:CFSTR("") minID:0];
	mDirty = YES;
}


-(CFMutableDictionaryRef)addReplicaWithIP:(const char *)inIPStr
	andDNS:(const char *)inDNSStr
	withParent:(CFMutableDictionaryRef)inParentDict
{
	CFMutableDictionaryRef replicaData = NULL;
	CFStringRef dnsString = NULL;
	
	if ( inIPStr == NULL )
		return NULL;
	
	replicaData = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	if ( replicaData != NULL )
	{
		[self addIPAddress:inIPStr toReplica:replicaData];
		
		if ( inDNSStr != NULL )
			dnsString = CFStringCreateWithCString( kCFAllocatorDefault, inDNSStr, kCFStringEncodingUTF8 );
		if ( dnsString != NULL ) {
			CFDictionaryAddValue( replicaData, CFSTR(kPWReplicaDNSKey), dnsString );
			CFRelease( dnsString );
			dnsString = NULL;
		}
		
		[self addReplica:replicaData withParent:inParentDict];
	}
	
	return replicaData;
}


-(CFMutableDictionaryRef)addReplica:(CFMutableDictionaryRef)inReplicaData
	withParent:(CFMutableDictionaryRef)inParentDict
{
	CFMutableArrayRef replicaArray = NULL;
	CFStringRef ipString = NULL;
	CFStringRef nameString = NULL;
	
	if ( inReplicaData == NULL || inParentDict == NULL || mReplicaDict == NULL )
		return NULL;
	
	if ( ! CFDictionaryGetValueIfPresent( inReplicaData, CFSTR(kPWReplicaIPKey), (const void **)&ipString ) )
		return NULL;
	if ( ! CFDictionaryGetValueIfPresent( inReplicaData, CFSTR(kPWReplicaNameKey), (const void **)&nameString ) )
		nameString = NULL;
	
	replicaArray = (CFMutableArrayRef) CFDictionaryGetValue( inParentDict, CFSTR(kPWReplicaReplicaKey) );
	if ( replicaArray != NULL && nameString != NULL )
	{
		// don't add duplicates
		
		CFIndex repIndex, repCount;
		CFDictionaryRef replicaRef;
		CFStringRef replicaNameString;
		
		repCount = CFArrayGetCount( replicaArray );
		for ( repIndex = 0; repIndex < repCount; repIndex++ )
		{
			replicaRef = [self getReplica:repIndex];
			if ( replicaRef == NULL )
				continue;
			
			if ( CFDictionaryGetValueIfPresent( replicaRef, CFSTR(kPWReplicaNameKey), (const void **)&replicaNameString ) )
			{
				if ( CFStringCompare( nameString, replicaNameString, (CFOptionFlags)0 ) == kCFCompareEqualTo )
				{
					// remove the old copy and refresh
					CFArrayRemoveValueAtIndex( replicaArray, repIndex );
					break;
				}
			}
		}
	}
	
	// has this replica been named?
	if ( nameString == NULL )
	{
		// add the Replica Name
		nameString = [self getNextReplicaName];
		if ( nameString != NULL )
			CFDictionaryAddValue( inReplicaData, CFSTR(kPWReplicaNameKey), nameString );
	}
	
	// add to the list of replicas
	if ( replicaArray == NULL )
	{
		replicaArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
		if ( replicaArray == NULL )
			return NULL;
			
		CFDictionaryAddValue( inParentDict, CFSTR(kPWReplicaReplicaKey), replicaArray );
		CFRelease( replicaArray );
	}
	CFArrayAppendValue( replicaArray, inReplicaData );
	
	// add to the old list of replicas for Tiger clients
	[self addReplicaToLegacyTigerList:inReplicaData];
	
	mDirty = YES;
	
	// add an ID range
	[self allocateIDRangeOfSize:500 forReplica:nameString minID:0];
	
	return inReplicaData;
}

-(void)addReplicaToLegacyTigerList:(CFMutableDictionaryRef)inReplicaData
{
	CFMutableArrayRef tigerReplicaArray = NULL;
	CFMutableDictionaryRef replicaDataCopy = NULL;
	
	tigerReplicaArray = [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)];
	if ( tigerReplicaArray == NULL )
	{
		tigerReplicaArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
		if ( tigerReplicaArray != NULL ) {
			CFDictionaryAddValue( mReplicaDict, CFSTR(kPWReplicaReplicaKey), tigerReplicaArray );
			CFRelease( tigerReplicaArray );
		}
	}
	if ( tigerReplicaArray != NULL )
	{
		// make a copy and remove the "Replicas" key from the entry
		replicaDataCopy = (CFMutableDictionaryRef)CFDictionaryCreateMutableCopy( kCFAllocatorDefault, 0, inReplicaData );
		if ( replicaDataCopy != NULL )
		{
			CFDictionaryRemoveValue( replicaDataCopy, CFSTR(kPWReplicaReplicaKey) );
			CFArrayAppendValue( tigerReplicaArray, replicaDataCopy );
			CFRelease( replicaDataCopy );
		}
	}
	
	mDirty = YES;
}


-(BOOL)addIPAddress:(const char *)inIPStr toReplica:(CFMutableDictionaryRef)inReplicaDict
{
	CFTypeRef valueRef;
	CFStringRef ipString;
	CFMutableArrayRef arrayRef;
	BOOL result = NO;
	
	if ( inReplicaDict == NULL || inIPStr == NULL )
		return result;
	
	ipString = CFStringCreateWithCString( kCFAllocatorDefault, inIPStr, kCFStringEncodingUTF8 );
	if ( ipString == NULL )
		return result;
	
	if ( CFDictionaryGetValueIfPresent( inReplicaDict, CFSTR(kPWReplicaIPKey), &valueRef ) )
	{
		if ( CFGetTypeID(valueRef) == CFArrayGetTypeID() )
		{
			arrayRef = (CFMutableArrayRef) valueRef;
			// Note: header says range should be (0,N-1), but reality is (0,N).
			if ( ! CFArrayContainsValue( arrayRef, CFRangeMake(0, CFArrayGetCount(arrayRef)), ipString ) )
			{
				CFArrayAppendValue( arrayRef, ipString );
				result = YES;
			}
		}
		else
		if ( CFGetTypeID(valueRef) == CFStringGetTypeID() &&
			 CFStringCompare( (CFStringRef)valueRef, ipString, 0 ) != kCFCompareEqualTo )
		{
			arrayRef = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
			if ( arrayRef != NULL )
			{
				CFArrayAppendValue( arrayRef, valueRef );
				CFArrayAppendValue( arrayRef, ipString );
				CFDictionaryReplaceValue( inReplicaDict, CFSTR(kPWReplicaIPKey), arrayRef );
				CFRelease( arrayRef );
				result = YES;
			}
		}
	}
	else
	{
		CFDictionaryAddValue( inReplicaDict, CFSTR(kPWReplicaIPKey), ipString );
		result = YES;
	}
	
	CFRelease( ipString );
		
	if ( result )
		[self setEntryModDateForReplica:inReplicaDict];
			
	return result;
}


-(void)addIPAddress:(const char *)inNewIPStr orReplaceIP:(const char *)inOldIPStr inReplica:(CFMutableDictionaryRef)inReplicaDict
{
	CFTypeRef valueRef;
	CFStringRef oldIPString;
	CFStringRef newIPString;
	CFMutableArrayRef arrayRef;
	CFIndex firstIndex;
	
	if ( inReplicaDict == NULL || inOldIPStr == NULL || inNewIPStr == NULL )
		return;
	
	oldIPString = CFStringCreateWithCString( kCFAllocatorDefault, inOldIPStr, kCFStringEncodingUTF8 );
	if ( oldIPString == NULL )
		return;
	
	if ( CFDictionaryGetValueIfPresent(inReplicaDict, CFSTR(kPWReplicaIPKey), &valueRef) )
	{
		if ( CFGetTypeID(valueRef) == CFArrayGetTypeID() )
		{
			arrayRef = (CFMutableArrayRef) valueRef;
			// Note: header says range should be (0,N-1), but reality is (0,N).
			
			firstIndex = CFArrayGetFirstIndexOfValue( arrayRef, CFRangeMake(0, CFArrayGetCount(arrayRef)), oldIPString );
			if ( firstIndex != kCFNotFound )
				CFArrayRemoveValueAtIndex( arrayRef, firstIndex );
			
			[self addIPAddress:inNewIPStr toReplica:inReplicaDict];
		}
		else
		if ( CFGetTypeID(valueRef) == CFStringGetTypeID() )
		{
			if ( CFStringCompare( (CFStringRef)valueRef, oldIPString, 0 ) == kCFCompareEqualTo )
			{
				newIPString = CFStringCreateWithCString( kCFAllocatorDefault, inNewIPStr, kCFStringEncodingUTF8 );
				if ( newIPString == NULL )
					return;
				
				CFDictionaryReplaceValue( inReplicaDict, CFSTR(kPWReplicaIPKey), newIPString );
				CFRelease( newIPString );
				[self setEntryModDateForReplica:inReplicaDict];
			}
			else
			{
				[self addIPAddress:inNewIPStr toReplica:inReplicaDict];
			}
		}
	}
	else
	{
		[self addIPAddress:inNewIPStr toReplica:inReplicaDict];
	}
	
	CFRelease( oldIPString );
}


-(CFMutableArrayRef)getIPAddressesFromDict:(CFDictionaryRef)inReplicaDict
{
	CFTypeRef valueRef;
	CFMutableArrayRef arrayRef;
	
	if ( inReplicaDict != NULL )
	{
		if ( CFDictionaryGetValueIfPresent( inReplicaDict, CFSTR(kPWReplicaIPKey), &valueRef ) )
		{
			if ( CFGetTypeID(valueRef) == CFArrayGetTypeID() )
			{
				CFRetain( (CFMutableArrayRef) valueRef );
				return (CFMutableArrayRef) valueRef;
			}
			else
			if ( CFGetTypeID(valueRef) == CFStringGetTypeID() )
			{
				arrayRef = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
				if ( arrayRef != NULL )
				{
					CFArrayAppendValue( arrayRef, valueRef );
					return arrayRef;
				}
			}
		}
	}
	
	return NULL;
}


-(int)saveXMLData
{
	char *replicaFilePath = pwsf_ReplicaFilePath();
	const char *pathToUse = replicaFilePath ? replicaFilePath : kPWReplicaFile;
	
	[self lock];
	int result = [self saveXMLDataToFile:pathToUse];
	if ( result == 0 )
	{
		[self statReplicaFile:pathToUse andGetModDate:&mReplicaFileModDate];
		mDirty = NO;
	}
	
	if ( replicaFilePath != NULL )
		free( replicaFilePath );
	[self unlock];
	
	return result;
}


-(int)saveXMLDataToFile:(const char *)inSaveFile
{
	CFStringRef myReplicaDataFilePathRef;
	CFURLRef myReplicaDataFileRef;
	CFWriteStreamRef myWriteStreamRef = NULL;
	CFPropertyListRef localDictRef = NULL;
	CFStringRef errorString;
	int returnValue = -1;
	struct stat sb;
	int err;
	char *saveDir;
	char *slash;
	
	if ( mReplicaDict == NULL )
		return 0;
	
	[self lock];

	// ensure the directory exists
	saveDir = strdup( inSaveFile );
	if ( saveDir != NULL ) {
		slash = rindex( saveDir, '/' );
		if ( slash != NULL ) {
			*slash = '\0';
			if ( lstat(saveDir, &sb) != 0 ) {
				err = pwsf_mkdir_p( saveDir, 0600 );
				if ( err != 0 ) {
					free( saveDir );
					[self unlock];
					return err;
				}
			}
		}
		free( saveDir );
	}
	
	if ( [self fileHasChanged] )
	{
		// merge updates on disk
		// get the disk version
		ReplicaFile *onDiskReplicaFile = [[ReplicaFile alloc] init];
		if ( onDiskReplicaFile != nil )
		{
			if ( [onDiskReplicaFile isHappy] )
				[self mergeReplicaList:onDiskReplicaFile];
			[onDiskReplicaFile free];
		}
	}
	
	localDictRef = CFRetain( mReplicaDict ); 
	
	do
	{
		myReplicaDataFilePathRef = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaFileTemp, kCFStringEncodingUTF8 );
		if ( myReplicaDataFilePathRef == NULL )
			break;
		
		myReplicaDataFileRef = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, myReplicaDataFilePathRef, kCFURLPOSIXPathStyle, false );
		
		CFRelease( myReplicaDataFilePathRef );
		
		if ( myReplicaDataFileRef == NULL )
			break;
		
		myWriteStreamRef = CFWriteStreamCreateWithFile( kCFAllocatorDefault, myReplicaDataFileRef );
		
		CFRelease( myReplicaDataFileRef );
		
		if ( myWriteStreamRef == NULL )
			break;
		
		if ( CFWriteStreamOpen( myWriteStreamRef ) )
		{		
			errorString = NULL;
			CFPropertyListWriteToStream( localDictRef, myWriteStreamRef, kCFPropertyListXMLFormat_v1_0, &errorString );
			CFWriteStreamClose( myWriteStreamRef );
			
			// paranoia check
			if ( lstat(kPWReplicaFileTemp, &sb) == 0 && sb.st_size > 0 )
			{
				unlink( inSaveFile );
				rename( kPWReplicaFileTemp, inSaveFile );
			}
			else
			{
				unlink( kPWReplicaFileTemp );
			}
			
			if ( errorString != NULL ) {
				CFRelease( errorString );
				break;
			}
			
			returnValue = 0;
		}
	}
	while (0);
	
	if ( myWriteStreamRef != NULL )
		CFRelease( myWriteStreamRef );
	if ( localDictRef != NULL )
		CFRelease( localDictRef );
	
	[self unlock];
	
	return returnValue;
}


-(void)stripSyncDates
{
	unsigned long repIndex;
	unsigned long repCount = [self replicaCount];
	CFMutableDictionaryRef curReplica;
	CFDateRef nowDate = CFDateCreate( kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() );
	
	// strip parent
	curReplica = (CFMutableDictionaryRef)[self getParent];
	if ( curReplica != NULL )
	{
		CFDictionaryRemoveValue( curReplica, CFSTR(kPWReplicaSyncTIDKey) );
		CFDictionaryRemoveValue( curReplica, CFSTR(kPWReplicaSyncDateKey) );
		CFDictionarySetValue( curReplica, CFSTR(kPWReplicaEntryModDateKey), nowDate );
	}
	
	// strip replicas
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		curReplica = (CFMutableDictionaryRef)[self getReplica:repIndex];
		if ( curReplica == NULL )
			continue;
		
		CFDictionaryRemoveValue( curReplica, CFSTR(kPWReplicaSyncTIDKey) );
		CFDictionaryRemoveValue( curReplica, CFSTR(kPWReplicaSyncDateKey) );
		CFDictionarySetValue( curReplica, CFSTR(kPWReplicaEntryModDateKey), nowDate );
	}
	
	if ( nowDate != NULL )
		CFRelease( nowDate );
	
	mDirty = YES;
}


//----------------------------------------------------------------------------------------------------
//	OldestSyncDate
//
//	Returns: date of the most distant sync session or NULL
//
//  If any valid replicas have not completed a sync session, the method returns NULL.
//	The caller should assume there is some data that has not propagated and act accordingly.
//----------------------------------------------------------------------------------------------------

-(CFDateRef)oldestSyncDate
{
	unsigned long repIndex;
	unsigned long repCount = [self replicaCount];
	CFMutableDictionaryRef curReplica = NULL;
	CFDateRef oldestSyncDate = NULL;
	CFDateRef replicaSyncDate = NULL;

	if ( ![self isHappy] )
		return NULL;
	
	// start with the parent
	curReplica = (CFMutableDictionaryRef)[self getParent];
	if ( curReplica != nil )
	{
		if ( [self getReplicaStatus:curReplica] != kReplicaPermissionDenied )
		{
			if ( CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncDateKey), (const void **)&replicaSyncDate) &&
				 CFGetTypeID(replicaSyncDate) == CFDateGetTypeID() )
				oldestSyncDate = replicaSyncDate;
			else
				return NULL;
		}
	}
	
	// check replicas
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		curReplica = (CFMutableDictionaryRef)[self getReplica:repIndex];
		if ( curReplica == nil )
			continue;
		
		if ( [self getReplicaStatus:curReplica] != kReplicaPermissionDenied )
		{
			replicaSyncDate = CFDictionaryGetValue( curReplica, CFSTR(kPWReplicaSyncDateKey) );
			
			if ( oldestSyncDate == NULL )
				oldestSyncDate = replicaSyncDate;
			else if ( replicaSyncDate != NULL && CFDateCompare(replicaSyncDate, oldestSyncDate, NULL) == kCFCompareLessThan )
				oldestSyncDate = replicaSyncDate;
		}
		else
		{
			return NULL;
		}
	}
	
	return oldestSyncDate;
}


//----------------------------------------------------------------------------------------------------
//	lowTIDForReplica:
//
//	Returns: ReplicaRole enum
//----------------------------------------------------------------------------------------------------

-(ReplicaRole)roleForReplica:(CFStringRef)replicaName
{
	ReplicaRole result = kReplicaRoleUnset;
	CFMutableDictionaryRef replicaDict = NULL;
	
	[self lock];
	
	// Check for top tier Parent
	if ( replicaName == NULL ||
		 CFStringGetLength(replicaName) == 0 ||
		 CFStringCompare(replicaName, CFSTR(kPWReplicaParentKey), kCFCompareCaseInsensitive) == kCFCompareEqualTo )
	{
		result = kReplicaRoleParent;
	}
	else
	{
		// Check existence
		replicaDict = [self getReplicaByName:replicaName];
		if ( replicaDict == NULL )
		{
			result = kReplicaRoleUnset;
		}
		else
		{
			// Check Tier
			int tier = 1;
			CFMutableDictionaryRef parentOfRepDict = [self getParentOfReplica:replicaDict];
			if ( parentOfRepDict != [self getParent] )
				tier = 2;
			
			CFArrayRef childArray = (CFArrayRef) CFDictionaryGetValue( replicaDict, CFSTR(kPWReplicaReplicaKey) );
			CFRelease( replicaDict );
			replicaDict = NULL;
			
			if ( childArray == NULL || CFArrayGetCount(childArray) == 0 )
			{
				result = ((tier == 1) ? kReplicaRoleTierOneReplica : kReplicaRoleTierTwoReplica);
			}
			else
			{
				result = kReplicaRoleRelay;
			}
		}
	}
	
	if ( replicaDict != NULL )
		CFRelease( replicaDict );
	
	[self unlock];
	
	return result;
}


//----------------------------------------------------------------------------------------------------
//	lowTIDForReplica:
//
//	Returns: the lowest TID for any replica that syncs with the one provided
//
//	Master		Syncs with first tier replicas and relays
//	Relay		Syncs with the Master and its replica children
//	Replica		Syncs with its parent (the master or a relay)
//----------------------------------------------------------------------------------------------------

-(SInt64)lowTIDForReplica:(CFStringRef)replicaName
{
	unsigned long repIndex = 0;
	unsigned long repCount = 0;
	CFMutableDictionaryRef curReplica = NULL;
	CFMutableDictionaryRef replicaDict = NULL;
	CFMutableDictionaryRef parentDict = NULL;
	CFNumberRef tidNumber = NULL;
	SInt64 tid = 0;
	SInt64 tidLow = 0;
	BOOL tidLowSet = NO;
	
	[self lock];
	
	switch ( [self roleForReplica:replicaName] )
	{
		case kReplicaRoleUnset:
			// nothing to count
			[self unlock];
			return 0;
			break;
		
		case kReplicaRoleParent:
			// top-level, nothing above
			replicaDict = (CFMutableDictionaryRef)[self getParent];
			CFRetain( replicaDict );
			break;
		
		case kReplicaRoleRelay:
		case kReplicaRoleTierOneReplica:
		case kReplicaRoleTierTwoReplica:
			replicaDict = [self getReplicaByName:replicaName];
			if ( replicaDict != NULL )
				parentDict = (CFMutableDictionaryRef)[self getParentOfReplica:replicaDict];
			break;
	}
	
	// get the tid for the parent
	if ( parentDict != NULL )
	{
		if ( CFDictionaryGetValueIfPresent(parentDict, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
				CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
				CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
		{
			tidLow = tid;
			tidLowSet = YES;
		}
	}
	
	// children
	if ( replicaDict != NULL )
	{
		CFArrayRef childArray = (CFArrayRef) CFDictionaryGetValue( replicaDict, CFSTR(kPWReplicaReplicaKey) );
		if ( childArray != NULL )
		{
			repCount = CFArrayGetCount( childArray );
			for ( repIndex = 0; repIndex < repCount; repIndex++ )
			{
				curReplica = (CFMutableDictionaryRef)CFArrayGetValueAtIndex( childArray, repIndex );
				if ( curReplica != NULL &&
					 CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
					 CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
					 CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
				{
					if ( tidLowSet == NO || tid < tidLow ) {
						tidLow = tid;
						tidLowSet = YES;
					}
				}
			}
		}
		
		CFRelease( replicaDict );
	}
	
	[self unlock];
	
	return tidLow;
}


//----------------------------------------------------------------------------------------------------
//	lowTID
//
//	Returns: the lowest TID for any replica
//----------------------------------------------------------------------------------------------------

-(SInt64)lowTID
{
	unsigned long repIndex = 0;
	unsigned long repCount = [self replicaCount];
	CFMutableDictionaryRef curReplica;
	CFNumberRef tidNumber = NULL;
	SInt64 tid = 0;
	SInt64 tidLow = 0;
	BOOL tidLowSet = NO;
	
	// parent
	curReplica = (CFMutableDictionaryRef)[self getParent];
	if ( curReplica != NULL )
	{
		if ( CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
				CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
				CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
		{
			tidLow = tid;
			tidLowSet = YES;
		}
	}
	
	// replicas
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		curReplica = (CFMutableDictionaryRef)[self getReplica:repIndex];
		if ( curReplica == NULL )
			continue;
		
		if ( CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
				CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
				CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
		{
			if ( tidLowSet == NO || tid < tidLow ) {
				tidLow = tid;
				tidLowSet = YES;
			}
		}
	}
	
	return tidLow;
}


//----------------------------------------------------------------------------------------------------
//	highTID
//
//	Returns: the highest TID for any replica
//----------------------------------------------------------------------------------------------------

-(SInt64)highTID
{
	unsigned long repIndex = 0;
	unsigned long repCount = [self replicaCount];
	CFMutableDictionaryRef curReplica;
	CFNumberRef tidNumber = NULL;
	SInt64 tid = 0;
	SInt64 tidHigh = 0;
	
	// parent
	curReplica = (CFMutableDictionaryRef)[self getParent];
	if ( curReplica != NULL )
	{
		if ( CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
				CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
				CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
		{
			if ( tid > tidHigh )
				tidHigh = tid;
		}
	}
	
	// replicas
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		curReplica = (CFMutableDictionaryRef)[self getReplica:repIndex];
		if ( curReplica == NULL )
			continue;
		
		if ( CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaSyncTIDKey), (const void **)&tidNumber) &&
				CFGetTypeID(tidNumber) == CFNumberGetTypeID() &&
				CFNumberGetValue(tidNumber, kCFNumberSInt64Type, &tid) )
		{
			if ( tid > tidHigh )
				tidHigh = tid;
		}
	}
	
	return tidHigh;
}


//----------------------------------------------------------------------------------------------------
//	DecommissionReplica
//
//	Returns: TRUE if the replica is added to the decommissioned list
//----------------------------------------------------------------------------------------------------

-(BOOL)decommissionReplica:(CFStringRef)replicaNameString
{
	BOOL result = NO;
	CFMutableArrayRef decomArray = NULL;
	CFMutableDictionaryRef repDict = NULL;
	CFMutableDictionaryRef parentOfRepDict = NULL;
	CFMutableArrayRef replicaArray = NULL;
	CFStringRef curReplicaNameString = NULL;
	CFIndex repIndex = 0;
	CFIndex repCount = 0;
	CFIndex subtreeRepCount = 0;
	
	if ( replicaNameString == NULL )
		return NO;
	
	[self lock];
	
	@try
	{
		decomArray = [self getArrayForKey:CFSTR(kPWReplicaDecommissionedListKey)];
		
		if ( mReplicaDict != NULL )
		{
			if ( decomArray == NULL )
			{
				decomArray = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );
				if ( decomArray != NULL )
				{
					CFDictionaryAddValue( mReplicaDict, CFSTR(kPWReplicaDecommissionedListKey), decomArray );
					CFRelease( decomArray );
				}
			}
			if ( decomArray != NULL )
			{
				if ( ! CFArrayContainsValue(decomArray, CFRangeMake(0, CFArrayGetCount(decomArray)), replicaNameString) )
				{
					CFArrayAppendValue( decomArray, replicaNameString );
					result = YES;
				}
				
				// remove the replica from the Leopard style list
				repDict = [self getReplicaByName:replicaNameString];
				if ( repDict != NULL )
				{
					parentOfRepDict = [self getParentOfReplica:repDict];
					if ( CFDictionaryGetValueIfPresent(parentOfRepDict, CFSTR(kPWReplicaReplicaKey), (const void **)&replicaArray) )
					{
						[self emptyFlatReplicaArray];
						subtreeRepCount = [self replicaCount:parentOfRepDict];
						repCount = CFArrayGetCount( replicaArray );
						
						for ( repIndex = 0; repIndex < repCount; repIndex++ )
						{
							repDict = (CFMutableDictionaryRef) CFArrayGetValueAtIndex( replicaArray, repIndex );
							if ( repDict != nil )
							{
								if ( CFDictionaryGetValueIfPresent( repDict, CFSTR(kPWReplicaNameKey), (const void **)&curReplicaNameString ) )
								{
									if ( CFStringCompare(replicaNameString, curReplicaNameString, 0) == kCFCompareEqualTo )
									{
										if ( subtreeRepCount == 1 )
											CFDictionaryRemoveValue( parentOfRepDict, CFSTR(kPWReplicaReplicaKey) );
										else
											CFArrayRemoveValueAtIndex( replicaArray, repIndex );
										break;
									}
								}
							}
						}
					}
				}
				
				// remove the replica from the Tiger style list
				replicaArray = [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)];
				if ( replicaArray != NULL )
				{
					repCount = CFArrayGetCount( replicaArray );
					for ( repIndex = 0; repIndex < repCount; repIndex++ )
					{
						repDict = (CFMutableDictionaryRef) CFArrayGetValueAtIndex( replicaArray, repIndex );
						if ( repDict != nil )
						{
							if ( CFDictionaryGetValueIfPresent( repDict, CFSTR(kPWReplicaNameKey), (const void **)&curReplicaNameString ) )
							{
								if ( CFStringCompare(replicaNameString, curReplicaNameString, 0) == kCFCompareEqualTo )
								{
									if ( repCount == 1 )
										CFDictionaryRemoveValue( mReplicaDict, CFSTR(kPWReplicaReplicaKey) );
									else
										CFArrayRemoveValueAtIndex( replicaArray, repIndex );
									break;
								}
							}
						}
					}
				}
			}
		}
	}
	@catch(id exception)
	{
	}
	
	[self unlock];
	
	return result;
}


//----------------------------------------------------------------------------------------------------
//	replicaIsNotDecommissioned
//----------------------------------------------------------------------------------------------------

-(BOOL)replicaIsNotDecommissioned:(CFStringRef)replicaNameString
{
	BOOL result = YES;
	CFMutableArrayRef decomArray = NULL;

	[self lock];
	
	decomArray = [self getArrayForKey:CFSTR(kPWReplicaDecommissionedListKey)];
	if ( decomArray != NULL )
	{
		if ( CFArrayContainsValue(decomArray, CFRangeMake(0, CFArrayGetCount(decomArray)), replicaNameString) )
			result = NO;
	}
	
	[self unlock];
	
	return result;
}


//----------------------------------------------------------------------------------------------------
//	RecommisionReplica
//----------------------------------------------------------------------------------------------------

-(void)recommisionReplica:(const char *)replicaName
{
	[self lock];

	CFMutableArrayRef decomArray = [self getArrayForKey:CFSTR(kPWReplicaDecommissionedListKey)];
	if ( decomArray != NULL )
	{
		CFStringRef replicaNameString = CFStringCreateWithCString( kCFAllocatorDefault, replicaName ? replicaName : "Parent", kCFStringEncodingUTF8 );
		CFIndex index;
		CFIndex itemCount = CFArrayGetCount( decomArray );
		CFStringRef curReplicaString;
		
		for ( index = 0; index < itemCount; index++ )
		{
			curReplicaString = (CFStringRef) CFArrayGetValueAtIndex( decomArray, index );
			if ( curReplicaString != NULL )
			{
				if ( CFStringCompare(curReplicaString, replicaNameString, 0) == kCFCompareEqualTo )
				{
					// found it
					if ( itemCount == 1 )
						CFDictionaryRemoveValue( mReplicaDict, CFSTR(kPWReplicaDecommissionedListKey) );
					else
						CFArrayRemoveValueAtIndex( decomArray, index );
					break;
				}
			}
		}
		
		if ( replicaNameString != NULL )
			CFRelease( replicaNameString );
	}
	
	[self unlock];
}


//----------------------------------------------------------------------------------------------------
//	ReplicaHasBeenPromotedToMaster
//----------------------------------------------------------------------------------------------------

-(BOOL)replicaHasBeenPromotedToMaster:(CFMutableDictionaryRef)inRepDict
{
	BOOL result = NO;
	CFDictionaryRef parentDict = [self getParent];
	CFMutableArrayRef parentIPArray = NULL;
	CFMutableArrayRef ipArray = NULL;
	CFStringRef ipString = NULL;
	CFIndex index;
	CFIndex ipCount;
	CFRange parentIPArrayRange;
	
	if ( parentDict == nil )
		return NO;
	
	parentIPArray = [self getIPAddressesFromDict:(CFMutableDictionaryRef)parentDict];
	if ( parentIPArray == nil )
		return NO;

	ipArray = [self getIPAddressesFromDict:inRepDict];
	if ( ipArray == nil ) {
		CFRelease( parentIPArray );
		return NO;
	}
	
	parentIPArrayRange = CFRangeMake( 0, CFArrayGetCount(parentIPArray) );
	
	ipArray = [self getIPAddressesFromDict:inRepDict];
	if ( ipArray == NULL ) {
		CFRelease( parentIPArray );
		return false;
	}
	
	ipCount = CFArrayGetCount( ipArray );
	for ( index = 0; index < ipCount; index++ )
	{
		ipString = (CFStringRef) CFArrayGetValueAtIndex( ipArray, index );
		if ( ipString != NULL && CFArrayContainsValue(parentIPArray, parentIPArrayRange, ipString) )
		{
			result = true;
			break;
		}
	}
	
	CFRelease( parentIPArray );
	CFRelease( ipArray );
	
	return result;
}


//----------------------------------------------------------------------------------------------------
//	stripDecommissionedArray
//----------------------------------------------------------------------------------------------------

-(void)stripDecommissionedArray
{
	[self lock];
	if ( mReplicaDict != NULL )
		CFDictionaryRemoveValue( mReplicaDict, CFSTR(kPWReplicaDecommissionedListKey) );
	[self unlock];
}


//----------------------------------------------------------------------------------------------------
//	divorceAllReplicas
//----------------------------------------------------------------------------------------------------

-(void)divorceAllReplicas
{
	if ( mReplicaDict == NULL )
		return;
	
	// remove all replicas from the master's entry
	CFMutableDictionaryRef parentDict = (CFMutableDictionaryRef)[self getParent];
	if ( parentDict != NULL )
		CFDictionaryRemoveValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
	
	// remove the Tiger legacy list
	CFDictionaryRemoveValue( mReplicaDict, CFSTR(kPWReplicaReplicaKey) );
	
	[self stripDecommissionedArray];
	
	mDirty = YES;
}


#pragma mark -
#pragma mark Per-Replica methods
#pragma mark -

//----------------------------------------------------------------------------------------------------
//	allocateIDRangeOfSize
//----------------------------------------------------------------------------------------------------

-(void)allocateIDRangeOfSize:(unsigned long)count forReplica:(CFStringRef)inReplicaName minID:(unsigned long)inMinID
{
	CFMutableDictionaryRef selfDict;
	CFStringRef rangeString;
	char firstID[35] = {0,};
	char lastID[35] = {0,};
	char myLastID[35] = {0,};
	char *myLastIDPtr = NULL;
	int err;
	PWFileEntry passRec;
	
	selfDict = [self getReplicaByName:inReplicaName];
	if ( selfDict == NULL )
		return;
	
	[self lock];
	
	if ( CFDictionaryGetValueIfPresent( selfDict, CFSTR(kPWReplicaIDRangeEndKey), (const void **)&rangeString ) )
		if ( CFStringGetCString( rangeString, myLastID, sizeof(myLastID), kCFStringEncodingUTF8 ) )
			myLastIDPtr = myLastID;
	
	[self getIDRangeOfSize:count after:myLastIDPtr start:firstID end:lastID];
	if ( pwsf_stringToPasswordRecRef(firstID, &passRec) == 1 && passRec.slot < inMinID )
	{
		passRec.slot = inMinID;
		pwsf_passwordRecRefToString( &passRec, firstID );
		passRec.slot = inMinID + count;
		pwsf_passwordRecRefToString( &passRec, lastID );
	}
	err = [self setIDRangeStart:firstID end:lastID forReplica:selfDict];
	[self setEntryModDateForReplica:selfDict];
	
	CFRelease( selfDict );
	mDirty = YES;
	
	[self unlock];
}


-(void)getIDRangeForReplica:(CFStringRef)inReplicaName start:(unsigned long *)outStart end:(unsigned long *)outEnd
{
	CFMutableDictionaryRef replicaDict = [self getReplicaByName:inReplicaName];
		
	if ( replicaDict != NULL )
	{
		[self getIDRangeStart:outStart end:outEnd forReplica:replicaDict];
		CFRelease( replicaDict );
	}
}


-(void)getIDRangeStart:(unsigned long *)outStart end:(unsigned long *)outEnd forReplica:(CFDictionaryRef)inReplicaDict
{
	CFStringRef rangeString;
	PWFileEntry passRec;
	char rangeStr[256];
	
	*outStart = 0;
	*outEnd = 0;
	
	if ( inReplicaDict == NULL )
		return;
	
	if ( CFDictionaryGetValueIfPresent( inReplicaDict, CFSTR(kPWReplicaIDRangeBeginKey), (const void **)&rangeString ) &&
		 CFStringGetCString( rangeString, rangeStr, sizeof(rangeStr), kCFStringEncodingUTF8 ) &&
		 pwsf_stringToPasswordRecRef( rangeStr, &passRec ) )
	{
		*outStart = passRec.slot;
	}
	
	if ( CFDictionaryGetValueIfPresent( inReplicaDict, CFSTR(kPWReplicaIDRangeEndKey), (const void **)&rangeString ) &&
		 CFStringGetCString( rangeString, rangeStr, sizeof(rangeStr), kCFStringEncodingUTF8 ) &&
		 pwsf_stringToPasswordRecRef( rangeStr, &passRec ) )
	{
		*outEnd = passRec.slot;
	}
}


-(void)setSyncDate:(CFDateRef)date forReplica:(CFStringRef)inReplicaName
{
	CFMutableDictionaryRef repDict;

	repDict = [self getReplicaByName:inReplicaName];
	if ( repDict != NULL )
	{
		CFDictionarySetValue( repDict, CFSTR(kPWReplicaSyncDateKey), date );
		CFRelease( repDict );
		mDirty = YES;
	}
}


-(void)setSyncDate:(CFDateRef)date andHighTID:(SInt64)tid forReplica:(CFStringRef)inReplicaName
{
	CFMutableDictionaryRef repDict = NULL;
	CFNumberRef aNumberRef = NULL;
	
	repDict = [self getReplicaByName:inReplicaName];
	if ( repDict != NULL )
	{
		CFDictionarySetValue( repDict, CFSTR(kPWReplicaSyncDateKey), date );
		
		aNumberRef = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt64Type, &tid );
		if ( aNumberRef != NULL ) {
			CFDictionarySetValue( repDict, CFSTR(kPWReplicaSyncTIDKey), aNumberRef );
			CFRelease( aNumberRef );
		}
		
		CFRelease( repDict );
		mDirty = YES;
	}
}


-(void)setEntryModDateForReplica:(CFMutableDictionaryRef)inReplicaDict
{
	CFDateRef nowDate = CFDateCreate( kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() );
	if ( nowDate != NULL ) {
		[self setKey:CFSTR(kPWReplicaEntryModDateKey) withDate:nowDate forReplica:inReplicaDict];
		CFRelease( nowDate );
	}
}


-(void)setSyncAttemptDateForReplica:(CFStringRef)inReplicaName
{
	CFDateRef nowDate = CFDateCreate( kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() );
	
	[self lock];
	[self setKey:CFSTR(kPWReplicaSyncAttemptKey) withDate:nowDate forReplicaWithName:inReplicaName];
	[self unlock];
}


-(void)setKey:(CFStringRef)key withDate:(CFDateRef)date forReplicaWithName:(CFStringRef)inReplicaName
{
	CFMutableDictionaryRef repDict;
	
	repDict = [self getReplicaByName:inReplicaName];
	if ( repDict == NULL )
		return;
	
	[self setKey:key withDate:date forReplica:repDict];
	CFRelease( repDict );
}

-(void)setKey:(CFStringRef)key withDate:(CFDateRef)date forReplica:(CFMutableDictionaryRef)inReplicaDict;
{
	CFDictionarySetValue( inReplicaDict, key, date );
	mDirty = YES;
}

-(CFMutableDictionaryRef)getReplicaByName:(CFStringRef)inReplicaName
{
	CFMutableDictionaryRef theDict = NULL;
	
	if ( inReplicaName == NULL || CFStringGetLength(inReplicaName) == 0 ||
		 CFStringCompare(inReplicaName, CFSTR("Parent"), 0) == kCFCompareEqualTo )
	{
		theDict = (CFMutableDictionaryRef)[self getParent];
	}
	else
	{
		theDict = [self findMatchToKey:CFSTR(kPWReplicaNameKey) withValue:inReplicaName];
	}
	
	if ( theDict != NULL )
		CFRetain( theDict );
	
	return theDict;
}


-(CFStringRef)getNameOfReplica:(CFMutableDictionaryRef)inReplicaDict
{
	if ( inReplicaDict == NULL )
		return NULL;
	
	return CFDictionaryGetValue( inReplicaDict, CFSTR(kPWReplicaNameKey) );
}


-(CFStringRef)getNameFromIPAddress:(const char *)inIPAddress
{
	CFStringRef ipAddrString = NULL;
	CFMutableDictionaryRef theReplica = NULL;
	CFStringRef nameString = NULL;
	
	ipAddrString = CFStringCreateWithCString( kCFAllocatorDefault, inIPAddress, kCFStringEncodingUTF8 );
	if ( ipAddrString != NULL )
	{
		theReplica = [self findMatchToKey:CFSTR(kPWReplicaIPKey) withValue:ipAddrString];
		CFRelease( ipAddrString );
	}
	
	if ( theReplica != NULL )
	{
		if ( !CFDictionaryGetValueIfPresent( theReplica, CFSTR(kPWReplicaNameKey), (const void **)&nameString ) )
			nameString = CFSTR("");		// it's the parent
	}
	
	return nameString;
}


-(UInt8)getReplicaSyncPolicy:(CFDictionaryRef)inReplicaDict
{
	return [self getReplicaSyncPolicy:inReplicaDict defaultPolicy:kReplicaSyncAnytime];
}


-(UInt8)getReplicaSyncPolicy:(CFDictionaryRef)inReplicaDict defaultPolicy:(UInt8)inDefaultPolicy
{
	CFStringRef valueString = NULL;
	UInt8 returnValue = inDefaultPolicy;
	
	valueString = CFDictionaryGetValue( inReplicaDict, CFSTR(kPWReplicaPolicyKey) );
	if ( valueString != NULL )
	{
		UInt8 testValue = [self getReplicaSyncPolicyForString:valueString];
		if ( testValue != kReplicaSyncInvalid )
			returnValue = testValue;
	}
	
	return returnValue;
}


//----------------------------------------------------------------------------------------------------
//	setReplicaSyncPolicy:forReplica:
//
//  Returns: TRUE if set
//----------------------------------------------------------------------------------------------------

-(BOOL)setReplicaSyncPolicy:(UInt8)policy forReplica:(CFStringRef)inReplicaName
{
	CFMutableDictionaryRef repDict;
	CFStringRef policyString = NULL;
	bool result = false;
	
	repDict = [self getReplicaByName:inReplicaName];
	if ( repDict == NULL )
		return result;
	
	switch( policy )
	{
		case kReplicaSyncDefault:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyDefaultKey, kCFStringEncodingUTF8 );
			break;
		case kReplicaSyncNever:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyNeverKey, kCFStringEncodingUTF8 );
			break;
		case kReplicaSyncOnlyIfDesperate:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyOnlyIfDesperateKey, kCFStringEncodingUTF8 );
			break;
		case kReplicaSyncOnSchedule:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyOnScheduleKey, kCFStringEncodingUTF8 );
			break;
		case kReplicaSyncOnDirty:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyOnDirtyKey, kCFStringEncodingUTF8 );
			break;
		case kReplicaSyncAnytime:
			policyString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaPolicyAnytimeKey, kCFStringEncodingUTF8 );
			break;
	}
	
	if ( policyString != NULL )
	{
		result = [self setReplicaSyncPolicyWithString:policyString forReplica:repDict];
		CFRelease( policyString );
	}
	
	CFRelease( repDict );
	
	return result;
}


-(BOOL)setReplicaSyncPolicyWithString:(CFStringRef)inPolicyString forReplica:(CFMutableDictionaryRef)inRepDict
{
	if ( inRepDict == NULL || inPolicyString == NULL )
		return NO;
	
	if ( [self getReplicaSyncPolicyForString:inPolicyString] == kReplicaSyncInvalid )
		return NO;
	
	CFDictionarySetValue( inRepDict, CFSTR(kPWReplicaPolicyKey), inPolicyString );
	[self setEntryModDateForReplica:inRepDict];
	mDirty = YES;
	
	return YES;
}


-(UInt8)getReplicaSyncPolicyForString:(CFStringRef)inPolicyString
{
	UInt8 returnValue = kReplicaSyncInvalid;
	
	if ( inPolicyString != NULL )
	{
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyDefaultKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncDefault;
		else
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyNeverKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncNever;
		else
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyOnlyIfDesperateKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncOnlyIfDesperate;
		else
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyOnScheduleKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncOnSchedule;
		else
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyOnDirtyKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncOnDirty;
		else
		if ( CFStringCompare(inPolicyString, CFSTR(kPWReplicaPolicyAnytimeKey), 0) == kCFCompareEqualTo )
			returnValue = kReplicaSyncAnytime;
	}
	
	return returnValue;
}


-(ReplicaStatus)getReplicaStatus:(CFDictionaryRef)inReplicaDict
{
	char valueStr[256];
	UInt8 returnValue = kReplicaActive;
	
	if ( [self getCStringFromDictionary:inReplicaDict forKey:CFSTR(kPWReplicaStatusKey) maxLen:sizeof(valueStr) result:valueStr] )
	{
		if ( strcmp( valueStr, kPWReplicaStatusActiveValue ) == 0 )
			returnValue = kReplicaActive;
		else
		if ( strcmp( valueStr, kPWReplicaStatusPermDenyValue ) == 0 )
			returnValue = kReplicaPermissionDenied;
		else
		if ( strcmp( valueStr, kPWReplicaStatusNotFoundValue ) == 0 )
			returnValue = kReplicaNotFound;
	}
	
	return returnValue;
}


-(void)setReplicaStatus:(ReplicaStatus)status forReplica:(CFMutableDictionaryRef)repDict
{
	CFStringRef statusString = NULL;
	
	if ( repDict == NULL )
		return;
	
	switch( status )
	{
		case kReplicaActive:
			statusString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaStatusActiveValue, kCFStringEncodingUTF8 );
			break;
		case kReplicaPermissionDenied:
			statusString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaStatusPermDenyValue, kCFStringEncodingUTF8 );
			break;
		case kReplicaNotFound:
			statusString = CFStringCreateWithCString( kCFAllocatorDefault, kPWReplicaStatusNotFoundValue, kCFStringEncodingUTF8 );
			break;
	}
	
	if ( statusString != NULL )
	{
		CFDictionarySetValue( repDict, CFSTR(kPWReplicaStatusKey), statusString );
		CFRelease( statusString );
		[self setEntryModDateForReplica:repDict];
		mDirty = YES;
	}
}


// utilities
-(CFStringRef)selfName
{
	if ( mSelfName != NULL )
	{
		[self lock];
		if ( mSelfName != NULL )
			CFRetain( mSelfName );
		[self unlock];
	}
	
	return mSelfName;
}


-(void)setSelfName:(CFStringRef)selfName
{
	[self lock];
	CFRetain( selfName );
	if ( mSelfName != NULL )
		CFRelease( mSelfName );
	
	mSelfName = selfName;
	[self unlock];
}


-(BOOL)getCStringFromDictionary:(CFDictionaryRef)inDict forKey:(CFStringRef)inKey maxLen:(long)inMaxLen result:(char *)outString
{
	BOOL result = NO;
	CFStringRef stringValue;
	
	if ( inDict != NULL )
	{
		stringValue = (CFStringRef)CFDictionaryGetValue( inDict, inKey );
		if ( stringValue != NULL && CFGetTypeID(stringValue) == CFStringGetTypeID() )
			result = CFStringGetCString( stringValue, outString, inMaxLen, kCFStringEncodingUTF8 );
	}
	
	return result;
}


-(BOOL)dirty
{
	return mDirty;
}


-(void)setDirty:(BOOL)dirty
{
	mDirty = dirty;
}


// replica array is at the top level
-(BOOL)isOldFormat
{
	CFDictionaryRef parentDict = [self getParent];
	
	// error, no format, old or new
	if ( parentDict == NULL )
		return NO;
	
	// if there are replica arrays inside of the parent server's entry, it's the Leopard format.
	// if the Tiger array is missing, call it an old format.
	CFArrayRef parentDictReplicas = (CFArrayRef)CFDictionaryGetValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
	if ( parentDictReplicas != NULL )
		return ( [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)] == NULL );
	
	// if there are replicas in the old place (top-level) but not in the new place,
	// the format is old.
	return ( [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)] != NULL );
}


// copy replica array inside the parent's dictionary, but also
// leave the original array for Tiger clients.
-(void)updateFormat
{
	CFMutableDictionaryRef parentDict = NULL;
	CFMutableArrayRef replicaArray = NULL;
	CFArrayRef parentDictReplicas = NULL;
	
	[self lock];
	replicaArray = [self getArrayForKey:CFSTR(kPWReplicaReplicaKey)];
	if ( replicaArray != NULL )
	{
		CFRetain( replicaArray );
		parentDict = (CFMutableDictionaryRef)[self getParent];
		if ( parentDict != NULL )
			CFDictionaryAddValue( parentDict, CFSTR(kPWReplicaReplicaKey), replicaArray );
		CFRelease( replicaArray );
	}
	else
	{
		// Check for 10.5.0 GM list with no legacy Tiger replica list
		parentDict = (CFMutableDictionaryRef)[self getParent];
		if ( parentDict != NULL )
		{
			parentDictReplicas = (CFArrayRef)CFDictionaryGetValue( parentDict, CFSTR(kPWReplicaReplicaKey) );
			if ( parentDictReplicas != NULL )
			{
				unsigned long repIndex;
				unsigned long repCount = [self replicaCount];
				CFMutableDictionaryRef curReplica = NULL;
	
				// create Tiger legacy list
				for ( repIndex = 0; repIndex < repCount; repIndex++ )
				{
					curReplica = (CFMutableDictionaryRef)[self getReplica:repIndex];
					if ( curReplica != NULL )
						[self addReplicaToLegacyTigerList:curReplica];
				}
			}
		}
	}
	
	mDirty = YES;
	[self unlock];
}


-(void)runningAsParent:(BOOL)parent
{
	mRunningAsParent = parent;
}


-(BOOL)isHappy
{
	return (mReplicaDict != NULL);
}


// other
-(CFStringRef)getNextReplicaName
{
	UInt32 repIndex = 0;
	UInt32 repCount = 0;
	CFDictionaryRef curReplica = NULL;
	CFStringRef curNameString = NULL;
	CFMutableArrayRef decomArray = NULL;
	const int replicaNameValuePrefixLen = sizeof(kPWReplicaNameValuePrefix) - 1;
	int tempReplicaNumber = 0;
	int nextReplicaNumber = 1;
	char replicaNameStr[256];
	char nextReplicaNameStr[256];
	char selfName[256];
	
	[self lock];

	// check active replicas
	repCount = [self replicaCount];
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		curReplica = [self getReplica:repIndex];
		if ( curReplica != NULL &&
			 CFDictionaryGetValueIfPresent(curReplica, CFSTR(kPWReplicaNameKey), (const void **)&curNameString) &&
			 CFStringHasPrefix(curNameString, CFSTR(kPWReplicaNameValuePrefix)) &&
			 CFStringGetCString(curNameString, replicaNameStr, sizeof(replicaNameStr), kCFStringEncodingUTF8) )
		{
			sscanf( replicaNameStr + replicaNameValuePrefixLen, "%d", &tempReplicaNumber );
			if ( tempReplicaNumber >= nextReplicaNumber )
				nextReplicaNumber = tempReplicaNumber + 1;
		}
	}
	
	// check decommissioned list
	decomArray = [self getArrayForKey:CFSTR(kPWReplicaDecommissionedListKey)];
	if ( decomArray != NULL )
	{
		repCount = CFArrayGetCount( decomArray );
		for ( repIndex = 0; repIndex < repCount; repIndex++ )
		{
			curNameString = CFArrayGetValueAtIndex( decomArray, repIndex );
			if ( curNameString != NULL &&
				 CFStringHasPrefix(curNameString, CFSTR(kPWReplicaNameValuePrefix)) &&
				 CFStringGetCString(curNameString, replicaNameStr, sizeof(replicaNameStr), kCFStringEncodingUTF8) )
			{
				sscanf( replicaNameStr + replicaNameValuePrefixLen, "%d", &tempReplicaNumber );
				if ( tempReplicaNumber >= nextReplicaNumber )
					nextReplicaNumber = tempReplicaNumber + 1;
			}
		}
	}
	
	[self unlock];
	
	// make name
	sprintf( nextReplicaNameStr, "%s%d", kPWReplicaNameValuePrefix, nextReplicaNumber );
	
	// if making a replica of a replica, add the other name to avoid collisions
	if ( mSelfName != NULL &&
		 CFStringHasPrefix(mSelfName, CFSTR(kPWReplicaNameValuePrefix)) &&
		 CFStringGetCString(mSelfName, selfName, sizeof(selfName), kCFStringEncodingUTF8) )
	{
		strcat( nextReplicaNameStr, "." );
		strcat( nextReplicaNameStr, selfName + replicaNameValuePrefixLen );
	}
	
	return CFStringCreateWithCString( kCFAllocatorDefault, nextReplicaNameStr, kCFStringEncodingUTF8 );
}


-(int)statReplicaFile:(const char *)inFilePath andGetModDate:(struct timespec *)outModDate
{
	struct stat sb;
	int result;
	
	if ( outModDate != NULL ) {
		outModDate->tv_sec = 0;
		outModDate->tv_nsec = 0;
	}
	
	result = lstat( inFilePath, &sb );
	if ( result == 0 && outModDate != NULL )
		*outModDate = sb.st_mtimespec;
	
	return result;
}


-(int)loadXMLData:(const char *)inFilePath
{
	CFStringRef myReplicaDataFilePathRef;
	CFURLRef myReplicaDataFileRef;
	CFReadStreamRef myReadStreamRef;
	CFPropertyListRef myPropertyListRef;
	CFStringRef errorString;
	CFPropertyListFormat myPLFormat;
	struct timespec modDate;
	int result = -1;
	
	[self lock];
	
	do
	{
		if ( [self statReplicaFile:inFilePath andGetModDate:&modDate] != 0 )
			break;
	
		myReplicaDataFilePathRef = CFStringCreateWithCString( kCFAllocatorDefault, inFilePath, kCFStringEncodingUTF8 );
		if ( myReplicaDataFilePathRef == NULL )
			break;
		
		myReplicaDataFileRef = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, myReplicaDataFilePathRef, kCFURLPOSIXPathStyle, false );
		
		CFRelease( myReplicaDataFilePathRef );
		
		if ( myReplicaDataFileRef == NULL )
			break;
		
		myReadStreamRef = CFReadStreamCreateWithFile( kCFAllocatorDefault, myReplicaDataFileRef );
		
		CFRelease( myReplicaDataFileRef );
		
		if ( myReadStreamRef == NULL )
			break;
		
		if ( ! CFReadStreamOpen( myReadStreamRef ) ) {
			CFRelease( myReadStreamRef );
			break;
		}
		
		errorString = NULL;
		myPLFormat = kCFPropertyListXMLFormat_v1_0;
		myPropertyListRef = CFPropertyListCreateFromStream( kCFAllocatorDefault, myReadStreamRef, 0, kCFPropertyListMutableContainersAndLeaves, &myPLFormat, &errorString );
		CFReadStreamClose( myReadStreamRef );
		CFRelease( myReadStreamRef );
		
		if ( errorString != NULL )
			CFRelease( errorString );
		
		if ( myPropertyListRef == NULL )
			break;
		
		if ( CFGetTypeID(myPropertyListRef) != CFDictionaryGetTypeID() ) {
			CFRelease( myPropertyListRef );
			break;
		}
		
		mReplicaDict = (CFMutableDictionaryRef)myPropertyListRef;
		mReplicaFileModDate = modDate;
		result = 0;
	}
	while (0);
	
	[self unlock];
	
	return result;
}


-(CFMutableArrayRef)getArrayForKey:(CFStringRef)key
{
	CFMutableArrayRef theArray = NULL;
	
	if ( mReplicaDict == NULL )
		return NULL;
	
	[self lock];
	theArray = (CFMutableArrayRef) CFDictionaryGetValue( mReplicaDict, key );
	[self unlock];
	if ( theArray == NULL || CFGetTypeID(theArray) != CFArrayGetTypeID() )
		return NULL;
	
	return theArray;
}


-(void)getIDRangeOfSize:(unsigned long)count after:(const char *)inMyLastID start:(char *)outFirstID end:(char *)outLastID
{
	PWFileEntry passRec, endPassRec;
	CFDictionaryRef replicaDict;
	CFStringRef rangeString;
	UInt32 repIndex, repCount = [self replicaCount];
	char rangeStr[35];
	
	bzero( &passRec, sizeof(PWFileEntry) );
	bzero( &endPassRec, sizeof(PWFileEntry) );
	
	endPassRec.slot = 0;
	
	if ( inMyLastID != NULL )
	{
		pwsf_stringToPasswordRecRef( inMyLastID, &passRec );
		endPassRec.slot = passRec.slot;
	}
	
	for ( repIndex = 0; repIndex < repCount; repIndex++ )
	{
		replicaDict = [self getReplica:repIndex];
		if ( replicaDict == NULL )
			continue;
		
		if ( ! CFDictionaryGetValueIfPresent( replicaDict, CFSTR(kPWReplicaIDRangeEndKey), (const void **)&rangeString ) )
			continue;
	
		if ( ! CFStringGetCString( rangeString, rangeStr, sizeof(rangeStr), kCFStringEncodingUTF8 ) )
			continue;
		
		if ( pwsf_stringToPasswordRecRef( rangeStr, &passRec ) == 0 )
			continue;
		
		if ( passRec.slot > endPassRec.slot )
			endPassRec.slot = passRec.slot;
	}
	
	replicaDict = [self getParent];
	if ( replicaDict != NULL )
	{
		if ( CFDictionaryGetValueIfPresent( replicaDict, CFSTR(kPWReplicaIDRangeEndKey), (const void **)&rangeString ) &&
			 CFStringGetCString( rangeString, rangeStr, sizeof(rangeStr), kCFStringEncodingUTF8 ) &&
			 pwsf_stringToPasswordRecRef( rangeStr, &passRec ) == 1
		   )
		{
			if ( passRec.slot > endPassRec.slot )
				endPassRec.slot = passRec.slot;		
		}
	}
	
	endPassRec.slot += (endPassRec.slot > 0) ? 20 : 1;
	pwsf_passwordRecRefToString( &endPassRec, outFirstID );
	
	endPassRec.slot += count;
	pwsf_passwordRecRefToString( &endPassRec, outLastID );
}


-(int)setIDRangeStart:(const char *)inFirstID end:(const char *)inLastID forReplica:(CFMutableDictionaryRef)inServerDict
{
	CFStringRef rangeString;
	
	rangeString = CFStringCreateWithCString( kCFAllocatorDefault, inFirstID, kCFStringEncodingUTF8 );
	if ( rangeString != NULL ) {
		CFDictionarySetValue( inServerDict, CFSTR(kPWReplicaIDRangeBeginKey), rangeString );
		CFRelease( rangeString );
	}
	
	rangeString = CFStringCreateWithCString( kCFAllocatorDefault, inLastID, kCFStringEncodingUTF8 );
	if ( rangeString != NULL ) {
		CFDictionarySetValue( inServerDict, CFSTR(kPWReplicaIDRangeEndKey), rangeString );
		CFRelease( rangeString );
	}
	
	return 0;
}


-(CFMutableDictionaryRef)findMatchToKey:(CFStringRef)inKey withValue:(CFStringRef)inValue
{
	CFMutableDictionaryRef theDict = NULL;
	CFTypeRef evalCFType;
	UInt32 repIndex, repCount;
	BOOL found = NO;
		
	theDict = (CFMutableDictionaryRef)[self getParent];
	if ( theDict != NULL )
	{
		if ( CFDictionaryGetValueIfPresent( theDict, inKey, (const void **)&evalCFType ) )
		{
			if ( CFGetTypeID(evalCFType) == CFStringGetTypeID() &&
				 CFStringCompare(inValue, (CFStringRef)evalCFType, 0) == kCFCompareEqualTo )
			{
				found = YES;
			}
			else
			if ( CFGetTypeID(evalCFType) == CFArrayGetTypeID() )
			{
				if ( CFArrayContainsValue((CFArrayRef)evalCFType, CFRangeMake(0, CFArrayGetCount((CFArrayRef)evalCFType) - 1), inValue) )
					found = YES;
			}
		}
	}
	
	if ( !found )
	{
		// clear the parent dictionary
		theDict = NULL;
		
		repCount = [self replicaCount];
		for ( repIndex = 0; repIndex < repCount; repIndex++ )
		{
			theDict = (CFMutableDictionaryRef)[self getReplica:repIndex];
			if ( theDict == NULL )
				break;
			
			if ( ! CFDictionaryGetValueIfPresent( theDict, inKey, (const void **)&evalCFType ) )
				continue;
			
			if ( CFGetTypeID(evalCFType) == CFStringGetTypeID() &&
				 CFStringCompare(inValue, (CFStringRef)evalCFType, 0) == kCFCompareEqualTo )
			{
				break;
			}
			else
			if ( CFGetTypeID(evalCFType) == CFArrayGetTypeID() )
			{
				if ( CFArrayContainsValue((CFArrayRef)evalCFType, CFRangeMake(0, CFArrayGetCount((CFArrayRef)evalCFType) - 1), inValue) )
					break;
			}
			
			theDict = NULL;
		}
	}
		
	return theDict;
}


-(CFMutableDictionaryRef)replicaDict
{
	return mReplicaDict;
}

@end