#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2015 -- leonerd@leonerd.org.uk

package App::MatrixTool::KeyStore;

use strict;
use warnings;

our $VERSION = '0.01';

use Errno qw( ENOENT );
use File::Basename qw( dirname );
use File::Path qw( make_path );
use MIME::Base64 qw( encode_base64 decode_base64 );

=head1 NAME

C<App::MatrixTool::KeyStore> - storage of remote server signing keys

=head1 DESCRIPTION

Provides a simple flat-file database that stores remote server signing keys,
indexed by server name and key ID. This database persists on a human-readable
text file, by default in the user's home directory under
F<$ENV{HOME}/.matrix/server-keys>.

=cut

sub new
{
   my $class = shift;
   my %args = @_;

   return bless {
      path => $args{path},
      keys_by_server => {},
   }, $class;
}

=head1 METHODS

=cut

sub _open_file
{
   my $self = shift;
   my ( $mode ) = @_;

   my $path = $self->{path};

   if( $mode eq ">>" and not -f $path ) {
      make_path( dirname( $path ) );
   }

   if( open my $fh, $mode, $path ) {
      return $fh;
   }

   return undef if $! == ENOENT and $mode eq "<";
   die "Cannot open $path - $!\n";
}

sub _read_file
{
   my $self = shift;
   return if $self->{have_read};

   if( my $fh = $self->_open_file( "<" ) ) {
      while( <$fh> ) {
         m/^\s*#/ and next; # ignore comment lines
         my ( $server, $id, $key ) = split m/\s+/, $_;

         defined $key or warn( "Unable to parse line $_" ), next;

         $self->{keys}{$server}{$id} = decode_base64( $key );
      }
   }

   $self->{have_read}++;
}

=head2 get_keys

   %keys = $keystore->get_keys( server_name => $name )

Returns a kvlist associating key IDs to byte strings containing the given
remote server's signing keys.

=cut

sub get_keys
{
   my $self = shift;
   my %args = @_;

   my $server_name = $args{server_name};
   $self->_read_file;

   my %keys;
   foreach my $id ( keys %{ $self->{keys}{$server_name} } ) {
      $keys{$id} = $self->{keys}{$server_name}{$id};
   }

   return %keys;
}

=head2 put_key

   $keystore->put_key( server_name => $name, key_id => $id, public_key => $bytes )

Stores a byte string representing the given server's key.

=cut

sub put_key
{
   my $self = shift;
   my %args = @_;

   my $server = $args{server_name};
   my $key_id = $args{key_id};

   if( exists $self->{keys}{$server}{$key_id} ) {
      return if $self->{keys}{$server}{$key_id} eq $args{public_key};
      warn "KeyStore is overwriting a key with a different value!\n";
   }

   my $fh = $self->_open_file( ">>" );
   $fh->print( "$server $key_id " . encode_base64( $args{public_key}, "" ) . "\n" );

   $self->{keys_by_server}{$server}{$key_id} = $args{public_key};
}

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
