Crypt-IDA

 view release on metacpan or  search on metacpan

lib/Crypt/IDA/SlidingWindow.pm  view on Meta::CPAN

package Crypt::IDA::SlidingWindow;

# Copyright (c) Declan Malone, 2019
#
# See LICENSE

# Sliding Window algorithm to support cleaner IDA split/combine code

use Class::Tiny qw(bundle yts splitting combining), {
    # our 5 pointers (not really meant to be passed to new)
    read_head => 0,		# first to be advanced
    read_tail => 0,		# } peri-
    processed => 0,		# } stal-
    write_head => 0,		# } sis 
    write_tail => 0,		# start of window; last to advance
    # substream pointers (could be on read end or write end)
    # bundle => set up in BUILD
    
    # required
    mode => undef,		# 'split' or 'combine'
    rows => undef,		# how many substreams in bundle?
    window => undef,

    # optional callbacks (might move to Algorithm?)
    cb_error => undef,
    cb_read_bundle => undef,
    cb_wrote_bundle => undef,
    cb_processed => undef,
};

sub BUILD {
    my ($self, $args) = @_;
    for my $req ( qw(mode rows window) ) {
	die "$req attribute required" unless defined $self->$req;
    }
    die "Bad mode!" unless
	$self->{mode} eq 'split' or $self->{mode} eq 'combine';
    for my $plus ( qw(rows window) ) {
	die "$plus attribute must be > 0" unless $self->$plus > 0;
    }
    for my $zero ( qw(read_head read_tail processed write_head
                      write_tail) ) {
	die "Setting $zero attribute not allowed" unless $self->$zero == 0;
    }

    # Need to set up yts
    $self->{yts} = $self->{rows};

    # Couldn't set up bundle in Class::Tiny call
    my @bundle;
    for (1 .. $self->{rows}) {
	push @bundle, { head => 0, tail => 0 }
    }
    $self->{bundle} = \@bundle;

    $self->splitting($self->{mode} eq 'split'   ? 1 : 0 );
    $self->combining($self->{mode} eq 'combine' ? 1 : 0 );

}

# 
sub _error {
    my ($self,@msg) = @_;
    my $cb = $self->{cb_error};
    return @msg unless defined $cb;
    $cb->(@msg);
    undef;
}

# For clarity, I won't combine advance_read and advance_write into a
# single sub. This makes it easier to understand what's happening in
# _advance_rw_substream.

sub advance_read {
    my ($self,$cols) = @_;

    die "Use advance_read_substream instead" if $self->{combining};
    my ($head,$tail) = ($self->{read_head}, $self->{read_tail});

    # Note that read_head can be up to two windows ahead of
    # write_tail, but never more than one window from read_tail.
    die "Would exceed read window" if $head + $cols - $tail > $self->{window};
    $self->{read_head} += $cols;
}

sub advance_write {
    my ($self,$cols) = @_;

    die "Use advance_write_substream instead" if $self->{splitting};
    my ($head,$tail) = ($self->{write_head}, $self->{write_tail});

    # Advance tail, but not past head.
    die "Write tail would overtake head" if $tail + $cols > $head;
    $self->{write_tail} += $cols;
}

# code for advancing read/write substreams is the same, apart from
# error messages, callbacks and overall pointer to possibly update
sub _advance_rw_substream;
sub advance_read_substream  { shift->_advance_rw_substream("read", @_) }
sub advance_write_substream { shift->_advance_rw_substream("write", @_)}


# Returns:
# * undef on error
# * 0 if OK and bundle pointer didn't advance
# * 1 if OK and bundle pointer did advance
sub _advance_rw_substream {
    my ($self,$which,$row,$cols) = @_;
    my ($ptr, $parent, $cb);
    if ($which eq "read") {
	die "No read substreams!" if $self->{splitting};
	($ptr, $parent, $cb) = ("head", "read_head", "cb_read_bundle")
    } elsif ($which eq "write") {
	die "No write substreams!" if $self->{combining};
	($ptr, $parent, $cb) = ("tail", "write_tail", "cb_wrote_bundle");
    } else {
	die "_advance_some_substream: $which?";
    }
    die "Row out of range" if $row >= $self->{rows};

    my $hash    = $self->{bundle}->[$row];
    my $old_val = $hash->{$ptr};
    if ($which eq "read") {
	die "Read would overflow input buffer"
	    if $old_val + $cols - $hash->{tail} > $self->{window};
    } else {
	die "Write tail would overtake head"
	    if $old_val + $cols > $hash->{head};
    }
    my $new_val = $hash->{$ptr} += $cols;

    # possibly advance parent pointer to new minimum
    return 0 unless $old_val == $self->{$parent};
    return 0 if --$self->{yts};

    my $new_yts = 1;
    for my $r (0 .. $self->{rows} - 1) {
	next if $r == $row;
	my $this_val = $self->{bundle}->[$r]->{$ptr};
	next if $this_val > $new_val;
	if ($this_val < $new_val) {
	    ($new_val, $new_yts) = ($this_val, 1);
	} else {
	    ++$new_yts;
	}
    }
    ($self->{$parent}, $self->{yts}) = ($new_val, $new_yts);
    $self->{$cb}->() if defined $self->{$cb};
    return 1;
}

# The names here reflect the names of the related I/O commands as used
# by the caller:
#
# * read_ok: how much should we read to fill input buffer?
# * process_ok: how much input can we process to produce output?
# * write_ok: how much should we write to empty output buffer?



( run in 0.873 second using v1.01-cache-2.11-cpan-39bf76dae61 )