peroksid
peroksid

Reputation: 357

Perl: pack int to arbitrary length byte string

I want to encode numbers in N bit containers and send them in a UDP packet. A receiver will know N, and the receiver will grab a number from exactly N bits.(N <= 64)

Somethink like this:

sub to56BIT {
     return pack("??", shift);
}

sub to24BIT {
     return pack("??", shift);
}

my $n = 7;
to24BIT($n);

On the receiver's side:

int n = Get_val24(byte_stream, offset);

Is there any way to do this in Perl?

I think solution might be:

sub packIntN {
        my $int = shift;
        my $length = shift;
        return pack("B" . $length, substr(unpack("B64", pack("Q>", $int)), 64 - $length));
}

But maybe there is more elegant way.

Input/Output example: We have a script test.pl:

use strict;
use warnings;

sub to24BIT {
        #???
}

my $n = 7;

print to24BIT($n);

I want this:

./test.pl | hexdump -C
00000000  00 00 07                                          |...|
00000003

Another script test2.pl:

use strict;
use warnings;

sub to40BIT {
        #???
}

my $n = 7;

print to40BIT($n);

I want this:

./test.pl | hexdump -C
00000000  00 00 00 00 07                                    |.....|
00000005

Upvotes: 3

Views: 396

Answers (2)

ikegami
ikegami

Reputation: 385546

Is N always going to be an integer factor of 8 (one of 8, 16, 24, 32, 40, 48, 56, 64)? If so, for speed, I recommend writing a packer for each size and use a dispatch table to find the right packer.

sub pack_8bit  {        pack('C',  $_[0])     }
sub pack_16bit {        pack('S>', $_[0])     }
sub pack_24bit { substr(pack('L>', $_[0]), 1) }
sub pack_32bit {        pack('L>', $_[0])     }
sub pack_40bit { substr(pack('Q>', $_[0]), 3) }
sub pack_48bit { substr(pack('Q>', $_[0]), 2) }
sub pack_56bit { substr(pack('Q>', $_[0]), 1) }
sub pack_64bit {        pack('Q>', $_[0])     }

{
   my %packers = (
       8 => \&pack_8bit,   40 => \&pack_40bit,
      16 => \&pack_16bit,  48 => \&pack_48bit,
      24 => \&pack_24bit,  56 => \&pack_56bit,
      32 => \&pack_32bit,  64 => \&pack_64bit,
   );

   sub pack_num {
      my $packer = $packers{$_[0]}
         or die;
      return $packer->($_[1]);
   }

   sub get_packer {
      my $packer = $packers{$_[0]}
         or die;
      return $packer;
   }
}

my $packed = pack_num(40, 7);
  -or-
my $packer = get_packer(40);
my $packed = $packer->(7);

If you're planning on packing multiple numbers into one string (like pack('L>*', @nums)), I'd also use a dispatch table like this, though I'm not sure what would be the fastest implementation of pack_24bit, pack_40bit, pack_48bit and pack_56bit (other than a C solution).

Upvotes: 3

meuh
meuh

Reputation: 12255

Bearing in mind that you will always have a whole number of bytes, I ended up with

substr(pack("Q>",$n<<(64-$len)),0,($len+7)/8);

and

unpack("Q>",$s.(0 x 8)) >> (64-$len);

as tried in this example:

#!/usr/bin/perl
$len = 40;
$n = 7;
$s = substr(pack("Q>",$n<<(64-$len)),0,($len+7)/8);
open(PIPE,"| hexdump -C");
print PIPE $s;
close PIPE;

$v = unpack("Q>",$s.(0 x 8)) >> (64-$len);
printf "%d\n",$v;

Upvotes: 2

Related Questions