text: /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
  #undef NDEBUG
  #include <pthread.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <sys/wait.h>
  #include <netdb.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
  #include <netinet/tcp.h>
  #include <signal.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <errno.h>
  #include <assert.h>
  #include <string.h>
  #include <unistd.h>
  #include <netinet/in.h>
  #include <fcntl.h>
  #include <time.h>
  
  #include "config.h"
  #include "cache.h"
  #include "hash.h"
  #include "jenkins_hash.h"
  #include "stats_prefix.h"
  #include "util.h"
  #include "protocol_binary.h"
  #ifdef TLS
  #include <openssl/ssl.h>
  #endif
  
  #ifndef _WIN32
  #define sock_write(fd, buf, count)  write(fd, buf, count)
  #define sock_read(fd, buf, nbyte)   read(fd, buf, nbyte)
  #define sock_close(fd)              close(fd)
  
  #define TMP_DIR                     "/tmp/"
  #define MEMCACHED_EXE               "./memcached-debug"
  #define TIMEDRUN_EXE                "./timedrun"
  #else
  #define TMP_DIR                     "./"
  #ifndef MEMCACHED_EXE
  #define MEMCACHED_EXE               "memcached-debug.exe"
  #endif /* #ifndef MEMCACHED_EXE */
  #define WINE_TEST_ENV               "WINE_TEST"
  #endif /* #ifndef _WIN32 */
  #define TMP_TEMPLATE TMP_DIR"test_file.XXXXXXX"
  
  enum test_return { TEST_SKIP, TEST_PASS, TEST_FAIL };
  
  struct conn {
      int sock;
  #ifdef TLS
      SSL_CTX   *ssl_ctx;
      SSL    *ssl;
  #endif
      ssize_t (*read)(struct conn  *c, void *buf, size_t count);
      ssize_t (*write)(struct conn *c, const void *buf, size_t count);
  };
  
  hash_func hash;
  
  static ssize_t tcp_read(struct conn *c, void *buf, size_t count);
  static ssize_t tcp_write(struct conn *c, const void *buf, size_t count);
  #ifdef TLS
  static ssize_t ssl_read(struct conn *c, void *buf, size_t count);
  static ssize_t ssl_write(struct conn *c, const void *buf, size_t count);
  #endif
  
  ssize_t tcp_read(struct conn *c, void *buf, size_t count) {
      assert (c != NULL);
      return sock_read(c->sock, buf, count);
  }
  
  ssize_t tcp_write(struct conn *c, const void *buf, size_t count) {
      assert (c != NULL);
      return sock_write(c->sock, buf, count);
  }
  
  #ifdef TLS
  ssize_t ssl_read(struct conn *c, void *buf, size_t count) {
      assert(c != NULL);
  #ifndef _WIN32
      return SSL_read(c->ssl, buf, count);
  #else
      /* Still using OpenSSL but will set expected/correct errno if error for memcached */
      return WinSSL_read(c->ssl, buf, count);
  #endif /* #ifndef _WIN32 */
  }
  
  ssize_t ssl_write(struct conn *c, const void *buf, size_t count) {
      assert(c != NULL);
  #ifndef _WIN32
      return SSL_write(c->ssl, buf, count);
  #else
      /* Still using OpenSSL but will set expected/correct errno if error for memcached */
      return WinSSL_write(c->ssl, buf, count);
  #endif /* #ifndef _WIN32 */
  }
  #endif
  
  static pid_t server_pid;
  static in_port_t port;
  static struct conn *con = NULL;
  static bool allow_closed_read = false;
  static bool enable_ssl = false;
  
  static void close_conn() {
      if (con == NULL) return;
  #ifdef TLS
      if (con->ssl) {
          SSL_shutdown(con->ssl);
          SSL_free(con->ssl);
      }
      if (con->ssl_ctx)
          SSL_CTX_free(con->ssl_ctx);
  #endif
      if (con->sock > 0) sock_close(con->sock);
      free(con);
      con = NULL;
  }
  
  static enum test_return cache_create_test(void)
  {
      cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
                                    NULL, NULL);
      assert(cache != NULL);
      cache_destroy(cache);
      return TEST_PASS;
  }
  
  const uint64_t constructor_pattern = 0xdeadcafebabebeef;
  
  static int cache_constructor(void *buffer, void *notused1, int notused2) {
      uint64_t *ptr = buffer;
      *ptr = constructor_pattern;
      return 0;
  }
  
  static enum test_return cache_constructor_test(void)
  {
      cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
                                    cache_constructor, NULL);
      assert(cache != NULL);
      uint64_t *ptr = cache_alloc(cache);
      uint64_t pattern = *ptr;
      cache_free(cache, ptr);
      cache_destroy(cache);
      return (pattern == constructor_pattern) ? TEST_PASS : TEST_FAIL;
  }
  
  static int cache_fail_constructor(void *buffer, void *notused1, int notused2) {
      return 1;
  }
  
  static enum test_return cache_fail_constructor_test(void)
  {
      enum test_return ret = TEST_PASS;
  
      cache_t *cache = cache_create("test", sizeof(uint64_t), sizeof(uint64_t),
                                    cache_fail_constructor, NULL);
      assert(cache != NULL);
      uint64_t *ptr = cache_alloc(cache);
      if (ptr != NULL) {
          ret = TEST_FAIL;
      }
      cache_destroy(cache);
      return ret;
  }
  
  static void *destruct_data = 0;
  
  static void cache_destructor(void *buffer, void *notused) {
      destruct_data = buffer;
  }
  
  static enum test_return cache_destructor_test(void)
  {
      cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
                                    NULL, cache_destructor);
      assert(cache != NULL);
      char *ptr = cache_alloc(cache);
      cache_free(cache, ptr);
      cache_destroy(cache);
  
      return (ptr == destruct_data) ? TEST_PASS : TEST_FAIL;
  }
  
  static enum test_return cache_reuse_test(void)
  {
      int ii;
      cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
                                    NULL, NULL);
      char *ptr = cache_alloc(cache);
      cache_free(cache, ptr);
      for (ii = 0; ii < 100; ++ii) {
          char *p = cache_alloc(cache);
          assert(p == ptr);
          cache_free(cache, ptr);
      }
      cache_destroy(cache);
      return TEST_PASS;
  }
  
  
  static enum test_return cache_bulkalloc(size_t datasize)
  {
      cache_t *cache = cache_create("test", datasize, sizeof(char*),
                                    NULL, NULL);
  #define ITERATIONS 1024
      void *ptr[ITERATIONS];
  
      for (int ii = 0; ii < ITERATIONS; ++ii) {
          ptr[ii] = cache_alloc(cache);
          assert(ptr[ii] != 0);
          memset(ptr[ii], 0xff, datasize);
      }
  
      for (int ii = 0; ii < ITERATIONS; ++ii) {
          cache_free(cache, ptr[ii]);
      }
  
  #undef ITERATIONS
      cache_destroy(cache);
      return TEST_PASS;
  }
  
  static enum test_return test_issue_161(void)
  {
      enum test_return ret = cache_bulkalloc(1);
      if (ret == TEST_PASS) {
          ret = cache_bulkalloc(512);
      }
  
      return ret;
  }
  
  static enum test_return cache_redzone_test(void)
  {
  #ifndef HAVE_UMEM_H
      cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
                                    NULL, NULL);
  
      /* Ignore SIGABRT */
      struct sigaction old_action;
      struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0};
      sigemptyset(&action.sa_mask);
      sigaction(SIGABRT, &action, &old_action);
  
  
      /* check memory debug.. */
      char *p = cache_alloc(cache);
      char old = *(p - 1);
      *(p - 1) = 0;
      cache_free(cache, p);
      assert(cache_error == -1);
      *(p - 1) = old;
  
      p[sizeof(uint32_t)] = 0;
      cache_free(cache, p);
      assert(cache_error == 1);
  
  
      /* restore signal handler */
      sigaction(SIGABRT, &old_action, NULL);
  
      cache_destroy(cache);
  
      return TEST_PASS;
  #else
      return TEST_SKIP;
  #endif
  }
  
  static enum test_return cache_limit_revised_downward_test(void)
  {
      int limit = 10, allocated_num = limit + 1, i;
      char ** alloc_objs = calloc(allocated_num, sizeof(char *));
  
      cache_t *cache = cache_create("test", sizeof(uint32_t), sizeof(char*),
                                    NULL, NULL);
      assert(cache != NULL);
  
      /* cache->limit is 0 and we can allocate limit+1 items */
      for (i = 0; i < allocated_num; i++) {
          alloc_objs[i] = cache_alloc(cache);
          assert(alloc_objs[i] != NULL);
      }
      assert(cache->total == allocated_num);
  
      /* revised downward cache->limit */
      cache_set_limit(cache, limit);
  
      /* If we free one item, the cache->total should decreased by one*/
      cache_free(cache, alloc_objs[0]);
  
      assert(cache->total == allocated_num-1);
      cache_destroy(cache);
  
      return TEST_PASS;
  }
  
  static enum test_return test_stats_prefix_find(void) {
      PREFIX_STATS *pfs1, *pfs2;
  
      stats_prefix_clear();
      pfs1 = stats_prefix_find("abc", 3);
      assert(pfs1 == NULL);
      pfs1 = stats_prefix_find("abc|", 4);
      assert(pfs1 == NULL);
  
      pfs1 = stats_prefix_find("abc:", 4);
      assert(pfs1 != NULL);
      assert(0ULL == (pfs1->num_gets + pfs1->num_sets + pfs1->num_deletes + pfs1->num_hits));
      pfs2 = stats_prefix_find("abc:", 4);
      assert(pfs1 == pfs2);
      pfs2 = stats_prefix_find("abc:d", 5);
      assert(pfs1 == pfs2);
      pfs2 = stats_prefix_find("xyz123:", 6);
      assert(pfs1 != pfs2);
      pfs2 = stats_prefix_find("ab:", 3);
      assert(pfs1 != pfs2);
      return TEST_PASS;
  }
  
  static enum test_return test_stats_prefix_record_get(void) {
      PREFIX_STATS *pfs;
      stats_prefix_clear();
  
      stats_prefix_record_get("abc:123", 7, false);
      pfs = stats_prefix_find("abc:123", 7);
      assert(1 == pfs->num_gets);
      assert(0 == pfs->num_hits);
      stats_prefix_record_get("abc:456", 7, false);
      assert(2 == pfs->num_gets);
      assert(0 == pfs->num_hits);
      stats_prefix_record_get("abc:456", 7, true);
      assert(3 == pfs->num_gets);
      assert(1 == pfs->num_hits);
      stats_prefix_record_get("def:", 4, true);
      assert(3 == pfs->num_gets);
      assert(1 == pfs->num_hits);
      return TEST_PASS;
  }
  
  static enum test_return test_stats_prefix_record_delete(void) {
      PREFIX_STATS *pfs;
      stats_prefix_clear();
  
      stats_prefix_record_delete("abc:123", 7);
      pfs = stats_prefix_find("abc:123", 7);
      assert(0 == pfs->num_gets);
      assert(0 == pfs->num_hits);
      assert(1 == pfs->num_deletes);
      assert(0 == pfs->num_sets);
      stats_prefix_record_delete("def:", 4);
      assert(1 == pfs->num_deletes);
      return TEST_PASS;
  }
  
  static enum test_return test_stats_prefix_record_set(void) {
      PREFIX_STATS *pfs;
      stats_prefix_clear();
  
      stats_prefix_record_set("abc:123", 7);
      pfs = stats_prefix_find("abc:123", 7);
      assert(0 == pfs->num_gets);
      assert(0 == pfs->num_hits);
      assert(0 == pfs->num_deletes);
      assert(1 == pfs->num_sets);
      stats_prefix_record_delete("def:", 4);
      assert(1 == pfs->num_sets);
      return TEST_PASS;
  }
  
  static enum test_return test_stats_prefix_dump(void) {
      int hashval = hash("abc", 3) % PREFIX_HASH_SIZE;
      char tmp[500];
      char *buf;
      const char *expected;
      int keynum;
      int length;
  
      stats_prefix_clear();
  
      assert(strcmp("END\r\n", (buf = stats_prefix_dump(&length))) == 0);
      assert(5 == length);
      stats_prefix_record_set("abc:123", 7);
      free(buf);
      expected = "PREFIX abc get 0 hit 0 set 1 del 0\r\nEND\r\n";
      assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
      assert(strlen(expected) == length);
      stats_prefix_record_get("abc:123", 7, false);
      free(buf);
      expected = "PREFIX abc get 1 hit 0 set 1 del 0\r\nEND\r\n";
      assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
      assert(strlen(expected) == length);
      stats_prefix_record_get("abc:123", 7, true);
      free(buf);
      expected = "PREFIX abc get 2 hit 1 set 1 del 0\r\nEND\r\n";
      assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
      assert(strlen(expected) == length);
      stats_prefix_record_delete("abc:123", 7);
      free(buf);
      expected = "PREFIX abc get 2 hit 1 set 1 del 1\r\nEND\r\n";
      assert(strcmp(expected, (buf = stats_prefix_dump(&length))) == 0);
      assert(strlen(expected) == length);
  
      stats_prefix_record_delete("def:123", 7);
      free(buf);
      /* NOTE: Prefixes can be dumped in any order, so we verify that
         each expected line is present in the string. */
      buf = stats_prefix_dump(&length);
      assert(strstr(buf, "PREFIX abc get 2 hit 1 set 1 del 1\r\n") != NULL);
      assert(strstr(buf, "PREFIX def get 0 hit 0 set 0 del 1\r\n") != NULL);
      assert(strstr(buf, "END\r\n") != NULL);
      free(buf);
  
      /* Find a key that hashes to the same bucket as "abc" */
      bool found_match = false;
      for (keynum = 0; keynum < PREFIX_HASH_SIZE * 100; keynum++) {
          snprintf(tmp, sizeof(tmp), "%d:", keynum);
          /* -1 because only the prefix portion is used when hashing */
          if (hashval == hash(tmp, strlen(tmp) - 1) % PREFIX_HASH_SIZE) {
              found_match = true;
              break;
          }
      }
      assert(found_match);
      stats_prefix_record_set(tmp, strlen(tmp));
      buf = stats_prefix_dump(&length);
      assert(strstr(buf, "PREFIX abc get 2 hit 1 set 1 del 1\r\n") != NULL);
      assert(strstr(buf, "PREFIX def get 0 hit 0 set 0 del 1\r\n") != NULL);
      assert(strstr(buf, "END\r\n") != NULL);
      snprintf(tmp, sizeof(tmp), "PREFIX %d get 0 hit 0 set 1 del 0\r\n", keynum);
      assert(strstr(buf, tmp) != NULL);
      free(buf);
  
      /* Marking the end of these tests */
      stats_prefix_clear();
  
      return TEST_PASS;
  }
  
  static enum test_return test_safe_strtoul(void) {
      uint32_t val;
      assert(safe_strtoul("123", &val));
      assert(val == 123);
      assert(safe_strtoul("+123", &val));
      assert(val == 123);
      assert(!safe_strtoul("", &val));  // empty
      assert(!safe_strtoul("123BOGUS", &val));  // non-numeric
      assert(!safe_strtoul(" issue221", &val));  // non-numeric
      /* Not sure what it does, but this works with ICC :/
         assert(!safe_strtoul("92837498237498237498029383", &val)); // out of range
      */
  
      // extremes:
      assert(safe_strtoul("4294967295", &val)); // 2**32 - 1
      assert(val == 4294967295L);
      /* This actually works on 64-bit ubuntu
         assert(!safe_strtoul("4294967296", &val)); // 2**32
      */
      assert(!safe_strtoul("-1", &val));  // negative
      return TEST_PASS;
  }
  
  
  static enum test_return test_safe_strtoull(void) {
      uint64_t val;
      assert(safe_strtoull("123", &val));
      assert(val == 123);
      assert(safe_strtoull("+123", &val));
      assert(val == 123);
      assert(!safe_strtoull("", &val));  // empty
      assert(!safe_strtoull("123BOGUS", &val));  // non-numeric
      assert(!safe_strtoull("92837498237498237498029383", &val)); // out of range
      assert(!safe_strtoull(" issue221", &val));  // non-numeric
  
      // extremes:
      assert(safe_strtoull("18446744073709551615", &val)); // 2**64 - 1
      assert(val == 18446744073709551615ULL);
      assert(!safe_strtoull("18446744073709551616", &val)); // 2**64
      assert(!safe_strtoull("-1", &val));  // negative
      return TEST_PASS;
  }
  
  static enum test_return test_safe_strtoll(void) {
      int64_t val;
      assert(safe_strtoll("123", &val));
      assert(val == 123);
      assert(safe_strtoll("+123", &val));
      assert(val == 123);
      assert(safe_strtoll("-123", &val));
      assert(val == -123);
      assert(!safe_strtoll("", &val));  // empty
      assert(!safe_strtoll("123BOGUS", &val));  // non-numeric
      assert(!safe_strtoll("92837498237498237498029383", &val)); // out of range
      assert(!safe_strtoll(" issue221", &val));  // non-numeric
  
      // extremes:
      assert(!safe_strtoll("18446744073709551615", &val)); // 2**64 - 1
      assert(safe_strtoll("9223372036854775807", &val)); // 2**63 - 1
      assert(val == 9223372036854775807LL);
      /*
        assert(safe_strtoll("-9223372036854775808", &val)); // -2**63
        assert(val == -9223372036854775808LL);
      */
      assert(!safe_strtoll("-9223372036854775809", &val)); // -2**63 - 1
  
      // We'll allow space to terminate the string.  And leading space.
      assert(safe_strtoll(" 123 foo", &val));
      assert(val == 123);
      return TEST_PASS;
  }
  
  static enum test_return test_safe_strtol(void) {
      int32_t val;
      assert(safe_strtol("123", &val));
      assert(val == 123);
      assert(safe_strtol("+123", &val));
      assert(val == 123);
      assert(safe_strtol("-123", &val));
      assert(val == -123);
      assert(!safe_strtol("", &val));  // empty
      assert(!safe_strtol("123BOGUS", &val));  // non-numeric
      assert(!safe_strtol("92837498237498237498029383", &val)); // out of range
      assert(!safe_strtol(" issue221", &val));  // non-numeric
  
      // extremes:
      /* This actually works on 64-bit ubuntu
         assert(!safe_strtol("2147483648", &val)); // (expt 2.0 31.0)
      */
      assert(safe_strtol("2147483647", &val)); // (- (expt 2.0 31) 1)
      assert(val == 2147483647L);
      /* This actually works on 64-bit ubuntu
         assert(!safe_strtol("-2147483649", &val)); // (- (expt -2.0 31) 1)
      */
  
      // We'll allow space to terminate the string.  And leading space.
      assert(safe_strtol(" 123 foo", &val));
      assert(val == 123);
      return TEST_PASS;
  }
  
  /**
   * Function to start the server and let it listen on a random port
   *
   * @param port_out where to store the TCP port number the server is
   *                 listening on
   * @param daemon set to true if you want to run the memcached server
   *               as a daemon process
   * @return the pid of the memcached server
   */
  static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) {
      char environment[80];
      snprintf(environment, sizeof(environment),
               "MEMCACHED_PORT_FILENAME="TMP_DIR"ports.%lu", (long)getpid());
      char *filename= environment + strlen("MEMCACHED_PORT_FILENAME=");
      char pid_file[80];
      snprintf(pid_file, sizeof(pid_file), TMP_DIR"pid.%lu", (long)getpid());
  
      remove(filename);
      remove(pid_file);
  
  #ifdef __sun
      /* I want to name the corefiles differently so that they don't
         overwrite each other
      */
      char coreadm[128];
      snprintf(coreadm, sizeof(coreadm),
               "coreadm -p core.%%f.%%p %lu", (unsigned long)getpid());
      system(coreadm);
  #endif
  
      pid_t pid;
  #ifndef _WIN32
      pid = fork();
      assert(pid != -1);
  
      if (pid == 0)
  #endif /* #ifndef _WIN32 */
      {
          /* Child */
          char *argv[24];
          int arg = 0;
          char tmo[24];
          snprintf(tmo, sizeof(tmo), "%u", timeout);
  
          putenv(environment);
  #ifdef __sun
          putenv("LD_PRELOAD=watchmalloc.so.1");
          putenv("MALLOC_DEBUG=WATCH");
  #endif
  
  #ifndef _WIN32
          if (!daemon) {
              argv[arg++] = TIMEDRUN_EXE;
              argv[arg++] = tmo;
          }
  #else
          /* In Windows, no such thing as killing a child process using a signal (e.g. SIGTERM) and the
           * child process can handle the termination using the signal. Process is killed immediately
           * via TerminateProcess (called internally by kill wrapper) unless an IPC mechanism is in
           * place before the TerminateProcess call. Therefore even creating a timedrun.exe won't solve
           * this issue unless memcached will add IPC to handle process kill gradefully from timedrun.exe.
           * To avoid adding IPC for the kill sequence, just don't use timedrun.
           * Ref: https://docs.microsoft.com/en-us/windows/win32/procthread/terminating-a-process
          */
  #endif /* #ifndef _WIN32 */
          argv[arg++] = MEMCACHED_EXE;
          argv[arg++] = "-A";
          argv[arg++] = "-p";
          argv[arg++] = "-1";
          argv[arg++] = "-U";
          argv[arg++] = "0";
  #ifdef TLS
          if (enable_ssl) {
              argv[arg++] = "-Z";
              argv[arg++] = "-o";
              argv[arg++] = "ssl_chain_cert=t/server_crt.pem";
              argv[arg++] = "-o";
              argv[arg++] = "ssl_key=t/server_key.pem";
          }
  #endif
  #ifndef DISABLE_RUNAS_USER
          /* Handle rpmbuild and the like doing this as root */
          if (getuid() == 0) {
              argv[arg++] = "-u";
              argv[arg++] = "root";
          }
  #endif /* #ifndef DISABLE_RUNAS_USER */
          if (daemon) {
              argv[arg++] = "-d";
              argv[arg++] = "-P";
              argv[arg++] = pid_file;
          }
  #ifdef MESSAGE_DEBUG
           argv[arg++] = "-vvv";
  #endif
  #ifdef HAVE_DROP_PRIVILEGES
          argv[arg++] = "-o";
          argv[arg++] = "relaxed_privileges";
  #endif
          argv[arg++] = NULL;
  #ifndef _WIN32
          assert(execv(argv[0], argv) != -1);
  #else
          assert(run_cmd_background(argv, &pid) != -1);
  #endif /* #ifndef _WIN32 */
      }
  
      /* Yeah just let us "busy-wait" for the file to be created ;-) */
      useconds_t wait_timeout = 1000000 * 10;
      useconds_t wait = 1000;
      while (access(filename, F_OK) == -1 && wait_timeout > 0) {
          usleep(wait);
          wait_timeout -= (wait > wait_timeout ? wait_timeout : wait);
      }
  
      if (access(filename, F_OK) == -1) {
          fprintf(stderr, "Failed to start the memcached server.\n");
          assert(false);
      }
  
      FILE *fp = fopen(filename, "r");
      if (fp == NULL) {
          fprintf(stderr, "Failed to open the file containing port numbers: %s\n",
                  strerror(errno));
          assert(false);
      }
  
      *port_out = (in_port_t)-1;
      char buffer[80];
      while ((fgets(buffer, sizeof(buffer), fp)) != NULL) {
          if (strncmp(buffer, "TCP INET: ", 10) == 0) {
              int32_t val;
              assert(safe_strtol(buffer + 10, &val));
              *port_out = (in_port_t)val;
          }
      }
      fclose(fp);
      assert(remove(filename) == 0);
  
      if (daemon) {
          /* loop and wait for the pid file.. There is a potential race
           * condition that the server just created the file but isn't
           * finished writing the content, so we loop a few times
           * reading as well */
          while (access(pid_file, F_OK) == -1) {
              usleep(10);
          }
  
          fp = fopen(pid_file, "r");
          if (fp == NULL) {
              fprintf(stderr, "Failed to open pid file: %s\n",
                      strerror(errno));
              assert(false);
          }
  
          /* Avoid race by retrying 20 times */
          for (int x = 0; x < 20 && fgets(buffer, sizeof(buffer), fp) == NULL; x++) {
              usleep(10);
          }
          fclose(fp);
  
          int32_t val;
          assert(safe_strtol(buffer, &val));
          pid = (pid_t)val;
      }
  
      return pid;
  }
  
  static enum test_return test_issue_44(void) {
      in_port_t port;
      pid_t pid = start_server(&port, true, 15);
  #ifndef WINDOWS_ONLY_SIGNALS
      assert(kill(pid, SIGHUP) == 0);
      sleep(1);
      assert(kill(pid, SIGTERM) == 0);
  #else
      /* SIGTERM signal is not generated under Windows. It is included for
       * ANSI compatibility. Although any signal is ignored internally in Windows
       * version of kill, SIGINT is used here since Windows generates this if a
       * process is terminated via Ctrl+C in command prompt. This is just an
       * emphasis that SIGTERM is unsupported in Windows although defined unless
       * raised internally by application. memcached only raise(SIGINT).
       * Ref: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal?view=vs-2019
       **/
      assert(kill(pid, SIGINT) == 0);
  #endif /* #ifndef WINDOWS_ONLY_SIGNALS */
  
      return TEST_PASS;
  }
  
  static struct addrinfo *lookuphost(const char *hostname, in_port_t port)
  {
      struct addrinfo *ai = 0;
      struct addrinfo hints = { .ai_family = AF_UNSPEC,
                                .ai_protocol = IPPROTO_TCP,
                                .ai_socktype = SOCK_STREAM };
      char service[NI_MAXSERV];
      int error;
  
      (void)snprintf(service, NI_MAXSERV, "%d", port);
      if ((error = getaddrinfo(hostname, service, &hints, &ai)) != 0) {
         if (error != EAI_SYSTEM) {
            fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
         } else {
            perror("getaddrinfo()");
         }
      }
  
      return ai;
  }
  
  static struct conn *connect_server(const char *hostname, in_port_t port,
                              bool nonblock, const bool ssl)
  {
      struct conn *c;
      if (!(c = (struct conn *)calloc(1, sizeof(struct conn)))) {
          fprintf(stderr, "Failed to allocate the client connection: %s\n",
                  strerror(errno));
          return NULL;
      }
  
      struct addrinfo *ai = lookuphost(hostname, port);
      int sock = -1;
      if (ai != NULL) {
         if ((sock = socket(ai->ai_family, ai->ai_socktype,
                            ai->ai_protocol)) != -1) {
            if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) {
               fprintf(stderr, "Failed to connect socket: %s\n",
                       strerror(errno));
               sock_close(sock);
               sock = -1;
            } else if (nonblock) {
  #ifndef _WIN32
                int flags = fcntl(sock, F_GETFL, 0);
                if (flags < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
  #else
                u_long flags = 1;
                if (ioctlsocket(sock, FIONBIO, &flags) < 0) {
  #endif /* #ifndef _WIN32 */
                    fprintf(stderr, "Failed to enable nonblocking mode: %s\n",
                            strerror(errno));
                    sock_close(sock);
                    sock = -1;
                }
            }
         } else {
            fprintf(stderr, "Failed to create socket: %s\n", strerror(errno));
         }
  
         freeaddrinfo(ai);
      }
      c->sock = sock;
  #ifdef TLS
      if (sock > 0 && ssl) {
          c->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
          if (c->ssl_ctx == NULL) {
              fprintf(stderr, "Failed to create the SSL context: %s\n",
                  strerror(errno));
              sock_close(sock);
              sock = -1;
          }
          c->ssl = SSL_new(c->ssl_ctx);
          if (c->ssl == NULL) {
              fprintf(stderr, "Failed to create the SSL object: %s\n",
                  strerror(errno));
              sock_close(sock);
              sock = -1;
          }
          SSL_set_fd (c->ssl, c->sock);
  #ifndef _WIN32
          int ret = SSL_connect(c->ssl);
  #else
          int ret = WinSSL_connect(c->ssl);
  #endif /* #ifndef _WIN32 */
          if (ret < 0) {
              int err = SSL_get_error(c->ssl, ret);
              if (err == SSL_ERROR_SYSCALL || err == SSL_ERROR_SSL) {
                  fprintf(stderr, "SSL connection failed with error code : %s\n",
                      strerror(errno));
                  sock_close(sock);
                  sock = -1;
              }
          }
          c->read = ssl_read;
          c->write = ssl_write;
      } else
  #endif
      {
          c->read = tcp_read;
          c->write = tcp_write;
      }
      return c;
  }
  
  static enum test_return test_vperror(void) {
      int rv = 0;
      int oldstderr = dup(STDERR_FILENO);
      assert(oldstderr >= 0);
      char tmpl[sizeof(TMP_TEMPLATE)+1];
      strncpy(tmpl, TMP_TEMPLATE, sizeof(TMP_TEMPLATE)+1);
  
      int newfile = mkstemp(tmpl);
      assert(newfile > 0);
      rv = dup2(newfile, STDERR_FILENO);
  #ifndef _WIN32
      assert(rv == STDERR_FILENO);
  #else
      /* MinGW has a bug(or specs?) that dup2 success returns 0 instead of the newfd
       * Ref: https://lists.gnu.org/archive/html/bug-gnulib/2009-07/msg00077.html
       */
       assert(rv == 0);
  #endif /* #ifndef _WIN32 */
      rv = close(newfile);
      assert(rv == 0);
  
      errno = EIO;
      vperror("Old McDonald had a farm.  %s", "EI EIO");
  
      /* Restore stderr */
      rv = dup2(oldstderr, STDERR_FILENO);
  #ifndef _WIN32
      assert(rv == STDERR_FILENO);
  #else
      assert(rv == 0);
  #endif /* #ifndef _WIN32 */
  
      /* Go read the file */
      char buf[80] = { 0 };
      FILE *efile = fopen(tmpl, "r");
      assert(efile);
      char *prv = fgets(buf, sizeof(buf), efile);
      assert(prv);
      fclose(efile);
  
      unlink(tmpl);
  
      char expected[80] = { 0 };
      snprintf(expected, sizeof(expected),
               "Old McDonald had a farm.  EI EIO: %s\n", strerror(EIO));
  
      /*
      fprintf(stderr,
              "\nExpected:  ``%s''"
              "\nGot:       ``%s''\n", expected, buf);
      */
  
      return strcmp(expected, buf) == 0 ? TEST_PASS : TEST_FAIL;
  }
  
  static void send_ascii_command(const char *buf) {
      off_t offset = 0;
      const char* ptr = buf;
      size_t len = strlen(buf);
  
      do {
          ssize_t nw = con->write((void*)con, ptr + offset, len - offset);
          if (nw == -1) {
              if (errno != EINTR) {
                  fprintf(stderr, "Failed to write: %s\n", strerror(errno));
                  abort();
              }
          } else {
              offset += nw;
          }
      } while (offset < len);
  }
  
  /*
   * This is a dead slow single byte read, but it should only read out
   * _one_ response and I don't have an input buffer... The current
   * implementation only supports single-line responses, so if you want to use
   * it for get commands you need to implement that first ;-)
   */
  static void read_ascii_response(char *buffer, size_t size) {
      off_t offset = 0;
      bool need_more = true;
      do {
          ssize_t nr = con->read(con, buffer + offset, 1);
          if (nr == -1) {
              if (errno != EINTR) {
                  fprintf(stderr, "Failed to read: %s\n", strerror(errno));
                  abort();
              }
          } else {
              assert(nr == 1);
              if (buffer[offset] == '\n') {
                  need_more = false;
                  buffer[offset + 1] = '\0';
              }
              offset += nr;
              assert(offset + 1 < size);
          }
      } while (need_more);
  }
  
  static enum test_return test_issue_92(void) {
      char buffer[1024];
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      send_ascii_command("stats cachedump 1 0 0\r\n");
  
      read_ascii_response(buffer, sizeof(buffer));
      assert(strncmp(buffer, "END", strlen("END")) == 0);
  
      send_ascii_command("stats cachedump 200 0 0\r\n");
      read_ascii_response(buffer, sizeof(buffer));
      assert(strncmp(buffer, "CLIENT_ERROR", strlen("CLIENT_ERROR")) == 0);
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
      return TEST_PASS;
  }
  
  static enum test_return test_issue_102(void) {
      char buffer[4096];
      memset(buffer, ' ', sizeof(buffer));
      buffer[sizeof(buffer) - 1] = '\0';
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      send_ascii_command(buffer);
      /* verify that the server closed the connection */
      assert(con->read(con, buffer, sizeof(buffer)) == 0);
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      snprintf(buffer, sizeof(buffer), "gets ");
      size_t offset = 5;
      while (offset < 4000) {
          offset += snprintf(buffer + offset, sizeof(buffer) - offset,
                             "%010u ", (unsigned int)offset);
      }
  
      send_ascii_command(buffer);
      usleep(250);
  
      send_ascii_command("\r\n");
      char rsp[80];
      read_ascii_response(rsp, sizeof(rsp));
      assert(strncmp(rsp, "END", strlen("END")) == 0);
      buffer[3]= ' ';
      send_ascii_command(buffer);
      usleep(250);
      send_ascii_command("\r\n");
      read_ascii_response(rsp, sizeof(rsp));
      assert(strncmp(rsp, "END", strlen("END")) == 0);
  
      memset(buffer, ' ', sizeof(buffer));
      int len = snprintf(buffer + 101, sizeof(buffer) - 101, "gets foo");
      buffer[101 + len] = ' ';
      buffer[sizeof(buffer) - 1] = '\0';
      send_ascii_command(buffer);
      /* verify that the server closed the connection */
      assert(con->read(con, buffer, sizeof(buffer)) == 0);
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      return TEST_PASS;
  }
  
  static enum test_return start_memcached_server(void) {
      server_pid = start_server(&port, false, 600);
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
      return TEST_PASS;
  }
  
  static enum test_return stop_memcached_server(void) {
      close_conn();
      if (server_pid != -1) {
  #ifndef _WIN32
          assert(kill(server_pid, SIGTERM) == 0);
  #else
          /* Windows returns -1 with ESRCH errno if server is already shutdown */
          assert(kill(server_pid, SIGTERM) == -1);
  #endif /* #ifndef _WIN32 */
      }
  
      return TEST_PASS;
  }
  
  static enum test_return shutdown_memcached_server(void) {
      char buffer[1024];
  
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      send_ascii_command("shutdown\r\n");
      /* verify that the server closed the connection */
      assert(con->read(con, buffer, sizeof(buffer)) == 0);
  
      close_conn();
  
      /* We set server_pid to -1 so that we don't later call kill() */
      if (kill(server_pid, 0) == 0) {
          server_pid = -1;
      }
  
      return TEST_PASS;
  }
  
  static void safe_send(const void* buf, size_t len, bool hickup)
  {
      off_t offset = 0;
      const char* ptr = buf;
  #ifdef MESSAGE_DEBUG
      uint8_t val = *ptr;
      assert(val == (uint8_t)0x80);
      fprintf(stderr, "About to send %lu bytes:", (unsigned long)len);
      for (int ii = 0; ii < len; ++ii) {
          if (ii % 4 == 0) {
              fprintf(stderr, "\n   ");
          }
          val = *(ptr + ii);
          fprintf(stderr, " 0x%02x", val);
      }
      fprintf(stderr, "\n");
      usleep(500);
  #endif
  
      do {
          size_t num_bytes = len - offset;
          if (hickup) {
              if (num_bytes > 1024) {
                  num_bytes = (rand() % 1023) + 1;
              }
          }
          ssize_t nw = con->write(con, ptr + offset, num_bytes);
          if (nw == -1) {
              if (errno != EINTR) {
                  fprintf(stderr, "Failed to write: %s\n", strerror(errno));
                  abort();
              }
          } else {
              if (hickup) {
                  usleep(100);
              }
              offset += nw;
          }
      } while (offset < len);
  }
  
  static bool safe_recv(void *buf, size_t len) {
      if (len == 0) {
          return true;
      }
      off_t offset = 0;
      do {
          ssize_t nr = con->read(con, ((char*)buf) + offset, len - offset);
          if (nr == -1) {
              if (errno != EINTR) {
                  fprintf(stderr, "Failed to read: %s\n", strerror(errno));
                  abort();
              }
          } else {
              if (nr == 0 && allow_closed_read) {
                  return false;
              }
              assert(nr != 0);
              offset += nr;
          }
      } while (offset < len);
  
      return true;
  }
  
  static bool safe_recv_packet(void *buf, size_t size) {
      protocol_binary_response_no_extras *response = buf;
      assert(size > sizeof(*response));
      if (!safe_recv(response, sizeof(*response))) {
          return false;
      }
      response->message.header.response.keylen = ntohs(response->message.header.response.keylen);
      response->message.header.response.status = ntohs(response->message.header.response.status);
      response->message.header.response.bodylen = ntohl(response->message.header.response.bodylen);
  
      size_t len = sizeof(*response);
  
      char *ptr = buf;
      ptr += len;
      if (!safe_recv(ptr, response->message.header.response.bodylen)) {
          return false;
      }
  
  #ifdef MESSAGE_DEBUG
      usleep(500);
      ptr = buf;
      len += response->message.header.response.bodylen;
      uint8_t val = *ptr;
      assert(val == (uint8_t)0x81);
      fprintf(stderr, "Received %lu bytes:", (unsigned long)len);
      for (int ii = 0; ii < len; ++ii) {
          if (ii % 4 == 0) {
              fprintf(stderr, "\n   ");
          }
          val = *(ptr + ii);
          fprintf(stderr, " 0x%02x", val);
      }
      fprintf(stderr, "\n");
  #endif
      return true;
  }
  
  static off_t storage_command(char*buf,
                               size_t bufsz,
                               uint8_t cmd,
                               const void* key,
                               size_t keylen,
                               const void* dta,
                               size_t dtalen,
                               uint32_t flags,
                               uint32_t exp) {
      /* all of the storage commands use the same command layout */
      protocol_binary_request_set *request = (void*)buf;
      assert(bufsz > sizeof(*request) + keylen + dtalen);
  
      memset(request, 0, sizeof(*request));
      request->message.header.request.magic = PROTOCOL_BINARY_REQ;
      request->message.header.request.opcode = cmd;
      request->message.header.request.keylen = htons(keylen);
      request->message.header.request.extlen = 8;
      request->message.header.request.bodylen = htonl(keylen + 8 + dtalen);
      request->message.header.request.opaque = 0xdeadbeef;
      request->message.body.flags = flags;
      request->message.body.expiration = exp;
  
      off_t key_offset = sizeof(protocol_binary_request_no_extras) + 8;
  
      memcpy(buf + key_offset, key, keylen);
      if (dta != NULL) {
          memcpy(buf + key_offset + keylen, dta, dtalen);
      }
  
      return key_offset + keylen + dtalen;
  }
  
  static off_t ext_command(char* buf,
                           size_t bufsz,
                           uint8_t cmd,
                           const void* ext,
                           size_t extlen,
                           const void* key,
                           size_t keylen,
                           const void* dta,
                           size_t dtalen) {
      protocol_binary_request_no_extras *request = (void*)buf;
      assert(bufsz > sizeof(*request) + extlen + keylen + dtalen);
  
      memset(request, 0, sizeof(*request));
      request->message.header.request.magic = PROTOCOL_BINARY_REQ;
      request->message.header.request.opcode = cmd;
      request->message.header.request.extlen = extlen;
      request->message.header.request.keylen = htons(keylen);
      request->message.header.request.bodylen = htonl(extlen + keylen + dtalen);
      request->message.header.request.opaque = 0xdeadbeef;
  
      off_t ext_offset = sizeof(protocol_binary_request_no_extras);
      off_t key_offset = ext_offset + extlen;
      off_t dta_offset = key_offset + keylen;
  
      if (ext != NULL) {
          memcpy(buf + ext_offset, ext, extlen);
      }
      if (key != NULL) {
          memcpy(buf + key_offset, key, keylen);
      }
      if (dta != NULL) {
          memcpy(buf + dta_offset, dta, dtalen);
      }
  
      return sizeof(*request) + extlen + keylen + dtalen;
  }
  
  static off_t raw_command(char* buf,
                           size_t bufsz,
                           uint8_t cmd,
                           const void* key,
                           size_t keylen,
                           const void* dta,
                           size_t dtalen) {
      /* all of the storage commands use the same command layout */
      return ext_command(buf, bufsz, cmd, NULL, 0, key, keylen, dta, dtalen);
  }
  
  static off_t flush_command(char* buf, size_t bufsz, uint8_t cmd, uint32_t exptime, bool use_extra) {
      protocol_binary_request_flush *request = (void*)buf;
      assert(bufsz > sizeof(*request));
  
      memset(request, 0, sizeof(*request));
      request->message.header.request.magic = PROTOCOL_BINARY_REQ;
      request->message.header.request.opcode = cmd;
  
      off_t size = sizeof(protocol_binary_request_no_extras);
      if (use_extra) {
          request->message.header.request.extlen = 4;
          request->message.body.expiration = htonl(exptime);
          request->message.header.request.bodylen = htonl(4);
          size += 4;
      }
  
      request->message.header.request.opaque = 0xdeadbeef;
  
      return size;
  }
  
  
  static off_t touch_command(char* buf,
                             size_t bufsz,
                             uint8_t cmd,
                             const void* key,
                             size_t keylen,
                             uint32_t exptime) {
      protocol_binary_request_touch *request = (void*)buf;
      assert(bufsz > sizeof(*request));
  
      memset(request, 0, sizeof(*request));
      request->message.header.request.magic = PROTOCOL_BINARY_REQ;
      request->message.header.request.opcode = cmd;
  
      request->message.header.request.keylen = htons(keylen);
      request->message.header.request.extlen = 4;
      request->message.body.expiration = htonl(exptime);
      request->message.header.request.bodylen = htonl(keylen + 4);
  
      request->message.header.request.opaque = 0xdeadbeef;
  
      off_t key_offset = sizeof(protocol_binary_request_no_extras) + 4;
  
      memcpy(buf + key_offset, key, keylen);
      return sizeof(protocol_binary_request_no_extras) + 4 + keylen;
  }
  
  static off_t arithmetic_command(char* buf,
                                  size_t bufsz,
                                  uint8_t cmd,
                                  const void* key,
                                  size_t keylen,
                                  uint64_t delta,
                                  uint64_t initial,
                                  uint32_t exp) {
      protocol_binary_request_incr *request = (void*)buf;
      assert(bufsz > sizeof(*request) + keylen);
  
      memset(request, 0, sizeof(*request));
      request->message.header.request.magic = PROTOCOL_BINARY_REQ;
      request->message.header.request.opcode = cmd;
      request->message.header.request.keylen = htons(keylen);
      request->message.header.request.extlen = 20;
      request->message.header.request.bodylen = htonl(keylen + 20);
      request->message.header.request.opaque = 0xdeadbeef;
      request->message.body.delta = htonll(delta);
      request->message.body.initial = htonll(initial);
      request->message.body.expiration = htonl(exp);
  
      off_t key_offset = sizeof(protocol_binary_request_no_extras) + 20;
  
      memcpy(buf + key_offset, key, keylen);
      return key_offset + keylen;
  }
  
  static void validate_response_header(protocol_binary_response_no_extras *response,
                                       uint8_t cmd, uint16_t status)
  {
      assert(response->message.header.response.magic == PROTOCOL_BINARY_RES);
      assert(response->message.header.response.opcode == cmd);
      assert(response->message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);
      assert(response->message.header.response.status == status);
      assert(response->message.header.response.opaque == 0xdeadbeef);
  
      if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
          switch (cmd) {
          case PROTOCOL_BINARY_CMD_ADDQ:
          case PROTOCOL_BINARY_CMD_APPENDQ:
          case PROTOCOL_BINARY_CMD_DECREMENTQ:
          case PROTOCOL_BINARY_CMD_DELETEQ:
          case PROTOCOL_BINARY_CMD_FLUSHQ:
          case PROTOCOL_BINARY_CMD_INCREMENTQ:
          case PROTOCOL_BINARY_CMD_PREPENDQ:
          case PROTOCOL_BINARY_CMD_QUITQ:
          case PROTOCOL_BINARY_CMD_REPLACEQ:
          case PROTOCOL_BINARY_CMD_SETQ:
              assert("Quiet command shouldn't return on success" == NULL);
          default:
              break;
          }
  
          switch (cmd) {
          case PROTOCOL_BINARY_CMD_ADD:
          case PROTOCOL_BINARY_CMD_REPLACE:
          case PROTOCOL_BINARY_CMD_SET:
          case PROTOCOL_BINARY_CMD_APPEND:
          case PROTOCOL_BINARY_CMD_PREPEND:
              assert(response->message.header.response.keylen == 0);
              assert(response->message.header.response.extlen == 0);
              assert(response->message.header.response.bodylen == 0);
              assert(response->message.header.response.cas != 0);
              break;
          case PROTOCOL_BINARY_CMD_FLUSH:
          case PROTOCOL_BINARY_CMD_NOOP:
          case PROTOCOL_BINARY_CMD_QUIT:
          case PROTOCOL_BINARY_CMD_DELETE:
              assert(response->message.header.response.keylen == 0);
              assert(response->message.header.response.extlen == 0);
              assert(response->message.header.response.bodylen == 0);
              assert(response->message.header.response.cas == 0);
              break;
  
          case PROTOCOL_BINARY_CMD_DECREMENT:
          case PROTOCOL_BINARY_CMD_INCREMENT:
              assert(response->message.header.response.keylen == 0);
              assert(response->message.header.response.extlen == 0);
              assert(response->message.header.response.bodylen == 8);
              assert(response->message.header.response.cas != 0);
              break;
  
          case PROTOCOL_BINARY_CMD_STAT:
              assert(response->message.header.response.extlen == 0);
              /* key and value exists in all packets except in the terminating */
              assert(response->message.header.response.cas == 0);
              break;
  
          case PROTOCOL_BINARY_CMD_VERSION:
              assert(response->message.header.response.keylen == 0);
              assert(response->message.header.response.extlen == 0);
              assert(response->message.header.response.bodylen != 0);
              assert(response->message.header.response.cas == 0);
              break;
  
          case PROTOCOL_BINARY_CMD_GET:
          case PROTOCOL_BINARY_CMD_GETQ:
          case PROTOCOL_BINARY_CMD_GAT:
          case PROTOCOL_BINARY_CMD_GATQ:
              assert(response->message.header.response.keylen == 0);
              assert(response->message.header.response.extlen == 4);
              assert(response->message.header.response.cas != 0);
              break;
  
          case PROTOCOL_BINARY_CMD_GETK:
          case PROTOCOL_BINARY_CMD_GETKQ:
          case PROTOCOL_BINARY_CMD_GATK:
          case PROTOCOL_BINARY_CMD_GATKQ:
              assert(response->message.header.response.keylen != 0);
              assert(response->message.header.response.extlen == 4);
              assert(response->message.header.response.cas != 0);
              break;
  
          default:
              /* Undefined command code */
              break;
          }
      } else {
          assert(response->message.header.response.cas == 0);
          assert(response->message.header.response.extlen == 0);
          if (cmd != PROTOCOL_BINARY_CMD_GETK &&
              cmd != PROTOCOL_BINARY_CMD_GATK) {
              assert(response->message.header.response.keylen == 0);
          }
      }
  }
  
  static enum test_return test_binary_noop(void) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } buffer;
  
      size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
                               PROTOCOL_BINARY_CMD_NOOP,
                               NULL, 0, NULL, 0);
  
      safe_send(buffer.bytes, len, false);
      safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
      validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_NOOP,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_quit_impl(uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } buffer;
      size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
                               cmd, NULL, 0, NULL, 0);
  
      safe_send(buffer.bytes, len, false);
      if (cmd == PROTOCOL_BINARY_CMD_QUIT) {
          safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
          validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_QUIT,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      }
  
      /* Socket should be closed now, read should return 0 */
      assert(con->read(con, buffer.bytes, sizeof(buffer.bytes)) == 0);
      close_conn();
      con = connect_server("127.0.0.1", port, false, enable_ssl);
      assert(con);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_quit(void) {
      return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUIT);
  }
  
  static enum test_return test_binary_quitq(void) {
      return test_binary_quit_impl(PROTOCOL_BINARY_CMD_QUITQ);
  }
  
  static enum test_return test_binary_set_impl(const char *key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
      uint64_t value = 0xdeadbeefdeadcafe;
      size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
                                   key, strlen(key), &value, sizeof(value),
                                   0, 0);
  
      /* Set should work over and over again */
      int ii;
      for (ii = 0; ii < 10; ++ii) {
          safe_send(send.bytes, len, false);
          if (cmd == PROTOCOL_BINARY_CMD_SET) {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response, cmd,
                                       PROTOCOL_BINARY_RESPONSE_SUCCESS);
          }
      }
  
      if (cmd == PROTOCOL_BINARY_CMD_SETQ) {
          return test_binary_noop();
      }
  
      send.request.message.header.request.cas = receive.response.message.header.response.cas;
      safe_send(send.bytes, len, false);
      if (cmd == PROTOCOL_BINARY_CMD_SET) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, cmd,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
          assert(receive.response.message.header.response.cas != send.request.message.header.request.cas);
      } else {
          return test_binary_noop();
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_set(void) {
      return test_binary_set_impl("test_binary_set", PROTOCOL_BINARY_CMD_SET);
  }
  
  static enum test_return test_binary_setq(void) {
      return test_binary_set_impl("test_binary_setq", PROTOCOL_BINARY_CMD_SETQ);
  }
  
  
  static enum test_return test_binary_add_impl(const char *key, uint8_t cmd) {
      uint64_t value = 0xdeadbeefdeadcafe;
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
      size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd, key,
                                   strlen(key), &value, sizeof(value),
                                   0, 0);
  
      /* Add should only work the first time */
      int ii;
      for (ii = 0; ii < 10; ++ii) {
          safe_send(send.bytes, len, false);
          if (ii == 0) {
              if (cmd == PROTOCOL_BINARY_CMD_ADD) {
                  safe_recv_packet(receive.bytes, sizeof(receive.bytes));
                  validate_response_header(&receive.response, cmd,
                                           PROTOCOL_BINARY_RESPONSE_SUCCESS);
              }
          } else {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response, cmd,
                                       PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS);
          }
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_add(void) {
      return test_binary_add_impl("test_binary_add", PROTOCOL_BINARY_CMD_ADD);
  }
  
  static enum test_return test_binary_addq(void) {
      return test_binary_add_impl("test_binary_addq", PROTOCOL_BINARY_CMD_ADDQ);
  }
  
  static enum test_return test_binary_replace_impl(const char* key, uint8_t cmd) {
      uint64_t value = 0xdeadbeefdeadcafe;
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
      size_t len = storage_command(send.bytes, sizeof(send.bytes), cmd,
                                   key, strlen(key), &value, sizeof(value),
                                   0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
      len = storage_command(send.bytes, sizeof(send.bytes),
                            PROTOCOL_BINARY_CMD_ADD,
                            key, strlen(key), &value, sizeof(value), 0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      len = storage_command(send.bytes, sizeof(send.bytes), cmd,
                            key, strlen(key), &value, sizeof(value), 0, 0);
      int ii;
      for (ii = 0; ii < 10; ++ii) {
          safe_send(send.bytes, len, false);
          if (cmd == PROTOCOL_BINARY_CMD_REPLACE) {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response,
                                       PROTOCOL_BINARY_CMD_REPLACE,
                                       PROTOCOL_BINARY_RESPONSE_SUCCESS);
          }
      }
  
      if (cmd == PROTOCOL_BINARY_CMD_REPLACEQ) {
          test_binary_noop();
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_replace(void) {
      return test_binary_replace_impl("test_binary_replace",
                                      PROTOCOL_BINARY_CMD_REPLACE);
  }
  
  static enum test_return test_binary_replaceq(void) {
      return test_binary_replace_impl("test_binary_replaceq",
                                      PROTOCOL_BINARY_CMD_REPLACEQ);
  }
  
  static enum test_return test_binary_delete_impl(const char *key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
      size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
                               key, strlen(key), NULL, 0);
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
      len = storage_command(send.bytes, sizeof(send.bytes),
                            PROTOCOL_BINARY_CMD_ADD,
                            key, strlen(key), NULL, 0, 0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      len = raw_command(send.bytes, sizeof(send.bytes),
                        cmd, key, strlen(key), NULL, 0);
      safe_send(send.bytes, len, false);
  
      if (cmd == PROTOCOL_BINARY_CMD_DELETE) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_DELETE,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      }
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_delete(void) {
      return test_binary_delete_impl("test_binary_delete",
                                     PROTOCOL_BINARY_CMD_DELETE);
  }
  
  static enum test_return test_binary_deleteq(void) {
      return test_binary_delete_impl("test_binary_deleteq",
                                     PROTOCOL_BINARY_CMD_DELETEQ);
  }
  
  static enum test_return test_binary_get_impl(const char *key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
  
      uint32_t expiration = htonl(3600);
      size_t extlen = 0;
      if (cmd == PROTOCOL_BINARY_CMD_GAT || cmd == PROTOCOL_BINARY_CMD_GATK)
          extlen = sizeof(expiration);
  
      size_t len = ext_command(send.bytes, sizeof(send.bytes), cmd,
                               extlen ? &expiration : NULL, extlen,
                               key, strlen(key), NULL, 0);
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
  
      len = storage_command(send.bytes, sizeof(send.bytes),
                            PROTOCOL_BINARY_CMD_ADD,
                            key, strlen(key), NULL, 0,
                            0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      /* run a little pipeline test ;-) */
      len = 0;
      int ii;
      for (ii = 0; ii < 10; ++ii) {
          union {
              protocol_binary_request_no_extras request;
              char bytes[1024];
          } temp;
          size_t l = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
                                 extlen ? &expiration : NULL, extlen,
                                 key, strlen(key), NULL, 0);
          memcpy(send.bytes + len, temp.bytes, l);
          len += l;
      }
  
      safe_send(send.bytes, len, false);
      for (ii = 0; ii < 10; ++ii) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, cmd,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_get(void) {
      return test_binary_get_impl("test_binary_get", PROTOCOL_BINARY_CMD_GET);
  }
  
  static enum test_return test_binary_getk(void) {
      return test_binary_get_impl("test_binary_getk", PROTOCOL_BINARY_CMD_GETK);
  }
  
  static enum test_return test_binary_gat(void) {
      return test_binary_get_impl("test_binary_gat", PROTOCOL_BINARY_CMD_GAT);
  }
  
  static enum test_return test_binary_gatk(void) {
      return test_binary_get_impl("test_binary_gatk", PROTOCOL_BINARY_CMD_GATK);
  }
  
  static enum test_return test_binary_getq_impl(const char *key, uint8_t cmd) {
      const char *missing = "test_binary_getq_missing";
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, temp, receive;
  
      uint32_t expiration = htonl(3600);
      size_t extlen = 0;
      if (cmd == PROTOCOL_BINARY_CMD_GATQ || cmd == PROTOCOL_BINARY_CMD_GATKQ)
          extlen = sizeof(expiration);
  
      size_t len = storage_command(send.bytes, sizeof(send.bytes),
                                   PROTOCOL_BINARY_CMD_ADD,
                                   key, strlen(key), NULL, 0,
                                   0, 0);
      size_t len2 = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
                                extlen ? &expiration : NULL, extlen,
                                missing, strlen(missing), NULL, 0);
      /* I need to change the first opaque so that I can separate the two
       * return packets */
      temp.request.message.header.request.opaque = 0xfeedface;
      memcpy(send.bytes + len, temp.bytes, len2);
      len += len2;
  
      len2 = ext_command(temp.bytes, sizeof(temp.bytes), cmd,
                         extlen ? &expiration : NULL, extlen,
                         key, strlen(key), NULL, 0);
      memcpy(send.bytes + len, temp.bytes, len2);
      len += len2;
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
      /* The first GETQ shouldn't return anything */
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_getq(void) {
      return test_binary_getq_impl("test_binary_getq", PROTOCOL_BINARY_CMD_GETQ);
  }
  
  static enum test_return test_binary_getkq(void) {
      return test_binary_getq_impl("test_binary_getkq", PROTOCOL_BINARY_CMD_GETKQ);
  }
  
  static enum test_return test_binary_gatq(void) {
      return test_binary_getq_impl("test_binary_gatq", PROTOCOL_BINARY_CMD_GATQ);
  }
  
  static enum test_return test_binary_gatkq(void) {
      return test_binary_getq_impl("test_binary_gatkq", PROTOCOL_BINARY_CMD_GATKQ);
  }
  
  static enum test_return test_binary_incr_impl(const char* key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response_header;
          protocol_binary_response_incr response;
          char bytes[1024];
      } send, receive;
      size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
                                      key, strlen(key), 1, 0, 0);
  
      int ii;
      for (ii = 0; ii < 10; ++ii) {
          safe_send(send.bytes, len, false);
          if (cmd == PROTOCOL_BINARY_CMD_INCREMENT) {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response_header, cmd,
                                       PROTOCOL_BINARY_RESPONSE_SUCCESS);
              assert(ntohll(receive.response.message.body.value) == ii);
          }
      }
  
      if (cmd == PROTOCOL_BINARY_CMD_INCREMENTQ) {
          test_binary_noop();
      }
      return TEST_PASS;
  }
  
  static enum test_return test_binary_incr(void) {
      return test_binary_incr_impl("test_binary_incr",
                                   PROTOCOL_BINARY_CMD_INCREMENT);
  }
  
  static enum test_return test_binary_incrq(void) {
      return test_binary_incr_impl("test_binary_incrq",
                                   PROTOCOL_BINARY_CMD_INCREMENTQ);
  }
  
  static enum test_return test_binary_decr_impl(const char* key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response_header;
          protocol_binary_response_decr response;
          char bytes[1024];
      } send, receive;
      size_t len = arithmetic_command(send.bytes, sizeof(send.bytes), cmd,
                                      key, strlen(key), 1, 9, 0);
  
      int ii;
      for (ii = 9; ii >= 0; --ii) {
          safe_send(send.bytes, len, false);
          if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response_header, cmd,
                                       PROTOCOL_BINARY_RESPONSE_SUCCESS);
              assert(ntohll(receive.response.message.body.value) == ii);
          }
      }
  
      /* decr on 0 should not wrap */
      safe_send(send.bytes, len, false);
      if (cmd == PROTOCOL_BINARY_CMD_DECREMENT) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response_header, cmd,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
          assert(ntohll(receive.response.message.body.value) == 0);
      } else {
          test_binary_noop();
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_decr(void) {
      return test_binary_decr_impl("test_binary_decr",
                                   PROTOCOL_BINARY_CMD_DECREMENT);
  }
  
  static enum test_return test_binary_decrq(void) {
      return test_binary_decr_impl("test_binary_decrq",
                                   PROTOCOL_BINARY_CMD_DECREMENTQ);
  }
  
  static enum test_return test_binary_version(void) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } buffer;
  
      size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
                               PROTOCOL_BINARY_CMD_VERSION,
                               NULL, 0, NULL, 0);
  
      safe_send(buffer.bytes, len, false);
      safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
      validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_VERSION,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_flush_impl(const char *key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
  
      size_t len = storage_command(send.bytes, sizeof(send.bytes),
                                   PROTOCOL_BINARY_CMD_ADD,
                                   key, strlen(key), NULL, 0, 0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      len = flush_command(send.bytes, sizeof(send.bytes), cmd, 2, true);
      safe_send(send.bytes, len, false);
      if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, cmd,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      }
  
      len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GET,
                        key, strlen(key), NULL, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      sleep(2);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
                               PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
  
      int ii;
      for (ii = 0; ii < 2; ++ii) {
          len = storage_command(send.bytes, sizeof(send.bytes),
                                PROTOCOL_BINARY_CMD_ADD,
                                key, strlen(key), NULL, 0, 0, 0);
          safe_send(send.bytes, len, false);
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
          len = flush_command(send.bytes, sizeof(send.bytes), cmd, 0, ii == 0);
          safe_send(send.bytes, len, false);
          if (cmd == PROTOCOL_BINARY_CMD_FLUSH) {
              safe_recv_packet(receive.bytes, sizeof(receive.bytes));
              validate_response_header(&receive.response, cmd,
                                       PROTOCOL_BINARY_RESPONSE_SUCCESS);
          }
  
          len = raw_command(send.bytes, sizeof(send.bytes),
                            PROTOCOL_BINARY_CMD_GET,
                            key, strlen(key), NULL, 0);
          safe_send(send.bytes, len, false);
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GET,
                                   PROTOCOL_BINARY_RESPONSE_KEY_ENOENT);
      }
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_flush(void) {
      return test_binary_flush_impl("test_binary_flush",
                                    PROTOCOL_BINARY_CMD_FLUSH);
  }
  
  static enum test_return test_binary_flushq(void) {
      return test_binary_flush_impl("test_binary_flushq",
                                    PROTOCOL_BINARY_CMD_FLUSHQ);
  }
  
  static enum test_return test_binary_concat_impl(const char *key, uint8_t cmd) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } send, receive;
      const char *value = "world";
  
      size_t len = raw_command(send.bytes, sizeof(send.bytes), cmd,
                                key, strlen(key), value, strlen(value));
  
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, cmd,
                               PROTOCOL_BINARY_RESPONSE_NOT_STORED);
  
      len = storage_command(send.bytes, sizeof(send.bytes),
                            PROTOCOL_BINARY_CMD_ADD,
                            key, strlen(key), value, strlen(value), 0, 0);
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_ADD,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      len = raw_command(send.bytes, sizeof(send.bytes), cmd,
                        key, strlen(key), value, strlen(value));
      safe_send(send.bytes, len, false);
  
      if (cmd == PROTOCOL_BINARY_CMD_APPEND || cmd == PROTOCOL_BINARY_CMD_PREPEND) {
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, cmd,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      } else {
          len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_NOOP,
                            NULL, 0, NULL, 0);
          safe_send(send.bytes, len, false);
          safe_recv_packet(receive.bytes, sizeof(receive.bytes));
          validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_NOOP,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      }
  
      len = raw_command(send.bytes, sizeof(send.bytes), PROTOCOL_BINARY_CMD_GETK,
                        key, strlen(key), NULL, 0);
  
      safe_send(send.bytes, len, false);
      safe_recv_packet(receive.bytes, sizeof(receive.bytes));
      validate_response_header(&receive.response, PROTOCOL_BINARY_CMD_GETK,
                               PROTOCOL_BINARY_RESPONSE_SUCCESS);
  
      assert(receive.response.message.header.response.keylen == strlen(key));
      assert(receive.response.message.header.response.bodylen == (strlen(key) + 2*strlen(value) + 4));
  
      char *ptr = receive.bytes;
      ptr += sizeof(receive.response);
      ptr += 4;
  
      assert(memcmp(ptr, key, strlen(key)) == 0);
      ptr += strlen(key);
      assert(memcmp(ptr, value, strlen(value)) == 0);
      ptr += strlen(value);
      assert(memcmp(ptr, value, strlen(value)) == 0);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_append(void) {
      return test_binary_concat_impl("test_binary_append",
                                     PROTOCOL_BINARY_CMD_APPEND);
  }
  
  static enum test_return test_binary_prepend(void) {
      return test_binary_concat_impl("test_binary_prepend",
                                     PROTOCOL_BINARY_CMD_PREPEND);
  }
  
  static enum test_return test_binary_appendq(void) {
      return test_binary_concat_impl("test_binary_appendq",
                                     PROTOCOL_BINARY_CMD_APPENDQ);
  }
  
  static enum test_return test_binary_prependq(void) {
      return test_binary_concat_impl("test_binary_prependq",
                                     PROTOCOL_BINARY_CMD_PREPENDQ);
  }
  
  static enum test_return test_binary_stat(void) {
      union {
          protocol_binary_request_no_extras request;
          protocol_binary_response_no_extras response;
          char bytes[1024];
      } buffer;
  
      size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
                               PROTOCOL_BINARY_CMD_STAT,
                               NULL, 0, NULL, 0);
  
      safe_send(buffer.bytes, len, false);
      do {
          safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
          validate_response_header(&buffer.response, PROTOCOL_BINARY_CMD_STAT,
                                   PROTOCOL_BINARY_RESPONSE_SUCCESS);
      } while (buffer.response.message.header.response.keylen != 0);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_illegal(void) {
      uint8_t cmd = 0x25;
      while (cmd != 0x00) {
          union {
              protocol_binary_request_no_extras request;
              protocol_binary_response_no_extras response;
              char bytes[1024];
          } buffer;
          size_t len = raw_command(buffer.bytes, sizeof(buffer.bytes),
                                   cmd, NULL, 0, NULL, 0);
          safe_send(buffer.bytes, len, false);
          safe_recv_packet(buffer.bytes, sizeof(buffer.bytes));
          validate_response_header(&buffer.response, cmd,
                                   PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND);
          ++cmd;
      }
  
      return TEST_PASS;
  }
  
  volatile bool hickup_thread_running;
  
  static void *binary_hickup_recv_verification_thread(void *arg) {
      protocol_binary_response_no_extras *response = malloc(65*1024);
      if (response != NULL) {
          while (safe_recv_packet(response, 65*1024)) {
              /* Just validate the packet format */
              validate_response_header(response,
                                       response->message.header.response.opcode,
                                       response->message.header.response.status);
          }
          free(response);
      }
      hickup_thread_running = false;
      allow_closed_read = false;
      return NULL;
  }
  
  static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t buffersize) {
      off_t offset = 0;
      char *key[256];
      uint64_t value = 0xfeedfacedeadbeef;
  
      while (hickup_thread_running &&
             offset + sizeof(protocol_binary_request_no_extras) < buffersize) {
          union {
              protocol_binary_request_no_extras request;
              char bytes[65 * 1024];
          } command;
          uint8_t cmd = (uint8_t)(rand() & 0xff);
          size_t len;
          size_t keylen = (rand() % 250) + 1;
  
          switch (cmd) {
          case PROTOCOL_BINARY_CMD_ADD:
          case PROTOCOL_BINARY_CMD_ADDQ:
          case PROTOCOL_BINARY_CMD_REPLACE:
          case PROTOCOL_BINARY_CMD_REPLACEQ:
          case PROTOCOL_BINARY_CMD_SET:
          case PROTOCOL_BINARY_CMD_SETQ:
              len = storage_command(command.bytes, sizeof(command.bytes), cmd,
                                    key, keylen , &value, sizeof(value),
                                    0, 0);
              break;
          case PROTOCOL_BINARY_CMD_APPEND:
          case PROTOCOL_BINARY_CMD_APPENDQ:
          case PROTOCOL_BINARY_CMD_PREPEND:
          case PROTOCOL_BINARY_CMD_PREPENDQ:
              len = raw_command(command.bytes, sizeof(command.bytes), cmd,
                                key, keylen, &value, sizeof(value));
              break;
          case PROTOCOL_BINARY_CMD_FLUSH:
          case PROTOCOL_BINARY_CMD_FLUSHQ:
              len = raw_command(command.bytes, sizeof(command.bytes), cmd,
                                NULL, 0, NULL, 0);
              break;
          case PROTOCOL_BINARY_CMD_NOOP:
              len = raw_command(command.bytes, sizeof(command.bytes), cmd,
                                NULL, 0, NULL, 0);
              break;
          case PROTOCOL_BINARY_CMD_DELETE:
          case PROTOCOL_BINARY_CMD_DELETEQ:
              len = raw_command(command.bytes, sizeof(command.bytes), cmd,
                               key, keylen, NULL, 0);
              break;
          case PROTOCOL_BINARY_CMD_DECREMENT:
          case PROTOCOL_BINARY_CMD_DECREMENTQ:
          case PROTOCOL_BINARY_CMD_INCREMENT:
          case PROTOCOL_BINARY_CMD_INCREMENTQ:
              len = arithmetic_command(command.bytes, sizeof(command.bytes), cmd,
                                       key, keylen, 1, 0, 0);
              break;
          case PROTOCOL_BINARY_CMD_VERSION:
              len = raw_command(command.bytes, sizeof(command.bytes),
                               PROTOCOL_BINARY_CMD_VERSION,
                               NULL, 0, NULL, 0);
              break;
          case PROTOCOL_BINARY_CMD_GET:
          case PROTOCOL_BINARY_CMD_GETK:
          case PROTOCOL_BINARY_CMD_GETKQ:
          case PROTOCOL_BINARY_CMD_GETQ:
              len = raw_command(command.bytes, sizeof(command.bytes), cmd,
                               key, keylen, NULL, 0);
              break;
  
          case PROTOCOL_BINARY_CMD_TOUCH:
          case PROTOCOL_BINARY_CMD_GAT:
          case PROTOCOL_BINARY_CMD_GATQ:
          case PROTOCOL_BINARY_CMD_GATK:
          case PROTOCOL_BINARY_CMD_GATKQ:
              len = touch_command(command.bytes, sizeof(command.bytes), cmd,
                                  key, keylen, 10);
              break;
  
          case PROTOCOL_BINARY_CMD_STAT:
              len = raw_command(command.bytes, sizeof(command.bytes),
                                PROTOCOL_BINARY_CMD_STAT,
                                NULL, 0, NULL, 0);
              break;
  
          case PROTOCOL_BINARY_CMD_SASL_LIST_MECHS:
          case PROTOCOL_BINARY_CMD_SASL_AUTH:
          case PROTOCOL_BINARY_CMD_SASL_STEP:
              /* Ignoring SASL */
          case PROTOCOL_BINARY_CMD_QUITQ:
          case PROTOCOL_BINARY_CMD_QUIT:
              /* I don't want to pass on the quit commands ;-) */
              cmd |= 0xf0;
              /* FALLTHROUGH */
          default:
              len = raw_command(command.bytes, sizeof(command.bytes),
                                cmd, NULL, 0, NULL, 0);
          }
  
          if ((len + offset) < buffersize) {
              memcpy(((char*)buffer) + offset, command.bytes, len);
              offset += len;
          } else {
              break;
          }
      }
      safe_send(buffer, offset, true);
  
      return TEST_PASS;
  }
  
  static enum test_return test_binary_pipeline_hickup(void)
  {
      size_t buffersize = 65 * 1024;
      void *buffer = malloc(buffersize);
      int ii;
  
      pthread_t tid;
      int ret;
      allow_closed_read = true;
      hickup_thread_running = true;
      if ((ret = pthread_create(&tid, NULL,
                                binary_hickup_recv_verification_thread, NULL)) != 0) {
          fprintf(stderr, "Can't create thread: %s\n", strerror(ret));
          free(buffer);
          return TEST_FAIL;
      }
  
      /* Allow the thread to start */
      usleep(250);
  
      srand((int)time(NULL));
      for (ii = 0; ii < 2; ++ii) {
          test_binary_pipeline_hickup_chunk(buffer, buffersize);
      }
  
      /* send quitq to shut down the read thread ;-) */
      size_t len = raw_command(buffer, buffersize, PROTOCOL_BINARY_CMD_QUITQ,
                               NULL, 0, NULL, 0);
      safe_send(buffer, len, false);
  
      pthread_join(tid, NULL);
      free(buffer);
      return TEST_PASS;
  }
  
  
  static enum test_return test_issue_101(void) {
      enum { max = 2 };
      enum test_return ret = TEST_PASS;
      struct conn *conns[max];
      int ii = 0;
  
      if (getenv("SKIP_TEST_101") != NULL) {
          return TEST_SKIP;
      }
  
      const char *command = "stats\r\nstats\r\nstats\r\nstats\r\nstats\r\n";
      size_t cmdlen = strlen(command);
  
      server_pid = start_server(&port, false, 1000);
  
      for (ii = 0; ii < max; ++ii) {
          conns[ii] = NULL;
          conns[ii] = connect_server("127.0.0.1", port, true, enable_ssl);
          assert(conns[ii]);
          assert(conns[ii]->sock > 0);
      }
  
      /* Send command on the connection until it blocks */
      for (ii = 0; ii < max; ++ii) {
          bool more = true;
          do {
              ssize_t err = conns[ii]->write(conns[ii], command, cmdlen);
              if (err == -1) {
                  switch (errno) {
                  case EINTR:
                      break;
                  case ENOMEM:
                  case EWOULDBLOCK:
                      more = false;
                      break;
                  default:
                      ret = TEST_FAIL;
                      goto cleanup;
                  }
              }
          } while (more);
      }
  
      /* Windows does not work like fork, just disable this for Windows.
       * This is just OK because there's separate test for test_binary_noop
       */
  #ifndef _WIN32
      pid_t child = 0;
  
      child = fork();
      if (child == (pid_t)-1) {
          abort();
      } else if (child > 0) {
          int stat;
          pid_t c;
          while ((c = waitpid(child, &stat, 0)) == (pid_t)-1 && errno == EINTR);
          assert(c == child);
          assert(stat == 0);
      } else {
          con = connect_server("127.0.0.1", port, false, enable_ssl);
          assert(con);
          ret = test_binary_noop();
          close_conn();
          exit(0);
      }
  #endif /* #ifndef _WIN32 */
  
   cleanup:
      /* close all connections */
      for (ii = 0; ii < max; ++ii) {
          struct conn *c = conns[ii];
          if (c == NULL) continue;
  #ifdef TLS
          if (c->ssl) {
              SSL_shutdown(c->ssl);
              SSL_free(c->ssl);
          }
          if (c->ssl_ctx)
              SSL_CTX_free(c->ssl_ctx);
  #endif
          if (c->sock > 0) sock_close(c->sock);
          free(conns[ii]);
          conns[ii] = NULL;
      }
  
      assert(kill(server_pid, SIGTERM) == 0);
  
      return ret;
  }
  
  typedef enum test_return (*TEST_FUNC)(void);
  struct testcase {
      const char *description;
      TEST_FUNC function;
  };
  
  struct testcase testcases[] = {
      { "cache_create", cache_create_test },
      { "cache_constructor", cache_constructor_test },
      { "cache_constructor_fail", cache_fail_constructor_test },
      { "cache_destructor", cache_destructor_test },
      { "cache_reuse", cache_reuse_test },
      { "cache_redzone", cache_redzone_test },
      { "cache_limit_revised_downward", cache_limit_revised_downward_test },
      { "stats_prefix_find", test_stats_prefix_find },
      { "stats_prefix_record_get", test_stats_prefix_record_get },
      { "stats_prefix_record_delete", test_stats_prefix_record_delete },
      { "stats_prefix_record_set", test_stats_prefix_record_set },
      { "stats_prefix_dump", test_stats_prefix_dump },
      { "issue_161", test_issue_161 },
      { "strtol", test_safe_strtol },
      { "strtoll", test_safe_strtoll },
      { "strtoul", test_safe_strtoul },
      { "strtoull", test_safe_strtoull },
      { "issue_44", test_issue_44 },
      { "vperror", test_vperror },
      { "issue_101", test_issue_101 },
      /* The following tests all run towards the same server */
      { "start_server", start_memcached_server },
      { "issue_92", test_issue_92 },
      { "issue_102", test_issue_102 },
      { "binary_noop", test_binary_noop },
      { "binary_quit", test_binary_quit },
      { "binary_quitq", test_binary_quitq },
      { "binary_set", test_binary_set },
      { "binary_setq", test_binary_setq },
      { "binary_add", test_binary_add },
      { "binary_addq", test_binary_addq },
      { "binary_replace", test_binary_replace },
      { "binary_replaceq", test_binary_replaceq },
      { "binary_delete", test_binary_delete },
      { "binary_deleteq", test_binary_deleteq },
      { "binary_get", test_binary_get },
      { "binary_getq", test_binary_getq },
      { "binary_getk", test_binary_getk },
      { "binary_getkq", test_binary_getkq },
      { "binary_gat", test_binary_gat },
      { "binary_gatq", test_binary_gatq },
      { "binary_gatk", test_binary_gatk },
      { "binary_gatkq", test_binary_gatkq },
      { "binary_incr", test_binary_incr },
      { "binary_incrq", test_binary_incrq },
      { "binary_decr", test_binary_decr },
      { "binary_decrq", test_binary_decrq },
      { "binary_version", test_binary_version },
      { "binary_flush", test_binary_flush },
      { "binary_flushq", test_binary_flushq },
      { "binary_append", test_binary_append },
      { "binary_appendq", test_binary_appendq },
      { "binary_prepend", test_binary_prepend },
      { "binary_prependq", test_binary_prependq },
      { "binary_stat", test_binary_stat },
      { "binary_illegal", test_binary_illegal },
      { "binary_pipeline_hickup", test_binary_pipeline_hickup },
      { "shutdown", shutdown_memcached_server },
      { "stop_server", stop_memcached_server },
      { NULL, NULL }
  };
  
  /* Stub out function defined in memcached.c */
  void STATS_LOCK(void);
  void STATS_UNLOCK(void);
  void STATS_LOCK(void)
  {}
  void STATS_UNLOCK(void)
  {}
  
  int main(int argc, char **argv)
  {
      int exitcode = 0;
      int ii = 0, num_cases = 0;
  
  #ifdef _WIN32
      // Initialize Winsock
      if (sock_startup()) {
          fprintf(stderr, "Winsock initialization failed!\n");
          return -1;
      }
  #endif /* #ifdef _WIN32 */
  
  #ifdef TLS
      if (getenv("SSL_TEST") != NULL) {
          SSLeay_add_ssl_algorithms();
          SSL_load_error_strings();
          enable_ssl = true;
      }
  #endif
      /* Initialized directly instead of using hash_init to avoid pulling in
         the definition of settings struct from memcached.h */
      hash = jenkins_hash;
      stats_prefix_init(':');
  
      for (num_cases = 0; testcases[num_cases].description; num_cases++) {
          /* Just counting */
      }
  
      printf("1..%d\n", num_cases);
  
      for (ii = 0; testcases[ii].description != NULL; ++ii) {
          fflush(stdout);
  #ifndef _WIN32
  #ifndef DEBUG
          /* the test program shouldn't run longer than 10 minutes... */
          alarm(600);
  #endif
  #else
          fprintf(stdout, "%d - %s...\n", ii + 1, testcases[ii].description);
  #endif /* #ifndef _WIN32 */
          enum test_return ret = testcases[ii].function();
          if (ret == TEST_SKIP) {
              fprintf(stdout, "ok # SKIP %d - %s\n", ii + 1, testcases[ii].description);
          } else if (ret == TEST_PASS) {
              fprintf(stdout, "ok %d - %s\n", ii + 1, testcases[ii].description);
          } else {
              fprintf(stdout, "not ok %d - %s\n", ii + 1, testcases[ii].description);
              exitcode = 1;
          }
          fflush(stdout);
      }
  
  #ifdef _WIN32
      sock_cleanup();
  #endif /* #ifdef _WIN32 */
  
      return exitcode;
  }
  