Algorithm-Kademlia
view release on metacpan or search on metacpan
lib/Algorithm/Kademlia.pm view on Meta::CPAN
use v5.42;
use experimental 'class';
#
package Algorithm::Kademlia v1.1.1 {
use parent 'Exporter';
our @EXPORT_OK = qw[xor_distance xor_bucket_index];
#
sub xor_distance ( $id1_bin, $id2_bin ) { $id1_bin^.$id2_bin }
sub xor_bucket_index ( $id1_bin, $id2_bin ) {
my $dist = $id1_bin^.$id2_bin;
my @bytes = unpack( 'C*', $dist );
my $len = scalar @bytes;
for my $i ( 0 .. $#bytes ) {
next if $bytes[$i] == 0;
my $byte = $bytes[$i];
for ( my $j = 7; $j >= 0; $j-- ) {
# Standard Kademlia: bucket i covers distance [2^i, 2^{i+1})
return ( ( $len - 1 - $i ) * 8 ) + $j if $byte & ( 1 << $j );
}
}
return undef; # Same ID
}
class Algorithm::Kademlia::RoutingTable v1.1.0 {
field $local_id_bin : param : writer : reader;
field $k : param //= 20;
field @buckets : reader;
#
ADJUST {
my $id_len = length $local_id_bin;
my $num_buckets = $id_len * 8;
@buckets = map { [] } 0 .. $num_buckets - 1
}
method add_peer ( $peer_id_bin, $peer_data ) {
my $idx = Algorithm::Kademlia::xor_bucket_index( $local_id_bin, $peer_id_bin );
return undef unless defined $idx;
my $bucket = $buckets[$idx];
# Find existing
my $existing_idx = -1;
for my $i ( 0 .. $#$bucket ) {
if ( $bucket->[$i]{id} eq $peer_id_bin ) {
$existing_idx = $i;
last;
}
}
if ( $existing_idx != -1 ) { # Move to tail (most recent)
my $peer = splice( @$bucket, $existing_idx, 1 );
$peer->{data} = $peer_data; # Update data
push @$bucket, $peer;
return undef;
}
if ( scalar @$bucket < $k ) {
push @$bucket, { id => $peer_id_bin, data => $peer_data };
return undef;
}
$bucket->[0]; # Bucket is full. Return oldest peer to be pinged.
}
method evict_peer ($peer_id_bin) {
my $idx = Algorithm::Kademlia::xor_bucket_index( $local_id_bin, $peer_id_bin ) // return;
my $bucket = $buckets[$idx];
@$bucket = grep { $_->{id} ne $peer_id_bin } @$bucket;
}
method find_closest ( $target_id_bin, $count = undef ) {
$count //= $k;
my @all_peers;
push @all_peers, @$_ for @buckets;
my @sorted = sort { ( $a->{id} ^.$target_id_bin ) cmp( $b->{id} ^.$target_id_bin ) } @all_peers;
splice @sorted, 0, $count;
}
method size () {
my $count = 0;
$count += scalar @$_ for @buckets;
$count;
}
method import_peers ($peer_list) {
for my $p (@$peer_list) {
# Directly push to avoid eviction logic during restore
my $idx = Algorithm::Kademlia::xor_bucket_index( $local_id_bin, $p->{id} );
next unless defined $idx;
push $buckets[$idx]->@*, $p;
}
}
};
( run in 0.661 second using v1.01-cache-2.11-cpan-140bd7fdf52 )