/*
 * Copyright 2009-present MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <bson/bson.h>
#define BSON_INSIDE
#include <bson/bson-context-private.h>
#include <bson/bson-iso8601-private.h>
#include <common-thread-private.h>
#undef BSON_INSIDE

#ifdef BSON_HAVE_STRINGS_H
#include <strings.h>
#endif

#include <common-json-private.h>
#include <common-macros-private.h> // BEGIN_IGNORE_DEPRECATIONS

#include <mlib/config.h>

#include <TestSuite.h>

#include <limits.h>

#define N_THREADS 4

static const char *gTestOids[] = {"000000000000000000000000",
                                  "010101010101010101010101",
                                  "0123456789abcdefafcdef03",
                                  "fcdeab182763817236817236",
                                  "ffffffffffffffffffffffff",
                                  "eeeeeeeeeeeeeeeeeeeeeeee",
                                  "999999999999999999999999",
                                  "111111111111111111111111",
                                  NULL};

static const char *gTestOidsCase[] = {"0123456789ABCDEFAFCDEF03",
                                      "FCDEAB182763817236817236",
                                      "FFFFFFFFFFFFFFFFFFFFFFFF",
                                      "EEEEEEEEEEEEEEEEEEEEEEEE",
                                      "01234567890ACBCDEFabcdef",
                                      NULL};

static const char *gTestOidsFail[] = {"                        ",
                                      "abasdf                  ",
                                      "asdfasdfasdfasdfasdf    ",
                                      "00000000000000000000000z",
                                      "00187263123ghh21382812a8",
                                      NULL};

BSON_THREAD_FUN(oid_worker, data)
{
   bson_context_t *context = data;
   bson_oid_t oid;
   bson_oid_t oid2;
   int i;

   bson_oid_init(&oid2, context);
   for (i = 0; i < 500000; i++) {
      bson_oid_init(&oid, context);
      BSON_ASSERT(false == bson_oid_equal(&oid, &oid2));
      BSON_ASSERT(0 < bson_oid_compare(&oid, &oid2));
      bson_oid_copy(&oid, &oid2);
   }

   BSON_THREAD_RETURN;
}

static void
test_bson_oid_init_from_string(void)
{
   bson_context_t *context;
   bson_oid_t oid;
   char str[25];
   int i;

   context = bson_context_new(BSON_CONTEXT_NONE);

   /*
    * Test successfully parsed oids.
    */

   for (i = 0; gTestOids[i]; i++) {
      bson_oid_init_from_string(&oid, gTestOids[i]);
      bson_oid_to_string(&oid, str);
      BSON_ASSERT(!strcmp(str, gTestOids[i]));
   }

   /*
    * Test successfully parsed oids (case-insensitive).
    */
   for (i = 0; gTestOidsCase[i]; i++) {
      char oid_lower[25];
      int j;

      bson_oid_init_from_string(&oid, gTestOidsCase[i]);
      bson_oid_to_string(&oid, str);
      BSON_ASSERT(!bson_strcasecmp(str, gTestOidsCase[i]));

      for (j = 0; gTestOidsCase[i][j]; j++) {
         oid_lower[j] = tolower(gTestOidsCase[i][j]);
      }

      oid_lower[24] = '\0';
      BSON_ASSERT(!strcmp(str, oid_lower));
   }

   /*
    * Test that sizeof(str) works (len of 25 with \0 instead of 24).
    */
   BSON_ASSERT(bson_oid_is_valid("ffffffffffffffffffffffff", 24));
   bson_oid_init_from_string(&oid, "ffffffffffffffffffffffff");
   bson_oid_to_string(&oid, str);
   BSON_ASSERT(bson_oid_is_valid(str, sizeof str));

   /*
    * Test the failures.
    */

   for (i = 0; gTestOidsFail[i]; i++) {
      bson_oid_init_from_string(&oid, gTestOidsFail[i]);
      bson_oid_to_string(&oid, str);
      BSON_ASSERT(strcmp(str, gTestOidsFail[i]));
   }

   bson_context_destroy(context);
}


static void
test_bson_oid_hash(void)
{
   bson_oid_t oid;

   bson_oid_init_from_string(&oid, "000000000000000000000000");
   BSON_ASSERT(bson_oid_hash(&oid) == 1487062149);
}


static void
test_bson_oid_compare(void)
{
   bson_oid_t oid;
   bson_oid_t oid2;

   bson_oid_init_from_string(&oid, "000000000000000000001234");
   bson_oid_init_from_string(&oid2, "000000000000000000001234");
   BSON_ASSERT(0 == bson_oid_compare(&oid, &oid2));
   BSON_ASSERT(true == bson_oid_equal(&oid, &oid2));

   bson_oid_init_from_string(&oid, "000000000000000000001234");
   bson_oid_init_from_string(&oid2, "000000000000000000004321");
   BSON_ASSERT(bson_oid_compare(&oid, &oid2) < 0);
   BSON_ASSERT(bson_oid_compare(&oid2, &oid) > 0);
   BSON_ASSERT(false == bson_oid_equal(&oid, &oid2));
}


static void
test_bson_oid_copy(void)
{
   bson_oid_t oid;
   bson_oid_t oid2;

   bson_oid_init_from_string(&oid, "000000000000000000001234");
   bson_oid_init_from_string(&oid2, "000000000000000000004321");
   bson_oid_copy(&oid, &oid2);
   BSON_ASSERT(true == bson_oid_equal(&oid, &oid2));
}


static void
test_bson_oid_init(void)
{
   bson_context_t *context;
   bson_oid_t oid;
   bson_oid_t oid2;
   int i;

   context = bson_context_new(BSON_CONTEXT_NONE);
   bson_oid_init(&oid, context);
   for (i = 0; i < 10000; i++) {
      bson_oid_init(&oid2, context);
      BSON_ASSERT(false == bson_oid_equal(&oid, &oid2));
      BSON_ASSERT(0 > bson_oid_compare(&oid, &oid2));
      bson_oid_copy(&oid2, &oid);
   }
   bson_context_destroy(context);

   /*
    * Test that the shared context works.
    */
   bson_oid_init(&oid, NULL);
   BSON_ASSERT(bson_context_get_default());
}


static char *
get_time_as_string(const bson_oid_t *oid)
{
   mcommon_string_append_t str;
   mcommon_string_new_as_append(&str);
   time_t time = bson_oid_get_time_t(oid);

   mcommon_iso8601_string_append(&str, time * 1000);
   return mcommon_string_from_append_destroy_with_steal(&str);
}


static void
test_bson_oid_get_time_t(void)
{
   bson_context_t *context;
   bson_oid_t oid;
   uint32_t start = (uint32_t)time(NULL);
   char *str;

   context = bson_context_new(BSON_CONTEXT_NONE);
   bson_oid_init(&oid, context);
   ASSERT_CMPUINT32((uint32_t)bson_oid_get_time_t(&oid), >=, start);
   ASSERT_CMPUINT32((uint32_t)bson_oid_get_time_t(&oid), <=, (uint32_t)time(NULL));

   bson_oid_init_from_string(&oid, "000000000000000000000000");
   str = get_time_as_string(&oid);
   ASSERT_CMPSTR(str, "1970-01-01T00:00:00Z");
   bson_free(str);

   /* if time_t is a signed int32, then a negative value may be interpreted
    * as a negative date when printing. */
   mlib_diagnostic_push();
   mlib_disable_constant_conditional_expression_warnings();
   if (sizeof(time_t) == 8) {
      mlib_diagnostic_pop();
      bson_oid_init_from_string(&oid, "7FFFFFFF0000000000000000");
      str = get_time_as_string(&oid);
      ASSERT_CMPSTR(str, "2038-01-19T03:14:07Z");
      bson_free(str);

      bson_oid_init_from_string(&oid, "800000000000000000000000");
      str = get_time_as_string(&oid);
      ASSERT_CMPSTR(str, "2038-01-19T03:14:08Z");
      bson_free(str);

      bson_oid_init_from_string(&oid, "FFFFFFFF0000000000000000");
      str = get_time_as_string(&oid);
      ASSERT_CMPSTR(str, "2106-02-07T06:28:15Z");
      bson_free(str);
   }

   bson_context_destroy(context);
}


static void
test_bson_oid_init_with_threads(void)
{
   bson_context_t *context;
   int i;
   int r;

   {
      bson_context_flags_t flags = BSON_CONTEXT_NONE;
      bson_context_t *contexts[N_THREADS];
      bson_thread_t threads[N_THREADS];

      for (i = 0; i < N_THREADS; i++) {
         contexts[i] = bson_context_new(flags);
         r = mcommon_thread_create(&threads[i], oid_worker, contexts[i]);
         BSON_ASSERT(r == 0);
      }

      for (i = 0; i < N_THREADS; i++) {
         mcommon_thread_join(threads[i]);
      }

      for (i = 0; i < N_THREADS; i++) {
         bson_context_destroy(contexts[i]);
      }
   }

   /*
    * Test threaded generation of oids using a single context;
    */
   {
      bson_thread_t threads[N_THREADS];

      context = bson_context_new(BSON_CONTEXT_THREAD_SAFE);

      for (i = 0; i < N_THREADS; i++) {
         r = mcommon_thread_create(&threads[i], oid_worker, context);
         BSON_ASSERT(r == 0);
      }

      for (i = 0; i < N_THREADS; i++) {
         r = mcommon_thread_join(threads[i]);
         BSON_ASSERT(r == 0);
      }

      bson_context_destroy(context);
   }
}


static void
test_bson_oid_counter_overflow(void)
{
   bson_oid_t oid;
   char str[25];
   bson_context_t *ctx = bson_context_new(BSON_CONTEXT_NONE);
   ctx->seq32 = 0xFFFFFF;

   bson_oid_init(&oid, ctx);
   bson_oid_to_string(&oid, str);
   /* check that the counter portion of the string is FFFFFF" */
   ASSERT_CMPSTR(str + (24 - 6), "ffffff");
   bson_oid_init(&oid, ctx);
   /* the next oid should have overflowed the counter. */
   bson_oid_to_string(&oid, str);
   ASSERT_CMPSTR(str + (24 - 6), "000000");
   bson_context_destroy(ctx);
}


#ifndef _WIN32
#include <sys/wait.h>


typedef struct {
   uint32_t timestamp; /* timestamp */
   uint64_t rand;      /* only really 5 bytes */
   uint32_t counter;   /* only really 3 bytes */
} _parsed_oid_t;


/* parse an oid into parts usable for comparison. */
static void
_parse_oid(bson_oid_t *oid, _parsed_oid_t *out)
{
   memset(out, 0, sizeof(_parsed_oid_t));
   memcpy(&out->timestamp, oid->bytes, 4);
   out->timestamp = BSON_UINT32_FROM_BE(out->timestamp);
   /* rand_bytes is 5 bytes starting at index 4. Read all of it into an 8 byte
    * uint64_t
    * and chop off the extra 3 bytes. */
   memcpy(&out->rand, oid->bytes + 1, 8);
   out->rand = BSON_UINT64_FROM_BE(out->rand) & 0x000000FFFFFFFFFF;

   /* counter is 3 bytes. Read four bytes and chop off extra 1. */
   memcpy(&out->counter, oid->bytes + 8, 4);
   out->counter = BSON_UINT32_FROM_BE(out->counter) & 0x00FFFFFF;
}


/* Only test where fork() is available. Does not exercise platform specific
 * code. */
static void
test_bson_oid_after_fork(void)
{
   bson_context_t *ctx;
   bson_oid_t parent_oid, self_check;
   _parsed_oid_t parent_parsed, self_check_parsed;
   pid_t pid;
   int child_exit_status = 0;

   /* a self check of the parsing utility. */
   bson_oid_init_from_string(&self_check, "AAAAAAAABBBBBBBBBBCCCCCC");
   _parse_oid(&self_check, &self_check_parsed);
   ASSERT_CMPUINT32(self_check_parsed.timestamp, ==, 0xAAAAAAAA);
   ASSERT_CMPUINT64(self_check_parsed.rand, ==, 0x000000BBBBBBBBBB);
   ASSERT_CMPUINT32(self_check_parsed.counter, ==, 0x00CCCCCC);

   bson_oid_init(&parent_oid, bson_context_get_default());
   _parse_oid(&parent_oid, &parent_parsed);
   pid = fork();
   if (pid == 0) {
      bson_oid_t child_oid, child_2_oid;
      _parsed_oid_t child_parsed, child_2_parsed;

      bson_oid_init(&child_oid, bson_context_get_default());
      _parse_oid(&child_oid, &child_parsed);
      ASSERT_CMPUINT64(child_parsed.rand, !=, parent_parsed.rand);
      ASSERT_CMPUINT32(child_parsed.counter, ==, parent_parsed.counter + 1);
      BSON_ASSERT(0 != bson_oid_compare(&parent_oid, &child_oid));

      /* but a different OID gets the same random bytes. */
      bson_oid_init(&child_2_oid, bson_context_get_default());
      _parse_oid(&child_2_oid, &child_2_parsed);
      ASSERT_CMPUINT64(child_2_parsed.rand, !=, parent_parsed.rand);
      ASSERT_CMPUINT64(child_2_parsed.rand, ==, child_parsed.rand);
      ASSERT_CMPUINT32(child_2_parsed.counter, ==, child_parsed.counter + 1);
      BSON_ASSERT(0 != bson_oid_compare(&child_oid, &child_2_oid));

      exit(0);
   } else {
      bson_oid_t parent_2_oid;
      _parsed_oid_t parent_2_parsed;

      BSON_ASSERT(-1 != waitpid(pid, &child_exit_status, 0 /* opts */));
      BSON_ASSERT(child_exit_status == 0);

      /* but initializing another OID in the parent does *not* change random
       * bytes. */
      bson_oid_init(&parent_2_oid, bson_context_get_default());
      _parse_oid(&parent_2_oid, &parent_2_parsed);
      ASSERT_CMPUINT64(parent_2_parsed.rand, ==, parent_parsed.rand);
      ASSERT_CMPUINT32(parent_2_parsed.counter, ==, parent_parsed.counter + 1);
   }

   /* now test with PID caching enabled. */
   ctx = bson_context_new(BSON_CONTEXT_NONE);
   bson_oid_init(&parent_oid, ctx);
   _parse_oid(&parent_oid, &parent_parsed);
   pid = fork();
   if (pid == 0) {
      bson_oid_t child_oid;
      _parsed_oid_t child_parsed;

      bson_oid_init(&child_oid, ctx);
      _parse_oid(&child_oid, &child_parsed);

      /* since PID is cached, random value does not get regenerated. */
      ASSERT_CMPUINT64(child_parsed.rand, ==, parent_parsed.rand);
      ASSERT_CMPUINT32(child_parsed.counter, ==, parent_parsed.counter + 1);
      BSON_ASSERT(0 != bson_oid_compare(&parent_oid, &child_oid));
      exit(0);
   } else {
      bson_oid_t parent_2_oid;
      _parsed_oid_t parent_2_parsed;

      BSON_ASSERT(-1 != waitpid(pid, &child_exit_status, 0 /* opts */));
      BSON_ASSERT(child_exit_status == 0);

      /* but initializing another OID in the parent does *not* change random
       * bytes. */
      bson_oid_init(&parent_2_oid, ctx);
      _parse_oid(&parent_2_oid, &parent_2_parsed);
      ASSERT_CMPUINT64(parent_2_parsed.rand, ==, parent_parsed.rand);
      ASSERT_CMPUINT32(parent_2_parsed.counter, ==, parent_parsed.counter + 1);
   }
   bson_context_destroy(ctx);
}
#endif


void
test_oid_install(TestSuite *suite)
{
   TestSuite_Add(suite, "/bson/oid/init", test_bson_oid_init);
   TestSuite_Add(suite, "/bson/oid/init_from_string", test_bson_oid_init_from_string);
   TestSuite_Add(suite, "/bson/oid/init_with_threads", test_bson_oid_init_with_threads);
   TestSuite_Add(suite, "/bson/oid/hash", test_bson_oid_hash);
   TestSuite_Add(suite, "/bson/oid/compare", test_bson_oid_compare);
   TestSuite_Add(suite, "/bson/oid/copy", test_bson_oid_copy);
   TestSuite_Add(suite, "/bson/oid/get_time_t", test_bson_oid_get_time_t);
   TestSuite_Add(suite, "/bson/oid/counter_overflow", test_bson_oid_counter_overflow);
#ifndef _WIN32
   if (!TestSuite_NoFork(suite)) {
      TestSuite_Add(suite, "/bson/oid/after_fork", test_bson_oid_after_fork);
   }
#endif
}
