Absolute hinky bare-bones implementation of multiformats in Perl
1package
2 Multiformats::Varint {
3 use strict;
4 use warnings;
5 use feature 'signatures';
6
7 use Exporter 'import';
8 our @EXPORT_OK = qw/varint_encode varint_decode varint_decode_raw varint_decode_stream/;
9
10 sub decode($self, $value) {
11 return varint_decode($value);
12 }
13
14 sub decode_raw($self, $value) {
15 return varint_decode_raw($value);
16 }
17
18 sub encode($self, $value) {
19 return varint_encode($value);
20 }
21
22 sub decode_stream($self, $stream) {
23 return varint_decode_stream($stream);
24 }
25
26 sub new($pkg) {
27 return bless({}, $pkg);
28 }
29
30 # varint_encode, varint_decode_raw and varint_decode lifted from python multiformats https://github.com/hashberg-io/multiformats
31 sub varint_encode($value) {
32 die 'Multiformats::Varint::varint_encode: cannot encode negative values' unless $value >= 0;
33 my @out = ();
34 while(1) {
35 my $next_byte = $value & 0b01111111;
36 $value >>= 7;
37 if($value > 0) {
38 push(@out, $next_byte | 0b10000000);
39 } else {
40 push(@out, $next_byte);
41 last;
42 }
43 }
44 die 'Multiformats::Varint::varint_encode: encoded varint > 9 bytes' unless scalar(@out) <= 9;
45 return wantarray
46 ? (pack('C*', @out), scalar(@out))
47 : pack('C*', @out)
48 ;
49 }
50
51 sub varint_decode($value) {
52 my ($x, $read) = varint_decode_raw($value);
53 die 'Multiformats::Varint::varint_decode: not all bytes used by encoding' if($read < length($value));
54 return $x;
55 }
56
57 sub varint_decode_stream($stream) {
58 my $expect_next = 1;
59 my $num_bytes_read = 0;
60 my $x = 0;
61
62 while($expect_next) {
63 die 'Multiformats::Varint::varint_decode_stream: no next byte to read' if $stream->eof;
64 my $raw_byte;
65 my $bread = $stream->read($raw_byte, 1);
66 my $next_byte = unpack('C', $raw_byte);
67 $x += ($next_byte & 0b01111111) << (7 * $num_bytes_read);
68 $expect_next = ($next_byte >> 7 == 0b1) ? 1 : undef;
69 $num_bytes_read += $bread;
70 }
71
72 return wantarray
73 ? ($x, $num_bytes_read)
74 : $x;
75 }
76
77 sub varint_decode_raw($value) {
78 my $expect_next = 1;
79 my $num_bytes_read = 0;
80 my $x = 0;
81
82 my @buf = unpack('C*', $value); # value is untouched, we'll need to lop the appropriate of bytes off
83 # via the num_bytes_read later
84
85 while($expect_next) {
86 die 'Multiformats::Varint::varint_decode_raw: no next byte to read' if $num_bytes_read >= scalar(@buf);
87 my $next_byte = $buf[$num_bytes_read];
88 $x += ($next_byte & 0b01111111) << (7 * $num_bytes_read);
89 $expect_next = ($next_byte >> 7 == 0b1) ? 1 : undef;
90 $num_bytes_read++;
91 }
92
93 return wantarray
94 ? ($x, $num_bytes_read)
95 : $x;
96 }
97}
98
99=pod
100
101=head1 NAME
102
103Multiformats::Varint - Varint decoding and encoding
104
105=head1 SYNOPSIS
106
107 use Multiformats::Varint qw/varint_encode varint_decode/;
108
109 my $encoded = varint_encode(300); # \xAC\x02
110 my $decoded = varint_decode("\xAC\x02"); # 300
111
112=head1 FUNCTIONS
113
114=head2 varint_encode(...)
115
116Encodes the given unsigned integer number to an unsigned Varint; returns a byte string. Will die if the varint is larger than the spec allows (>9 bytes).
117
118=head2 varint_decode(...)
119
120Decodes the given byte string to an unsigned integer. Will die if there are more bytes passed than required to decode a Varint.
121
122=head2 varint_decode_raw(...)
123
124Like varint_decode, but will not die when there are bytes left in the input.
125
126When called in scalar context will return the decoded unsigned integer, when called in list context will return a list containing the unsigned integer, and the number of bytes used from the input. Does not alter the input value, so you will have to use C<substr> or some other mechanism to strip the used bytes out of the input value.
127
128=cut
129
1301;