/**
 * oath_totp_generate:
 * @secret: the shared secret string
 * @secret_length: length of @secret
 * @now: Unix time value to compute TOTP for
 * @time_step_size: time step system parameter (typically 30)
 * @start_offset: Unix time of when to start counting time steps (typically 0)
 * @digits: number of requested digits in the OTP, excluding checksum
 * @output_otp: output buffer, must have room for the output OTP plus zero
 *
 * Generate a one-time-password using the time-variant TOTP algorithm
 * described in RFC 6238.  The input parameters are taken as time
 * values.
 *
 * The system parameter @time_step_size describes how long the time
 * window for each OTP is.  The recommended value is 30 seconds, and
 * you can use the value 0 or the symbol
 * %OATH_TOTP_DEFAULT_TIME_STEP_SIZE to indicate this.
 *
 * The system parameter @start_offset denote the Unix time when time
 * steps are started to be counted.  The recommended value is 0, to
 * fall back on the Unix epoch) and you can use the symbol
 * %OATH_TOTP_DEFAULT_START_TIME to indicate this.
 *
 * The @output_otp buffer must have room for at least @digits
 * characters, plus one for the terminating NUL.
 *
 * Currently only values 6, 7 and 8 for @digits are supported.  This
 * restriction may be lifted in future versions.
 *
 * Returns: On success, %OATH_OK (zero) is returned, otherwise an
 *   error code is returned.
 *
 * Since: 1.4.0
 **/
int
oath_totp_generate (const char *secret,
		    size_t secret_length,
		    time_t now,
		    unsigned time_step_size,
		    time_t start_offset, unsigned digits, char *output_otp)
{
  uint64_t nts;

  if (time_step_size == 0)
    time_step_size = OATH_TOTP_DEFAULT_TIME_STEP_SIZE;

  nts = (now - start_offset) / time_step_size;

  return oath_hotp_generate (secret,
			     secret_length,
			     nts,
			     digits,
			     false, OATH_HOTP_DYNAMIC_TRUNCATION, output_otp);
}
int
main (void)
{
  oath_rc rc;
  char nulls[1] = "\x00";
  char secret[32] = "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30"
    "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30"
    "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30\x31\x32";
  size_t secretlen = 20;
  char otp[10];
  uint64_t moving_factor;
  unsigned digits;

  rc = oath_init ();
  if (rc != OATH_OK)
    {
      printf ("oath_init: %d\n", rc);
      return 1;
    }

  moving_factor = 1099511627776ULL;
  rc = oath_hotp_generate (nulls, 1, moving_factor, 6, false,
			   OATH_HOTP_DYNAMIC_TRUNCATION, otp);
  if (rc != OATH_OK)
    {
      printf ("oath_hotp_generate %llu: %d\n",
	      (long long unsigned) moving_factor, rc);
      return 1;
    }
  if (strcmp ("363425", otp) != 0)
    {
      printf ("oath_hotp_generate got otp %s\n", otp);
      return 1;
    }

  for (digits = 6; digits <= 8; digits++)
    for (moving_factor = 0; moving_factor < MAX_ITER; moving_factor++)
      {
	rc = oath_hotp_generate (secret, secretlen, moving_factor,
				 digits, false, OATH_HOTP_DYNAMIC_TRUNCATION,
				 otp);
	if (rc != OATH_OK)
	  {
	    printf ("oath_hotp_generate: %d\n", rc);
	    return 1;
	  }

#if DEBUG
	printf ("otp[%d]: %s\n", (unsigned) moving_factor, otp);
#endif

	if (!expect[digits][moving_factor])
	  {
	    printf ("no test vector for digits %d counter %ld\n",
		    digits, (long) moving_factor);
	    return 1;
	  }

	if (strcmp (otp, expect[digits][moving_factor]) != 0)
	  {
	    printf ("otp[%d][%ld] got %s expected %s\n",
		    digits, (long) moving_factor, otp,
		    expect[digits][moving_factor]);
	    return 1;
	  }

	rc = oath_hotp_generate (secret, 32, moving_factor,
				 digits, false, OATH_HOTP_DYNAMIC_TRUNCATION,
				 otp);
	if (rc != OATH_OK)
	  {
	    printf ("oath_hotp_generate: %d\n", rc);
	    return 1;
	  }
      }

  for (digits = 0; digits < 6; digits++)
    {
      rc = oath_hotp_generate (secret, secretlen, moving_factor,
			       digits, false, OATH_HOTP_DYNAMIC_TRUNCATION,
			       otp);
      if (rc != OATH_INVALID_DIGITS)
	{
	  printf ("oath_hotp_generate %d digits %d\n", digits, rc);
	  return 1;
	}
    }
  for (digits = 9; digits < 15; digits++)
    {
      rc = oath_hotp_generate (secret, secretlen, moving_factor,
			       digits, false, OATH_HOTP_DYNAMIC_TRUNCATION,
			       otp);
      if (rc != OATH_INVALID_DIGITS)
	{
	  printf ("oath_hotp_generate %d digits %d\n", digits, rc);
	  return 1;
	}
    }

  rc = oath_done ();
  if (rc != OATH_OK)
    {
      printf ("oath_done: %d\n", rc);
      return 1;
    }

  return 0;
}
/**
 * oath_totp_validate2_callback:
 * @secret: the shared secret string
 * @secret_length: length of @secret
 * @now: Unix time value to compute TOTP for
 * @time_step_size: time step system parameter (typically 30)
 * @start_offset: Unix time of when to start counting time steps (typically 0)
 * @digits: number of requested digits in the OTP
 * @window: how many OTPs after start counter to test
 * @otp_pos: output search position in search window (may be NULL).
 * @strcmp_otp: function pointer to a strcmp-like function.
 * @strcmp_handle: caller handle to be passed on to @strcmp_otp.
 *
 * Validate an OTP according to OATH TOTP algorithm per RFC 6238.
 *
 * Validation is implemented by generating a number of potential OTPs
 * and performing a call to the @strcmp_otp function, to compare the
 * potential OTP against the given @otp.  It has the following
 * prototype:
 *
 * int (*oath_validate_strcmp_function) (void *handle, const char *test_otp);
 *
 * The function should be similar to strcmp in that it return 0 only
 * on matches.  It differs by permitting use of negative return codes
 * as indication of internal failures in the callback.  Positive
 * values indicate OTP mismatch.
 *
 * This callback interface is useful when you cannot compare OTPs
 * directly using normal strcmp, but instead for example only have a
 * hashed OTP.  You would then typically pass in the hashed OTP in the
 * @strcmp_handle and let your implementation of @strcmp_otp hash the
 * test_otp OTP using the same hash, and then compare the results.
 *
 * Currently only OTP lengths of 6, 7 or 8 digits are supported.  This
 * restrictions may be lifted in future versions, although some
 * limitations are inherent in the protocol.
 *
 * Returns: Returns absolute value of position in OTP window (zero is
 *   first position), or %OATH_INVALID_OTP if no OTP was found in OTP
 *   window, or an error code.
 *
 * Since: 1.10.0
 **/
int
oath_totp_validate2_callback (const char *secret,
			      size_t secret_length,
			      time_t now,
			      unsigned time_step_size,
			      time_t start_offset,
			      unsigned digits,
			      size_t window,
			      int *otp_pos,
			      oath_validate_strcmp_function strcmp_otp,
			      void *strcmp_handle)
{
  unsigned iter = 0;
  char tmp_otp[10];
  int rc;
  uint64_t nts;

  if (time_step_size == 0)
    time_step_size = OATH_TOTP_DEFAULT_TIME_STEP_SIZE;

  nts = (now - start_offset) / time_step_size;

  do
    {
      rc = oath_hotp_generate (secret,
			       secret_length,
			       nts + iter,
			       digits,
			       false, OATH_HOTP_DYNAMIC_TRUNCATION, tmp_otp);
      if (rc != OATH_OK)
	return rc;

      if ((rc = strcmp_otp (strcmp_handle, tmp_otp)) == 0)
	{
	  if (otp_pos)
	    *otp_pos = iter;
	  return iter;
	}
      if (rc < 0)
	return OATH_STRCMP_ERROR;

      if (iter > 0)
	{
	  rc = oath_hotp_generate (secret,
				   secret_length,
				   nts - iter,
				   digits,
				   false, OATH_HOTP_DYNAMIC_TRUNCATION,
				   tmp_otp);
	  if (rc != OATH_OK)
	    return rc;

	  if ((rc = strcmp_otp (strcmp_handle, tmp_otp)) == 0)
	    {
	      if (otp_pos)
		*otp_pos = -iter;
	      return iter;
	    }
	  if (rc < 0)
	    return OATH_STRCMP_ERROR;
	}
    }
  while (window - iter++ > 0);

  return OATH_INVALID_OTP;
}
int
main (int argc, char *argv[])
{
  struct gengetopt_args_info args_info;
  char *secret;
  size_t secretlen = 0;
  int rc;
  size_t window;
  uint64_t moving_factor;
  unsigned digits;
  char otp[10];
  time_t now, when, t0, time_step_size;
  int totpflags = 0;

  set_program_name (argv[0]);

  if (cmdline_parser (argc, argv, &args_info) != 0)
    return EXIT_FAILURE;

  if (args_info.version_given)
    {
      char *p;
      int l = -1;

      if (strcmp (oath_check_version (NULL), OATH_VERSION) != 0)
	l = asprintf (&p, "OATH Toolkit liboath.so %s oath.h %s",
		      oath_check_version (NULL), OATH_VERSION);
      else if (strcmp (OATH_VERSION, PACKAGE_VERSION) != 0)
	l = asprintf (&p, "OATH Toolkit %s",
		      oath_check_version (NULL), OATH_VERSION);
      version_etc (stdout, "oathtool", l == -1 ? "OATH Toolkit" : p,
		   PACKAGE_VERSION, "Simon Josefsson", (char *) NULL);
      if (l != -1)
	free (p);
      return EXIT_SUCCESS;
    }

  if (args_info.help_given)
    usage (EXIT_SUCCESS);

  if (args_info.inputs_num == 0)
    {
      cmdline_parser_print_help ();
      emit_bug_reporting_address ();
      return EXIT_SUCCESS;
    }

  rc = oath_init ();
  if (rc != OATH_OK)
    error (EXIT_FAILURE, 0, "liboath initialization failed: %s",
	   oath_strerror (rc));

  if (args_info.base32_flag)
    {
      rc = oath_base32_decode (args_info.inputs[0],
			       strlen (args_info.inputs[0]),
			       &secret, &secretlen);
      if (rc != OATH_OK)
	error (EXIT_FAILURE, 0, "base32 decoding failed: %s",
	       oath_strerror (rc));
    }
  else
    {
      secretlen = 1 + strlen (args_info.inputs[0]) / 2;
      secret = malloc (secretlen);
      if (!secret)
	error (EXIT_FAILURE, errno, "malloc");

      rc = oath_hex2bin (args_info.inputs[0], secret, &secretlen);
      if (rc != OATH_OK)
	error (EXIT_FAILURE, 0, "hex decoding of secret key failed");
    }

  if (args_info.counter_orig)
    moving_factor = args_info.counter_arg;
  else
    moving_factor = 0;

  if (args_info.digits_orig)
    digits = args_info.digits_arg;
  else
    digits = 6;

  if (args_info.window_orig)
    window = args_info.window_arg;
  else
    window = 0;

  if (digits != 6 && digits != 7 && digits != 8)
    error (EXIT_FAILURE, 0, "only digits 6, 7 and 8 are supported");

  if (validate_otp_p (args_info.inputs_num) && !args_info.digits_orig)
    digits = strlen (args_info.inputs[1]);
  else if (validate_otp_p (args_info.inputs_num) && args_info.digits_orig &&
	   args_info.digits_arg != strlen (args_info.inputs[1]))
    error (EXIT_FAILURE, 0,
	   "given one-time password has bad length %d != %ld",
	   args_info.digits_arg, strlen (args_info.inputs[1]));

  if (args_info.inputs_num > 2)
    error (EXIT_FAILURE, 0, "too many parameters");

  if (args_info.verbose_flag)
    {
      char *tmp;

      tmp = malloc (2 * secretlen + 1);
      if (!tmp)
	error (EXIT_FAILURE, errno, "malloc");

      oath_bin2hex (secret, secretlen, tmp);

      printf ("Hex secret: %s\n", tmp);
      free (tmp);

      rc = oath_base32_encode (secret, secretlen, &tmp, NULL);
      if (rc != OATH_OK)
	error (EXIT_FAILURE, 0, "base32 encoding failed: %s",
	       oath_strerror (rc));

      printf ("Base32 secret: %s\n", tmp);
      free (tmp);

      if (args_info.inputs_num == 2)
	printf ("OTP: %s\n", args_info.inputs[1]);
      printf ("Digits: %d\n", digits);
      printf ("Window size: %ld\n", window);
    }

  if (args_info.totp_given)
    {
      now = time (NULL);
      when = parse_time (args_info.now_arg, now);
      t0 = parse_time (args_info.start_time_arg, now);
      time_step_size = parse_duration (args_info.time_step_size_arg);

      if (when == BAD_TIME)
	error (EXIT_FAILURE, 0, "cannot parse time `%s'", args_info.now_arg);

      if (t0 == BAD_TIME)
	error (EXIT_FAILURE, 0, "cannot parse time `%s'",
	       args_info.start_time_arg);

      if (time_step_size == BAD_TIME)
	error (EXIT_FAILURE, 0, "cannot parse time `%s'",
	       args_info.time_step_size_arg);

      if (strcmp (args_info.totp_arg, "sha256") == 0)
	totpflags = OATH_TOTP_HMAC_SHA256;
      else if (strcmp (args_info.totp_arg, "sha512") == 0)
	totpflags = OATH_TOTP_HMAC_SHA512;

      if (args_info.verbose_flag)
	verbose_totp (t0, time_step_size, when);
    }
  else
    {
      if (args_info.verbose_flag)
	verbose_hotp (moving_factor);
    }

  if (generate_otp_p (args_info.inputs_num) && !args_info.totp_given)
    {
      size_t iter = 0;

      do
	{
	  rc = oath_hotp_generate (secret,
				   secretlen,
				   moving_factor + iter,
				   digits,
				   false, OATH_HOTP_DYNAMIC_TRUNCATION, otp);
	  if (rc != OATH_OK)
	    error (EXIT_FAILURE, 0,
		   "generating one-time password failed (%d)", rc);

	  printf ("%s\n", otp);
	}
      while (window - iter++ > 0);
    }
  else if (generate_otp_p (args_info.inputs_num) && args_info.totp_given)
    {
      size_t iter = 0;

      do
	{
	  rc = oath_totp_generate2 (secret,
				    secretlen,
				    when + iter * time_step_size,
				    time_step_size, t0, digits, totpflags,
				    otp);
	  if (rc != OATH_OK)
	    error (EXIT_FAILURE, 0,
		   "generating one-time password failed (%d)", rc);

	  printf ("%s\n", otp);
	}
      while (window - iter++ > 0);
    }
  else if (validate_otp_p (args_info.inputs_num) && !args_info.totp_given)
    {
      rc = oath_hotp_validate (secret,
			       secretlen,
			       moving_factor, window, args_info.inputs[1]);
      if (rc == OATH_INVALID_OTP)
	error (EXIT_OTP_INVALID, 0,
	       "password \"%s\" not found in range %ld .. %ld",
	       args_info.inputs[1],
	       (long) moving_factor, (long) moving_factor + window);
      else if (rc < 0)
	error (EXIT_FAILURE, 0,
	       "validating one-time password failed (%d)", rc);
      printf ("%d\n", rc);
    }
  else if (validate_otp_p (args_info.inputs_num) && args_info.totp_given)
    {
      rc = oath_totp_validate4 (secret,
				secretlen,
				when,
				time_step_size,
				t0,
				window,
				NULL, NULL, totpflags, args_info.inputs[1]);
      if (rc == OATH_INVALID_OTP)
	error (EXIT_OTP_INVALID, 0,
	       "password \"%s\" not found in range %ld .. %ld",
	       args_info.inputs[1],
	       (long) ((when - t0) / time_step_size - window / 2),
	       (long) ((when - t0) / time_step_size + window / 2));
      else if (rc < 0)
	error (EXIT_FAILURE, 0,
	       "validating one-time password failed (%d)", rc);
      printf ("%d\n", rc);
    }

  free (secret);
  oath_done ();

  return EXIT_SUCCESS;
}