/* -*- 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.
 */

/*
** File:        prlayer.c
** Description: Routines for handling pushable protocol modules on sockets.
*/

#include "primpl.h"
#include "prerror.h"
#include "prmem.h"
#include "prlock.h"
#include "prlog.h"
#include "prio.h"

#include <string.h> /* for memset() */

void PR_CALLBACK pl_FDDestructor(PRFileDesc *fd)
{
    PR_ASSERT(fd != NULL);
    if (NULL != fd->lower) fd->lower->higher = fd->higher;
    if (NULL != fd->higher) fd->higher->lower = fd->lower;
    PR_DELETE(fd);
}

/*
** Default methods that just call down to the next fd.
*/
static PRStatus PR_CALLBACK pl_TopClose (PRFileDesc *fd)
{
    PRStatus status;
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);
    PR_ASSERT(fd->secret == NULL);
    PR_ASSERT(fd->methods->file_type == PR_DESC_LAYERED);

    status = (fd->lower->methods->close)(fd->lower);
    fd->lower = fd->higher = NULL;

    fd->dtor(fd);
    return status;
}

static PRInt32 PR_CALLBACK pl_DefRead (PRFileDesc *fd, void *buf, PRInt32 amount)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->read)(fd->lower, buf, amount);
}

static PRInt32 PR_CALLBACK pl_DefWrite (
    PRFileDesc *fd, const void *buf, PRInt32 amount)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->write)(fd->lower, buf, amount);
}

static PRInt32 PR_CALLBACK pl_DefAvailable (PRFileDesc *fd)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->available)(fd->lower);
}

static PRInt64 PR_CALLBACK pl_DefAvailable64 (PRFileDesc *fd)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->available64)(fd->lower);
}

static PRStatus PR_CALLBACK pl_DefFsync (PRFileDesc *fd)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->fsync)(fd->lower);
}

static PRInt32 PR_CALLBACK pl_DefSeek (
    PRFileDesc *fd, PRInt32 offset, PRSeekWhence how)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->seek)(fd->lower, offset, how);
}

static PRInt64 PR_CALLBACK pl_DefSeek64 (
    PRFileDesc *fd, PRInt64 offset, PRSeekWhence how)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->seek64)(fd->lower, offset, how);
}

static PRStatus PR_CALLBACK pl_DefFileInfo (PRFileDesc *fd, PRFileInfo *info)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->fileInfo)(fd->lower, info);
}

static PRStatus PR_CALLBACK pl_DefFileInfo64 (PRFileDesc *fd, PRFileInfo64 *info)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->fileInfo64)(fd->lower, info);
}

static PRInt32 PR_CALLBACK pl_DefWritev (PRFileDesc *fd, PRIOVec *iov, PRInt32 size,
                                    PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->writev)(fd->lower, iov, size, timeout);
}

static PRStatus PR_CALLBACK pl_DefConnect (
    PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->connect)(fd->lower, addr, timeout);
}

static PRFileDesc* PR_CALLBACK pl_TopAccept (
    PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout)
{
    PRStatus rv;
    PRFileDesc *newfd;

    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    newfd = (fd->lower->methods->accept)(fd->lower, addr, timeout);
    if (newfd != NULL)
    {
        PRFileDesc *newstack = PR_NEW(PRFileDesc);
        if (NULL != newstack)
        {
            *newstack = *fd;  /* make a copy of the accepting layer */
            rv = PR_PushIOLayer(newfd, PR_TOP_IO_LAYER, newstack);
            if (PR_SUCCESS == rv) return newfd;  /* that's it */
        }
        PR_Close(newfd);  /* we failed for local reasons */
    }
    return NULL;
}

static PRStatus PR_CALLBACK pl_DefBind (PRFileDesc *fd, const PRNetAddr *addr)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->bind)(fd->lower, addr);
}

static PRStatus PR_CALLBACK pl_DefListen (PRFileDesc *fd, PRIntn backlog)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->listen)(fd->lower, backlog);
}

static PRStatus PR_CALLBACK pl_DefShutdown (PRFileDesc *fd, PRIntn how)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->shutdown)(fd->lower, how);
}

static PRInt32 PR_CALLBACK pl_DefRecv (
    PRFileDesc *fd, void *buf, PRInt32 amount,
    PRIntn flags, PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->recv)(
        fd->lower, buf, amount, flags, timeout);
}

static PRInt32 PR_CALLBACK pl_DefSend (
    PRFileDesc *fd, const void *buf,
    PRInt32 amount, PRIntn flags, PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout);
}

static PRInt32 PR_CALLBACK pl_DefRecvfrom (
    PRFileDesc *fd, void *buf, PRInt32 amount,
    PRIntn flags, PRNetAddr *addr, PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->recvfrom)(
        fd->lower, buf, amount, flags, addr, timeout);
}

static PRInt32 PR_CALLBACK pl_DefSendto (
    PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
    const PRNetAddr *addr, PRIntervalTime timeout)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->sendto)(
        fd->lower, buf, amount, flags, addr, timeout);
}

static PRInt16 PR_CALLBACK pl_DefPoll (
    PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->poll)(fd->lower, in_flags, out_flags);
}

static PRInt32 PR_CALLBACK pl_DefAcceptread (
    PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr, void *buf,
    PRInt32 amount, PRIntervalTime t)
{
    PR_ASSERT(sd != NULL);
    PR_ASSERT(sd->lower != NULL);

    return sd->lower->methods->acceptread(sd->lower, nd, raddr, buf, amount, t);
}

static PRInt32 PR_CALLBACK pl_DefTransmitfile (
    PRFileDesc *sd, PRFileDesc *fd, const void *headers, PRInt32 hlen,
    PRTransmitFileFlags flags, PRIntervalTime t)
{
    PR_ASSERT(sd != NULL);
    PR_ASSERT(sd->lower != NULL);

    return sd->lower->methods->transmitfile(
        sd->lower, fd, headers, hlen, flags, t);
}

static PRStatus PR_CALLBACK pl_DefGetsockname (PRFileDesc *fd, PRNetAddr *addr)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->getsockname)(fd->lower, addr);
}

static PRStatus PR_CALLBACK pl_DefGetpeername (PRFileDesc *fd, PRNetAddr *addr)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->getpeername)(fd->lower, addr);
}

static PRStatus PR_CALLBACK pl_DefGetsockopt (
    PRFileDesc *fd, PRSockOption optname, void* optval, PRInt32* optlen)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->getsockopt)(fd->lower, optname, optval, optlen);
}

static PRStatus PR_CALLBACK pl_DefSetsockopt (
    PRFileDesc *fd, PRSockOption optname, const void* optval, PRInt32 optlen)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->setsockopt)(fd->lower, optname, optval, optlen);
}

static PRStatus PR_CALLBACK pl_DefGetsocketoption (
    PRFileDesc *fd, PRSocketOptionData *data)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->getsocketoption)(fd->lower, data);
}

static PRStatus PR_CALLBACK pl_DefSetsocketoption (
    PRFileDesc *fd, const PRSocketOptionData *data)
{
    PR_ASSERT(fd != NULL);
    PR_ASSERT(fd->lower != NULL);

    return (fd->lower->methods->setsocketoption)(fd->lower, data);
}

/* Methods for the top of the stack.  Just call down to the next fd. */
static PRIOMethods pl_methods = {
    PR_DESC_LAYERED,
    pl_TopClose,
    pl_DefRead,
    pl_DefWrite,
    pl_DefAvailable,
    pl_DefAvailable64,
    pl_DefFsync,
    pl_DefSeek,
    pl_DefSeek64,
    pl_DefFileInfo,
    pl_DefFileInfo64,
    pl_DefWritev,
    pl_DefConnect,
    pl_TopAccept,
    pl_DefBind,
    pl_DefListen,
    pl_DefShutdown,
    pl_DefRecv,
    pl_DefSend,
    pl_DefRecvfrom,
    pl_DefSendto,
    pl_DefPoll,
    pl_DefAcceptread,
    pl_DefTransmitfile,
    pl_DefGetsockname,
    pl_DefGetpeername,
    pl_DefGetsockopt,
    pl_DefSetsockopt,
    pl_DefGetsocketoption,
    pl_DefSetsocketoption
};

PR_IMPLEMENT(const PRIOMethods*) PR_GetDefaultIOMethods()
{
    return &pl_methods;
}  /* PR_GetDefaultIOMethods */

PR_IMPLEMENT(PRFileDesc*) PR_CreateIOLayerStub(
    PRDescIdentity ident, const PRIOMethods *methods)
{
    PRFileDesc *fd = NULL;
    PR_ASSERT((PR_NSPR_IO_LAYER != ident) && (PR_TOP_IO_LAYER != ident));
    if ((PR_NSPR_IO_LAYER == ident) || (PR_TOP_IO_LAYER == ident))
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
    else
    {
        fd = PR_NEWZAP(PRFileDesc);
        if (NULL == fd)
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        else
        {
            fd->methods = methods;
            fd->dtor = pl_FDDestructor;
            fd->identity = ident;
        }
    }
    return fd;
}  /* PR_CreateIOLayerStub */

PR_IMPLEMENT(PRStatus) PR_PushIOLayer(
    PRFileDesc *stack, PRDescIdentity id, PRFileDesc *fd)
{
    PRFileDesc *insert = PR_GetIdentitiesLayer(stack, id);

    PR_ASSERT(fd != NULL);
    PR_ASSERT(stack != NULL);
    PR_ASSERT(insert != NULL);
    if ((NULL == stack) || (NULL == fd) || (NULL == insert))
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }

    if (stack == insert)
    {
        /* going on top of the stack */
        PRFileDesc copy = *stack;
        *stack = *fd;
        *fd = copy;
        fd->higher = stack;
        stack->lower = fd;
        stack->higher = NULL;
    }
    else
    {
        /* going somewhere in the middle of the stack */
        fd->lower = insert;
        fd->higher = insert->higher;

        insert->higher = fd;
        insert->higher->lower = fd;
    }

    return PR_SUCCESS;
}

PR_IMPLEMENT(PRFileDesc*) PR_PopIOLayer(PRFileDesc *stack, PRDescIdentity id)
{
    PRFileDesc *extract = PR_GetIdentitiesLayer(stack, id);

    PR_ASSERT(0 != id);
    PR_ASSERT(NULL != stack);
    PR_ASSERT(NULL != extract);
    if ((NULL == stack) || (0 == id) || (NULL == extract))
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return NULL;
    }

    if (extract == stack)
    {
        /* popping top layer of the stack */
        PRFileDesc copy = *stack;
        extract = stack->lower;
        *stack = *extract;
        *extract = copy;
        stack->higher = NULL;
    }
    else
    {
        extract->lower->higher = extract->higher;
        extract->higher->lower = extract->lower;
    }
    extract->higher = extract->lower = NULL;
    return extract;
}  /* PR_PopIOLayer */

#define ID_CACHE_INCREMENT 16
typedef struct _PRIdentity_cache
{
    PRLock *ml;
    char **name;
    PRIntn length;
    PRDescIdentity ident;
} _PRIdentity_cache;

static _PRIdentity_cache identity_cache;

PR_IMPLEMENT(PRDescIdentity) PR_GetUniqueIdentity(const char *layer_name)
{
    PRDescIdentity identity, length;
    char **names = NULL, *name = NULL, **old = NULL;

    if (!_pr_initialized) _PR_ImplicitInitialization();

    PR_ASSERT((PRDescIdentity)0x7fff > identity_cache.ident);

    if (NULL != layer_name)
    {
        name = (char*)PR_Malloc(strlen(layer_name) + 1);
        if (NULL == name)
        {
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            return PR_INVALID_IO_LAYER;
        }
        strcpy(name, layer_name);
    }

    /* this initial code runs unsafe */
retry:
    PR_ASSERT(NULL == names);
    length = identity_cache.length;
    if (length < (identity_cache.ident + 1))
    {
        length += ID_CACHE_INCREMENT;
        names = (char**)PR_CALLOC(length * sizeof(char*));
        if (NULL == names)
        {
            if (NULL != name) PR_DELETE(name);
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            return PR_INVALID_IO_LAYER;
        }
    }

    /* now we get serious about thread safety */
    PR_Lock(identity_cache.ml);
    PR_ASSERT(identity_cache.ident <= identity_cache.length);
    identity = identity_cache.ident + 1;
    if (identity > identity_cache.length)  /* there's no room */
    {
        /* we have to do something - hopefully it's already done */
        if ((NULL != names) && (length >= identity))
        {
            /* what we did is still okay */
            memcpy(
                names, identity_cache.name,
                identity_cache.length * sizeof(char*));
            old = identity_cache.name;
            identity_cache.name = names;
            identity_cache.length = length;
            names = NULL;
        }
        else
        {
            PR_ASSERT(identity_cache.ident <= identity_cache.length);
            PR_Unlock(identity_cache.ml);
            if (NULL != names) PR_DELETE(names);
            goto retry;
        }
    }
    if (NULL != name) /* there's a name to be stored */
    {
        identity_cache.name[identity] = name;
    }
    identity_cache.ident = identity;
    PR_ASSERT(identity_cache.ident <= identity_cache.length);
    PR_Unlock(identity_cache.ml);

    if (NULL != old) PR_DELETE(old);
    if (NULL != names) PR_DELETE(names);

    return identity;
}  /* PR_GetUniqueIdentity */

PR_IMPLEMENT(const char*) PR_GetNameForIdentity(PRDescIdentity ident)
{
    if (!_pr_initialized) _PR_ImplicitInitialization();

    if (PR_TOP_IO_LAYER == ident) return NULL;

    PR_ASSERT(ident <= identity_cache.ident);
    return (ident > identity_cache.ident) ? NULL : identity_cache.name[ident];
}  /* PR_GetNameForIdentity */

PR_IMPLEMENT(PRDescIdentity) PR_GetLayersIdentity(PRFileDesc* fd)
{
    PR_ASSERT(NULL != fd);
    return fd->identity;
}  /* PR_GetLayersIdentity */

PR_IMPLEMENT(PRFileDesc*) PR_GetIdentitiesLayer(PRFileDesc* fd, PRDescIdentity id)
{
    PRFileDesc *layer = fd;

    if (PR_TOP_IO_LAYER == id) return fd;

    for (layer = fd; layer != NULL; layer = layer->lower)
    {
        if (id == layer->identity) return layer;
    }
    for (layer = fd; layer != NULL; layer = layer->higher)
    {
        if (id == layer->identity) return layer;
    }
    return NULL;
}  /* PR_GetIdentitiesLayer */

void _PR_InitLayerCache()
{
    memset(&identity_cache, 0, sizeof(identity_cache));
    identity_cache.ml = PR_NewLock();
    PR_ASSERT(NULL != identity_cache.ml);
}  /* _PR_InitLayerCache */

/* prlayer.c */
