/*
 * Test of glib bug #324234 - if socket events can be raised spuriously 
 * on win32.
 * Copyright (C) 2007  Steven Brown
 *
 * This test case is public domain, no warranties, etc..
 *
 * Steven Brown <swbrown@variadic.org>
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#ifdef WIN32
#  include <windows.h>
#  include <winsock2.h>
#  include <ws2tcpip.h>
#else
#  include <sys/socket.h>
#  include <netinet/in.h>
#  include <arpa/inet.h>
#  include <netdb.h>
#endif

#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

GMainLoop *mainLoop;

gboolean spuriousEvent(GIOChannel *source, GIOCondition condition, gpointer data) {
  fprintf(stderr, "Spurious event (likely) received, condition = [%s%s%s%s], test failed\n", condition & G_IO_IN ? "i" : "", condition & G_IO_HUP ? "h" : "", condition & G_IO_OUT ? "o" : "", condition & G_IO_ERR ? "e" : "");
  exit(1);
}

gboolean possiblyTriggerSpuriousEvent() {
  return FALSE;
}

gboolean testComplete() {
  g_main_loop_quit(mainLoop);
  return FALSE;
}

int main(int argc, char *argv[]) {
  mainLoop = g_main_loop_new(0, FALSE);

  // Init winsock.
#ifdef WIN32
    WSADATA wsaData;
    WORD wsaVersionRequested = MAKEWORD(2, 2);
    int result = WSAStartup(wsaVersionRequested, &wsaData);
    if(result != 0) {
      fprintf(stderr, "WSAStartup failed, test is broken\n");
      exit(1);
    }
    if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
      fprintf(stderr, "Could not get a suitable version of winsock, test is broken\n");
      exit(1);
    }
#endif

  // Start an asynchronous connect to a target that is guaranteed to be 
  // dropping packets on the given port, so we won't get a real 
  // response during this test (requires network connectivity).  
  // www.variadic.org tcp port 40000 is configured this way.
  struct sockaddr_in sockaddr;
  memset(&sockaddr, 0, sizeof(struct sockaddr_in));
  sockaddr.sin_family = PF_INET;
  sockaddr.sin_port = htons(40000);
  struct hostent *hostent = gethostbyname("www.variadic.org");
  if(!hostent) {
    fprintf(stderr, "Failed to resolve test host, test is broken\n");
    exit(1);
  }
  memcpy(&sockaddr.sin_addr, hostent->h_addr, sizeof(sockaddr.sin_addr));
  int sock;
  if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
    fprintf(stderr, "Failed to create the socket, test is broken\n");
    exit(1);
  }
#ifdef WIN32
  u_long iMode = 1;
  ioctlsocket(sock, FIONBIO, &iMode);
#else
  fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK);
#endif
  if(connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == -1) {
#ifdef WIN32
    if(WSAGetLastError() != WSAEWOULDBLOCK) {
#else
    if(errno != EINPROGRESS) {
#endif
      fprintf(stderr, "Failed to start the async connect, test is broken\n");
      exit(1);
    }
  }

  // Wrap it in glib stuff and listen to it for events.  None should 
  // occur.
  GIOChannel *ioChannel;
#ifdef WIN32
  ioChannel = g_io_channel_win32_new_socket(sock);
#else
  ioChannel = g_io_channel_unix_new(sock);
#endif
  if(!g_io_add_watch(ioChannel, G_IO_IN | G_IO_HUP | G_IO_OUT | G_IO_ERR, spuriousEvent, 0)) {
    fprintf(stderr, "Failed to add watch to the GIOChannel, test is broken\n");
  }

  // Start a timer to fire after the connect is in progress, which will 
  // trigger a spurious OUT event on the socket if this bug is in play.
  if(!g_timeout_add((guint) (0.5 * 1000), possiblyTriggerSpuriousEvent, 0)) {
    fprintf(stderr, "Failed to add spurious event trigger timer, test is broken\n");
  }

  // Start a timer to finish the test successfully.
  if(!g_timeout_add((guint) ((0.5 + 0.5) * 1000), testComplete, 0)) {
    fprintf(stderr, "Failed to add completion timer, test is broken\n");
  }

  g_main_loop_run(mainLoop);

  // Deinit winsock.
#ifdef WIN32
  WSACleanup();
#endif

  printf("Test completed successfully\n");
  return(0);
}

