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

#ifndef lint 
static char copyright[] = "@(#) Copyright (c) 1995 Regents of the University of Michigan.\nAll rights reserved.\n";
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Memory.h>
#include "ldap-int.h"


int
nsldapi_connect_to_host( LDAP *ld, Sockbuf *sb, char *host, unsigned long address,
	int port, int async, int secure )
/*
 * if host == NULL, connect using address
 * "address" and "port" must be in network byte order
 * zero is returned upon success, -1 if fatal error, -2 EINPROGRESS
 * if -1 is returned, ld_errno is set
 * async is only used ifndef NO_REFERRALS (non-0 means don't wait for connect)
 * XXX async is not used yet!
 */
{
	void			*tcps;
	short 			i;
	int				err;
#ifdef SUPPORT_OPENTRANSPORT
    InetHostInfo	hi;
#else /* SUPPORT_OPENTRANSPORT */
    struct hostInfo	hi;
#endif /* SUPPORT_OPENTRANSPORT */

	LDAPDebug( LDAP_DEBUG_TRACE, "connect_to_host: %s:%d\n",
	    ( host == NULL ) ? "(by address)" : host, ntohs( port ), 0 );

	/* Initialize OpenTransport, or find out from the host app whether it is installed */
	(void)tcp_init();
	
	if ( host != NULL && gethostinfobyname( host, &hi ) != noErr ) {
		LDAP_SET_LDERRNO( ld, LDAP_CONNECT_ERROR, NULL, NULL );
		return( -1 );
	}

	if ( ld->ld_socket_fn == NULL ) {
		tcps = tcpopen( NULL, TCP_BUFSIZ );
	} else {
		tcps = ld->ld_socket_fn( AF_INET, SOCK_STREAM, 0 );
	}

	if ( tcps == NULL ) {
		LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL, NULL );
		return( -1 );
	}

	if ( secure && ld->ld_ssl_enable_fn( tcps ) < 0 ) {
		if ( ld->ld_close_fn == NULL ) {
			tcpclose( (tcpstream *)tcps );
		} else {
			ld->ld_close_fn( tcps );
		}
		LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL, NULL );
		return( -1 );
	}

#ifdef SUPPORT_OPENTRANSPORT
    for ( i = 0; host == NULL || hi.addrs[ i ] != 0; ++i ) {
    	if ( host != NULL ) {
			SAFEMEMCPY( (char *)&address, (char *)&hi.addrs[ i ], sizeof( long ));
		}
#else /* SUPPORT_OPENTRANSPORT */
    for ( i = 0; host == NULL || hi.addr[ i ] != 0; ++i ) {
    	if ( host != NULL ) {
			SAFEMEMCPY( (char *)&address, (char *)&hi.addr[ i ], sizeof( long ));
		}
#endif /* SUPPORT_OPENTRANSPORT */


		if ( ld->ld_connect_fn == NULL ) {
			if ( tcpconnect( tcps, address, port ) == 0 ) {
				err = -1;
			} else {
				err = 0;
			}
		} else {
			struct sockaddr_in	sin;
			(void)memset( (char *)&sin, 0, sizeof( struct sockaddr_in ));
			sin.sin_family = AF_INET;
			sin.sin_port = port;
			sin.sin_addr.s_addr = address;

			err = ld->ld_connect_fn( tcps, (struct sockaddr *)&sin,
				sizeof( struct sockaddr_in ));
		}

		if ( err == 0 ) {
			sb->sb_sd = (void *)tcps;
			return( 0 );
		}

		if ( host == NULL ) {	/* using single address -- not hi.addrs array */
			break;
		}
	}
	
	LDAPDebug( LDAP_DEBUG_TRACE, "tcpconnect failed\n", 0, 0, 0 );
	LDAP_SET_LDERRNO( ld, LDAP_CONNECT_ERROR, NULL, NULL );
	LDAP_SET_ERRNO( ld, EHOSTUNREACH );  /* close enough */

	if ( ld->ld_close_fn == NULL ) {
		tcpclose( (tcpstream *)tcps );
	} else {
		ld->ld_close_fn( tcps );
	}
	return( -1 );
}


void
nsldapi_close_connection( LDAP *ld, Sockbuf *sb )
{
	if ( ld->ld_close_fn == NULL ) {
		tcpclose( (tcpstream *)sb->sb_sd );
	} else {
		ld->ld_close_fn( sb->sb_sd );
	}
}


#ifdef KERBEROS
char *
host_connected_to( Sockbuf *sb )
{
	ip_addr addr;
	
#ifdef SUPPORT_OPENTRANSPORT
    InetHostInfo	hi;
#else /* SUPPORT_OPENTRANSPORT */
    struct hostInfo	hi;
#endif /* SUPPORT_OPENTRANSPORT */

	if ( tcpgetpeername( (tcpstream *)sb->sb_sd, &addr, NULL ) != noErr ) {
		return( NULL );
	}

#ifdef SUPPORT_OPENTRANSPORT
	if ( gethostinfobyaddr( addr, &hi ) == noErr ) {
		return( strdup( hi.name ));
	}
#else /* SUPPORT_OPENTRANSPORT */
	if ( gethostinfobyaddr( addr, &hi ) == noErr ) {
		return( strdup( hi.cname ));
	}
#endif /* SUPPORT_OPENTRANSPORT */

	return( NULL );
}
#endif /* KERBEROS */


struct tcpstreaminfo {
	struct tcpstream	*tcpsi_stream;
	Boolean				tcpsi_check_read;
	Boolean				tcpsi_is_read_ready;
	Boolean				tcpsi_check_write;
	Boolean				tcpsi_is_write_ready;
};

/* for BSD-compatible I/O functions */
struct stdselectinfo {
	fd_set	ssi_readfds;
	fd_set	ssi_writefds;
	fd_set	ssi_use_readfds;
	fd_set	ssi_use_writefds;
};

struct selectinfo {
	short					si_count;
	struct tcpstreaminfo	*si_streaminfo;
	struct stdselectinfo	si_stdinfo;
};


void
nsldapi_mark_select_write( LDAP *ld, Sockbuf *sb )
{
	struct selectinfo	*sip;
	struct tcpstreaminfo	*tcpsip;
	short			i;

	LDAPDebug( LDAP_DEBUG_TRACE, "mark_select_write: stream %x\n",
	    (tcpstream *)sb->sb_sd, 0, 0 );

	if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
		return;
	}

	if ( ld->ld_select_fn != NULL ) {
		if ( !FD_ISSET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_writefds )) {
		    FD_SET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_writefds );
		}
		return;
	}

	for ( i = 0; i < sip->si_count; ++i ) {    /* make sure stream is not already in the list... */
		if ( sip->si_streaminfo[ i ].tcpsi_stream == (tcpstream *)sb->sb_sd) {
			sip->si_streaminfo[ i ].tcpsi_check_write = true;
			sip->si_streaminfo[ i ].tcpsi_is_write_ready = false;
			return;
		}
	}

	/* add a new stream element to our array... */
	if ( sip->si_count <= 0 ) {
		tcpsip = (struct tcpstreaminfo *)malloc( sizeof( struct tcpstreaminfo ));
	} else {
		tcpsip = (struct tcpstreaminfo *)realloc( sip->si_streaminfo,
		    ( sip->si_count + 1 ) * sizeof( struct tcpstreaminfo ));
	}

	if ( tcpsip != NULL ) {
		tcpsip[ sip->si_count ].tcpsi_stream = (tcpstream *)sb->sb_sd;
		tcpsip[ sip->si_count ].tcpsi_check_write = true;
		tcpsip[ sip->si_count ].tcpsi_is_write_ready = false;
		sip->si_streaminfo = tcpsip;
		++sip->si_count;
	}
}


void
nsldapi_mark_select_read( LDAP *ld, Sockbuf *sb )
{
	struct selectinfo		*sip;
	struct tcpstreaminfo	*tcpsip;
	short					i;
	
	LDAPDebug( LDAP_DEBUG_TRACE, "mark_select_read: stream %x\n", (tcpstream *)sb->sb_sd, 0, 0 );

	if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
		return;
	}

	if ( ld->ld_select_fn != NULL ) {		
		if ( !FD_ISSET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_readfds )) {
			FD_SET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_readfds );
		}
		return;
	}

	for ( i = 0; i < sip->si_count; ++i ) {	/* make sure stream is not already in the list... */
		if ( sip->si_streaminfo[ i ].tcpsi_stream == (tcpstream *)sb->sb_sd ) {
			sip->si_streaminfo[ i ].tcpsi_check_read = true;
			sip->si_streaminfo[ i ].tcpsi_is_read_ready = false;
            sip->si_streaminfo[ i ].tcpsi_check_write = true;
            sip->si_streaminfo[ i ].tcpsi_is_write_ready = false;
			return;
		}
	}

	/* add a new stream element to our array... */
	if ( sip->si_count <= 0 ) {
		tcpsip = (struct tcpstreaminfo *)malloc( sizeof( struct tcpstreaminfo ));
	} else {
		tcpsip = (struct tcpstreaminfo *)realloc( sip->si_streaminfo,
				( sip->si_count + 1 ) * sizeof( struct tcpstreaminfo ));
	}
	
	if ( tcpsip != NULL ) {
		tcpsip[ sip->si_count ].tcpsi_stream = (tcpstream *)sb->sb_sd;
		tcpsip[ sip->si_count ].tcpsi_check_read = true;
		tcpsip[ sip->si_count ].tcpsi_is_read_ready = false;
		tcpsip[ sip->si_count ].tcpsi_check_write = true;
		tcpsip[ sip->si_count ].tcpsi_is_write_ready = false;
		sip->si_streaminfo = tcpsip;
		++sip->si_count;
	}
}


void
nsldapi_mark_select_clear( LDAP *ld, Sockbuf *sb )
{
	struct selectinfo	*sip;
	short				i;

	LDAPDebug( LDAP_DEBUG_TRACE, "mark_select_clear: stream %x\n", (tcpstream *)sb->sb_sd, 0, 0 );

	if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
		return;
	}

	if ( ld->ld_select_fn != NULL ) {
		FD_CLR( (int)sb->sb_sd, &sip->si_stdinfo.ssi_writefds );
		FD_CLR( (int)sb->sb_sd, &sip->si_stdinfo.ssi_readfds );

	} else if ( sip->si_count > 0 && sip->si_streaminfo != NULL ) {
		for ( i = 0; i < sip->si_count; ++i ) {
			if ( sip->si_streaminfo[ i ].tcpsi_stream == (tcpstream *)sb->sb_sd ) {
				break;
			}
		}
		if ( i < sip->si_count ) {
			--sip->si_count;
			for ( ; i < sip->si_count; ++i ) {
				sip->si_streaminfo[ i ] = sip->si_streaminfo[ i + 1 ];
			}
			/* we don't bother to use realloc to make the si_streaminfo array smaller */
		}
	}
}


int
nsldapi_is_write_ready( LDAP *ld, Sockbuf *sb )
{
    struct selectinfo    *sip;
    short                i;
    
    if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
        return( 0 );    /* punt */
    }

    if ( ld->ld_select_fn != NULL ) {
        return( FD_ISSET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_use_writefds));
    }

    if ( sip->si_count > 0 && sip->si_streaminfo != NULL ) {
        for ( i = 0; i < sip->si_count; ++i ) {
            if ( sip->si_streaminfo[ i ].tcpsi_stream == (tcpstream *)sb->sb_sd ) {
#ifdef LDAP_DEBUG
                if ( sip->si_streaminfo[ i ].tcpsi_is_write_ready ) {
                    LDAPDebug( LDAP_DEBUG_TRACE, "is_write_ready: stream %x READY\n",
                            (tcpstream *)sb->sb_sd, 0, 0 );
                } else {
                    LDAPDebug( LDAP_DEBUG_TRACE, "is_write_ready: stream %x Not Ready\n",
                            (tcpstream *)sb->sb_sd, 0, 0 );
                }
#endif /* LDAP_DEBUG */
                return( sip->si_streaminfo[ i ].tcpsi_is_write_ready ? 1 : 0 );
            }
        }
    }

    LDAPDebug( LDAP_DEBUG_TRACE, "is_write_ready: stream %x: NOT FOUND\n", (tcpstream *)sb->sb_sd, 0, 0 );
    return( 0 );
}


int
nsldapi_is_read_ready( LDAP *ld, Sockbuf *sb )
{
	struct selectinfo	*sip;
	short				i;
	
	if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
		return( 0 );	/* punt */
	}

	if ( ld->ld_select_fn != NULL ) {
		return( FD_ISSET( (int)sb->sb_sd, &sip->si_stdinfo.ssi_use_readfds ));
	}

	if ( sip->si_count > 0 && sip->si_streaminfo != NULL ) {
		for ( i = 0; i < sip->si_count; ++i ) {
			if ( sip->si_streaminfo[ i ].tcpsi_stream == (tcpstream *)sb->sb_sd ) {
#ifdef LDAP_DEBUG
				if ( sip->si_streaminfo[ i ].tcpsi_is_read_ready ) {
					LDAPDebug( LDAP_DEBUG_TRACE, "is_read_ready: stream %x READY\n",
							(tcpstream *)sb->sb_sd, 0, 0 );
				} else {
					LDAPDebug( LDAP_DEBUG_TRACE, "is_read_ready: stream %x Not Ready\n",
							(tcpstream *)sb->sb_sd, 0, 0 );
				}
#endif /* LDAP_DEBUG */
				return( sip->si_streaminfo[ i ].tcpsi_is_read_ready ? 1 : 0 );
			}
		}
	}

	LDAPDebug( LDAP_DEBUG_TRACE, "is_read_ready: stream %x: NOT FOUND\n", (tcpstream *)sb->sb_sd, 0, 0 );
	return( 0 );
}


void *
nsldapi_new_select_info()
{
	struct selectinfo	*sip;
		
	if (( sip = (struct selectinfo *)calloc( 1, sizeof( struct selectinfo ))) != NULL ) {
		FD_ZERO( &sip->si_stdinfo.ssi_readfds );
		FD_ZERO( &sip->si_stdinfo.ssi_writefds );
	}

	return( (void *)sip );
}


void
nsldapi_free_select_info( void *sip )
{
	if ( sip != NULL ) {
		free( sip );
	}
}


int
nsldapi_do_ldap_select( LDAP *ld, struct timeval *timeout )
{
	struct selectinfo	*sip;
	Boolean				ready, gotselecterr;
	unsigned long		ticks, endticks;
	short				i, err;
	static int			tblsize = 0;

	LDAPDebug( LDAP_DEBUG_TRACE, "do_ldap_select\n", 0, 0, 0 );

	if (( sip = (struct selectinfo *)ld->ld_selectinfo ) == NULL ) {
		return( -1 );
	}

	if ( ld->ld_select_fn != NULL ) {
		if ( tblsize == 0 ) {
			tblsize = FD_SETSIZE - 1;
		}

		sip->si_stdinfo.ssi_use_readfds = sip->si_stdinfo.ssi_readfds;
		sip->si_stdinfo.ssi_use_writefds = sip->si_stdinfo.ssi_writefds;

		return( ld->ld_select_fn( tblsize, &sip->si_stdinfo.ssi_use_readfds,
		    &sip->si_stdinfo.ssi_use_writefds, NULL, timeout ));
	}

	if ( sip->si_count == 0 ) {
		return( 1 );
	}

	if ( timeout != NULL ) {
		endticks = 60 * timeout->tv_sec + ( 60 * timeout->tv_usec ) / 1000000 + TickCount();
	}

	for ( i = 0; i < sip->si_count; ++i ) {
		if ( sip->si_streaminfo[ i ].tcpsi_check_read ) {
			sip->si_streaminfo[ i ].tcpsi_is_read_ready = false;
		}
        if ( sip->si_streaminfo[ i ].tcpsi_check_write ) {
            sip->si_streaminfo[ i ].tcpsi_is_write_ready = false;
        }
	}

	ready = gotselecterr = false;
	do {
		for ( i = 0; i < sip->si_count; ++i ) {
			if ( sip->si_streaminfo[ i ].tcpsi_check_read && !sip->si_streaminfo[ i ].tcpsi_is_read_ready ) {
				if (( err = tcpreadready( sip->si_streaminfo[ i ].tcpsi_stream )) > 0 ) {
					sip->si_streaminfo[ i ].tcpsi_is_read_ready = ready = true;
                } else if ( err < 0 ) {
                    gotselecterr = true;
                }
            }
			if ( sip->si_streaminfo[ i ].tcpsi_check_write && !sip->si_streaminfo[ i ].tcpsi_is_write_ready ) {
					if (( err = tcpwriteready( sip->si_streaminfo[ i ].tcpsi_stream )) > 0 ) {
							sip->si_streaminfo[ i ].tcpsi_is_write_ready = ready = true;
					} else if ( err < 0 ) {
							gotselecterr = true;
					}
			}
		}

		if ( !ready && !gotselecterr ) {
			Delay( 2L, &ticks );
			SystemTask();
		}
	} while ( !ready && !gotselecterr && ( timeout == NULL || ticks < endticks ));

	LDAPDebug( LDAP_DEBUG_TRACE, "do_ldap_select returns %d\n", ready ? 1 : ( gotselecterr ? -1 : 0 ), 0, 0 );
	return( ready ? 1 : ( gotselecterr ? -1 : 0 ));
}
