/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "primpl.h"

#include <string.h>

/*****************************************************************************/
/************************** Invalid I/O method object ************************/
/*****************************************************************************/
PRIOMethods _pr_faulty_methods = {(PRDescType)0,
                                  (PRCloseFN)_PR_InvalidStatus,
                                  (PRReadFN)_PR_InvalidInt,
                                  (PRWriteFN)_PR_InvalidInt,
                                  (PRAvailableFN)_PR_InvalidInt,
                                  (PRAvailable64FN)_PR_InvalidInt64,
                                  (PRFsyncFN)_PR_InvalidStatus,
                                  (PRSeekFN)_PR_InvalidInt,
                                  (PRSeek64FN)_PR_InvalidInt64,
                                  (PRFileInfoFN)_PR_InvalidStatus,
                                  (PRFileInfo64FN)_PR_InvalidStatus,
                                  (PRWritevFN)_PR_InvalidInt,
                                  (PRConnectFN)_PR_InvalidStatus,
                                  (PRAcceptFN)_PR_InvalidDesc,
                                  (PRBindFN)_PR_InvalidStatus,
                                  (PRListenFN)_PR_InvalidStatus,
                                  (PRShutdownFN)_PR_InvalidStatus,
                                  (PRRecvFN)_PR_InvalidInt,
                                  (PRSendFN)_PR_InvalidInt,
                                  (PRRecvfromFN)_PR_InvalidInt,
                                  (PRSendtoFN)_PR_InvalidInt,
                                  (PRPollFN)_PR_InvalidInt16,
                                  (PRAcceptreadFN)_PR_InvalidInt,
                                  (PRTransmitfileFN)_PR_InvalidInt,
                                  (PRGetsocknameFN)_PR_InvalidStatus,
                                  (PRGetpeernameFN)_PR_InvalidStatus,
                                  (PRReservedFN)_PR_InvalidInt,
                                  (PRReservedFN)_PR_InvalidInt,
                                  (PRGetsocketoptionFN)_PR_InvalidStatus,
                                  (PRSetsocketoptionFN)_PR_InvalidStatus,
                                  (PRSendfileFN)_PR_InvalidInt,
                                  (PRConnectcontinueFN)_PR_InvalidStatus,
                                  (PRReservedFN)_PR_InvalidInt,
                                  (PRReservedFN)_PR_InvalidInt,
                                  (PRReservedFN)_PR_InvalidInt,
                                  (PRReservedFN)_PR_InvalidInt};

PRIntn _PR_InvalidInt(void) {
  PR_NOT_REACHED("I/O method is invalid");
  PR_SetError(PR_INVALID_METHOD_ERROR, 0);
  return -1;
} /* _PR_InvalidInt */

PRInt16 _PR_InvalidInt16(void) {
  PR_NOT_REACHED("I/O method is invalid");
  PR_SetError(PR_INVALID_METHOD_ERROR, 0);
  return -1;
} /* _PR_InvalidInt */

PRInt64 _PR_InvalidInt64(void) {
  PRInt64 rv;
  LL_I2L(rv, -1);
  PR_NOT_REACHED("I/O method is invalid");
  PR_SetError(PR_INVALID_METHOD_ERROR, 0);
  return rv;
} /* _PR_InvalidInt */

/*
 * An invalid method that returns PRStatus
 */

PRStatus _PR_InvalidStatus(void) {
  PR_NOT_REACHED("I/O method is invalid");
  PR_SetError(PR_INVALID_METHOD_ERROR, 0);
  return PR_FAILURE;
} /* _PR_InvalidDesc */

/*
 * An invalid method that returns a pointer
 */

PRFileDesc* _PR_InvalidDesc(void) {
  PR_NOT_REACHED("I/O method is invalid");
  PR_SetError(PR_INVALID_METHOD_ERROR, 0);
  return NULL;
} /* _PR_InvalidDesc */

PR_IMPLEMENT(PRDescType) PR_GetDescType(PRFileDesc* file) {
  return file->methods->file_type;
}

PR_IMPLEMENT(PRStatus) PR_Close(PRFileDesc* fd) {
  return (fd->methods->close)(fd);
}

PR_IMPLEMENT(PRInt32) PR_Read(PRFileDesc* fd, void* buf, PRInt32 amount) {
  return ((fd->methods->read)(fd, buf, amount));
}

PR_IMPLEMENT(PRInt32)
PR_Write(PRFileDesc* fd, const void* buf, PRInt32 amount) {
  return ((fd->methods->write)(fd, buf, amount));
}

PR_IMPLEMENT(PRInt32)
PR_Seek(PRFileDesc* fd, PRInt32 offset, PRSeekWhence whence) {
  return ((fd->methods->seek)(fd, offset, whence));
}

PR_IMPLEMENT(PRInt64)
PR_Seek64(PRFileDesc* fd, PRInt64 offset, PRSeekWhence whence) {
  return ((fd->methods->seek64)(fd, offset, whence));
}

PR_IMPLEMENT(PRInt32) PR_Available(PRFileDesc* fd) {
  return ((fd->methods->available)(fd));
}

PR_IMPLEMENT(PRInt64) PR_Available64(PRFileDesc* fd) {
  return ((fd->methods->available64)(fd));
}

PR_IMPLEMENT(PRStatus) PR_GetOpenFileInfo(PRFileDesc* fd, PRFileInfo* info) {
  return ((fd->methods->fileInfo)(fd, info));
}

PR_IMPLEMENT(PRStatus)
PR_GetOpenFileInfo64(PRFileDesc* fd, PRFileInfo64* info) {
  return ((fd->methods->fileInfo64)(fd, info));
}

PR_IMPLEMENT(PRStatus) PR_Sync(PRFileDesc* fd) {
  return ((fd->methods->fsync)(fd));
}

PR_IMPLEMENT(PRStatus)
PR_Connect(PRFileDesc* fd, const PRNetAddr* addr, PRIntervalTime timeout) {
  return ((fd->methods->connect)(fd, addr, timeout));
}

PR_IMPLEMENT(PRStatus) PR_ConnectContinue(PRFileDesc* fd, PRInt16 out_flags) {
  return ((fd->methods->connectcontinue)(fd, out_flags));
}

PR_IMPLEMENT(PRFileDesc*)
PR_Accept(PRFileDesc* fd, PRNetAddr* addr, PRIntervalTime timeout) {
  return ((fd->methods->accept)(fd, addr, timeout));
}

PR_IMPLEMENT(PRStatus) PR_Bind(PRFileDesc* fd, const PRNetAddr* addr) {
  return ((fd->methods->bind)(fd, addr));
}

PR_IMPLEMENT(PRStatus) PR_Shutdown(PRFileDesc* fd, PRShutdownHow how) {
  return ((fd->methods->shutdown)(fd, how));
}

PR_IMPLEMENT(PRStatus) PR_Listen(PRFileDesc* fd, PRIntn backlog) {
  return ((fd->methods->listen)(fd, backlog));
}

PR_IMPLEMENT(PRInt32)
PR_Recv(PRFileDesc* fd, void* buf, PRInt32 amount, PRIntn flags,
        PRIntervalTime timeout) {
  return ((fd->methods->recv)(fd, buf, amount, flags, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_Send(PRFileDesc* fd, const void* buf, PRInt32 amount, PRIntn flags,
        PRIntervalTime timeout) {
  return ((fd->methods->send)(fd, buf, amount, flags, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_Writev(PRFileDesc* fd, const PRIOVec* iov, PRInt32 iov_size,
          PRIntervalTime timeout) {
  if (iov_size > PR_MAX_IOVECTOR_SIZE) {
    PR_SetError(PR_BUFFER_OVERFLOW_ERROR, 0);
    return -1;
  }
  return ((fd->methods->writev)(fd, iov, iov_size, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_RecvFrom(PRFileDesc* fd, void* buf, PRInt32 amount, PRIntn flags,
            PRNetAddr* addr, PRIntervalTime timeout) {
  return ((fd->methods->recvfrom)(fd, buf, amount, flags, addr, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_SendTo(PRFileDesc* fd, const void* buf, PRInt32 amount, PRIntn flags,
          const PRNetAddr* addr, PRIntervalTime timeout) {
  return ((fd->methods->sendto)(fd, buf, amount, flags, addr, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_TransmitFile(PRFileDesc* sd, PRFileDesc* fd, const void* hdr, PRInt32 hlen,
                PRTransmitFileFlags flags, PRIntervalTime timeout) {
  return ((sd->methods->transmitfile)(sd, fd, hdr, hlen, flags, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_AcceptRead(PRFileDesc* sd, PRFileDesc** nd, PRNetAddr** raddr, void* buf,
              PRInt32 amount, PRIntervalTime timeout) {
  return ((sd->methods->acceptread)(sd, nd, raddr, buf, amount, timeout));
}

PR_IMPLEMENT(PRStatus) PR_GetSockName(PRFileDesc* fd, PRNetAddr* addr) {
  return ((fd->methods->getsockname)(fd, addr));
}

PR_IMPLEMENT(PRStatus) PR_GetPeerName(PRFileDesc* fd, PRNetAddr* addr) {
  return ((fd->methods->getpeername)(fd, addr));
}

PR_IMPLEMENT(PRStatus)
PR_GetSocketOption(PRFileDesc* fd, PRSocketOptionData* data) {
  return ((fd->methods->getsocketoption)(fd, data));
}

PR_IMPLEMENT(PRStatus)
PR_SetSocketOption(PRFileDesc* fd, const PRSocketOptionData* data) {
  return ((fd->methods->setsocketoption)(fd, data));
}

PR_IMPLEMENT(PRInt32)
PR_SendFile(PRFileDesc* sd, PRSendFileData* sfd, PRTransmitFileFlags flags,
            PRIntervalTime timeout) {
  return ((sd->methods->sendfile)(sd, sfd, flags, timeout));
}

PR_IMPLEMENT(PRInt32)
PR_EmulateAcceptRead(PRFileDesc* sd, PRFileDesc** nd, PRNetAddr** raddr,
                     void* buf, PRInt32 amount, PRIntervalTime timeout) {
  PRInt32 rv = -1;
  PRNetAddr remote;
  PRFileDesc* accepted = NULL;

  /*
  ** The timeout does not apply to the accept portion of the
  ** operation - it waits indefinitely.
  */
  accepted = PR_Accept(sd, &remote, PR_INTERVAL_NO_TIMEOUT);
  if (NULL == accepted) {
    return rv;
  }

  rv = PR_Recv(accepted, buf, amount, 0, timeout);
  if (rv >= 0) {
    /* copy the new info out where caller can see it */
#define AMASK ((PRPtrdiff)7) /* mask for alignment of PRNetAddr */
    PRPtrdiff aligned = (PRPtrdiff)buf + amount + AMASK;
    *raddr = (PRNetAddr*)(aligned & ~AMASK);
    memcpy(*raddr, &remote, PR_NETADDR_SIZE(&remote));
    *nd = accepted;
    return rv;
  }

  PR_Close(accepted);
  return rv;
}

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

#if defined(XP_UNIX) || defined(WIN32)

/*
 * An implementation based on memory-mapped files
 */

#  define SENDFILE_MMAP_CHUNK (256 * 1024)

PR_IMPLEMENT(PRInt32)
PR_EmulateSendFile(PRFileDesc* sd, PRSendFileData* sfd,
                   PRTransmitFileFlags flags, PRIntervalTime timeout) {
  PRInt32 rv, count = 0;
  PRInt32 len, file_bytes, index = 0;
  PRFileInfo info;
  PRIOVec iov[3];
  PRFileMap* mapHandle = NULL;
  void* addr = (void*)0; /* initialized to some arbitrary value. Keeps compiler
                            warnings down. */
  PRUint32 file_mmap_offset, alignment;
  PRInt64 zero64;
  PROffset64 file_mmap_offset64;
  PRUint32 addr_offset, mmap_len;

  /* 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;
  }

  alignment = PR_GetMemMapAlignment();

  /* number of initial bytes to skip in mmap'd segment */
  addr_offset = sfd->file_offset % alignment;

  /* find previous mmap alignment boundary */
  file_mmap_offset = sfd->file_offset - addr_offset;

  /*
   * If the file is large, mmap and send the file in chunks so as
   * to not consume too much virtual address space
   */
  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) {
    LL_I2L(zero64, 0);
    mapHandle = PR_CreateFileMap(sfd->fd, zero64, PR_PROT_READONLY);
    if (!mapHandle) {
      count = -1;
      goto done;
    }
    LL_I2L(file_mmap_offset64, file_mmap_offset);
    addr = PR_MemMap(mapHandle, file_mmap_offset64, 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 % alignment) == 0);

    LL_I2L(file_mmap_offset64, file_mmap_offset);
    addr = PR_MemMap(mapHandle, file_mmap_offset64, 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;
}

#else

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

#  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;
}

#endif

/* priometh.c */
