package Crypt::MultiKey::FIDO2::Device;
our $VERSION = '0.000_002'; # TRIAL VERSION
# ABSTRACT: Represent a connected fido_dev_t struct of libfido2

use v5.12;
use warnings;
use Carp 'croak';
use Crypt::MultiKey::FIDO2;


sub new {
   my ($class, $path)= @_;
   croak "libfido2 support not available; install libfido2 and then reinstall Crypt::MultiKey"
      unless Crypt::MultiKey::FIDO2::available();

   my $self= bless {}, $class;
   $self->open($path) or croak "open($path): ".$self->fido_err;
   return $self;
}


sub path { $_[0]{path} }
sub fido_err { $_[0]{fido_err} }


sub aaguid             { shift->_cbor_attrs->{aaguid} }
sub aaguid_hex         { Crypt::MultiKey::FIDO2::_format_guid(shift->_cbor_attrs->{aaguid}) }
sub algorithms         { shift->_cbor_attrs->{algorithms} }
sub certifications     { shift->_cbor_attrs->{certifications} }
sub extensions         { shift->_cbor_attrs->{extensions} }
sub fwversion          { shift->_cbor_attrs->{fwversion} }
sub maxcredbloblen     { shift->_cbor_attrs->{maxcredbloblen} }
sub maxcredcntlst      { shift->_cbor_attrs->{maxcredcntlst} }
sub maxcredidlen       { shift->_cbor_attrs->{maxcredidlen} }
sub maxlargeblob       { shift->_cbor_attrs->{maxlargeblob} }
sub maxmsgsiz          { shift->_cbor_attrs->{maxmsgsiz} }
sub maxrpid_minpinlen  { shift->_cbor_attrs->{maxrpid_minpinlen} }
sub minpinlen          { shift->_cbor_attrs->{minpinlen} }
sub new_pin_required   { shift->_cbor_attrs->{new_pin_required} }
sub options            { shift->_cbor_attrs->{options} }
sub protocols          { shift->_cbor_attrs->{protocols} }
sub rk_remaining       { shift->_cbor_attrs->{rk_remaining} }
sub transports         { shift->_cbor_attrs->{transports} }
sub uv_attempts        { shift->_cbor_attrs->{uv_attempts} }
sub uv_modality        { shift->_cbor_attrs->{uv_modality} }
sub versions           { shift->_cbor_attrs->{versions} }


sub make_hmac_secret_credential {
   my ($self, %options)= @_;
   my ($pin, $discoverable, $rp_domain, $rp_name, $user_name, $user_display_name, $user_icon) =
      delete @options{qw( pin discoverable rp_domain rp_name user_name user_display_name user_icon )};
   croak "Unknown parameter ".join(', ', keys %options)
      if keys %options;
   $self->_make_hmac_secret_credential($pin,
      $discoverable // 1,
      $rp_domain // 'crypt-multikey.local',
      $rp_name // 'Crypt::MultiKey',
      $user_name // 'Crypt::MultiKey::FIDO2::Device',
      $user_display_name // 'Crypt::MultiKey::FIDO2::Device',
      $user_icon);
}


sub assert_hmac_secret {
   my ($self, %options)= @_;
   my ($pin, $credential, $challenge, $rp_domain) =
      delete @options{qw( pin credential challenge rp_domain )};
   croak "Unknown parameter ".join(', ', keys %options)
      if keys %options;
   $credential= [ $credential ]
      unless ref $credential eq 'ARRAY';
   my ($idx, $resp)= $self->_assert_hmac_secret($pin,
      $rp_domain // 'crypt-multikey.local',
      $credential,
      Crypt::MultiKey::sha256($challenge));
   if (defined $idx) {
      return ($resp, $credential->[$idx]);
   } else {
      return;
   }
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Crypt::MultiKey::FIDO2::Device - Represent a connected fido_dev_t struct of libfido2

=head1 DESCRIPTION

This class represents a connection to a FIDO2 device (the fido_dev_t struct of libfido2)

This is an incomplete abstraction of the libfido2 API, providing just enough for the
L<Crype::MultiKey::PKey::FIDO2> implementation.

=head1 CONSTRUCTORS

=head2 new

  $dev= Crype::MultiKey::FIDO2::Device->new($dev_path);

=head1 ATTRIBUTES

=head2 path

The current host device path name for the authenticator, such as C<< '/dev/hidraw1' >>.

=head2 fido_err

The last error code received from the libfido2 API.

=head2 aaguid

A hardware identifier for the model/version of the authenticator, as raw 16 bytes.

=head2 aaguid_hex

A hardware identifier for the model/version of the authenticator, formatted in standard GUID
hex notation.

=head2 algorithms

An arrayref describing public-key algorithms supported by the authenticator.
Each element is a hashref containing fields like C<type> and C<cose>.

=head2 certifications

A hashref of certification identifiers reported by the authenticator.

=head2 extensions

An arrayref of extension names supported by the authenticator.

=head2 fwversion

The firmware version reported by the authenticator.

=head2 maxcredbloblen

Maximum supported size of a credential blob.

=head2 maxcredcntlst

Maximum number of credential IDs accepted in an allow-list.

=head2 maxcredidlen

Maximum supported credential ID length.

=head2 maxlargeblob

Maximum supported size of the large-blob storage area.

=head2 maxmsgsiz

Maximum CTAP message size supported by the authenticator.

=head2 maxrpid_minpinlen

Maximum number of RP IDs tracked for minimum PIN length policy.

=head2 minpinlen

Minimum PIN length currently enforced by the authenticator.

=head2 new_pin_required

True if the authenticator requires a new PIN to be set.

=head2 options

A hashref of authenticator option flags.

=head2 protocols

An arrayref of supported PIN/UV protocol version numbers.

=head2 rk_remaining

The remaining number of discoverable credentials the authenticator reports it can store,
or a negative value if not reported.

=head2 transports

An arrayref of supported transport names.

=head2 uv_attempts

Number of remaining built-in user-verification attempts, if reported.

=head2 uv_modality

A bitmask describing built-in user-verification methods supported by the authenticator.

=head2 versions

An arrayref of supported CTAP/U2F protocol version strings.

=head2 is_fido2

True if authenticator supports FIDO2

=head2 supports_pin

True if the authenticator supports a PIN.

=head2 has_pin

True if the authenticator has a PIN set.

=head2 supports_uv

True if the authenticator supports user verification (UV), such as PIN
or biometric authentication, in addition to simple user presence checks
like a button press.

=head2 has_uv

True if the authenticator currently has a usable user verification (UV)
method configured, such as a PIN or biometric. This does not mean UV will
be required, only that it is available if requested.

=head1 METHODS

=head2 open

  $bool= $device->open($dev_path);

Open a new device path.  On success, any device already open in the current object is closed.
On failure, the error is stored in L</fido_err>.

=head2 get_touch_begin

  $device->get_touch_begin // croak $device->fido_err;

Begin a "get touch" process.  This tells the device that we want to poll for a touch event.
It signals to the user that a touch is requested, and reports whether this occurs via
L</get_touch_status>.

=head2 get_touch_status

  $touched= $device->get_touch_status;
  $touched= $device->get_touch_status($wait_seconds);

Check whether this device has received a touch since L</get_touch_begin> was called.  If it
returns true, the touch occurred and the "get touch" process is complete.  If it returns false,
no touch occurred and the "get touch" is ongoing.  If it returns C<undef>, there was an error
and the "get touch" is most likely terminated.

=head2 cancel

  $device->cancel;

Cancel an ongoing operation on the device, such as a "get touch" process.

=head2 make_hmac_secret_credential

  my $cred= $device->make_hmac_secret_credential(%options);
  # %options:
  #  discoverable      => $bool,  # whether credential can be seen on device
  #  rp_domain         => $str,   # default "crypt-multikey.local"
  #  rp_name           => $str,   # default "Crypt::MultiKey"
  #  user_name         => $str,   # who or what will be using the credential
  #  user_display_name => $str,
  #  user_icon         => $data,
  #  pin               => $pin_password,

Creates a new credential on the authenticator, for purposes of using the hmac-secret extension.
FIDO devices have a limited space for storing credentials, so use this method sparingly.
The return value is a hashref of attributes about the credential. Devices may or may not require
the C<pin>.

=head2 assert_hmac_secret

  ($resp, $cred)= $device->assert_hmac_secret(%options);
  # %options:
  #  credential => $cred_or_arrayref,  # one or more credentials to test
  #  challenge  => $message,           # sha256($message) will become HMAC salt
  #  rp_domain  => $domain_name,       # must match value used during make_credential
  #  pin        => $pin_password,      # only for authenticators that require it

Assert that the device possesses one of the credentials (each a hashref as returned by
L</make_hmac_secret_credential>), and compute hmac-secret on 'challenge' using that credential.
The response (HMAC bytes) and which credential was used (same hashref as provided) are returned
as a list.  This method croaks on errors I<unless> the only error was that none of the
credentials exist on the device, in which case it returns an empty list.

This can test a device for multiple credentials in one call, so long as the C<rp_domain> and
C<challenge> is the same for each of them.  Devices may or may not require the C<pin>.

=head1 VERSION

version 0.000_002

=head1 AUTHOR

Michael Conrad <mike@nrdvana.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Michael Conrad.

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

=cut
