Search code examples
perlpack

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

Solution

  • 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).