{
    Double Commander
    -------------------------------------------------------------------------
    General Hash Unit: This unit defines the common types, functions,
    and procedures

    Copyright (C) 2009-2024 Alexander Koblov (alexx2000@mail.ru)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
}

unit uHash;

{$mode delphi}

interface

uses
  Classes, SysUtils, DCPcrypt2, crc;

type
  THashContext   = TDCP_hash;
  THashAlgorithm = (HASH_BLAKE2S, HASH_BLAKE2SP, HASH_BLAKE2B, HASH_BLAKE2BP, HASH_BLAKE3,
                    HASH_CHECKSUM32, HASH_CRC32, HASH_HAVAL, HASH_MD4, HASH_MD5, HASH_RIPEMD128, HASH_RIPEMD160,
                    HASH_SFV, HASH_SHA1, HASH_SHA224, HASH_SHA256, HASH_SHA384, HASH_SHA512,
                    HASH_SHA3_224, HASH_SHA3_256, HASH_SHA3_384, HASH_SHA3_512, HASH_TIGER, HASH_XXH3_128,
                    HASH_BEST
                    );

var
  HashFileExt: array[Low(THashAlgorithm)..Pred(High(THashAlgorithm))] of String = (
                 'blake2s', 'blake2sp', 'blake2b', 'blake2bp', 'blake3', 'checksum32', 'crc32', 'haval',
                 'md4', 'md5', 'ripemd128', 'ripemd160', 'sfv', 'sha', 'sha224', 'sha256',
                 'sha384', 'sha512', 'sha3', 'sha3', 'sha3', 'sha3', 'tiger', 'xxh128'
               );

var
  HashName: array[Low(THashAlgorithm)..Pred(High(THashAlgorithm))] of String = (
                 'blake2s', 'blake2sp', 'blake2b', 'blake2bp', 'blake3', 'checksum32', 'crc32', 'haval',
                 'md4', 'md5', 'ripemd128', 'ripemd160', 'sfv', 'sha1_160', 'sha2_224',
                 'sha2_256', 'sha2_384', 'sha2_512', 'sha3_224', 'sha3_256',
                 'sha3_384', 'sha3_512', 'tiger', 'xxh3_128'
               );

  HashFirst: array[0..11] of THashAlgorithm = (
               HASH_SFV,      HASH_SHA224, HASH_SHA3_224,
               HASH_BLAKE3,   HASH_SHA256, HASH_SHA3_256,
               HASH_XXH3_128, HASH_SHA384, HASH_SHA3_384,
               HASH_SHA1,     HASH_SHA512, HASH_SHA3_512
             );

procedure HashInit(out Context: THashContext; Algorithm: THashAlgorithm);
procedure HashUpdate(var Context: THashContext; const Buffer; BufLen: LongWord);
procedure HashFinal(var Context: THashContext; out Hash: String);

function HashString(const Line: String; IgnoreCase, IgnoreWhiteSpace: Boolean): LongWord;

{ Helper functions }
function TrimHash(const AHash: String): String;
function FileExtIsHash(const FileExt: String): Boolean;
function FileExtToHashAlg(const FileExt: String): THashAlgorithm;

implementation

uses
  LazUTF8, DCPhaval, DCPmd4, DCPmd5, DCPripemd128, DCPripemd160, DCPChecksum32, DCPcrc32,
  DCPsha1, DCPsha256, DCPsha512, DCPtiger, DCPblake2, DCPblake3, DCPsha3, DCPxxh3;

procedure HashInit(out Context: THashContext; Algorithm: THashAlgorithm);
begin
  if (Algorithm = HASH_BEST) then
  begin
{$IF DEFINED(CPUX86_64)}
    Algorithm:= HASH_BLAKE3;
{$ELSEIF DEFINED(CPUAARCH64)}
    Algorithm:= HASH_BLAKE3;
{$ELSEIF DEFINED(CPU64)}
    Algorithm:= HASH_BLAKE2B;
{$ELSE}
    Algorithm:= HASH_BLAKE2S;
{$ENDIF}
  end;
  case Algorithm of
    HASH_BLAKE2S:    Context:= TDCP_blake2s.Create(nil);
    HASH_BLAKE2SP:   Context:= TDCP_blake2sp.Create(nil);
    HASH_BLAKE2B:    Context:= TDCP_blake2b.Create(nil);
    HASH_BLAKE2BP:   Context:= TDCP_blake2bp.Create(nil);
    HASH_BLAKE3:     Context:= TDCP_blake3.Create(nil);
    HASH_CHECKSUM32: Context:= TDCP_checksum32.Create(nil);
    HASH_CRC32:      Context:= TDCP_crc32.Create(nil);
    HASH_HAVAL:      Context:= TDCP_haval.Create(nil);
    HASH_MD4:        Context:= TDCP_md4.Create(nil);
    HASH_MD5:        Context:= TDCP_md5.Create(nil);
    HASH_RIPEMD128:  Context:= TDCP_ripemd128.Create(nil);
    HASH_RIPEMD160:  Context:= TDCP_ripemd160.Create(nil);
    HASH_SFV:        Context:= TDCP_crc32.Create(nil);
    HASH_SHA1:       Context:= TDCP_sha1.Create(nil);
    HASH_SHA224:     Context:= TDCP_sha224.Create(nil);
    HASH_SHA256:     Context:= TDCP_sha256.Create(nil);
    HASH_SHA384:     Context:= TDCP_sha384.Create(nil);
    HASH_SHA512:     Context:= TDCP_sha512.Create(nil);
    HASH_SHA3_224:   Context:= TDCP_sha3_224.Create(nil);
    HASH_SHA3_256:   Context:= TDCP_sha3_256.Create(nil);
    HASH_SHA3_384:   Context:= TDCP_sha3_384.Create(nil);
    HASH_SHA3_512:   Context:= TDCP_sha3_512.Create(nil);
    HASH_TIGER:      Context:= TDCP_tiger.Create(nil);
    HASH_XXH3_128:   Context:= TDCP_xxh3_128.Create(nil);
  end;

  Context.Init;
end;

procedure HashUpdate(var Context: THashContext; const Buffer; BufLen: LongWord);
begin
  Context.Update(Buffer, BufLen);
end;

procedure HashFinal(var Context: THashContext; out Hash: String);
var
  I, HashSize: LongWord;
  Digest: array of Byte;
begin
  Hash:= EmptyStr;
  HashSize:= Context.HashSize div 8;
  SetLength(Digest, HashSize);
  Context.Final(Pointer(Digest)^);
  for I := 0 to HashSize - 1 do
    Hash := Hash + HexStr(Digest[I], 2);
  Hash := LowerCase(Hash);
  FreeAndNil(Context);
end;

function HashString(const Line: String; IgnoreCase, IgnoreWhiteSpace: Boolean): LongWord;
var
  S: String;
  I, J, L: Integer;
begin
  S := Line;
  if IgnoreWhiteSpace then
  begin
    J := 1;
    L := Length(Line);
    for I:= 1 to L do
    begin
      // Skip white spaces
      if not (Line[I] in [#9, #32]) then
      begin
        S[J] := Line[I];
        Inc(J);
      end;
    end;
    SetLength(S, J - 1);
  end;
  if IgnoreCase then S := UTF8LowerCase(S);

  Result := crc32(0, nil, 0);
  Result := crc32(Result, PByte(S), Length(S));
end;

function TrimHash(const AHash: String): String;
var
  I, J: Integer;
begin
  J:= 0;
  Result:= EmptyStr;
  SetLength(Result, Length(AHash));
  for I:= 1 to Length(AHash) do
  begin
    if (AHash[I] in ['0'..'9', 'A'..'F', 'a'..'f']) then
    begin
      Inc(J);
      Result[J]:= AHash[I];
    end;
  end;
  SetLength(Result, J);
end;

function FileExtIsHash(const FileExt: String): Boolean;
var
  I: THashAlgorithm;
begin
  Result:= False;
  for I:= Low(HashFileExt) to High(HashFileExt) do
  begin
    if SameText(FileExt, HashFileExt[I]) then Exit(True);
  end;
end;

function FileExtToHashAlg(const FileExt: String): THashAlgorithm;
var
  I: THashAlgorithm;
begin
  for I:= Low(HashFileExt) to High(HashFileExt) do
  begin
    if SameText(FileExt, HashFileExt[I]) then Exit(I);
  end;
end;

end.

