// Copyright 1999 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/OmniNetworking/ONInterface.m,v 1.7 1999/06/08 03:58:29 bungi 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;
    
    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
    [name getCString:ifr.ifr_name maxLength:sizeof(ifr.ifr_name)];
    if (ioctl(someSocket, OSIOCGIFBRDADDR, (caddr_t)&ifr) < 0) {
        if (iftype != IFT_LOOP)
            [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];
    
    // 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) {
        type = ONEtherInterfaceType;
        maximumTransmissionUnit = ETHERMTU;
    } else
#endif
#if defined(IFT_FDDI) && defined(FDDIMTU)
    if (iftype == IFT_FDDI) {
        type = ONFDDIInterfaceType;
        maximumTransmissionUnit = FDDIMTU;
    } else
#endif
#if defined(IFT_PPP)
    if (iftype == IFT_PPP) {
            type = 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, type);
            maximumTransmissionUnit = 0;
        } else {
            maximumTransmissionUnit = ifr.ifr_mtu;
        }
    }

    return self;
}

@end

@implementation ONInterface

+ (NSArray *)interfaces;
{
    if (!interfaces) {
        struct ifreq requestBuffer[MAX_INTERFACES], *ifrp;
        struct ifconf ifc;
        NSString *ifname;
        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()];

        ifrp = (struct ifreq *) ifc.ifc_buf;
        while ((char *) ifrp < &ifc.ifc_buf[ifc.ifc_len]) {
            unsigned int nameLength;
            ONInterface *interface;

            // 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 will assume that we will get pairs of results, one for the requested address family and one in the link layer and then we'll IGNORE the ones in the address family that we want since we can get that info via other ioctls, but we can't get the link layer info any other way (I'm probably missing something).

#warning TJW: This is bad.  In some cases there are AF_LINK entries for en%d but no AF_INET entry.  For example, anduril has a network interface on the motherboard, but is using a network card.  Only the network card is configured to have an ip address.  en0 entry has only a AF_LINK entry and no AF_INET entry.  We need to scan for matching AF_LINK/AF_INET pairs here.
            if (ifrp->ifr_addr.sa_family == AF_LINK) {
                for (nameLength = 0; nameLength < IFNAMSIZ; nameLength++)
                    if (!ifrp->ifr_name[nameLength])
                        break;

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


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

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

    return interfaces;
}

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

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

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

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

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

- (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 (type) {
        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:@"type"];
    [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 */

