How to compare a couple binary bytes in code? - perl

I read a binary file and want to make sure that some specific bytes have some specific value. What's the most perl way of doing this?
my $blob = File::Slurp::read_file( 'blob.bin', {binmode=>'raw'} );
substr( $blob, 4, 4 ) == #equals what?
I want to test if bytes 5-8 equal 0x32 0x32 0x00 0x04. What should I compare the substr to?

substr( $blob, 4, 4 ) eq "\x32\x32\x00\x04"
If it's a 32-bit unsigned number, you might prefer the following:
unpack( "N", substr( $blob, 4, 4 ) ) == 0x32320004 # Big endian
unpack( "L>", substr( $blob, 4, 4 ) ) == 0x32320004 # Big endian
unpack( "L<", substr( $blob, 4, 4 ) ) == 0x04003232 # Little endian
unpack( "L", substr( $blob, 4, 4 ) ) == ... # Native endian
(Use l instead oaf L for signed 32-bit ints.)
substr can even be avoided when using unpack.
unpack( "x4 N", $blob ) == 0x32320004
You could also use a regex match.
$blob =~ /^.{4}\x32\x32\x00\x04/s
$blob =~ /^ .{4} \x32\x32\x00\x04 /sx
my $packed = pack( "N", 0x32320004 );
$blob =~ /^ .{4} \Q$packed\E /sx

Related

Pad string with characters

I have
$data_dec = 7;
$data_bin = sprintf("%08b",data_dec);
and $data_bin is
00000111
How do I pad with "X" instead of zeros while maintaining 8-bits? Expected data:
XXXXX111
substr( sprintf( "XXXXXXX%b", $n ), -8 )
sprintf( "%8b", $n ) =~ tr/ /X/r

sprintf pad string on right with dash

I need to pad a string on the right with dashes ('-'). e.g. convert 'M' to 'M-----'.
sprintf "%-6s", "M"; gives me 'M '. I tried printf "%-6-s", "M";, and printf "%--6s", "M";, but neither of those work...
Can this be done with sprinf and if so, how?
It can't be done with sprintf alone. (sprintf will only pad with spaces or with zeroes.)
sprintf("%-6s", $s) =~ tr/ /-/r
or
substr($s.("-" x 6), 0, 6)
or
$s . ("-" x (6-length($s)))
sprintf only supports padding with 0 and , so no. You can pad with one of those then replace the padding, but the problem with that, is that you run the risk of replacing any padding characters in the original string. For example sprintf('%-6s', ' M') =~ s/ /-/gr produces --M---.
From the FAQ:
If you need to pad with a character other than blank or zero you can
use one of the following methods. They all generate a pad string with
the x operator and combine that with $text. These methods do not
truncate $text.
Left and right padding with any character, creating a new string:
my $padded = $pad_char x ( $pad_len - length( $text ) ) . $text;
my $padded = $text . $pad_char x ( $pad_len - length( $text ) );
Left and right padding with any character, modifying $text directly:
substr( $text, 0, 0 ) = $pad_char x ( $pad_len - length( $text ) );
$text .= $pad_char x ( $pad_len - length( $text ) );
If you do it often, you could wrap it in a subroutine.
sub pad {
my ($str, $padding, $length) = #_;
my $pad_length = $length - length $str;
$pad_length = 0 if $pad_length < 0;
$padding x= $pad_length;
$str.$padding;
}
say pad('M', '-', 6);
say pad('MMMMMM', '-', 6);
say pad('12345', '-', 6);
say pad('1234567', '-', 6);
say pad(' ', '-', 6);
Output:
M-----
MMMMMM
12345-
1234567
--

Perl Calculate netmask from CIDR

I found a little function on a website here
http://jmorano.moretrix.com/2010/08/calculate-netmask-in-perl
I am using it, but it does not produce the same thing as he gets.
I am putting a CIDR address in a variable like this :
126.126.126.250/24
The function should return something like
255.255.255.0
But instead, it returns
0.0.0.255
The only thing that i modified in the function is
my($network, $netbit) = split ///, $subnet;
TO
my($network, $netbit) = split /\//, $subnet;
and the return is just changed into "print"
return $netmask; TO print "$netmask \n" ;
I guess there's something wrong in here
my $_bit = ( 2 ** (32 - $netbit) ) - 1;
Because when i print $_bit i just have "255"
But i can't figure what is it.
One more backslash needed,
my ($full_mask) = unpack( "N", pack( "C4", split(/\./, '255.255.255.255') ) );
^
although it would make more sense as
my ($full_mask) = unpack( "N", pack( "C4", 255,255,255,255 ) );
or
my ($full_mask) = unpack( "N", pack( "C4", (255) x4 ) );
Instead of writing your own function, consider using an existing module like Net::Netmask:
use strict;
use warnings;
use 5.010;
use Net::Netmask;
my $cidr = '126.126.126.250/24';
my $block = Net::Netmask->new2( $cidr ) or die $Net::Netmask::error;
say $block->mask;
Output:
255.255.255.0
A smaller alternative to the link in the question:
sub _smaller_calc_netmask {
my($subnet) = #_;
# e.g.: 10.0.0.0/24 192.168.1.0/16
my($network, $netbit) = split m'/', $subnet; # quote as match delimiter hack
# Decimal representation of mask
my $mask = (2 ** $netbit - 1) << (32 - $netbit);
# convert decimal representation to our familiar form
my $netmask = join( '.', unpack( "C4", pack( "N", $mask ) ) );
return $netmask;
}

Decoding 3-byte integer in Perl

I'm reading a binary file format that starts out with 4 constant check bytes, followed by 3 octets that indicate how long the data portion of the record will be. I can decode this as follows:
read($fh, $x, 7) or do {
last if eof;
die "Can't read: $!";
};
my ($type, $l1, $l2, $l3) = unpack("a4 C3", $x);
my $length = $l1 << 16 | $l2 << 8 | $l3;
Is there a more direct way to read that 3-byte value, without intermediate variables? Something I'm missing in the pack specifications maybe? I haven't used pack very much except for hex encoding and other dilettantish pursuits.
You could insert a null byte into the string in order to be able to use the "N" format:
substr($x, 4, 0, "\0");
my ($type, $length) = unpack "a4 N", $x;
Edit: Or else unpack in two steps:
my ($type, $length) = unpack "a4 a3", $x;
$length = unpack "N", "\0" . $length;
my $type = unpack("a4", $x);
my $len = unpack("N", "\0".substr($x, 4));
or
my ($type, $plen) = unpack("a4 a3", $x);
my $len = unpack("N", "\0$plen");
No, unpack doesn't support 3-byte (or arbitrary length) integers, but you could use an unsigned 16-bit big-endian int to save a little work:
my ($type, $l1, $l23) = unpack("a4 Cn", $x);
my $length = $l1 << 16 | $l23;
Solution: Your method for getting the type is fine. However, I suggest that you unpack the length separately as a four-byte integer, then discard the first byte of those four bytes. This is more efficient even though it overlaps and discards the last byte of the type.
my $type = unpack("a4", $x);
my $length = unpack("x3N", $x); # skips the first 3 bytes of your original 7-byte string
$length = $length & 0xFFFFFF; # returns only the last 3 bytes of the four-byte integer

Writing (and reading) bits to binary files in Perl + EOF handling

I have two related problems (in Perl):
Write data to binary files, in the format: single bit flag followed by 8 bits
Read back the same format
I tried this (and other variations but for the life of me I can't figure this out):
binmode(OUT);
my $bit = pack("B1", '1');
my $byte = pack("H2", "02");
print OUT $bit . $byte;
Using a hex editor, I see I get 16 bits:
1000000000000020
What I want is 9 bits:
100000020
Also: Suppose I write out two of these patterns. That means I end up with 9 + 9 = 18 bits. I am not sure how to handle the last byte (padding?)
This is to compress and uncompress files, with space at premium. I was hoping there would be some simple idiomatic way to do this that I am not aware of.
Files are sequences of bytes. If you want to print out bits, you'll have to use some form of buffering.
my $bits = '';
$bits .= '1'; # Add 1 bit.
$bits .= unpack('B8', pack('C', 0x02)); # Add 8 bits.
$bits .= substr(unpack('B8', pack('C', 0x02)), -6); # Add 6 bits.
This prints as much as the buffer as possible:
my $len = ( length($bits) >> 3 ) << 3;
print($fh, pack('B*', substr($bits, 0, $len, '')));
You'll eventually need to pad the buffer so that you have a multiple of 8 bits in order to flush out the rest. You could simply pad with zeroes.
$bits .= "0" x ( -length($bits) % 8 );
If you're smart, though, you can come up with a padding scheme that can be used to indicate where the file actually ends. Remember, you can't rely on just the file length anymore. If you don't use a smart padding scheme, you'll have to use another method.
One example of a smart padding scheme would be:
$bits .= "0";
$bits .= "1" x ( -length($bits) % 8 );
Then, to unpad, remove all trailing 1 bits and the 0 bit before that.
You can use Bit::Vector to manage your bits and conversion with some more ease,
use Bit::Vector;
my $bit = Bit::Vector->new_Bin( 1, '1' );
my $byte = Bit::Vector->new_Bin( 8, '00000010' );
my $byte_9 = Bit::Vector->new_Bin( 9, '000000010' );
my $nineBits = Bit::Vector->new_Bin( 9, '100000000' );
my $carry = Bit::Vector->new_Bin( 9, '000000000' );
my $ORed = Bit::Vector->new_Bin( 9, '000000000' );
my $added = Bit::Vector->new_Bin( 9, '000000000' );
$ORed->Union($nineBits,$byte_9);
print "bit: 0x". $bit->to_Hex(). "\n";
print "byte 2: 0x". $byte->to_Hex(). "\n";
print "nineBits: 0x". $nineBits->to_Hex(). "\n";
print "nineBits: 0x". $nineBits->to_Bin(). "\n";
print "ORed bit and byte 0x". $ORed->to_Dec(). "\n";
open BINOUT, ">out.bin"
or die "\nCan't open out.bin for writing: $!\n";
binmode BINOUT;
print BINOUT pack ('B*', $ORed->to_Bin()) ."\n"
Here's the output
>perl bitstuff.pl
bit: 0x1
byte 2: 0x02
nineBits: 0x100
nineBits: 0x100000000
ORed bit and byte 0x-254
>cat out.bin
\201^#