#import "ONHost-InternalAPI.h"

#ifdef ONHOST_USE_THREADSAFE_LOOKUPD_GETHOSTBYNAME

#import <Foundation/Foundation.h>
#import <rpc/types.h>
#import <rpc/xdr.h>
#import <mach/port.h>
#import <mach/kern_return.h>
#import <netinet/in.h>
#import <netdb.h>

#import "ONHostAddress.h"

// These lookupd header files are missing on Rhapsody (although the protocol is apparently the same as it's been since NextStep 1.x)

/* <netinfo/lookup_types.h> ***********************************************/
/*
 * Lookup protocol definitions - internal to NeXT
 * Copyright (C) 1989 by NeXT, Inc.
 */

/*
 * the port to lookupd is obtained through the (internal)
 * _lookupd_port() syscall instead of through the nameserver
 * with LOOKUP_SERVER_NAME as "C Library Lookup"
 */

#define UNIT_SIZE 4

typedef struct unit {
        char data[UNIT_SIZE];
} unit;

#define MAX_INLINE_UNITS 2047
#define MAX_LOOKUP_NAMELEN 256
#define MAX_INLINE_DATA (MAX_INLINE_UNITS * sizeof(unit))

typedef char lookup_name[MAX_LOOKUP_NAMELEN];
typedef unit inline_data[MAX_INLINE_UNITS];
typedef char *ooline_data;
/**************************************************************************/

/* derived from <netinfo/lookup.defs> *************************************/
kern_return_t _lookup_link(port_t server, lookup_name name, int *procno);
kern_return_t _lookup_all(port_t server, int proc, inline_data indata, unsigned int indataCnt, ooline_data *outdata, unsigned int *outdataCnt);
kern_return_t _lookup_one(port_t server, int proc, inline_data indata, unsigned int indataCnt, inline_data outdata, unsigned int *outdataCnt);
kern_return_t _lookup_ooall(port_t server, int proc, ooline_data indata, unsigned int indataCnt, ooline_data *outdata, unsigned int *outdataCnt);
/**************************************************************************/

/* derived from <netinfo/_lu_types.x **************************************/

typedef char *_lu_string;
bool_t xdr__lu_string();

struct _lu_hostent {
    struct {
        u_int h_names_len;
        _lu_string *h_names_val;
    } h_names;
    struct {
        u_int h_addrs_len;
        u_long *h_addrs_val;
    } h_addrs;
};

typedef struct _lu_hostent _lu_hostent;
bool_t xdr__lu_hostent();

typedef _lu_hostent *_lu_hostent_ptr;
bool_t xdr__lu_hostent_ptr();

/**************************************************************************/

// Internals of System.framework visible to us
extern int _lu_running();	// fetches/refreshes _lu_port
extern port_t _lu_port;		// Mach port for lookupd obtained via magic

// Rhapsody declares this; OPENSTEP doesn't
#ifndef RHAPSODY
extern void xdr_free();
#endif

// Internals of ONHost visible to us
extern NSRecursiveLock *ONHostLookupLock;

// Locally kept state
static int gethostbyname_proc_nr;
static int lookupd_port;

// An almost-threadsafe reimplementation of NeXT's lookupd RPC

#define REQUEST_BUF_SIZE (MAX_LOOKUP_NAMELEN + 4)
#define REPLY_BUF_SIZE MAX_INLINE_UNITS

static BOOL ONHost_lookupdLink(const char *procname, port_t *port, int *handle)
{
    kern_return_t krt;

    // check that lookupd is still alive. This operation not strictly threadsafe but shouldn't ever cause a problem in practice, especially since we're only competing with non-ONHost lookups.
    if (!_lu_running())
        return NO;

    if (gethostbyname_proc_nr <= 0 || lookupd_port != _lu_port) {
        lookupd_port = _lu_port;
        // lookup_link is MiG-generated
        krt = _lookup_link(lookupd_port, (char *)procname,
                           &gethostbyname_proc_nr);
        if (krt != KERN_SUCCESS) {
            gethostbyname_proc_nr = 0;
            return NO;
        }
    }

    *port = lookupd_port;
    *handle = gethostbyname_proc_nr;

    return YES;
}

static _lu_hostent_ptr ONHost_lookupdGethostbyname(port_t port, int handle, const char *hostname)
{
    XDR xdr;
    char request_buf[REQUEST_BUF_SIZE];
    char reply_buf[REPLY_BUF_SIZE];
    int reply_buf_size;
    int lu_errno;
    _lu_hostent_ptr reply;

    xdrmem_create(&xdr, request_buf, REQUEST_BUF_SIZE, XDR_ENCODE);
    // xdr__lu_string is rpcgen-generated
    if (!xdr__lu_string(&xdr, &hostname))
        return NULL;

    reply_buf_size = REPLY_BUF_SIZE;
    // perform the actual lookup
    if (_lookup_one(port, handle, (void *)request_buf, XDR_GETPOS(&xdr), (void *)reply_buf, &reply_buf_size) != KERN_SUCCESS)
        return NULL;

    xdrmem_create(&xdr, reply_buf, reply_buf_size * sizeof(unit), XDR_DECODE);
    reply = NULL;
    if (!xdr__lu_hostent_ptr(&xdr, &reply) || !xdr_int(&xdr, &lu_errno))
        return NULL;

    if (reply == NULL)
        [ONHost _raiseExceptionForHostErrorNumber:lu_errno hostname:[NSString stringWithCString:hostname]];

    return reply;
}
    
@implementation ONHost (LookupdHack)

- (void)_lookupHostInfoByLookupDaemon;
{
    port_t localLookupdPort;
    int localGetHostByNameHandle;
    _lu_hostent_ptr hostEntryPointer;
    unsigned int addressIndex;
    BOOL successfulLookupdLink;

    [ONHostLookupLock lock];
    successfulLookupdLink = ONHost_lookupdLink("gethostbyname", &localLookupdPort, &localGetHostByNameHandle);
    [ONHostLookupLock unlock];

    if (!successfulLookupdLink) {
        [self _lookupHostInfoByPipe];
        return;
    }

    hostEntryPointer = ONHost_lookupdGethostbyname(localLookupdPort, localGetHostByNameHandle, [hostname cString]);

    if (!hostEntryPointer) {
        [self _lookupHostInfoByPipe];
        return;
    }

    for (addressIndex = 0; addressIndex < hostEntryPointer->h_addrs.h_addrs_len; addressIndex++) {
        struct in_addr ipAddress;

        ipAddress.s_addr = hostEntryPointer->h_addrs.h_addrs_val[addressIndex];
        [addresses addObject:[ONHostAddress hostAddressWithInternetAddress:&ipAddress]];
    }

    // Someday we should make use of the alias information returned in h_names

    xdr_free(xdr__lu_hostent_ptr, &hostEntryPointer);
}

@end

#endif // ONHOST_USE_THREADSAFE_LOOKUPD_GETHOSTBYNAME
