// Copyright 1999-2002 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "ONInterface.h"

// ONInterface.h decides whether ONInterface is supported by this platform or not
#ifdef HAVE_ONInterface

#import "ONHostAddress.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniBase/system.h> // For OBSocketClose()

#import <sys/ioctl.h>
#import <sys/socket.h>
#import <sys/sockio.h>
#import <net/if.h>
#import <net/if_types.h>
#import <netinet/in.h>

#import <arpa/inet.h>         // for inet_ntoa()
#import <net/if_dl.h>         // for 'struct sockaddr_dl'
#import <netinet/if_ether.h>  // for ETHERMTU
#import <netinet/if_fddi.h>   // for FDDIMTU


RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniNetworking/ONInterface.m,v 1.15 2002/03/09 01:54:06 kc Exp $")


// We'll guess that this is wildly larger than the maximum number of interfaces on the machine.  I don't see that there is a way to get the number of interfaces so that you don't have to have a hard-coded value here.  Sucks.
#define MAX_INTERFACES 100

#define IFR_NEXT(ifr)	\
    ((struct ifreq *) ((char *) (ifr) + sizeof(*(ifr)) + \
      MAX(0, (int) (ifr)->ifr_addr.sa_len - (int) sizeof((ifr)->ifr_addr))))

static NSArray *interfaces = nil;

@interface ONInterface (PrivateAPI)
- (id)_initWithName:(NSString *)ifname address:(struct sockaddr_dl *)sdl socket:(int)aSocket;
@end

@implementation ONInterface (PrivateAPI)

- (id)_initWithName:(NSString *)ifname address:(struct sockaddr_dl *)sdl socket:(int)someSocket;
{
    struct ifreq ifr;
    struct sockaddr_in *sin;
    int iftype;
    int linkLayerAddressLength;
    
    name = [ifname copy];
    iftype = sdl->sdl_type;
    // get the interface address
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, OSIOCGIFADDR, (caddr_t)&ifr) < 0)
        [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get interface address for %@, errno = %d", isa, NSStringFromSelector(_cmd), ifname, OMNI_ERRNO()];
    sin = (struct sockaddr_in *)&ifr.ifr_addr;
    interfaceAddress = [[ONHostAddress hostAddressWithInternetAddress:&sin->sin_addr] retain];

    // get the destination address (for point-to-point links).  If fail, only an error for PPP links.
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, OSIOCGIFDSTADDR, (caddr_t)&ifr) < 0) {
        if (iftype == IFT_PPP)
            [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get destination address for %@, errno = %d", isa, NSStringFromSelector(_cmd), ifname, OMNI_ERRNO()];
    } else {
        sin = (struct sockaddr_in *)&ifr.ifr_dstaddr;
        destinationAddress = [[ONHostAddress hostAddressWithInternetAddress:&sin->sin_addr] retain];
    }

    // get the broadcast address.  If fail, not an error on loopback or point-to-point links
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, OSIOCGIFBRDADDR, (caddr_t)&ifr) < 0) {
        if (iftype != IFT_LOOP && iftype != IFT_PPP)
            [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get broadcast address for %@, errno = %d", isa, NSStringFromSelector(_cmd), ifname, OMNI_ERRNO()];
    } else {
        sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
        broadcastAddress = [[ONHostAddress hostAddressWithInternetAddress:&sin->sin_addr] retain];
    }

    // get the net addr mask
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, OSIOCGIFNETMASK, (caddr_t)&ifr) < 0)
        [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get net address mask for %@, errno = %d", isa, NSStringFromSelector(_cmd), ifname, OMNI_ERRNO()];
    sin = (struct sockaddr_in *)&ifr.ifr_addr;
    netmaskAddress = [[ONHostAddress hostAddressWithInternetAddress:&sin->sin_addr] retain];
    
    // get the link layer address (for ethernet, this is the MAC address)
    linkLayerAddressLength = sdl->sdl_alen;
    if (linkLayerAddressLength > 0)
        linkLayerAddress = [[NSData alloc] initWithBytes:(unsigned char *)LLADDR(sdl) length:linkLayerAddressLength];
    
    // Other stats are requested by putting the name of the interface in the ioctl buffer.  Whack, but what else is there?
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, SIOCGIFFLAGS, (caddr_t)&ifr) < 0)
        [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get interface flags for %@, errno = %d", isa, NSStringFromSelector(_cmd), ifname, OMNI_ERRNO()];
    flags = ifr.ifr_flags;

    // Many interfaces don't suppor the 'get MTU' ioctl, rather requiring you to use a hard coded define.  Lame, lame, lame.
#if defined(ETHERMTU) && defined(IFT_ETHER) && defined(IFT_LOOP)
    if (iftype == IFT_ETHER || iftype == IFT_LOOP) {
        interfaceType = ONEtherInterfaceType;
        maximumTransmissionUnit = ETHERMTU;
    } else
#endif
#if defined(IFT_FDDI) && defined(FDDIMTU)
    if (iftype == IFT_FDDI) {
        interfaceType = ONFDDIInterfaceType;
        maximumTransmissionUnit = FDDIMTU;
    } else
#endif
#if defined(IFT_PPP)
    if (iftype == IFT_PPP) {
            interfaceType = ONPPPInterfaceType;
        // supports the MTU ioctl
    } else
#endif
        ; // make the syntax work out

    if (!maximumTransmissionUnit) {
        memset(&ifr, 0, sizeof(ifr));
        [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
        if (ioctl(someSocket, SIOCGIFMTU, (caddr_t)&ifr) < 0) {
            // Log a message that we can't get the MTU.  We'll raise in -maximumTransmissionUnit.
            NSLog(@"Cannot get MTU for interface %@.  It is probably of an unaccounted for type in ONInterface (type = %d).", ifname, interfaceType);
            maximumTransmissionUnit = 0;
        } else {
            maximumTransmissionUnit = ifr.ifr_mtu;
        }
    }
    
    return self;
}

@end

@implementation ONInterface

+ (NSArray *)interfaces;
{
    if (!interfaces) {
        struct ifreq requestBuffer[MAX_INTERFACES], *linkInterface;
        struct ifconf ifc;
        int interfaceSocket;
        int family;
        NSMutableArray *newInterfaces;

        newInterfaces = [NSMutableArray array];
        
        ifc.ifc_len = sizeof(requestBuffer);
        ifc.ifc_buf = (caddr_t)requestBuffer;

        // Since we get at this info via an ioctl, we need a temporary little socket.  This will only get AF_INET interfaces, but we probably don't care about anything else.  If we do end up caring later, we should add a ONAddressFamily and at a -interfaces method to it.
        family = AF_INET;
        if ((interfaceSocket = socket(family, SOCK_DGRAM, 0)) < 0)
            [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to create temporary socket, errno = %d", self, NSStringFromSelector(_cmd), OMNI_ERRNO()];

        if (ioctl(interfaceSocket, SIOCGIFCONF, &ifc) != 0)
            [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get list of network interfaces, errno = %d", self, NSStringFromSelector(_cmd), OMNI_ERRNO()];

        linkInterface = (struct ifreq *) ifc.ifc_buf;
        while ((char *) linkInterface < &ifc.ifc_buf[ifc.ifc_len]) {
            // The ioctl returns both the entries having the address (AF_INET) and the link layer entries (AF_LINK).  The AF_LINK entry has the link layer address which contains the interface type.  This is the only way I can see to get this information.  We cannot assume that we will get both an AF_LINK and AF_INET entry since the interface may not be configured.  For example, if you have a 10Mb port on the motherboard and a 100Mb card, you may not configure the motherboard port.

            // For each AF_LINK entry...
            if (linkInterface->ifr_addr.sa_family == AF_LINK) {
                struct ifreq *inetInterface;

                // if there is a matching AF_INET entry
                inetInterface = (struct ifreq *) ifc.ifc_buf;
                while ((char *) inetInterface < &ifc.ifc_buf[ifc.ifc_len]) {
                    if (inetInterface->ifr_addr.sa_family == AF_INET &&
                        strncmp(inetInterface->ifr_name, linkInterface->ifr_name, sizeof(linkInterface->ifr_name)) == 0) {
                        NSString *ifname;
                        unsigned int nameLength;
                        ONInterface *interface;

                        // Create an interface object.  Note that as described above, we pass the AF_LINK entry, not the AF_INET entry since these is no other way to get the interface type.
                        for (nameLength = 0; nameLength < IFNAMSIZ; nameLength++)
                            if (linkInterface->ifr_name[nameLength] == '\0')
                                break;

                        ifname = [[NSString alloc] initWithCString:linkInterface->ifr_name length:nameLength];


                        interface = [[self alloc] _initWithName:ifname address:(struct sockaddr_dl *)&linkInterface->ifr_addr socket:interfaceSocket];
                        [newInterfaces addObject:interface];
                        [interface release];

                        // We will not assume that there is only one AF_INET entry per AF_LINK entry.
                        // What happens when we have an interface that has multiple IP addresses, or can that even happen?

                        // break;
                    }
                    inetInterface = IFR_NEXT(inetInterface);
                }
            }
            linkInterface = IFR_NEXT(linkInterface);
        }

        interfaces = [[NSArray alloc] initWithArray:newInterfaces];
        OBSocketClose(interfaceSocket);
    }

    return interfaces;
}

+ (NSString *)stringForLinkLayerAddress:(NSData *)address;
{
    NSMutableString *stringValue;
    const unsigned char *bytes;
    int numBytes;
    int byteIndex;
    
    numBytes = [address length];
    if ((address == nil) || (numBytes == 0))
        return nil;

    bytes = [address bytes];
    stringValue = [NSMutableString string];
    for (byteIndex = 0; byteIndex < numBytes; byteIndex++) {
        unsigned int byteValue;
        
        if (byteIndex > 0)
            [stringValue appendString:@":"];
        byteValue = (unsigned int)bytes[byteIndex];
        [stringValue appendFormat:@"%02x", byteValue];
    }
    
    return stringValue;
}

- (NSString *)name;
{
    return name;
}

- (ONHostAddress *)interfaceAddress;
{
    return interfaceAddress;
}

- (ONHostAddress *)destinationAddress;
{
    return destinationAddress;
}

- (ONHostAddress *)broadcastAddress;
{
    return broadcastAddress;
}

- (ONHostAddress *)netmaskAddress;
{
    return netmaskAddress;
}

- (NSData *)linkLayerAddress;
{
    return linkLayerAddress;
}

- (NSString *)linkLayerAddressString;
{
    return [[self class] stringForLinkLayerAddress:[self linkLayerAddress]];
}

- (ONInterfaceType)interfaceType;
{
    return interfaceType;
}

- (unsigned int)maximumTransmissionUnit;
{
    if (!maximumTransmissionUnit) {
        [NSException raise:NSGenericException format:@"+[%@ %@] -- Unable to get interface MTU for %@", isa, NSStringFromSelector(_cmd), name];
    }

    return maximumTransmissionUnit;
}


- (BOOL)isUp;
{
    return (flags & IFF_UP) != 0;
}

- (BOOL)supportsBroadcast;
{
    return (flags & IFF_BROADCAST) != 0;
}

- (BOOL)isLoopback;
{
    return (flags & IFF_LOOPBACK) != 0;
}

- (BOOL)isPointToPoint;
{
    return (flags & IFF_POINTOPOINT) != 0;
}

- (BOOL)supportsAddressResolutionProtocol;
{
    return (flags & IFF_NOARP) == 0;
}

- (BOOL)supportsPromiscuousMode;
{
    return (flags & IFF_PROMISC) != 0;
}

- (BOOL)isSimplex;
{
    return (flags & IFF_SIMPLEX) != 0;
}

- (BOOL)supportsMulticast;
{
    return (flags & IFF_MULTICAST) != 0;
}

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;
    NSString *typeString;
    NSMutableArray *flagArray;
    
    debugDictionary = [super debugDictionary];
    if (name)
        [debugDictionary setObject:name forKey:@"name"];
    if (interfaceAddress)
        [debugDictionary setObject:interfaceAddress forKey:@"interfaceAddress"];
    if (destinationAddress)
        [debugDictionary setObject:destinationAddress forKey:@"destinationAddress"];
    if (broadcastAddress)
        [debugDictionary setObject:broadcastAddress forKey:@"broadcastAddress"];
    if (netmaskAddress)
        [debugDictionary setObject:netmaskAddress forKey:@"netmaskAddress"];

    switch (interfaceType) {
        case ONEtherInterfaceType:
            typeString = @"ether";
            break;
        case ONPPPInterfaceType:
            typeString = @"ppp";
            break;
        case ONFDDIInterfaceType:
            typeString = @"fddi";
            break;
        case ONUnknownInterfaceType:
        default:
            typeString = @"unknown";
            break;
    }
    [debugDictionary setObject:typeString forKey:@"interfaceType"];
    [debugDictionary setObject:[NSNumber numberWithUnsignedInt:maximumTransmissionUnit] forKey:@"maximumTransmissionUnit"];

    flagArray = [NSMutableArray array];
    if ([self isUp])
        [flagArray addObject:@"UP"];
    if ([self supportsBroadcast])
        [flagArray addObject:@"BROADCAST"];
    if ([self isLoopback])
        [flagArray addObject:@"LOOPBACK"];
    if ([self isPointToPoint])
        [flagArray addObject:@"POINTOPOINT"];
    if ([self supportsAddressResolutionProtocol])
        [flagArray addObject:@"ARP"];
    if ([self supportsPromiscuousMode])
        [flagArray addObject:@"PROMISC"];
    if ([self isSimplex])
        [flagArray addObject:@"SIMPLEX"];
    if ([self supportsMulticast])
        [flagArray addObject:@"MULTICAST"];
    [debugDictionary setObject:flagArray forKey:@"flags"];
    
    return debugDictionary;
}

@end


#endif /* HAVE_ONInterface */

