use strict; use warnings; use Data::ParseBinary::Core; package Data::ParseBinary::Enum; our @ISA = qw{Data::ParseBinary::Adapter}; # TODO: implement as macro in terms of SymmetricMapping (macro) # that is implemented as MappingAdapter sub _init { my ($self, @params) = @_; my $decode = {}; my $encode = {}; $self->{have_default} = 0; $self->{default_action} = undef; while (@params) { my $key = shift @params; my $value = shift @params; if ($key eq '_default_') { $self->{have_default} = 1; $self->{default_action} = $value; if (ref $value) { if ($value != $Data::ParseBinary::BaseConstruct::DefaultPass) { die "Enum Error: got invalid value as default"; } } elsif (exists $encode->{$value}) { die "Enum Error: $value should not be defined as regular case"; } else { $self->{default_value} = shift @params; } next; } $encode->{$key} = $value; $decode->{$value} = $key; } $self->{encode} = $encode; $self->{decode} = $decode; } sub _decode { my ($self, $value) = @_; if (exists $self->{decode}->{$value}) { return $self->{decode}->{$value}; } if ($self->{have_default}) { if (ref($self->{default_action}) and $self->{default_action} == $Data::ParseBinary::BaseConstruct::DefaultPass) { return $value; } return $self->{default_action}; } die "Enum: unrecognized value $value, and no default defined"; } sub _encode { my ($self, $tvalue) = @_; if (exists $self->{encode}->{$tvalue}) { return $self->{encode}->{$tvalue}; } if ($self->{have_default}) { if (ref($self->{default_action}) and $self->{default_action} == $Data::ParseBinary::BaseConstruct::DefaultPass) { return $tvalue; } return $self->{default_value}; } die "Enum: unrecognized value $tvalue"; } package Data::ParseBinary::FlagsEnum; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, @mapping) = @_; my @pairs; die "FlagsEnum: Mapping should be even" if @mapping % 2 == 1; while (@mapping) { my $name = shift @mapping; my $value = shift @mapping; push @pairs, [$name, $value]; } $self->{pairs} = \@pairs; } sub _decode { my ($self, $value) = @_; my $hash = {}; foreach my $rec (@{ $self->{pairs} }) { $hash->{$rec->[0]} = 1 if $value & $rec->[1]; } return $hash; } sub _encode { my ($self, $tvalue) = @_; my $value = 0; foreach my $rec (@{ $self->{pairs} }) { if (exists $tvalue->{$rec->[0]} and $tvalue->{$rec->[0]}) { $value |= $rec->[1]; } } return $value; } package Data::ParseBinary::ExtractingAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $sub_name) = @_; $self->{sub_name} = $sub_name; } sub _decode { my ($self, $value) = @_; return $value->{$self->{sub_name}}; } sub _encode { my ($self, $tvalue) = @_; return {$self->{sub_name} => $tvalue}; } package Data::ParseBinary::IndexingAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $index) = @_; $self->{index} = $index || 0; } sub _decode { my ($self, $value) = @_; return $value->[$self->{index}]; } sub _encode { my ($self, $tvalue) = @_; return [ ('') x $self->{index}, $tvalue ]; } package Data::ParseBinary::JoinAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _decode { my ($self, $value) = @_; return join '', @$value; } sub _encode { my ($self, $tvalue) = @_; return [split '', $tvalue]; } package Data::ParseBinary::ConstAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $value) = @_; $self->{value} = $value; } sub _decode { my ($self, $value) = @_; if (not $value eq $self->{value}) { die "Const Error: expected $self->{value} got $value"; } return $value; } sub _encode { my ($self, $tvalue) = @_; if (not defined $self->_get_name()) { # if we don't have a name, then just use the value return $self->{value}; } if (defined $tvalue and $tvalue eq $self->{value}) { return $self->{value}; } die "Const Error: expected $self->{value} got ". (defined $tvalue ? $tvalue : "undef"); } package Data::ParseBinary::LengthValueAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _decode { my ($self, $value) = @_; return $value->[1]; } sub _encode { my ($self, $tvalue) = @_; return [length($tvalue), $tvalue]; } package Data::ParseBinary::PaddedStringAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, %params) = @_; if (not defined $params{length}) { die "PaddedStringAdapter: you must specify length"; } $self->{length} = $params{length}; $self->{encoding} = $params{encoding}; $self->{padchar} = defined $params{padchar} ? $params{padchar} : "\x00"; $self->{paddir} = $params{paddir} || "right"; $self->{trimdir} = $params{trimdir} || "right"; if (not grep($_ eq $self->{paddir}, qw{right left center})) { die "PaddedStringAdapter: paddir should be one of {right left center}"; } if (not grep($_ eq $self->{trimdir}, qw{right left})) { die "PaddedStringAdapter: trimdir should be one of {right left}"; } } sub _decode { my ($self, $value) = @_; my $tvalue = $value; my $char = $self->{padchar}; if ($self->{paddir} eq 'right' or $self->{paddir} eq 'center') { $tvalue =~ s/$char*\z//; } elsif ($self->{paddir} eq 'left' or $self->{paddir} eq 'center') { $tvalue =~ s/\A$char*//; } return $tvalue; } sub _encode { my ($self, $tvalue) = @_; my $value = $tvalue; if (length($value) < $self->{length}) { my $add = $self->{length} - length($value); my $char = $self->{padchar}; if ($self->{paddir} eq 'right') { $value .= $char x $add; } elsif ($self->{paddir} eq 'left') { $value = ($char x $add) . $value; } elsif ($self->{paddir} eq 'center') { my $add_left = $add / 2; my $add_right = $add_left + ($add % 2 == 0 ? 0 : 1); $value = ($char x $add_left) . $value . ($char x $add_right); } } if (length($value) > $self->{length}) { my $remove = length($value) - $self->{length}; if ($self->{trimdir} eq 'right') { substr($value, $self->{length}, $remove, ''); } elsif ($self->{trimdir} eq 'left') { substr($value, 0, $remove, ''); } } return $value; } #package Data::ParseBinary::StringAdapter; #our @ISA = qw{Data::ParseBinary::Adapter}; # #sub _init { # my ($self, $encoding) = @_; # $self->{encoding} = $encoding; #} # #sub _decode { # my ($self, $value) = @_; # my $tvalue; # if ($self->{encoding}) { # die "TODO: Should implement different encodings"; # } else { # $tvalue = $value; # } # return $tvalue; #} # #sub _encode { # my ($self, $tvalue) = @_; # my $value; # if ($self->{encoding}) { # die "TODO: Should implement different encodings"; # } else { # $value = $tvalue; # } # return $value; #} package Data::ParseBinary::CStringAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $terminators) = @_; $self->{regex} = qr/[$terminators]*\z/; $self->{terminator} = substr($terminators, 0, 1); } sub _decode { my ($self, $value) = @_; $value =~ s/$self->{regex}//; return $value; } sub _encode { my ($self, $tvalue) = @_; return $tvalue . $self->{terminator}; } package Data::ParseBinary::LamdaValidator; our @ISA = qw{Data::ParseBinary::Validator}; sub _init { my ($self, @params) = @_; $self->{coderef} = shift @params; } sub _validate { my ($self, $value) = @_; return $self->{coderef}->($value); } package Data::ParseBinary::FirstUnitAndTheRestAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; # this adapter move from a length of bytes, to one unit and the rest # as an array sub _init { my ($self, $unit_length, $first_name, $the_rest) = @_; $first_name ||= 'FirstUnit'; $the_rest ||= 'TheRest'; $self->{unit_length} = $unit_length; $self->{first_name} = $first_name; $self->{the_rest} = $the_rest; } sub _decode { my ($self, $value) = @_; $value = join('', $value->{$self->{first_name}}, @{ $value->{$self->{the_rest}} } ); return $value; } sub _encode { my ($self, $tvalue) = @_; my $u_len = $self->{unit_length}; die "Length of input should be dividable by unit_length" unless length($tvalue) % $u_len == 0; my @units = map substr($tvalue, $_*$u_len, $u_len), 0..(length($tvalue) / $u_len - 1); my $first = shift @units; my $value = { $self->{first_name} => $first, $self->{the_rest} => \@units }; return $value; } package Data::ParseBinary::CharacterEncodingAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $encoding) = @_; $self->{encoding} = $encoding; require Encode; } sub _decode { my ($self, $octets) = @_; my $string = Encode::decode($self->{encoding}, $octets); return $string; } sub _encode { my ($self, $string) = @_; my $octets = Encode::encode($self->{encoding}, $string); return $octets; } package Data::ParseBinary::ExtendedNumberAdapter; our @ISA = qw{Data::ParseBinary::Adapter}; sub _init { my ($self, $is_signed, $is_bigendian) = @_; $self->{is_signed} = $is_signed; $self->{is_bigendian} = $is_bigendian; require Math::BigInt; } sub _decode { my ($self, $value) = @_; if (not $self->{is_bigendian}) { $value = join '', reverse split '', $value; } my $is_negative; if ($self->{is_signed}) { my $first_char = ord($value); if ($first_char > 127) { $value = ~$value; $is_negative = 1; } } my $hexed = unpack "H*", $value; my $number = Math::BigInt->new("0x$hexed"); if ($is_negative) { $number->binc()->bneg(); } return $number; } sub _encode { my ($self, $number) = @_; $number = Math::BigInt->new($number); my $is_negative; if ($self->{is_signed}) { if ($number->sign() eq '-') { $is_negative = 1; $number->binc()->babs(); } } else { if ($number->sign() eq '-') { die "Was given a negative number for unsigned integer"; } } my $hexed = $number->as_hex(); substr($hexed, 0, 2, ''); my $packed = pack "H*", ("0"x(16-length($hexed))).$hexed; if ($is_negative) { $packed = ~$packed; } if (not $self->{is_bigendian}) { $packed = join '', reverse split '', $packed; } return $packed; } 1;