/*
 * 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>
 * - initial revision
 */


#include "eventmon.h"
#include "cache.h"
#include "ev_ipv6.h"

#define s6_addr16 __u6_addr.__u6_addr16

#ifndef	kSCPropNetIPv6DestAddresses
#define kSCPropNetIPv6DestAddresses		SCSTR("DestAddresses")
#endif

#ifndef	kSCPropNetIPv6Flags
#define kSCPropNetIPv6Flags			SCSTR("Flags")
#endif

#ifndef	kSCPropNetIPv6PrefixLength
#define kSCPropNetIPv6PrefixLength		SCSTR("PrefixLength")
#endif

#ifdef	NOTYET
#ifndef	kSCPropNetIPv6ScopeID
#define kSCPropNetIPv6ScopeID			SCSTR("ScopeID")
#endif
#endif	/* NOTYET */


static void
appendAddress(CFMutableDictionaryRef dict, CFStringRef key, struct sockaddr_in6 *sin6)
{
	CFStringRef		addr;
	CFArrayRef		addrs;
	CFMutableArrayRef	newAddrs;
	char			str[64];

	addrs = CFDictionaryGetValue(dict, key);
	if (addrs) {
		newAddrs = CFArrayCreateMutableCopy(NULL, 0, addrs);
	} else {
		newAddrs = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}

	if (inet_ntop(AF_INET6, (const void *)&sin6->sin6_addr, str, sizeof(str)) == NULL) {
		SCLog(TRUE, LOG_ERR, CFSTR("inet_ntop() failed: %s"), strerror(errno));
		str[0] = '\0';
	}

	addr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), str);
	CFArrayAppendValue(newAddrs, addr);
	CFRelease(addr);

	CFDictionarySetValue(dict, key, newAddrs);
	CFRelease(newAddrs);
	return;
}


static void
appendFlags(CFMutableDictionaryRef dict, int flags6)
{
	CFArrayRef		flags;
	CFMutableArrayRef	newFlags;
	CFNumberRef		v6Flags;

	flags = CFDictionaryGetValue(dict, kSCPropNetIPv6Flags);
	if (flags) {
		newFlags = CFArrayCreateMutableCopy(NULL, 0, flags);
	} else {
		newFlags = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}

	v6Flags = CFNumberCreate(NULL, kCFNumberIntType, &flags6);
	CFArrayAppendValue(newFlags, v6Flags);
	CFRelease(v6Flags);

	CFDictionarySetValue(dict, kSCPropNetIPv6Flags, newFlags);
	CFRelease(newFlags);
	return;
}


static void
appendPrefixLen(CFMutableDictionaryRef dict, struct sockaddr_in6 *sin6)
{
	register u_int8_t	*name		= &sin6->sin6_addr.s6_addr[0];
	CFNumberRef		prefixLen;
	CFArrayRef		prefixLens;
	CFMutableArrayRef	newPrefixLens;

	register int		byte;
	register int		bit;
	int			plen		= 0;

	for (byte = 0; byte < sizeof(struct in6_addr); byte++, plen += 8) {
		if (name[byte] != 0xff) {
			break;
		}
	}

	if (byte == sizeof(struct in6_addr)) {
		goto append;
	}

	for (bit = 7; bit != 0; bit--, plen++) {
		if (!(name[byte] & (1 << bit))) {
			break;
		}
	}

	for (; bit != 0; bit--) {
		if (name[byte] & (1 << bit)) {
			plen = 0;
			goto append;
		}
	}

	byte++;
	for (; byte < sizeof(struct in6_addr); byte++) {
		if (name[byte]) {
			plen = 0;
			goto append;
		}
	}

    append :

	prefixLens = CFDictionaryGetValue(dict, kSCPropNetIPv6PrefixLength);
	if (prefixLens) {
		newPrefixLens = CFArrayCreateMutableCopy(NULL, 0, prefixLens);
	} else {
		newPrefixLens = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}

	prefixLen = CFNumberCreate(NULL, kCFNumberIntType, &plen);
	CFArrayAppendValue(newPrefixLens, prefixLen);
	CFRelease(prefixLen);

	CFDictionarySetValue(dict, kSCPropNetIPv6PrefixLength, newPrefixLens);
	CFRelease(newPrefixLens);
	return;
}


#ifdef	NOTYET
static void
appendScopeID(CFMutableDictionaryRef dict, struct sockaddr_in6 *sin6)
{
	CFNumberRef		scope;
	CFArrayRef		scopes;
	CFMutableArrayRef	newScopes;

	scopes = CFDictionaryGetValue(dict, kSCPropNetIPv6ScopeID);
	if (scopes) {
		newScopes = CFArrayCreateMutableCopy(NULL, 0, scopes);
	} else {
		newScopes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	}

	scope = CFNumberCreate(NULL, kCFNumberSInt32Type, &sin6->sin6_scope_id);
	CFArrayAppendValue(newScopes, scope);
	CFRelease(scope);

	CFDictionarySetValue(dict, kSCPropNetIPv6ScopeID, newScopes);
	CFRelease(newScopes);
	return;
}
#endif	/* NOTYET */


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, kSCPropNetIPv6Addresses);
				CFDictionaryRemoveValue(newDict, kSCPropNetIPv6DestAddresses);
				CFDictionaryRemoveValue(newDict, kSCPropNetIPv6Flags);
				CFDictionaryRemoveValue(newDict, kSCPropNetIPv6PrefixLength);
#ifdef	NOTYET
				CFDictionaryRemoveValue(newDict, kSCPropNetIPv6ScopeID);
#endif	/* NOTYET */
			}
			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_ipv6(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;
	int			sock		= -1;

	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) {
		struct in6_ifreq	ifr6;
#define	flags6	ifr6.ifr_ifru.ifru_flags6
		struct sockaddr_in6	*sin6;

		if (ifa->ifa_addr->sa_family != AF_INET6) {
			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 */
			}
		}

		if (sock < 0) {
			sock = dgram_socket(AF_INET6);
			if (sock < 0) {
				SCLog(TRUE, LOG_NOTICE, CFSTR("interface_update_ipv6: socket open failed, %s"), strerror(errno));
				goto error;
			}
		}

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

		newDict = getIF(key, oldIFs, newIFs);

		sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;

		/* XXX: embedded link local addr check */
		if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
			u_int16_t	index;

			index = sin6->sin6_addr.s6_addr16[1];
			if (index != 0) {
				sin6->sin6_addr.s6_addr16[1] = 0;
				if (sin6->sin6_scope_id == 0) {
					sin6->sin6_scope_id = ntohs(index);
				}
			}
		}

		bzero((char *)&ifr6, sizeof(ifr6));
		strncpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name));
		ifr6.ifr_addr = *sin6;
		if (ioctl(sock, SIOCGIFAFLAG_IN6, &ifr6) == -1) {
			/* if flags not available for this address */
			SCLog(TRUE, LOG_NOTICE, CFSTR("interface_update_ipv6: ioctl failed, %s"), strerror(errno));
		}

		appendAddress  (newDict, kSCPropNetIPv6Addresses, sin6);
#ifdef	NOTYET
		appendScopeID  (newDict, sin6);
#endif	/* NOTYET */
		appendPrefixLen(newDict, (struct sockaddr_in6 *)ifa->ifa_netmask);
		appendFlags    (newDict, flags6);


		if (ifa->ifa_flags & IFF_POINTOPOINT) {
			struct sockaddr_in6	*dst6;

			dst6 = (struct sockaddr_in6 *)ifa->ifa_dstaddr;

			/* XXX: embedded link local addr check */
			if (IN6_IS_ADDR_LINKLOCAL(&dst6->sin6_addr)) {
				u_int16_t	index;

				index = dst6->sin6_addr.s6_addr16[1];
				if (index != 0) {
					dst6->sin6_addr.s6_addr16[1] = 0;
					if (dst6->sin6_scope_id == 0) {
						dst6->sin6_scope_id = ntohs(index);
					}
				}
			}

			appendAddress(newDict, kSCPropNetIPv6DestAddresses, dst6);
		}

		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,
									  kSCEntNetIPv6);
		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);
	if (sock >= 0)	close(sock);
	CFRelease(oldIFs);
	CFRelease(newIFs);

	return;
}
