/*
 * Copyright (c) 2002-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@
 */

/*
 * Modification History
 *
 * August 5, 2002	Allan Nathanson <ajn@apple.com>
 * - split code out from eventmon.c
 */

#include "eventmon.h"
#include "cache.h"
#include "ev_appletalk.h"

// from <netat/ddp.h>
#define DDP_MIN_NETWORK	0x0001
#define DDP_MAX_NETWORK	0xfffe


static int
get_atalk_interface_cfg(const char *if_name, at_if_cfg_t *cfg)
{
	int 	fd;

	/* open socket */
	if ((fd = socket(AF_APPLETALK, SOCK_RAW, 0)) < 0)
		return -1;

	/* get config info for given interface */
	strncpy(cfg->ifr_name, if_name, sizeof(cfg->ifr_name));
	if (ioctl(fd, AIOCGETIFCFG, (caddr_t)cfg) < 0) {
		(void)close(fd);
		return -1;
	}

	(void)close(fd);
	return 0;
}


static CFMutableDictionaryRef
getIF(CFStringRef key, CFMutableDictionaryRef oldIFs, CFMutableDictionaryRef newIFs)
{
	CFDictionaryRef		dict		= NULL;
	CFMutableDictionaryRef	newDict		= NULL;

	if (CFDictionaryGetValueIfPresent(newIFs, key, (const void **)&dict)) {
		newDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
	} else {
		dict = cache_SCDynamicStoreCopyValue(store, key);
		if (dict) {
			CFDictionarySetValue(oldIFs, key, dict);
			if (isA_CFDictionary(dict)) {
				newDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
				CFDictionaryRemoveValue(newDict, kSCPropNetAppleTalkNetworkID);
				CFDictionaryRemoveValue(newDict, kSCPropNetAppleTalkNodeID);
				CFDictionaryRemoveValue(newDict, kSCPropNetAppleTalkNetworkRange);
				CFDictionaryRemoveValue(newDict, kSCPropNetAppleTalkDefaultZone);
			}
			CFRelease(dict);
		}
	}

	if (!newDict) {
		newDict = CFDictionaryCreateMutable(NULL,
						    0,
						    &kCFTypeDictionaryKeyCallBacks,
						    &kCFTypeDictionaryValueCallBacks);
	}

	return newDict;
}


static void
updateStore(const void *key, const void *value, void *context)
{
	CFDictionaryRef dict;
	CFDictionaryRef newDict = (CFDictionaryRef)value;
	CFDictionaryRef	oldIFs	= (CFDictionaryRef)context;

	dict = CFDictionaryGetValue(oldIFs, key);

	if (!dict || !CFEqual(dict, newDict)) {
		if (CFDictionaryGetCount(newDict) > 0) {
			cache_SCDynamicStoreSetValue(store, key, newDict);
		} else if (dict) {
			cache_SCDynamicStoreRemoveValue(store, key);
		}
	}

	return;
}


__private_extern__
void
interface_update_appletalk(struct ifaddrs *ifap, const char *if_name)
{
	struct ifaddrs		*ifa;
	struct ifaddrs		*ifap_temp	= NULL;
	CFStringRef		interface;
	boolean_t		interfaceFound	= FALSE;
	CFStringRef		key		= NULL;
	CFMutableDictionaryRef	oldIFs;
	CFMutableDictionaryRef	newDict		= NULL;
	CFMutableDictionaryRef	newIFs;

	oldIFs = CFDictionaryCreateMutable(NULL,
					   0,
					   &kCFTypeDictionaryKeyCallBacks,
					   &kCFTypeDictionaryValueCallBacks);
	newIFs = CFDictionaryCreateMutable(NULL,
					   0,
					   &kCFTypeDictionaryKeyCallBacks,
					   &kCFTypeDictionaryValueCallBacks);

	if (!ifap) {
		if (getifaddrs(&ifap_temp) < 0) {
			SCLog(TRUE, LOG_ERR, CFSTR("getifaddrs() failed: %s"), strerror(errno));
			goto error;
		}
		ifap = ifap_temp;
	}

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		at_if_cfg_t		cfg;
		int			iVal;
		CFNumberRef		num;
		struct sockaddr_at	*sat;

		if (ifa->ifa_addr->sa_family != AF_APPLETALK) {
			continue;			/* sorry, not interested */
		}

		/* check if this is the requested interface */
		if (if_name) {
			if (strncmp(if_name, ifa->ifa_name, IFNAMSIZ) == 0) {
				interfaceFound = TRUE;	/* yes, this is the one I want */
			} else {
				continue;		/* sorry, not interested */
			}
		}

		/* get the current cache information */
		interface = CFStringCreateWithCString(NULL, ifa->ifa_name, kCFStringEncodingMacRoman);
		key       = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
									  kSCDynamicStoreDomainState,
									  interface,
									  kSCEntNetAppleTalk);
		CFRelease(interface);

		newDict = getIF(key, oldIFs, newIFs);

		sat = (struct sockaddr_at *)ifa->ifa_addr;

		iVal = (int)sat->sat_addr.s_net;
		num  = CFNumberCreate(NULL, kCFNumberIntType, &iVal);
		CFDictionarySetValue(newDict, kSCPropNetAppleTalkNetworkID, num);
		CFRelease(num);

		iVal = (int)sat->sat_addr.s_node;
		num  = CFNumberCreate(NULL, kCFNumberIntType, &iVal);
		CFDictionarySetValue(newDict, kSCPropNetAppleTalkNodeID, num);
		CFRelease(num);

		if (get_atalk_interface_cfg(ifa->ifa_name, &cfg) == 0) {
			CFStringRef		zone;

			/*
			 * Set starting and ending net values
			 */
			if (!(((cfg.netStart == 0) && (cfg.netEnd == 0)) ||
			      ((cfg.netStart == DDP_MIN_NETWORK) && (cfg.netEnd == DDP_MAX_NETWORK)))) {
				CFMutableArrayRef	array;

				array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

				iVal = cfg.netStart;
				num  = CFNumberCreate(NULL, kCFNumberIntType, &iVal);
				CFArrayAppendValue(array, num);
				CFRelease(num);

				iVal = cfg.netEnd;
				num  = CFNumberCreate(NULL, kCFNumberIntType, &iVal);
				CFArrayAppendValue(array, num);
				CFRelease(num);

				CFDictionarySetValue(newDict, kSCPropNetAppleTalkNetworkRange, array);
				CFRelease(array);
			}

			/*
			 * Set the default zone
			 */
			zone = CFStringCreateWithPascalString(NULL,
							      (ConstStr255Param)&cfg.zonename,
							      kCFStringEncodingMacRoman);
			CFDictionarySetValue(newDict, kSCPropNetAppleTalkDefaultZone, zone);
			CFRelease(zone);
		}

		CFDictionarySetValue(newIFs, key, newDict);
		CFRelease(newDict);
		CFRelease(key);
	}

	/* if the last address[es] were removed from the target interface */
	if (if_name && !interfaceFound) {
		interface = CFStringCreateWithCString(NULL, if_name, kCFStringEncodingMacRoman);
		key       = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
									  kSCDynamicStoreDomainState,
									  interface,
									  kSCEntNetAppleTalk);
		CFRelease(interface);

		newDict = getIF(key, oldIFs, newIFs);

		CFDictionarySetValue(newIFs, key, newDict);
		CFRelease(newDict);
		CFRelease(key);
	}

	CFDictionaryApplyFunction(newIFs, updateStore, oldIFs);

    error :

	if (ifap_temp)	freeifaddrs(ifap_temp);
	CFRelease(oldIFs);
	CFRelease(newIFs);

	return;
}


__private_extern__
void
interface_update_atalk_address(struct kev_atalk_data *aEvent, const char *if_name)
{
	CFStringRef		interface;
	CFStringRef		key;
	CFDictionaryRef		dict;
	CFMutableDictionaryRef	newDict = NULL;
	CFNumberRef		newNode, newNet;
	int			node;
	int			net;

	/* get the current cache information */
	interface = CFStringCreateWithCString(NULL, if_name, kCFStringEncodingMacRoman);
	key       = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
								  kSCDynamicStoreDomainState,
								  interface,
								  kSCEntNetAppleTalk);
	CFRelease(interface);

	dict = cache_SCDynamicStoreCopyValue(store, key);
	if (dict) {
		if (isA_CFDictionary(dict)) {
			newDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
		}
		CFRelease(dict);
	}

	if (!newDict) {
		newDict = CFDictionaryCreateMutable(NULL,
						    0,
						    &kCFTypeDictionaryKeyCallBacks,
						    &kCFTypeDictionaryValueCallBacks);
	}

	/* Update node/net values in cache */
	node	= (int)aEvent->node_data.address.s_node;
	net	= (int)aEvent->node_data.address.s_net;

	newNode	= CFNumberCreate(NULL, kCFNumberIntType, &node);
	newNet 	= CFNumberCreate(NULL, kCFNumberIntType, &net);

	CFDictionarySetValue(newDict, kSCPropNetAppleTalkNodeID, newNode);
	CFDictionarySetValue(newDict, kSCPropNetAppleTalkNetworkID, newNet);

	CFRelease(newNode);
	CFRelease(newNet);

	/* update cache */
	cache_SCDynamicStoreSetValue(store, key, newDict);
	CFRelease(newDict);
	CFRelease(key);
	return;
}


__private_extern__
void
interface_update_atalk_zone(struct kev_atalk_data *aEvent, const char *if_name)
{
	CFStringRef		interface;
	CFStringRef		key;
	CFDictionaryRef		dict;
	CFMutableDictionaryRef	newDict	= NULL;
	CFStringRef		newZone;

	/* get the current cache information */
	interface = CFStringCreateWithCString(NULL, if_name, kCFStringEncodingMacRoman);
	key       = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
								  kSCDynamicStoreDomainState,
								  interface,
								  kSCEntNetAppleTalk);
	CFRelease(interface);

	dict = cache_SCDynamicStoreCopyValue(store, key);
	if (dict) {
		if (isA_CFDictionary(dict)) {
			newDict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
		}
		CFRelease(dict);
	}

	if (!newDict) {
		newDict = CFDictionaryCreateMutable(NULL,
						    0,
						    &kCFTypeDictionaryKeyCallBacks,
						    &kCFTypeDictionaryValueCallBacks);
	}

	/* Update zone value in cache */
	newZone = CFStringCreateWithPascalString(NULL, (ConstStr255Param)&(aEvent->node_data.zone), kCFStringEncodingMacRoman);

	CFDictionarySetValue(newDict, kSCPropNetAppleTalkDefaultZone, newZone);

	CFRelease(newZone);

	/* update cache */
	cache_SCDynamicStoreSetValue(store, key, newDict);
	CFRelease(newDict);
	CFRelease(key);
	return;
}


__private_extern__
void
interface_update_shutdown_atalk()
{
	CFStringRef			cacheKey;
	CFDictionaryRef			dict;
	CFArrayRef			ifList = NULL;
	CFIndex 			count, index;
	CFStringRef 			interface;
	CFStringRef			key;

	cacheKey  = SCDynamicStoreKeyCreateNetworkInterface(NULL,
							    kSCDynamicStoreDomainState);

	dict = cache_SCDynamicStoreCopyValue(store, cacheKey);
	CFRelease(cacheKey);

	if (dict) {
		if (isA_CFDictionary(dict)) {
			/*get a list of the interfaces*/
			ifList  = isA_CFArray(CFDictionaryGetValue(dict, kSCDynamicStorePropNetInterfaces));
			if (ifList) {
				count = CFArrayGetCount(ifList);

				/*iterate through list and remove AppleTalk data*/
				for (index = 0; index < count; index++) {
					interface = CFArrayGetValueAtIndex(ifList, index);
					key       = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
												  kSCDynamicStoreDomainState,
												  interface,
												  kSCEntNetAppleTalk);
					cache_SCDynamicStoreRemoveValue(store, key);
					CFRelease(key);
				}
			}
		}
		CFRelease(dict);
	}

	return;
}
