package Crypt::Sodium::XS::hash;
use strict;
use warnings;

use Crypt::Sodium::XS;
use Exporter 'import';

_define_constants();

{
  my @bases = qw(
    init
  );

  my @constant_bases = qw(
    BYTES
  );

  my $default = [
    "hash",
    (map { "hash_$_" } @bases),
    (map { "hash_$_" } @constant_bases, "PRIMITIVE"),
  ];
  my $sha256 = [
    "hash_sha256",
    (map { "hash_sha256_$_" } @bases),
    (map { "hash_sha256_$_" } @constant_bases),
  ];
  my $sha512 = [
    "hash_sha512",
    (map { "hash_sha512_$_" } @bases),
    (map { "hash_sha512_$_" } @constant_bases),
  ];
  my $sha3256 = [
    "hash_sha3256",
    (map { "hash_sha3256_$_" } @bases),
    (map { "hash_sha3256_$_" } @constant_bases),
  ];
  my $sha3512 = [
    "hash_sha3512",
    (map { "hash_sha3512_$_" } @bases),
    (map { "hash_sha3512_$_" } @constant_bases),
  ];
  my $features = ['hash_sha3_available'];

  our %EXPORT_TAGS = (
    all => [ @$features, @$default, @$sha256, @$sha512 ],
    features => $features,
    default => $default,
    sha2 => [ @$sha256, @$sha512 ],
    sha256 => $sha256,
    sha512 => $sha512,
    sha3 => [ @$sha3256, @$sha3512 ],
    sha3256 => $sha3256,
    sha3512 => $sha3512,
  );

  our @EXPORT_OK = @{$EXPORT_TAGS{all}};
}

*sha3_available = \&hash_sha3_available;

package Crypt::Sodium::XS::OO::hash;
use parent 'Crypt::Sodium::XS::OO::Base';

my %methods = (
  default => {
    BYTES => \&Crypt::Sodium::XS::hash::hash_BYTES,
    PRIMITIVE => \&Crypt::Sodium::XS::hash::hash_PRIMITIVE,
    hash => \&Crypt::Sodium::XS::hash::hash,
    init => sub { die "init is not supported for default primitive" },
  },
  sha256 => {
    BYTES => \&Crypt::Sodium::XS::hash::hash_sha256_BYTES,
    PRIMITIVE => sub { 'sha256' },
    hash => \&Crypt::Sodium::XS::hash::hash_sha256,
    init => \&Crypt::Sodium::XS::hash::hash_sha256_init,
  },
  sha512 => {
    BYTES => \&Crypt::Sodium::XS::hash::hash_sha512_BYTES,
    PRIMITIVE => sub { 'sha512' },
    hash => \&Crypt::Sodium::XS::hash::hash_sha512,
    init => \&Crypt::Sodium::XS::hash::hash_sha512_init,
  },
  Crypt::Sodium::XS::hash->sha3_available ? (
    sha3256 => {
      BYTES => \&Crypt::Sodium::XS::hash::hash_sha3256_BYTES,
      PRIMITIVE => sub { 'sha3256' },
      hash => \&Crypt::Sodium::XS::hash::hash_sha3256,
      init => \&Crypt::Sodium::XS::hash::hash_sha3256_init,
    },
    sha3512 => {
      BYTES => \&Crypt::Sodium::XS::hash::hash_sha3512_BYTES,
      PRIMITIVE => sub { 'sha3512' },
      hash => \&Crypt::Sodium::XS::hash::hash_sha3512,
      init => \&Crypt::Sodium::XS::hash::hash_sha3512_init,
    },
  ) : (),
);

sub Crypt::Sodium::XS::hash::primitives { keys %methods }
*primitives = \&Crypt::Sodium::XS::hash::primitives;
*sha3_available = \&Crypt::Sodium::XS::hash::sha3_available;

sub BYTES { my $self = shift; goto $methods{$self->{primitive}}->{BYTES}; }
sub PRIMITIVE { my $self = shift; goto $methods{$self->{primitive}}->{PRIMITIVE}; }
sub hash { my $self = shift; goto $methods{$self->{primitive}}->{hash}; }
sub init { my $self = shift; goto $methods{$self->{primitive}}->{init}; }

1;

__END__

=encoding utf8

=head1 NAME

Crypt::Sodium::XS::hash - SHA2 cryptographic hashing

=head1 SYNOPSIS

  use Crypt::Sodium::XS;
  my $xs_hash = Crypt::Sodium::XS->hash;

  my $hash = $xs_hash->hash("arbitrary input data");

  my $multipart = $xs_hash->init;
  $multipart->update("arbitrary");
  $multipart->update("input", " data");
  $hash = $multipart->final;

=head1 DESCRIPTION

The SHA-2 (SHA-256 and SHA-512) and SHA-3 (SHA3-256 and SHA3-512)  functions are
provided for interoperability with other applications. If you are looking for a
generic hash function and not specifically SHA-2 or SHA-3, using
L<Crypt::Sodium::XS::generichash> (BLAKE2b) might be a better choice.

These functions are also not suitable for hashing passwords or deriving keys
from passwords. Use L<Crypt::Sodium::XS::pwhash> instead.

These functions are not keyed and are thus deterministic. In addition, the
untruncated versions are vulnerable to length extension attacks.

A message can be hashed in a single pass, but a multi-part API is also
available to process a message as a sequence of multiple chunks.

=head1 CONSTRUCTOR

The constructor is called with the C<Crypt::Sodium::XS-E<gt>hash> method.

  my $xs_hash = Crypt::Sodium::XS->hash;
  my $xs_hash = Crypt::Sodium::XS->hash(primitive => 'sha256');

Returns a new hash object.

Implementation detail: the returned object is blessed into
C<Crypt::Sodium::XS::OO::hash>.

=head1 ATTRIBUTES

=head2 primitive

  my $primitive = $xs_hash->primitive;
  $xs_hash->primitive('sha256');

Gets or sets the primitive used for all operations by this object. It must be
one of the primitives listed in L</PRIMITIVES>, including C<default>.

=head1 METHODS

=head2 sha3_available

  my $has_sha3 = $xs_hash->sha3_available;
  my $has_sha3 = Crypt::Sodium::XS::hash->sha3_available;

Returns true if L<Crypt::Sodium::XS> supports SHA-3, false otherwise. SHA-3
will only be supported if L<Crypt::Sodium::XS> was built with a new enough
version of libsodium (at least 1.0.22).

Can be called as a class method.

=head2 primitives

  my @primitives = $xs_hash->primitives;
  my @primitives = Crypt::Sodium::XS::hash->primitives;

Returns a list of all supported primitive names, including C<default>.

Can be called as a class method.

=head2 PRIMITIVE

  my $primitive = $xs_hash->PRIMITIVE;

Returns the primitive used for all operations by this object. Note this will
never be C<default> but would instead be the primitive it represents.

=head2 hash

  my $hash = $xs_hash->hash($message);

C<$message> is the message to hash. It may be a L<Crypt::Sodium::XS::MemVault>.

Returns the hash output of L</BYTES> bytes.

=head2 init

  my $multipart = $xs_hash->init($flags);

C<$flags> is optional. It is the flags used for the multipart protected memory
object. See L<Crypt::Sodium::XS::ProtMem>.

Returns a multipart hashing object. See L</MULTI-PART INTERFACE>.

=head2 BYTES

  my $xs_hash_size = $xs_hash->BYTES;

Returns the size, in bytes, of hash output.

=head1 MULTI-PART INTERFACE

A multipart hashing object is created by calling the L</init> method. Data to
be hashed is added by calling the L</update> method of that object as many
times as desired. An output hash is generated by calling its L</final> method.
Do not use the object after calling L</final>.

The multipart hashing object is an opaque object which provides the following
methods:

=head2 clone

  my $multipart_copy = $multipart->clone;

Returns a cloned copy of the multipart hashing object, duplicating its internal
state.

=head2 final

  my $xs_hash = $multipart->final;

Retruns the final hash for all data added with L</update>.

Once C<final> has been called, the hashing object must not be used further.

=head2 update

  $multipart->update(@messages);

Adds all given arguments (stringified) to hashed data. Any argument may be a
L<Crypt::Sodium::XS::MemVault>.

=head1 PRIMITIVES

=over 4

=item * sha256

=item * sha512 (default)

=item * sha3256

=item * sha3512

=back

Note: SHA3-256 and SHA3-512 were added in libsodium 1.0.22. Check for
availability with L</sha3_available>.

=head1 FUNCTIONS

The object API above is the recommended way to use this module. The functions
and constants documented below can be imported instead or in addition.

Nothing is exported by default. A C<:features> tag imports the C<*_available>
feature test functions. A C<:default> tag imports the functions and constants
documented below. A separate C<:E<lt>primitiveE<gt>> import tag is provided for
each of the primitives listed in L</PRIMITIVES>. These tags import the
C<hash_E<lt>primitiveE<gt>_*> functions and constants for that primitive. A
C<:all> tag imports everything.

=head2 hash_sha3_available

Same as L</sha3_available>.

=head2 hash (function)

=head2 hash_E<lt>primitiveE<gt>

  my $hash = hash($message);

Same as L</hash> (in L</METHODS> above).

=head2 hash_init

=head2 hash_E<lt>primitiveE<gt>_init

  my $multipart = hash_init($flags);

Same as L</init>.

=head1 CONSTANTS

=head2 hash_PRIMITIVE

  my $default_primitive = hash_PRIMITIVE();

Same as L</PRIMITIVE>.

=head2 hash_BYTES

=head2 hash_E<lt>primitiveE<gt>_BYTES

  my $hash_size = hash_BYTES();

Same as L</BYTES>.

=head1 SEE ALSO

=over

=item L<Crypt::Sodium::XS>

=item L<https://doc.libsodium.org/advanced/sha-2_hash_function>

=item L<https://libsodium.gitbook.io/doc/advanced/sha-3_hash_function>

=back

=head1 FEEDBACK

For reporting bugs, giving feedback, submitting patches, etc. please use the
following:

=over 4

=item *

RT queue at L<https://rt.cpan.org/Dist/Display.html?Name=Crypt-Sodium-XS>

=item *

IRC channel C<#sodium> on C<irc.perl.org>.

=item *

Email the author directly.

=back

=head1 AUTHOR

Brad Barden E<lt>perlmodules@5c30.orgE<gt>

=head1 COPYRIGHT & LICENSE

Copyright (c) 2022 Brad Barden. All rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut
