/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include <errno.h>

#define OTUNIXERRORS 1		/* We want OpenTransport error codes */
#include <OpenTransport.h>
#include <OpenTptInternet.h>	// All the internet typedefs

#include "macsock.h" /* from macsock library */
#include "primpl.h"

void _MD_InitNetAccess()
{
}

static void macsock_map_error(int err)
{
	switch (err) {
		case EBADF:
			PR_SetError(PR_BAD_DESCRIPTOR_ERROR, err);
			break;
		case EADDRNOTAVAIL:
			PR_SetError(PR_ADDRESS_NOT_AVAILABLE_ERROR, err);
			break;
		case EINPROGRESS:
			PR_SetError(PR_IN_PROGRESS_ERROR, err);
			break;
		case EWOULDBLOCK:
			PR_SetError(PR_WOULD_BLOCK_ERROR, err);
			break;
		case ENOTSOCK:
			PR_SetError(PR_NOT_SOCKET_ERROR, err);
			break;
		case EAFNOSUPPORT:
			PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, err);
			break;
		case ETIMEDOUT:
			PR_SetError(PR_IO_TIMEOUT_ERROR, err);
			break;
		case ECONNREFUSED:
			PR_SetError(PR_CONNECT_REFUSED_ERROR, err);
			break;
		case ENETUNREACH:
			PR_SetError(PR_NETWORK_UNREACHABLE_ERROR, err);
			break;
		case EADDRINUSE:
			PR_SetError(PR_ADDRESS_IN_USE_ERROR, err);
			break;
		case EFAULT:
			PR_SetError(PR_ACCESS_FAULT_ERROR, err);
			break;
		/*
		 * UNIX domain sockets are not supported in NSPR
		 */
		case EACCES:
			PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, err);
			break;
		case EINTR:
			PR_SetError(PR_PENDING_INTERRUPT_ERROR, err);
			break;
		case EINVAL:
			PR_SetError(PR_INVALID_ARGUMENT_ERROR, err);
			break;
		case EIO:
			PR_SetError(PR_IO_ERROR, err);
			break;
		case ENOENT:
			PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, err);
			break;
		case ENXIO:
			PR_SetError(PR_IO_ERROR, err);
			break;
		case EPROTOTYPE:
			PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, err);
			break;
		default:
			PR_SetError(PR_UNKNOWN_ERROR, err);
			break;
	}
}

// Errors returned:
// ENETDOWN - no MacTCP driver
// EPROTONOSUPPORT - bad socket type/protocol
// ENOBUFS - not enough space for another socket, or failure in socket creation routine
PRInt32 _MD_socket(int domain, int type, int protocol)
{
	int	err;
	
	err = macsock_socket(domain, type, protocol);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

// Errors:
// EBADF -- bad socket id
// EFAULT -- bad address format
PRInt32 _MD_bind(PRFileDesc *fd, PRNetAddr *addr, PRUint32 addrlen)
{
	int	err;
	int sID = fd->secret->md.osfd;
	
	err = macsock_bind(sID, (const struct sockaddr *)addr, addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}	

// Errors:
// EBADF -- bad socket id
// EOPNOTSUPP -- socket is already connected, and closing
// EISCONN -- already connected
// EINPROGRESS -- connecting right now
PRInt32 _MD_listen(PRFileDesc *fd, PRIntn backlog)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_listen(sID, backlog);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}	

// Errors:
// EBADF -- bad socket id
PRInt32 _MD_getsockname(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *addrlen)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_getsockname(sID, (struct sockaddr *)addr, (int *)addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}	

// Errors: 
// EBADF - bad socket id
// ENOPROTOOPT - The option is unknown
PRStatus _MD_getsockopt(PRFileDesc *fd, PRInt32 level, PRInt32 optname, char* optval, PRInt32* optlen)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_getsockopt(sID, level, optname, optval, optlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return PR_FAILURE;
	 }
	 
	return PR_SUCCESS;
}

// Errors:
// EBADF - bad socket id
// ENOTCONN - socket hasnŐt been properly created 
PRStatus _MD_setsockopt(PRFileDesc *fd, PRInt32 level, PRInt32 optname, const char* optval, PRInt32 optlen)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_setsockopt(sID, level, optname, optval, optlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return PR_FAILURE;
	 }
	 
	return PR_SUCCESS;
}

PRInt32 _MD_socketavailable(PRFileDesc *fd, size_t *bytes)
{
	int	err;
	int sID = fd->secret->md.osfd;

	// Careful of the return value here.  0 => failure, 1 => success
	err = macsock_socketavailable(sID, bytes);
	if (err == 0) {
		_PR_MD_CURRENT_THREAD()->md.osErrCode = -1;
	    return -1;
	 }
	 
	return 0;
}

PRInt32 _MD_accept(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *addrlen, PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_accept(sID, (struct sockaddr *)addr, (int *)addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

// Connect
// check the arguments validity
// issue the connect call to the stream
// Errors:
//	EBADF -- bad socket id, bad MacTCP stream
//	EAFNOSUPPORT -- bad address format
//	EADDRINUSE -- we are listening, or duplicate socket
//  EINPROGRESS -- we are connecting right now
//  EISCONN -- already connected
//  ECONNREFUSED -- other side has closed, or open has failed
//  EALREADY -- we are connected
//  EINTR -- user interrupted
PRInt32 _MD_connect(PRFileDesc *fd, PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_connect(sID, (struct sockaddr *)addr, addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

// Errors:
// EBADF - bad socket ID
// ENOTCONN - no such connection
PRInt32 _MD_recv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_recv(sID, buf, amount, flags);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

PRInt32 _MD_send(PRFileDesc *fd,const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_send(sID, buf, amount, flags);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

PRInt32 _MD_recvfrom(PRFileDesc *fd, void *buf, PRInt32 amount, 
                               PRIntn flags, PRNetAddr *addr, PRUint32 *addrlen,
                               PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_recvfrom(sID, buf, amount, flags, (struct sockaddr *)addr, (int *)addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

                               
PRInt32 _MD_sendto(PRFileDesc *fd,const void *buf, PRInt32 amount, 
                               PRIntn flags, PRNetAddr *addr, PRUint32 addrlen,
                               PRIntervalTime timeout)
{
#pragma unused (timeout)

	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_sendto(sID, buf, amount, flags, (struct sockaddr *)addr, addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

// Errors:
// EBADF -- bad socket id
PRInt32 _MD_closesocket(PRInt32 osfd)
{
	int	err;
	int sID = osfd;

	err = macsock_close(sID);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}

PRInt32 _MD_writev(PRFileDesc *fd, struct PRIOVec *iov, PRInt32 iov_size, PRIntervalTime timeout)
{
#pragma unused (fd, iov, iov_size, timeout)

	PR_ASSERT(0);
	_PR_MD_CURRENT_THREAD()->md.osErrCode = unimpErr;
    return -1;
}                               

PRInt32 _MD_shutdown(PRFileDesc *fd, PRIntn how)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_shutdown(sID, how);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 
	return err;
}                               

PRStatus _MD_getpeername(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *addrlen)
{
	int	err;
	int sID = fd->secret->md.osfd;

	err = macsock_getpeername(sID, (struct sockaddr *)addr, (int *)addrlen);
	if (err == -1) {
		macsock_map_error(errno);
	    return PR_FAILURE;
	 }
	 
	return PR_SUCCESS;
}

void _MD_makenonblock(PRFileDesc *fd)
{
	int	err;
	int sID = fd->secret->md.osfd;
	int optval =1;

	err = macsock_setsockopt(sID, SOL_SOCKET, FIONBIO, (const void *)&optval, 0);
	if (err == -1) {
		macsock_map_error(errno);
	 }
}                               

struct hostent *gethostbyname(const char * name)
{
	return macsock_gethostbyname((char *)name);
}

struct hostent *gethostbyaddr(const void *addr, int addrlen, int type)
{
	return macsock_gethostbyaddr(addr, addrlen, type);
}

PRStatus _MD_gethostname(char *name, int namelen)
{
	int	err;

	err = macsock_gethostname(name, namelen);
	if (err != noErr) {
		macsock_map_error(err);
	    return PR_FAILURE;
	 }
	 
	return PR_SUCCESS;
}

#define kIPName		"ip"
static struct protoent sIPProto = {kIPName, NULL, INET_IP};
static struct protoent sTCPProto = {kTCPName, NULL, INET_TCP};
static struct protoent sUDPProto = {kUDPName, NULL, INET_UDP};

struct protoent *getprotobyname(const char * name)
{
    if (strcmp(name, kIPName) == 0)
    	return (&sIPProto);
    	
    if (strcmp(name, kTCPName) == 0)
    	return (&sTCPProto);
    	
    if (strcmp(name, kUDPName) == 0)
    	return (&sUDPProto);
    	
ErrorExit:
	_PR_MD_CURRENT_THREAD()->md.osErrCode = paramErr;
    return NULL;
}


struct protoent *getprotobynumber(int number)
{
    if (number == INET_IP)
    	return (&sIPProto);
    	
    if (number == INET_TCP)
    	return (&sTCPProto);
    	
    if (number == INET_UDP)
    	return (&sUDPProto);
    	
ErrorExit:
	_PR_MD_CURRENT_THREAD()->md.osErrCode = paramErr;
    return NULL;
}

PRInt32 _MD_poll(PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
{
    PRInt32 osfd;
	fd_set rd, wt, ex;
    PRFileDesc *bottom;
    PRPollDesc *pd, *epd;
    PRInt32 ready, maxfd = -1;
    PRInt16 in_flags, out_flags;
	struct timeval tv, *tvp = NULL;

	FD_ZERO(&rd);
	FD_ZERO(&wt);
	FD_ZERO(&ex);

    for (pd = pds, epd = pd + npds; pd < epd; pd++)
    {
        if (NULL == pd->fd) continue;

        in_flags = (bottom->methods->poll)(pd->fd, pd->in_flags, &pd->out_flags);
        if (0 != (in_flags & pd->out_flags)) ready += 1;
        else
        {
            bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);
            PR_ASSERT(NULL != bottom);
            osfd = bottom->secret->md.osfd;

		    if (osfd > maxfd) maxfd = osfd;
		    if (in_flags & PR_POLL_READ) FD_SET(osfd, &rd);
		    if (in_flags & PR_POLL_WRITE) FD_SET(osfd, &wt);
		    if (in_flags & PR_POLL_EXCEPT) FD_SET(osfd, &ex);
		}
    }

	if (timeout != PR_INTERVAL_NO_TIMEOUT)
	{
        PRInt32 ticksPerSecond = PR_TicksPerSecond();
        tv.tv_sec = timeout / ticksPerSecond;
        tv.tv_usec = timeout - (ticksPerSecond * tv.tv_sec);
        tv.tv_usec = (PR_USEC_PER_SEC * tv.tv_usec) / ticksPerSecond;
        tvp = &tv;
 	}
	
	ready = select(maxfd + 1, &rd, &wt, &ex, tvp);
	
	if (ready > 0)
	{
		ready = 0;
		for (pd = pds, epd = pd + npds; pd < epd; pd++)
		{
			out_flags = 0;
			in_flags = pd->in_flags;

			if ((NULL == pd->fd) || (0 == in_flags)) continue;
			bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);
			osfd = bottom->secret->md.osfd;

            if (FD_ISSET(osfd, &ex)) out_flags |= PR_POLL_EXCEPT;
            else if (FD_ISSET(osfd, &rd) || FD_ISSET(osfd, &wt))
                out_flags = pd->in_flags & (PR_POLL_READ | PR_POLL_WRITE);
            pd->out_flags = out_flags;
            if (out_flags) ready++;
		}
		/* 
		Can't do this assert because MacSock returns write fds even if we did not
		set it in the original write fd set.
		*/
		/*PR_ASSERT(ready > 0);*/
	}
	else PR_ASSERT(ready == 0);

	return ready;
}

int _MD_mac_get_nonblocking_connect_error(int osfd)
{
	int	err;

	err = macsock_getconnectstatus (osfd);
	if (err == -1) {
		macsock_map_error(errno);
	    return -1;
	 }
	 	 
	return err;
}

