The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

##
## Warning, despite name of the file, subrotines from here belongs
## to Google::ProtocolBuffers::Codec namespace
##
use strict;
no warnings 'numeric';
use warnings FATAL => 'substr';
no warnings 'portable';
use constant MAX_UINT64 => 0xFFFF_FFFF_FFFF_FFFF;
use constant MAX_SINT64 => 0x7FFF_FFFF_FFFF_FFFF;
use constant MIN_SINT64 =>-0x8000_0000_0000_0000;
## Signature of all encode_* subs:
## encode_*($buffer, $value);
## Encoded value of $value will be appended to $buffer, which is a string
## passed by reference. No meaningfull value is returned, in case of errors
## an exception it thrown.
##
## Signature of all encode_* subs:
## my $value = decode_*($buffer, $position);
## $buffer is a string passed by reference, no copy is performed and it
## is not modified. $position is a number variable passed by reference
## (index in the string $buffer where to start decoding of a value), it
## is incremented by decode_* subs. In case of errors an exception is
## thrown.
sub decode_varint {
my $v = 0;
my $shift = 0;
my $l = length($_[0]);
while (1) {
die BROKEN_MESSAGE() if $_[1] >= $l;
my $b = ord(substr($_[0], $_[1]++, 1));
$v += (($b & 0x7F) << $shift);
$shift += 7;
last if ($b & 0x80)==0;
die if $shift > 63;
}
return $v;
}
##
## Both signed and unsigned 32/64 ints are encoded by this sub.
## Must it be more restrictive and don't allow negative values for uint types?
## Moreover, should we check that the number is an integer and not a float,
## for example? And truncate int32 types to 32 bits?
##
sub encode_int {
if ($_[1]>=0) {
encode_varint($_[0], $_[1]);
} else {
## We need a positive 64 bit integer, which bit representation is
## the same as of this negative value, static_cast<uint64>(int64).
## unpack('Q', pack('q', $_[1])) is slightly slower than
## 2^64 + $v === (2^64-1) + $v + 1, for $v<0
encode_varint($_[0], (MAX_UINT64+$_[1])+1);
}
}
sub decode_int {
my $v = decode_varint(@_);
if ($v>MAX_SINT64()) {
return ($v-MAX_UINT64())-1;
} else {
return $v;
}
}
##
## $_[1]<<1 is subject to overflow: a value that fit into
## Perl's int (IV) may need unsigned int (UV) to fit,
## and I don't know how to make Perl do that cast.
##
sub encode_sint {
if ($_[1]>=MAX_SINT64()) {
encode_varint($_[0], Math::BigInt->new($_[1])<<1);
} elsif ($_[1]<=MIN_SINT64) {
encode_varint($_[0], ((-Math::BigInt->new($_[1]))<<1)-1);
} elsif ($_[1]>=0) {
encode_varint($_[0], $_[1]<<1);
} else {
encode_varint($_[0], ((-$_[1])<<1)-1);
}
}
sub encode_fixed64 {
$_[0] .= pack('V', $_[1] & 0xFFFF_FFFF);
$_[0] .= pack('V', $_[1] >> 32);
}
sub decode_fixed64 {
die BROKEN_MESSAGE() if $_[1]+8 > length($_[0]);
my $a = unpack('V', substr($_[0], $_[1], 4));
my $b = unpack('V', substr($_[0], $_[1]+4, 4));
$_[1] += 8;
return $a | ($b<<32);
}
sub encode_sfixed64 {
my $v = ($_[1]<0) ? (MAX_UINT64()+$_[1])+1 : $_[1];
$_[0] .= pack('V', $v & 0xFFFF_FFFF);
$_[0] .= pack('V', $v >> 32);
}
sub decode_sfixed64 {
die BROKEN_MESSAGE() if $_[1]+8 > length($_[0]);
my $a = unpack('V', substr($_[0], $_[1], 4));
my $b = unpack('V', substr($_[0], $_[1]+4, 4));
$_[1] += 8;
$a |= $b<<32;
if ($a>MAX_SINT64()) {
return ($a-MAX_UINT64())-1;
} else {
return $a;
}
}
1;