/*
 * Functions that emulate PR_AcceptRead and PR_TransmitFile for SSL sockets.
 * Each Layered NSPR protocol (like SSL) must unfortunately contain its 
 * own implementation of these functions.  This code was taken from NSPR.
 *
 * The contents of this file are subject to the Mozilla 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/MPL/
 * 
 * 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 the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 *
 * $Id: emulate.c,v 1.2 2000/05/18 01:32:53 nelsonb%netscape.com Exp $
 */

#include "nspr.h"

#if defined( XP_UNIX )
#include <fcntl.h>
#endif
#if defined(WIN32)
#include <windef.h>
#include <winbase.h>
#endif
#include <string.h>

#define AMASK 7	/* mask for alignment of PRNetAddr */

/*
 * _PR_EmulateAcceptRead
 *
 *  Accept an incoming connection on sd, set *nd to point to the
 *  newly accepted socket, read 'amount' bytes from the accepted
 *  socket.
 *
 *  buf is a buffer of length = amount + (2 * sizeof(PRNetAddr)) + 32
 *  *raddr points to the PRNetAddr of the accepted connection upon
 *  return
 *
 *  return number of bytes read or -1 on error
 *
 */
PRInt32 
ssl_EmulateAcceptRead(	PRFileDesc *   sd, 
			PRFileDesc **  nd,
			PRNetAddr **   raddr, 
			void *         buf, 
			PRInt32        amount, 
			PRIntervalTime timeout)
{
    PRFileDesc *   newsockfd;
    PRInt32        rv;
    PRNetAddr      remote;

    if (!(newsockfd = PR_Accept(sd, &remote, PR_INTERVAL_NO_TIMEOUT))) {
	return -1;
    }

    rv = PR_Recv(newsockfd, buf, amount, 0, timeout);
    if (rv >= 0) {
	ptrdiff_t pNetAddr = (((ptrdiff_t)buf) + amount + AMASK) & ~AMASK;

	*nd = newsockfd;
	*raddr = (PRNetAddr *)pNetAddr;
	memcpy((void *)pNetAddr, &remote, sizeof(PRNetAddr));
	return rv;
    }

    PR_Close(newsockfd);
    return -1;
}


#if !defined( XP_UNIX ) && !defined( WIN32 )
/*
 * _PR_EmulateTransmitFile
 *
 *  Send file fd across socket sd. If headers is non-NULL, 'hlen'
 *  bytes of headers is sent before sending the file.
 *
 *  PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *  return number of bytes sent or -1 on error
 *
 */
#define _TRANSMITFILE_BUFSIZE	(16 * 1024)

PRInt32 
ssl_EmulateTransmitFile(    PRFileDesc *        sd, 
			    PRFileDesc *        fd,
			    const void *        headers, 
			    PRInt32             hlen, 
			    PRTransmitFileFlags flags,
			    PRIntervalTime      timeout)
{
    char *  buf 	= NULL;
    PRInt32 count 	= 0;
    PRInt32 rlen;
    PRInt32 rv;

    buf = PR_MALLOC(_TRANSMITFILE_BUFSIZE);
    if (buf == NULL) {
	PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
	return -1;
    }

    /*
     * send headers, first
     */
    while (hlen) {
	rv =  PR_Send(sd, headers, hlen, 0, timeout);
	if (rv < 0) {
	    /* PR_Send() has invoked PR_SetError(). */
	    rv = -1;
	    goto done;
	} 
	count += rv;
	headers = (const void*) ((const char*)headers + rv);
	hlen -= rv;
    }
    /*
     * send file, next
     */
    while ((rlen = PR_Read(fd, buf, _TRANSMITFILE_BUFSIZE)) > 0) {
	while (rlen) {
	    char *bufptr = buf;

	    rv =  PR_Send(sd, bufptr, rlen,0,PR_INTERVAL_NO_TIMEOUT);
	    if (rv < 0) {
		/* PR_Send() has invoked PR_SetError(). */
		rv = -1;
		goto done;
	    } 
	    count += rv;
	    bufptr = ((char*)bufptr + rv);
	    rlen -= rv;
	}
    }
    if (rlen == 0) {
	/*
	 * end-of-file
	 */
	if (flags & PR_TRANSMITFILE_CLOSE_SOCKET)
	    PR_Close(sd);
	rv = count;
    } else {
	PR_ASSERT(rlen < 0);
	/* PR_Read() has invoked PR_SetError(). */
	rv = -1;
    }

done:
    if (buf)
	PR_DELETE(buf);
    return rv;
}
#else

#define TRANSMITFILE_MMAP_CHUNK (256 * 1024)

/*
 * _PR_UnixTransmitFile
 *
 *  Send file fd across socket sd. If headers is non-NULL, 'hlen'
 *  bytes of headers is sent before sending the file.
 *
 *  PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *  return number of bytes sent or -1 on error
 *
 */

PRInt32 
ssl_EmulateTransmitFile(    PRFileDesc *        sd, 
			    PRFileDesc *        fd,
			    const void *        headers, 
			    PRInt32             hlen, 
			    PRTransmitFileFlags flags,
			    PRIntervalTime      timeout)
{
    void *            addr;
    PRFileMap *       mapHandle = NULL;
    PRInt32           count     = 0;
    PRInt32           index     = 0;
    PRInt32           len	= 0;
    PRInt32           rv;
    struct PRFileInfo info;
    struct PRIOVec    iov[2];

    /* Get file size */
    if (PR_SUCCESS != PR_GetOpenFileInfo(fd, &info)) {
	count = -1;
	goto done;
    }
    if (hlen) {
	iov[index].iov_base = (char *) headers;
	iov[index].iov_len  = hlen;
	index++;
    }
    if (info.size > 0) {
	mapHandle = PR_CreateFileMap(fd, info.size, PR_PROT_READONLY);
	if (mapHandle == NULL) {
	    count = -1;
	    goto done;
	}
	/*
	 * If the file is large, mmap and send the file in chunks so as
	 * to not consume too much virtual address space
	 */
	len = PR_MIN(info.size , TRANSMITFILE_MMAP_CHUNK );
	/*
	 * Map in (part of) file. Take care of zero-length files.
	 */
	if (len) {
	    addr = PR_MemMap(mapHandle, 0, len);
	    if (addr == NULL) {
		count = -1;
		goto done;
	    }
	}
	iov[index].iov_base = (char*)addr;
	iov[index].iov_len = len;
	index++;
    }
    if (!index)
    	goto done;
    rv = PR_Writev(sd, iov, index, timeout);
    if (len) {
	PR_MemUnmap(addr, len);
    }
    if (rv >= 0) {
	PR_ASSERT(rv == hlen + len);
	info.size -= len;
	count     += rv;
    } else {
	count = -1;
	goto done;
    }
    /*
     * send remaining bytes of the file, if any
     */
    len = PR_MIN(info.size , TRANSMITFILE_MMAP_CHUNK );
    while (len > 0) {
	/*
	 * Map in (part of) file
	 */
	PR_ASSERT((count - hlen) % TRANSMITFILE_MMAP_CHUNK == 0);
	addr = PR_MemMap(mapHandle, count - hlen, len);
	if (addr == NULL) {
	    count = -1;
	    goto done;
	}
	rv =  PR_Send(sd, addr, len, 0, timeout);
	PR_MemUnmap(addr, len);
	if (rv >= 0) {
	    PR_ASSERT(rv == len);
	    info.size -= rv;
	    count     += rv;
	    len = PR_MIN(info.size , TRANSMITFILE_MMAP_CHUNK );
	} else {
	    count = -1;
	    goto done;
	}
    }
done:
    if ((count >= 0) && (flags & PR_TRANSMITFILE_CLOSE_SOCKET))
	PR_Close(sd);
    if (mapHandle != NULL)
    	PR_CloseFileMap(mapHandle);
    return count;
}
#endif  /* XP_UNIX */




#if !defined( XP_UNIX ) && !defined( WIN32 )
/*
 * _PR_EmulateSendFile
 *
 *  Send file sfd->fd across socket sd. The header and trailer buffers
 *  specified in the 'sfd' argument are sent before and after the file,
 *  respectively.
 *
 *  PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *  return number of bytes sent or -1 on error
 *
 */

PRInt32 
ssl_EmulateSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                    PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    char *       buf = NULL;
    const void * buffer;
    PRInt32      rv;
    PRInt32      count = 0;
    PRInt32      rlen;
    PRInt32      buflen;
    PRInt32      sendbytes;
    PRInt32      readbytes;

#define _SENDFILE_BUFSIZE   (16 * 1024)

    buf = (char*)PR_MALLOC(_SENDFILE_BUFSIZE);
    if (buf == NULL) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return -1;
    }

    /*
     * send header, first
     */
    buflen = sfd->hlen;
    buffer = sfd->header;
    while (buflen) {
        rv =  PR_Send(sd, buffer, buflen, 0, timeout);
        if (rv < 0) {
            /* PR_Send() has invoked PR_SetError(). */
            rv = -1;
            goto done;
        } else {
            count += rv;
            buffer = (const void*) ((const char*)buffer + rv);
            buflen -= rv;
        }
    }
    /*
     * send file, next
     */

    if (PR_Seek(sfd->fd, sfd->file_offset, PR_SEEK_SET) < 0) {
        rv = -1;
        goto done;
    }
    sendbytes = sfd->file_nbytes;
    if (sendbytes == 0) {
        /* send entire file */
        while ((rlen = PR_Read(sfd->fd, buf, _SENDFILE_BUFSIZE)) > 0) {
            while (rlen) {
                char *bufptr = buf;

                rv =  PR_Send(sd, bufptr, rlen, 0, timeout);
                if (rv < 0) {
                    /* PR_Send() has invoked PR_SetError(). */
                    rv = -1;
                    goto done;
                } else {
                    count += rv;
                    bufptr = ((char*)bufptr + rv);
                    rlen -= rv;
                }
            }
        }
        if (rlen < 0) {
            /* PR_Read() has invoked PR_SetError(). */
            rv = -1;
            goto done;
        }
    } else {
        readbytes = PR_MIN(sendbytes, _SENDFILE_BUFSIZE);
        while (readbytes && ((rlen = PR_Read(sfd->fd, buf, readbytes)) > 0)) {
            while (rlen) {
                char *bufptr = buf;

                rv =  PR_Send(sd, bufptr, rlen, 0, timeout);
                if (rv < 0) {
                    /* PR_Send() has invoked PR_SetError(). */
                    rv = -1;
                    goto done;
                } else {
                    count += rv;
                    sendbytes -= rv;
                    bufptr = ((char*)bufptr + rv);
                    rlen -= rv;
                }
            }
	    readbytes = PR_MIN(sendbytes, _SENDFILE_BUFSIZE);
        }
        if (rlen < 0) {
            /* PR_Read() has invoked PR_SetError(). */
            rv = -1;
            goto done;
        } else if (sendbytes != 0) {
            /*
             * there are fewer bytes in file to send than specified
             */
	    PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
            rv = -1;
            goto done;
        }
    }
    /*
     * send trailer, last
     */
    buflen = sfd->tlen;
    buffer = sfd->trailer;
    while (buflen) {
        rv =  PR_Send(sd, buffer, buflen, 0, timeout);
        if (rv < 0) {
            /* PR_Send() has invoked PR_SetError(). */
            rv = -1;
            goto done;
        } else {
            count += rv;
            buffer = (const void*) ((const char*)buffer + rv);
            buflen -= rv;
        }
    }
    rv = count;

done:
    if (buf)
        PR_DELETE(buf);
    if ((rv >= 0) && (flags & PR_TRANSMITFILE_CLOSE_SOCKET))
	PR_Close(sd);
    return rv;
}

#else /* UNIX and NT handled below */

/*
 * _PR_UnixSendFile
 *
 *    Send file sfd->fd across socket sd. If header/trailer are specified
 *    they are sent before and after the file, respectively.
 *
 *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *    return number of bytes sent or -1 on error
 *
 */
#define SENDFILE_MMAP_CHUNK    (256 * 1024)

PRInt32 
ssl_EmulateSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                    PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    void *            addr;
    PRFileMap *       mapHandle  	= NULL;
    PRInt32           count 		= 0;
    PRInt32           file_bytes;
    PRInt32           index 		= 0;
    PRInt32           len;
    PRInt32           rv;
    PRUint32          addr_offset;
    PRUint32          file_mmap_offset;
    PRUint32          mmap_len;
    PRUint32          pagesize;
    struct PRFileInfo info;
    struct PRIOVec    iov[3];

    /* Get file size */
    if (PR_SUCCESS != PR_GetOpenFileInfo(sfd->fd, &info)) {
	count = -1;
	goto done;
    }
    if (sfd->file_nbytes && 
		(info.size < (sfd->file_offset + sfd->file_nbytes))) {
        /*
         * there are fewer bytes in file to send than specified
         */
	PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        count = -1;
        goto done;
    }
    if (sfd->file_nbytes)
        file_bytes = sfd->file_nbytes;
    else
        file_bytes = info.size - sfd->file_offset;

#if defined(WIN32)
    {
        SYSTEM_INFO sysinfo;
        GetSystemInfo(&sysinfo);
        pagesize = sysinfo.dwAllocationGranularity;
    }
#else
    pagesize = PR_GetPageSize();
#endif
    /*
     * If the file is large, mmap and send the file in chunks so as
     * to not consume too much virtual address space
     */
    if (!sfd->file_offset || !(sfd->file_offset & (pagesize - 1))) {
        /*
         * case 1: page-aligned file offset
         */
        mmap_len = PR_MIN(file_bytes, SENDFILE_MMAP_CHUNK);
        len      = mmap_len;
        file_mmap_offset = sfd->file_offset;
        addr_offset = 0;
    } else {
        /*
         * case 2: non page-aligned file offset
         */
        /* find previous page boundary */
        file_mmap_offset = (sfd->file_offset & ~(pagesize - 1));

        /* number of initial bytes to skip in mmap'd segment */
        addr_offset = sfd->file_offset - file_mmap_offset;
        PR_ASSERT(addr_offset > 0);
        mmap_len = PR_MIN(file_bytes + addr_offset, SENDFILE_MMAP_CHUNK);
        len      = mmap_len - addr_offset;
    }
    /*
     * Map in (part of) file. Take care of zero-length files.
     */
    if (len > 0) {
	mapHandle = PR_CreateFileMap(sfd->fd, info.size, PR_PROT_READONLY);
	if (!mapHandle) {
	    count = -1;
	    goto done;
	}
	addr = PR_MemMap(mapHandle, file_mmap_offset, mmap_len);
        if (!addr) {
            count = -1;
            goto done;
        }
    }
    /*
     * send headers, first, followed by the file
     */
    if (sfd->hlen) {
        iov[index].iov_base = (char *) sfd->header;
        iov[index].iov_len  = sfd->hlen;
        index++;
    }
    if (len) {
        iov[index].iov_base = (char*)addr + addr_offset;
        iov[index].iov_len  = len;
        index++;
    }
    if ((file_bytes == len) && (sfd->tlen)) {
        /*
         * all file data is mapped in; send the trailer too
         */
        iov[index].iov_base = (char *) sfd->trailer;
        iov[index].iov_len  = sfd->tlen;
        index++;
    }
    rv = PR_Writev(sd, iov, index, timeout);
    if (len)
        PR_MemUnmap(addr, mmap_len);
    if (rv < 0) {
        count = -1;
        goto done;
    }

    PR_ASSERT(rv == sfd->hlen + len + ((len == file_bytes) ? sfd->tlen : 0));

    file_bytes -= len;
    count      += rv;
    if (!file_bytes)    /* header, file and trailer are sent */
	goto done;

    /*
     * send remaining bytes of the file, if any
     */
    len = PR_MIN(file_bytes, SENDFILE_MMAP_CHUNK);
    while (len > 0) {
	/*
	 * Map in (part of) file
	 */
	file_mmap_offset = sfd->file_offset + count - sfd->hlen;
	PR_ASSERT((file_mmap_offset % pagesize) == 0);

	addr = PR_MemMap(mapHandle, file_mmap_offset, len);
        if (!addr) {
            count = -1;
            goto done;
        }
	rv = PR_Send(sd, addr, len, 0, timeout);
	PR_MemUnmap(addr, len);
	if (rv < 0) {
	    count = -1;
	    goto done;
	}

	PR_ASSERT(rv == len);
	file_bytes -= rv;
	count      += rv;
	len = PR_MIN(file_bytes, SENDFILE_MMAP_CHUNK);
    }
    PR_ASSERT(0 == file_bytes);
    if (sfd->tlen) {
        rv =  PR_Send(sd, sfd->trailer, sfd->tlen, 0, timeout);
        if (rv >= 0) {
            PR_ASSERT(rv == sfd->tlen);
            count += rv;
        } else
            count = -1;
    }
done:
    if (mapHandle)
    	PR_CloseFileMap(mapHandle);
    if ((count >= 0) && (flags & PR_TRANSMITFILE_CLOSE_SOCKET))
	PR_Close(sd);
    return count;
}
#endif /* UNIX and NT */
