/*
** Copyright (c) 2017 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains generic code for dealing with hashes used for
** naming artifacts.  Specific hash algorithms are implemented separately
** (for example in sha1.c and sha3.c).  This file contains the generic
** interface logic.
**
** "hname" is intended to be an abbreviation of "hash name".
*/
#include "config.h"
#include "hname.h"


#if INTERFACE
/*
** Code numbers for the allowed hash algorithms.
*/
#define HNAME_ERROR  0      /* Not a valid hash */
#define HNAME_SHA1   1      /* SHA1 */
#define HNAME_K256   2      /* SHA3-256 */

/*
** Minimum and maximum lengths for a hash value when hex encoded.
*/
#define HNAME_MIN  40     /* Length for SHA1 */
#define HNAME_MAX  64     /* Length for SHA3-256 */

/*
** Hash lengths for the various algorithms
*/
#define HNAME_LEN_SHA1   40
#define HNAME_LEN_K256   64

/*
** The number of distinct hash algorithms:
*/
#define HNAME_COUNT 2     /* Just SHA1 and SHA3-256. Let's keep it that way! */

/*
** Hash naming policies
*/
#define HPOLICY_SHA1           0      /* Use SHA1 hashes */
#define HPOLICY_AUTO           1      /* SHA1 but auto-promote to SHA3 */
#define HPOLICY_SHA3           2      /* Use SHA3 hashes */
#define HPOLICY_SHA3_ONLY      3      /* Use SHA3 hashes exclusively */
#define HPOLICY_SHUN_SHA1      4      /* Shun all SHA1 objects */

#endif /* INTERFACE */

/*
** Return a human-readable name for the hash algorithm given a hash with
** a length of nHash hexadecimal digits.
*/
const char *hname_alg(int nHash){
  if( nHash==HNAME_LEN_SHA1 ) return "SHA1";
  if( nHash==HNAME_LEN_K256 ) return "SHA3-256";
  return "?";
}

/*
** Return the integer hash algorithm code number (ex: HNAME_K256) for
** the hash string provided.  Or return HNAME_ERROR (0) if the input string
** is not a valid artifact hash string.
*/
int hname_validate(const char *zHash, int nHash){
  int id;
  switch( nHash ){
    case HNAME_LEN_SHA1:   id = HNAME_SHA1;  break;
    case HNAME_LEN_K256:   id = HNAME_K256;  break;
    default:               return HNAME_ERROR;
  }
  if( !validate16(zHash, nHash) ) return HNAME_ERROR;
  return id;
}

/*
** Verify that zHash is a valid hash for the content in pContent.
** Return true if the hash is correct.  Return false if the content
** does not match the hash.
**
** Actually, the returned value is one of the hash algorithm constants
** corresponding to the hash that matched if the hash is correct.
** (Examples: HNAME_SHA1 or HNAME_K256).  And the return is HNAME_ERROR
** if the hash does not match.
*/
int hname_verify_hash(Blob *pContent, const char *zHash, int nHash){
  int id = HNAME_ERROR;
  switch( nHash ){
    case HNAME_LEN_SHA1: {
      Blob hash;
      sha1sum_blob(pContent, &hash);
      if( memcmp(blob_buffer(&hash),zHash,HNAME_LEN_SHA1)==0 ) id = HNAME_SHA1;
      blob_reset(&hash);
      break;
    }
    case HNAME_LEN_K256: {
      sha3sum_init(256);
      sha3sum_step_blob(pContent);
      if( memcmp(sha3sum_finish(0),zHash,64)==0 ) id = HNAME_K256;
      break;
    }
  }
  return id;
}

/*
** Verify that zHash is a valid hash for the content of a file on
** disk named zFile.
**
** Return true if the hash is correct.  Return false if the content
** does not match the hash.
**
** Actually, the returned value is one of the hash algorithm constants
** corresponding to the hash that matched if the hash is correct.
** (Examples: HNAME_SHA1 or HNAME_K256).  And the return is HNAME_ERROR
** if the hash does not match.
*/
int hname_verify_file_hash(const char *zFile, const char *zHash, int nHash){
  int id = HNAME_ERROR;
  switch( nHash ){
    case HNAME_LEN_SHA1: {
      Blob hash;
      if( sha1sum_file(zFile, RepoFILE, &hash) ) break;
      if( memcmp(blob_buffer(&hash),zHash,HNAME_LEN_SHA1)==0 ) id = HNAME_SHA1;
      blob_reset(&hash);
      break;
    }
    case HNAME_LEN_K256: {
      Blob hash;
      if( sha3sum_file(zFile, RepoFILE, 256, &hash) ) break;
      if( memcmp(blob_buffer(&hash),zHash,64)==0 ) id = HNAME_LEN_K256;
      blob_reset(&hash);
      break;
    }
  }
  return id;
}

/*
** Compute a hash on blob pContent.  Write the hash into blob pHashOut.
** This routine assumes that pHashOut is uninitialized.
**
** The preferred hash is used for iHType==0 and the alternative hash is
** used if iHType==1.  (The interface is designed to accommodate more than
** just two hashes, but HNAME_COUNT is currently fixed at 2.)
**
** Depending on the hash policy, the alternative hash may be disallowed.
** If the alterative hash is disallowed, the routine returns 0.  This
** routine returns 1 if iHType>0 and the alternative hash is allowed,
** and it always returns 1 when iHType==0.
**
** Alternative hash is disallowed for all hash policies except auto,
** sha1 and sha3.
*/
int hname_hash(const Blob *pContent, unsigned int iHType, Blob *pHashOut){
  assert( iHType==0 || iHType==1 );
  if( iHType==1 ){
    switch( g.eHashPolicy ){
      case HPOLICY_AUTO:
      case HPOLICY_SHA1:
        sha3sum_blob(pContent, 256, pHashOut);
        return 1;
      case HPOLICY_SHA3:
        sha1sum_blob(pContent, pHashOut);
        return 1;
    }
  }
  if( iHType==0 ){
    switch( g.eHashPolicy ){
      case HPOLICY_SHA1:
      case HPOLICY_AUTO:
        sha1sum_blob(pContent, pHashOut);
        return 1;
      case HPOLICY_SHA3:
      case HPOLICY_SHA3_ONLY:
      case HPOLICY_SHUN_SHA1:
        sha3sum_blob(pContent, 256, pHashOut);
        return 1;
    }
  }
  blob_init(pHashOut, 0, 0);
  return 0;
}

/*
** Return the default hash policy for repositories that do not currently
** have an assigned hash policy.
**
** Make the default HPOLICY_AUTO if there are SHA1 artifacts but no SHA3
** artifacts in the repository.  Make the default HPOLICY_SHA3 if there
** are one or more SHA3 artifacts or if the repository is initially empty.
*/
int hname_default_policy(void){
  if( db_exists("SELECT 1 FROM blob WHERE length(uuid)>40")
   || !db_exists("SELECT 1 FROM blob WHERE length(uuid)==40")
  ){
    return HPOLICY_SHA3;
  }else{
    return HPOLICY_AUTO;
  }
}

/*
** Names of the hash policies.
*/
static const char *const azPolicy[] = {
  "sha1", "auto", "sha3", "sha3-only", "shun-sha1"
};

/* Return the name of the current hash policy.
*/
const char *hpolicy_name(void){
  return azPolicy[g.eHashPolicy];
}


/*
** COMMAND: hash-policy*
**
** Usage: fossil hash-policy ?NEW-POLICY?
**
** Query or set the hash policy for the current repository.  Available hash
** policies are as follows:
**
**   sha1              New artifact names are created using SHA1
**
**   auto              New artifact names are created using SHA1, but
**                     automatically change the policy to "sha3" when
**                     any SHA3 artifact enters the repository.
**
**   sha3              New artifact names are created using SHA3, but
**                     older artifacts with SHA1 names may be reused.
**
**   sha3-only         Use only SHA3 artifact names.  Do not reuse legacy
**                     SHA1 names.
**
**   shun-sha1         Shun any SHA1 artifacts received by sync operations
**                     other than clones.  Older legacy SHA1 artifacts are
**                     allowed during a clone.
**
** The default hash policy for existing repositories is "auto", which will
** immediately promote to "sha3" if the repository contains one or more
** artifacts with SHA3 names.  The default hash policy for new repositories
** is "shun-sha1".
*/
void hash_policy_command(void){
  int i;
  db_find_and_open_repository(0, 0);
  if( g.argc!=2 && g.argc!=3 ) usage("?NEW-POLICY?");
  if( g.argc==2 ){
    fossil_print("%s\n", azPolicy[g.eHashPolicy]);
    return;
  }
  for(i=HPOLICY_SHA1; i<=HPOLICY_SHUN_SHA1; i++){
    if( fossil_strcmp(g.argv[2],azPolicy[i])==0 ){
      if( i==HPOLICY_AUTO
       && db_exists("SELECT 1 FROM blob WHERE length(uuid)>40")
      ){
        i = HPOLICY_SHA3;
      }
      g.eHashPolicy = i;
      db_set_int("hash-policy", i, 0);
      fossil_print("%s\n", azPolicy[i]);
      return;
    }
  }
  fossil_fatal("unknown hash policy \"%s\" - should be one of: sha1 auto"
               " sha3 sha3-only shun-sha1", g.argv[2]);
}
