## ## Warning, despite name of the file, subrotines from here belongs ## to Google::ProtocolBuffers::Codec namespace ## package Google::ProtocolBuffers::Codec; use strict; use warnings FATAL => 'substr'; use Math::BigInt; use constant TWO_IN_64 => Math::BigInt->new("0x1_0000_0000_0000_0000"); use constant MAX_SINT64 => Math::BigInt->new( "0x7FFF_FFFF_FFFF_FFFF"); ## 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; ## if $_[1]+1 > $l my $b = ord(substr($_[0], $_[1]++, 1)); if ($shift==28) { $shift = Math::BigInt->new($shift); } $v += (($b & 0x7F) << $shift); $shift += 7; last if ($b & 0x80)==0; die "Number is too long" if $shift > 63; } return $v; } 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(int64). ## 2^64 + $v === (2^64-1) + $v + 1, for $v<0 encode_varint($_[0], (TWO_IN_64+$_[1])); } } sub decode_int { my $v = decode_varint(@_); if ($v>MAX_SINT64) { return ($v - TWO_IN_64); } 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_SINT32()) { encode_varint($_[0], Math::BigInt->new($_[1])<<1); } elsif ($_[1]<=MIN_SINT32()) { 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]>>16)>>16); } 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; if ($b==0) { return $a } else { $b = Math::BigInt->new($b); return $a | ($b<<32); } } sub encode_sfixed64 { if ($_[1]>=0) { $_[0] .= pack('V', $_[1] & 0xFFFF_FFFF); $_[0] .= pack('V', ($_[1]>>16)>>16); } else { ## We need a positive 64 bit integer, which bit representation is ## the same as of this negative value, static_cast(int64). ## 2^64 + $v === (2^64-1) + $v + 1, for $v<0 my $v = (TWO_IN_64+$_[1]); $_[0] .= pack('V', $v & 0xFFFF_FFFF); $_[0] .= pack('V', ($v>>16)>>16); } } 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; if ($b==0) { return $a; } else { $b = (Math::BigInt->new($b)<<32) | $a; return ($b>MAX_SINT64()) ? $b-TWO_IN_64() : $b; } } 1;