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

/* Win95 Sockets module
 *
 */

#if defined(_WIN64)
#  include <winsock2.h>
#endif
#include "primpl.h"

#define READ_FD 1
#define WRITE_FD 2
#define CONNECT_FD 3

static PRInt32 socket_io_wait(PROsfd osfd, PRInt32 fd_type,
                              PRIntervalTime timeout);

/* --- SOCKET IO --------------------------------------------------------- */

static PRBool socketFixInet6RcvBuf = PR_FALSE;

void _PR_MD_InitSockets(void) {
  OSVERSIONINFO osvi;

  memset(&osvi, 0, sizeof(osvi));
  osvi.dwOSVersionInfoSize = sizeof(osvi);
  GetVersionEx(&osvi);

  if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) {
    /* if Windows XP (32-bit) */
    socketFixInet6RcvBuf = PR_TRUE;
  }
}

void _PR_MD_CleanupSockets(void) { socketFixInet6RcvBuf = PR_FALSE; }

PROsfd _PR_MD_SOCKET(int af, int type, int flags) {
  SOCKET sock;
  u_long one = 1;

  sock = socket(af, type, flags);

  if (sock == INVALID_SOCKET) {
    _PR_MD_MAP_SOCKET_ERROR(WSAGetLastError());
    return (PROsfd)sock;
  }

  /*
  ** Make the socket Non-Blocking
  */
  if (ioctlsocket(sock, FIONBIO, &one) != 0) {
    PR_SetError(PR_UNKNOWN_ERROR, WSAGetLastError());
    closesocket(sock);
    return -1;
  }

  if (af == AF_INET6 && socketFixInet6RcvBuf) {
    int bufsize;
    int len = sizeof(bufsize);
    int rv;

    /* Windows XP 32-bit returns an error on getpeername() for AF_INET6
     * sockets if the receive buffer size is greater than 65535 before
     * the connection is initiated. The default receive buffer size may
     * be 128000 so fix it here to always be <= 65535. See bug 513659
     * and IBM DB2 support technote "Receive/Send IPv6 Socket Size
     * Problem in Windows XP SP2 & SP3".
     */
    rv = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&bufsize, &len);
    if (rv == 0 && bufsize > 65535) {
      bufsize = 65535;
      setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&bufsize, len);
    }
  }

  return (PROsfd)sock;
}

/*
** _MD_CloseSocket() -- Close a socket
**
*/
PRInt32 _MD_CloseSocket(PROsfd osfd) {
  PRInt32 rv;

  rv = closesocket((SOCKET)osfd);
  if (rv < 0) {
    _PR_MD_MAP_CLOSE_ERROR(WSAGetLastError());
  }

  return rv;
}

PRInt32 _MD_SocketAvailable(PRFileDesc* fd) {
  PRInt32 result;

  if (ioctlsocket(fd->secret->md.osfd, FIONREAD, &result) < 0) {
    PR_SetError(PR_BAD_DESCRIPTOR_ERROR, WSAGetLastError());
    return -1;
  }
  return result;
}

PROsfd _MD_Accept(PRFileDesc* fd, PRNetAddr* raddr, PRUint32* rlen,
                  PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  SOCKET sock;
  PRInt32 rv, err;

  while ((sock = accept(osfd, (struct sockaddr*)raddr, rlen)) == -1) {
    err = WSAGetLastError();
    if ((err == WSAEWOULDBLOCK) && (!fd->secret->nonblocking)) {
      if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0) {
        break;
      }
    } else {
      _PR_MD_MAP_ACCEPT_ERROR(err);
      break;
    }
  }
  return (sock);
} /* end _MD_accept() */

PRInt32 _PR_MD_CONNECT(PRFileDesc* fd, const PRNetAddr* addr, PRUint32 addrlen,
                       PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv;
  int err;

  if ((rv = connect(osfd, (struct sockaddr*)addr, addrlen)) == -1) {
    err = WSAGetLastError();
    if ((!fd->secret->nonblocking) && (err == WSAEWOULDBLOCK)) {
      rv = socket_io_wait(osfd, CONNECT_FD, timeout);
      if (rv < 0) {
        return (-1);
      } else {
        PR_ASSERT(rv > 0);
        /* it's connected */
        return (0);
      }
    }
    _PR_MD_MAP_CONNECT_ERROR(err);
  }
  return rv;
}

PRInt32 _PR_MD_BIND(PRFileDesc* fd, const PRNetAddr* addr, PRUint32 addrlen) {
  PRInt32 rv;

  rv =
      bind(fd->secret->md.osfd, (const struct sockaddr*)&(addr->inet), addrlen);

  if (rv == SOCKET_ERROR) {
    _PR_MD_MAP_BIND_ERROR(WSAGetLastError());
    return -1;
  }

  return 0;
}

PRInt32 _PR_MD_LISTEN(PRFileDesc* fd, PRIntn backlog) {
  PRInt32 rv;

  rv = listen(fd->secret->md.osfd, backlog);

  if (rv == SOCKET_ERROR) {
    _PR_MD_MAP_DEFAULT_ERROR(WSAGetLastError());
    return -1;
  }

  return 0;
}

PRInt32 _PR_MD_RECV(PRFileDesc* fd, void* buf, PRInt32 amount, PRIntn flags,
                    PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  int osflags;

  if (0 == flags) {
    osflags = 0;
  } else {
    PR_ASSERT(PR_MSG_PEEK == flags);
    osflags = MSG_PEEK;
  }
  while ((rv = recv(osfd, buf, amount, osflags)) == -1) {
    if (((err = WSAGetLastError()) == WSAEWOULDBLOCK) &&
        (!fd->secret->nonblocking)) {
      rv = socket_io_wait(osfd, READ_FD, timeout);
      if (rv < 0) {
        return -1;
      }
    } else {
      _PR_MD_MAP_RECV_ERROR(err);
      break;
    }
  } /* end while() */
  return (rv);
}

PRInt32 _PR_MD_SEND(PRFileDesc* fd, const void* buf, PRInt32 amount,
                    PRIntn flags, PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRInt32 bytesSent = 0;

  while (bytesSent < amount) {
    while ((rv = send(osfd, buf, amount, 0)) == -1) {
      if (((err = WSAGetLastError()) == WSAEWOULDBLOCK) &&
          (!fd->secret->nonblocking)) {
        rv = socket_io_wait(osfd, WRITE_FD, timeout);
        if (rv < 0) {
          return -1;
        }
      } else {
        _PR_MD_MAP_SEND_ERROR(err);
        return -1;
      }
    }
    bytesSent += rv;
    if (fd->secret->nonblocking) {
      break;
    }
    if (bytesSent < amount) {
      rv = socket_io_wait(osfd, WRITE_FD, timeout);
      if (rv < 0) {
        return -1;
      }
    }
  }
  return bytesSent;
}

PRInt32 _PR_MD_SENDTO(PRFileDesc* fd, const void* buf, PRInt32 amount,
                      PRIntn flags, const PRNetAddr* addr, PRUint32 addrlen,
                      PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRInt32 bytesSent = 0;

  do {
    while ((rv = sendto(osfd, buf, amount, 0, (struct sockaddr*)addr,
                        addrlen)) == -1) {
      if (((err = WSAGetLastError()) == WSAEWOULDBLOCK) &&
          (!fd->secret->nonblocking)) {
        rv = socket_io_wait(osfd, WRITE_FD, timeout);
        if (rv < 0) {
          return -1;
        }
      } else {
        _PR_MD_MAP_SENDTO_ERROR(err);
        return -1;
      }
    }
    bytesSent += rv;
    if (fd->secret->nonblocking) {
      break;
    }
    if (bytesSent < amount) {
      rv = socket_io_wait(osfd, WRITE_FD, timeout);
      if (rv < 0) {
        return -1;
      }
    }
  } while (bytesSent < amount);
  return bytesSent;
}

#if defined(_WIN64)

static PRCallOnceType _pr_has_connectex_once;
typedef BOOL(PASCAL FAR* _pr_win_connectex_ptr)(
    _In_ SOCKET s, _In_reads_bytes_(namelen) const struct sockaddr FAR* name,
    _In_ int namelen, _In_reads_bytes_opt_(dwSendDataLength) PVOID lpSendBuffer,
    _In_ DWORD dwSendDataLength, _Out_ LPDWORD lpdwBytesSent,
    _Inout_ LPOVERLAPPED lpOverlapped);

#  ifndef WSAID_CONNECTEX
#    define WSAID_CONNECTEX                              \
      {                                                  \
        0x25a207b9, 0xddf3, 0x4660, {                    \
          0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e \
        }                                                \
      }
#  endif
#  ifndef SIO_GET_EXTENSION_FUNCTION_POINTER
#    define SIO_GET_EXTENSION_FUNCTION_POINTER 0xC8000006
#  endif
#  ifndef TCP_FASTOPEN
#    define TCP_FASTOPEN 15
#  endif

#  ifndef SO_UPDATE_CONNECT_CONTEXT
#    define SO_UPDATE_CONNECT_CONTEXT 0x7010
#  endif

static _pr_win_connectex_ptr _pr_win_connectex = NULL;

static PRStatus PR_CALLBACK _pr_set_connectex(void) {
  _pr_win_connectex = NULL;
  SOCKET sock;
  PRInt32 dwBytes;
  int rc;

  /* Dummy socket needed for WSAIoctl */
  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock == INVALID_SOCKET) {
    return PR_SUCCESS;
  }

  GUID guid = WSAID_CONNECTEX;
  rc = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
                &_pr_win_connectex, sizeof(_pr_win_connectex), &dwBytes, NULL,
                NULL);
  if (rc != 0) {
    _pr_win_connectex = NULL;
    return PR_SUCCESS;
  }

  rc = closesocket(sock);
  return PR_SUCCESS;
}

PRInt32 _PR_MD_TCPSENDTO(PRFileDesc* fd, const void* buf, PRInt32 amount,
                         PRIntn flags, const PRNetAddr* addr, PRUint32 addrlen,
                         PRIntervalTime timeout) {
  if (!_fd_waiting_for_overlapped_done_lock) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
  }

  if (PR_CallOnce(&_pr_has_connectex_once, _pr_set_connectex) != PR_SUCCESS) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
  }

  if (_pr_win_connectex == NULL) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
  }

  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRInt32 bytesSent = 0;
  DWORD rvSent;

  BOOL option = 1;
  rv = setsockopt((SOCKET)osfd, IPPROTO_TCP, TCP_FASTOPEN, (char*)&option,
                  sizeof(option));
  if (rv != 0) {
    err = WSAGetLastError();
    PR_LOG(_pr_io_lm, PR_LOG_MIN,
           ("_PR_MD_TCPSENDTO error set opt TCP_FASTOPEN failed %d\n", err));
    if (err == WSAENOPROTOOPT) {
      PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    } else {
      _PR_MD_MAP_SETSOCKOPT_ERROR(err);
    }
    return -1;
  }

  /* ConnectEx requires the socket to be initially bound. We will use
   * INADDR_ANY. */
  PRNetAddr bindAddr;
  memset(&bindAddr, 0, sizeof(bindAddr));
  bindAddr.raw.family = addr->raw.family;

  rv = bind((SOCKET)osfd, (const struct sockaddr*)&(bindAddr.inet),
            PR_NETADDR_SIZE(&bindAddr));
  if (rv != 0) {
    err = WSAGetLastError();
    PR_LOG(_pr_io_lm, PR_LOG_MIN,
           ("_PR_MD_TCPSENDTO error bind failed %d\n", err));
    _PR_MD_MAP_SETSOCKOPT_ERROR(err);
    return -1;
  }

  PR_LOG(_pr_io_lm, PR_LOG_MIN,
         ("_PR_MD_TCPSENDTO calling _pr_win_connectex  %d %p\n", amount,
          (char*)buf));

  rvSent = 0;
  memset(&fd->secret->ol, 0, sizeof(fd->secret->ol));
  /* ConnectEx return TRUE on a success and FALSE on an error. */
  if (_pr_win_connectex((SOCKET)osfd, (struct sockaddr*)addr, addrlen, buf,
                        amount, &rvSent, &fd->secret->ol) == TRUE) {
    /* When ConnectEx is used, all previously set socket options and
     * property are not enabled and to enable them
     * SO_UPDATE_CONNECT_CONTEXT option need to be set. */
    rv = setsockopt((SOCKET)osfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL,
                    0);
    if (rv != 0) {
      err = WSAGetLastError();
      PR_LOG(_pr_io_lm, PR_LOG_MIN,
             ("_PR_MD_TCPSENDTO setting SO_UPDATE_CONNECT_CONTEXT failed %d\n",
              err));
      _PR_MD_MAP_SETSOCKOPT_ERROR(err);
      return -1;
    }
    /* We imitate Linux here. SendTo will return number of bytes send but
     * it can not return connection success at the same time, so we return
     * number of bytes send and "connection success" will be return on the
     * connectcontinue. */
    fd->secret->alreadyConnected = PR_TRUE;
    return rvSent;
  } else {
    err = WSAGetLastError();
    PR_LOG(_pr_io_lm, PR_LOG_MIN,
           ("_PR_MD_TCPSENDTO error _pr_win_connectex failed %d\n", err));
    if (err != ERROR_IO_PENDING) {
      _PR_MD_MAP_CONNECT_ERROR(err);
      return -1;
    } else if (fd->secret->nonblocking) {
      /* Remember that overlapped structure is set. We will need to get
       * the final result of ConnectEx call. */
      fd->secret->overlappedActive = PR_TRUE;

      /* ConnectEx will copy supplied data to a internal buffer and send
       * them during Fast Open or after connect. Therefore we can assumed
       * this data already send. */
      if (amount > 0) {
        return amount;
      }

      _PR_MD_MAP_CONNECT_ERROR(WSAEWOULDBLOCK);
      return -1;
    }
    // err is ERROR_IO_PENDING and socket is blocking, so query
    // GetOverlappedResult.
    err = ERROR_IO_INCOMPLETE;
    while (err == ERROR_IO_INCOMPLETE) {
      rv = socket_io_wait(osfd, WRITE_FD, timeout);
      if (rv < 0) {
        return -1;
      }
      rv = GetOverlappedResult((HANDLE)osfd, &fd->secret->ol, &rvSent, FALSE);
      if (rv == TRUE) {
        return rvSent;
      } else {
        err = WSAGetLastError();
        if (err != ERROR_IO_INCOMPLETE) {
          _PR_MD_MAP_CONNECT_ERROR(err);
          return -1;
        }
      }
    }
  }
  return -1;
}
#endif

PRInt32 _PR_MD_RECVFROM(PRFileDesc* fd, void* buf, PRInt32 amount, PRIntn flags,
                        PRNetAddr* addr, PRUint32* addrlen,
                        PRIntervalTime timeout) {
  PROsfd osfd = fd->secret->md.osfd;
  PRInt32 rv, err;

  while ((rv = recvfrom(osfd, buf, amount, 0, (struct sockaddr*)addr,
                        addrlen)) == -1) {
    if (((err = WSAGetLastError()) == WSAEWOULDBLOCK) &&
        (!fd->secret->nonblocking)) {
      rv = socket_io_wait(osfd, READ_FD, timeout);
      if (rv < 0) {
        return -1;
      }
    } else {
      _PR_MD_MAP_RECVFROM_ERROR(err);
      break;
    }
  }
  return (rv);
}

PRInt32 _PR_MD_WRITEV(PRFileDesc* fd, const PRIOVec* iov, PRInt32 iov_size,
                      PRIntervalTime timeout) {
  int index;
  int sent = 0;
  int rv;

  for (index = 0; index < iov_size; index++) {
    rv = _PR_MD_SEND(fd, iov[index].iov_base, iov[index].iov_len, 0, timeout);
    if (rv > 0) {
      sent += rv;
    }
    if (rv != iov[index].iov_len) {
      if (rv < 0) {
        if (fd->secret->nonblocking &&
            (PR_GetError() == PR_WOULD_BLOCK_ERROR) && (sent > 0)) {
          return sent;
        } else {
          return -1;
        }
      }
      /* Only a nonblocking socket can have partial sends */
      PR_ASSERT(fd->secret->nonblocking);
      return sent;
    }
  }
  return sent;
}

PRInt32 _PR_MD_SHUTDOWN(PRFileDesc* fd, PRIntn how) {
  PRInt32 rv;

  rv = shutdown(fd->secret->md.osfd, how);
  if (rv < 0) {
    _PR_MD_MAP_SHUTDOWN_ERROR(WSAGetLastError());
  }
  return rv;
}

PRStatus _PR_MD_GETSOCKNAME(PRFileDesc* fd, PRNetAddr* addr, PRUint32* len) {
  PRInt32 rv;

  rv = getsockname((SOCKET)fd->secret->md.osfd, (struct sockaddr*)addr, len);
  if (rv == 0) {
    return PR_SUCCESS;
  } else {
    _PR_MD_MAP_GETSOCKNAME_ERROR(WSAGetLastError());
    return PR_FAILURE;
  }
}

PRStatus _PR_MD_GETPEERNAME(PRFileDesc* fd, PRNetAddr* addr, PRUint32* len) {
  PRInt32 rv;

  rv = getpeername((SOCKET)fd->secret->md.osfd, (struct sockaddr*)addr, len);
  if (rv == 0) {
    return PR_SUCCESS;
  } else {
    _PR_MD_MAP_GETPEERNAME_ERROR(WSAGetLastError());
    return PR_FAILURE;
  }
}

PRStatus _PR_MD_GETSOCKOPT(PRFileDesc* fd, PRInt32 level, PRInt32 optname,
                           char* optval, PRInt32* optlen) {
  PRInt32 rv;

  rv = getsockopt((SOCKET)fd->secret->md.osfd, level, optname, optval, optlen);
  if (rv == 0) {
    return PR_SUCCESS;
  } else {
    _PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
    return PR_FAILURE;
  }
}

PRStatus _PR_MD_SETSOCKOPT(PRFileDesc* fd, PRInt32 level, PRInt32 optname,
                           const char* optval, PRInt32 optlen) {
  PRInt32 rv;

  rv = setsockopt((SOCKET)fd->secret->md.osfd, level, optname, optval, optlen);
  if (rv == 0) {
    return PR_SUCCESS;
  } else {
    _PR_MD_MAP_SETSOCKOPT_ERROR(WSAGetLastError());
    return PR_FAILURE;
  }
}

void _MD_MakeNonblock(PRFileDesc* f) { return; /* do nothing */ }

/*
 * socket_io_wait --
 *
 * Wait for socket i/o, periodically checking for interrupt.
 *
 * This function returns 1 on success.  On failure, it returns
 * -1 and sets the error codes.  It never returns 0.
 */
#define _PR_INTERRUPT_CHECK_INTERVAL_SECS 5

static PRInt32 socket_io_wait(PROsfd osfd, PRInt32 fd_type,
                              PRIntervalTime timeout) {
  PRInt32 rv = -1;
  struct timeval tv;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRIntervalTime elapsed, remaining;
  PRBool wait_for_remaining;
  fd_set rd_wr, ex;
  int err, len;

  switch (timeout) {
    case PR_INTERVAL_NO_WAIT:
      PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
      break;
    case PR_INTERVAL_NO_TIMEOUT:
      /*
       * This is a special case of the 'default' case below.
       * Please see the comments there.
       */
      tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
      tv.tv_usec = 0;
      FD_ZERO(&rd_wr);
      FD_ZERO(&ex);
      do {
        FD_SET(osfd, &rd_wr);
        FD_SET(osfd, &ex);
        switch (fd_type) {
          case READ_FD:
            rv = _MD_SELECT(0, &rd_wr, NULL, NULL, &tv);
            break;
          case WRITE_FD:
            rv = _MD_SELECT(0, NULL, &rd_wr, NULL, &tv);
            break;
          case CONNECT_FD:
            rv = _MD_SELECT(0, NULL, &rd_wr, &ex, &tv);
            break;
          default:
            PR_ASSERT(0);
            break;
        } /* end switch() */
        if (rv == -1) {
          _PR_MD_MAP_SELECT_ERROR(WSAGetLastError());
          break;
        }
        if (rv > 0 && fd_type == CONNECT_FD) {
          /*
           * Call Sleep(0) to work around a Winsock timing bug.
           */
          Sleep(0);
          if (FD_ISSET((SOCKET)osfd, &ex)) {
            len = sizeof(err);
            if (getsockopt(osfd, SOL_SOCKET, SO_ERROR, (char*)&err, &len) ==
                SOCKET_ERROR) {
              _PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
              return -1;
            }
            if (err != 0) {
              _PR_MD_MAP_CONNECT_ERROR(err);
            } else {
              PR_SetError(PR_UNKNOWN_ERROR, 0);
            }
            return -1;
          }
          if (FD_ISSET((SOCKET)osfd, &rd_wr)) {
            /* it's connected */
            return 1;
          }
          PR_ASSERT(0);
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
      } while (rv == 0);
      break;
    default:
      remaining = timeout;
      FD_ZERO(&rd_wr);
      FD_ZERO(&ex);
      do {
        /*
         * We block in _MD_SELECT for at most
         * _PR_INTERRUPT_CHECK_INTERVAL_SECS seconds,
         * so that there is an upper limit on the delay
         * before the interrupt bit is checked.
         */
        wait_for_remaining = PR_TRUE;
        tv.tv_sec = PR_IntervalToSeconds(remaining);
        if (tv.tv_sec > _PR_INTERRUPT_CHECK_INTERVAL_SECS) {
          wait_for_remaining = PR_FALSE;
          tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
          tv.tv_usec = 0;
        } else {
          tv.tv_usec = PR_IntervalToMicroseconds(
              remaining - PR_SecondsToInterval(tv.tv_sec));
        }
        FD_SET(osfd, &rd_wr);
        FD_SET(osfd, &ex);
        switch (fd_type) {
          case READ_FD:
            rv = _MD_SELECT(0, &rd_wr, NULL, NULL, &tv);
            break;
          case WRITE_FD:
            rv = _MD_SELECT(0, NULL, &rd_wr, NULL, &tv);
            break;
          case CONNECT_FD:
            rv = _MD_SELECT(0, NULL, &rd_wr, &ex, &tv);
            break;
          default:
            PR_ASSERT(0);
            break;
        } /* end switch() */
        if (rv == -1) {
          _PR_MD_MAP_SELECT_ERROR(WSAGetLastError());
          break;
        }
        if (rv > 0 && fd_type == CONNECT_FD) {
          /*
           * Call Sleep(0) to work around a Winsock timing bug.
           */
          Sleep(0);
          if (FD_ISSET((SOCKET)osfd, &ex)) {
            len = sizeof(err);
            if (getsockopt(osfd, SOL_SOCKET, SO_ERROR, (char*)&err, &len) ==
                SOCKET_ERROR) {
              _PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
              return -1;
            }
            if (err != 0) {
              _PR_MD_MAP_CONNECT_ERROR(err);
            } else {
              PR_SetError(PR_UNKNOWN_ERROR, 0);
            }
            return -1;
          }
          if (FD_ISSET((SOCKET)osfd, &rd_wr)) {
            /* it's connected */
            return 1;
          }
          PR_ASSERT(0);
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
        /*
         * We loop again if _MD_SELECT timed out and the
         * timeout deadline has not passed yet.
         */
        if (rv == 0) {
          if (wait_for_remaining) {
            elapsed = remaining;
          } else {
            elapsed = PR_SecondsToInterval(tv.tv_sec) +
                      PR_MicrosecondsToInterval(tv.tv_usec);
          }
          if (elapsed >= remaining) {
            PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
            rv = -1;
            break;
          } else {
            remaining = remaining - elapsed;
          }
        }
      } while (rv == 0);
      break;
  }
  return (rv);
} /* end socket_io_wait() */
