File Coverage

blib/lib/Protocol/CassandraCQL/Frame.pm
Criterion Covered Total %
statement 124 131 94.6
branch 12 16 75.0
condition n/a
subroutine 35 37 94.5
pod 26 28 92.8
total 197 212 92.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2013 -- leonerd@leonerd.org.uk
5              
6             package Protocol::CassandraCQL::Frame;
7              
8 7     7   71345 use strict;
  7         17  
  7         247  
9 7     7   36 use warnings;
  7         14  
  7         280  
10              
11             our $VERSION = '0.11';
12              
13 7     7   36 use Carp;
  7         10  
  7         489  
14              
15 7     7   7817 use Encode qw( encode_utf8 decode_utf8 );
  7         74131  
  7         742  
16 7     7   4055 use Socket qw( AF_INET AF_INET6 );
  7         21692  
  7         13297  
17              
18             # TODO: At least the lower-level methods of this class should be rewritten in
19             # efficient XS code
20              
21             =head1 NAME
22              
23             C - a byte buffer storing the content of a CQL message frame
24              
25             =head1 DESCRIPTION
26              
27             This class provides wire-protocol encoding and decoding support for
28             constructing and parsing Cassandra CQL message frames. An object represents a
29             buffer during construction or parsing.
30              
31             To construct a message frame, create a new empty object and use the C
32             methods to append data to it, before eventually obtaining the actual frame
33             bytes using C. Each C method returns the frame object, allowing
34             them to be easily chained:
35              
36             my $bytes = Protocol::CassandraCQL::Frame->new
37             ->pack_short( 123 )
38             ->pack_int( 45678 )
39             ->pack_string( "here is the data" )
40             ->bytes;
41              
42             To parse a message frame, create a new object from the bytes in the message,
43             and use the C methods to consume the values from it.
44              
45             my $frame = Protocol::CassandraCQL::Frame->new( $bytes );
46             my $s = $frame->unpack_short;
47             my $i = $frame->unpack_int;
48             my $str = $frame->unpack_string;
49              
50             =cut
51              
52             =head1 CONSTRUCTOR
53              
54             =head2 $frame = Protocol::CassandraCQL::Frame->new( $bytes )
55              
56             Returns a new frame buffer, optionally initialised with the given byte string.
57              
58             =cut
59              
60             sub new
61             {
62 81     81 1 36218 my $class = shift;
63 81         141 my $bytes = "";
64 81 100       274 $bytes = $_[0] if defined $_[0];
65 81         474 bless \$bytes, $class;
66             }
67              
68             =head1 METHODS
69              
70             =cut
71              
72             # Legacy back-compat methods
73             # DO NOT USE THESE - see Protocol::CassandraCQL::parse_frame and ::build_frame instead
74              
75             sub parse
76             {
77 0     0 0 0 shift; # class
78 0 0       0 my ( $version, $flags, $id, $opcode, $body ) = Protocol::CassandraCQL::parse_frame( $_[0] )
79             or return;
80 0         0 return ( $version, $flags, $id, $opcode, Protocol::CassandraCQL::Frame->new( $body ) );
81             }
82              
83             sub build
84             {
85 0     0 0 0 my $self = shift;
86 0         0 return Protocol::CassandraCQL::build_frame( @_[0..3], $self->bytes );
87             }
88              
89             =head2 $bytes = $frame->bytes
90              
91             Returns the byte string currently in the buffer.
92              
93             =cut
94              
95 50     50 1 150 sub bytes { ${$_[0]} }
  50         264  
96              
97             =head2 $frame->pack_byte( $v )
98              
99             =head2 $v = $frame->unpack_byte
100              
101             Add or remove a byte value.
102              
103             =cut
104              
105 8     8 1 16 sub pack_byte { my ( $self, $v ) = @_;
106 8         47 $$self .= pack "C", $v;
107 8         23 $self }
108 1     1 1 7 sub unpack_byte { my ( $self ) = @_;
109 1         10 unpack "C", substr $$self, 0, 1, "" }
110              
111             =head2 $frame->pack_short( $v )
112              
113             =head2 $v = $frame->unpack_short
114              
115             Add or remove a short value.
116              
117             =cut
118              
119 84     84 1 128 sub pack_short { my ( $self, $v ) = @_;
120 84         195 $$self .= pack "S>", $v;
121 84         135 $self }
122 132     132 1 1155 sub unpack_short { my ( $self ) = @_;
123 132         419 unpack "S>", substr $$self, 0, 2, "" }
124              
125             =head2 $frame->pack_int( $v )
126              
127             =head2 $v = $frame->unpack_int
128              
129             Add or remove an int value.
130              
131             =cut
132              
133 26     26 1 44 sub pack_int { my ( $self, $v ) = @_;
134 26         70 $$self .= pack "l>", $v;
135 26         44 $self }
136 71     71 1 123 sub unpack_int { my ( $self ) = @_;
137 71         336 unpack "l>", substr $$self, 0, 4, "" }
138              
139             =head2 $frame->pack_string( $v )
140              
141             =head2 $v = $frame->unpack_string
142              
143             Add or remove a string value.
144              
145             =cut
146              
147 26     26 1 51 sub pack_string { my ( $self, $v ) = @_;
148 26         98 my $b = encode_utf8( $v );
149 26         170 $self->pack_short( length $b );
150 26         36 $$self .= $b;
151 26         63 $self }
152 76     76 1 1077 sub unpack_string { my ( $self ) = @_;
153 76         156 my $l = $self->unpack_short;
154 76         296 decode_utf8( substr $$self, 0, $l, "" ) }
155              
156             =head2 $frame->pack_lstring( $v )
157              
158             =head2 $v = $frame->unpack_lstring
159              
160             Add or remove a long string value.
161              
162             =cut
163              
164 11     11 1 26 sub pack_lstring { my ( $self, $v ) = @_;
165 11         29 my $b = encode_utf8( $v );
166 11         82 $self->pack_int( length $b );
167 11         17 $$self .= $b;
168 11         28 $self }
169 2     2 1 713 sub unpack_lstring { my ( $self ) = @_;
170 2         6 my $l = $self->unpack_int;
171 2         10 decode_utf8( substr $$self, 0, $l, "" ) }
172              
173             =head2 $frame->pack_uuid( $v )
174              
175             =head2 $v = $frame->unpack_uuid
176              
177             Add or remove a UUID as a plain 16-byte raw scalar
178              
179             =cut
180              
181 1     1 1 5 sub pack_uuid { my ( $self, $v ) = @_;
182 1         6 $$self .= pack "a16", $v;
183 1         2 $self }
184 1     1 1 7 sub unpack_uuid { my ( $self ) = @_;
185 1         5 substr $$self, 0, 16, "" }
186              
187             =head2 $frame->pack_string_list( $v )
188              
189             =head2 $v = $frame->unpack_string_list
190              
191             Add or remove a list of strings from or to an ARRAYref
192              
193             =cut
194              
195 4     4 1 11 sub pack_string_list { my ( $self, $v ) = @_;
196 4         13 $self->pack_short( scalar @$v );
197 4         14 $self->pack_string($_) for @$v;
198 4         11 $self }
199 4     4 1 61 sub unpack_string_list { my ( $self ) = @_;
200 4         13 my $n = $self->unpack_short;
201 4         11 [ map { $self->unpack_string } 1 .. $n ] }
  8         69  
202              
203             =head2 $frame->pack_bytes( $v )
204              
205             =head2 $v = $frame->unpack_bytes
206              
207             Add or remove opaque bytes or C.
208              
209             =cut
210              
211 8     8 1 19 sub pack_bytes { my ( $self, $v ) = @_;
212 8 100       19 if( defined $v ) { $self->pack_int( length $v ); $$self .= $v }
  7         51  
  7         9  
213 1         4 else { $self->pack_int( -1 ) }
214 8         27 $self }
215 19     19 1 30 sub unpack_bytes { my ( $self ) = @_;
216 19         51 my $l = $self->unpack_int;
217 19 100       133 $l > 0 ? substr $$self, 0, $l, "" : undef }
218              
219             =head2 $frame->pack_short_bytes( $v )
220              
221             =head2 $v = $frame->unpack_short_bytes
222              
223             Add or remove opaque short bytes.
224              
225             =cut
226              
227 26     26 1 132 sub pack_short_bytes { my ( $self, $v ) = @_;
228 26         62 $self->pack_short( length $v );
229 26         36 $$self .= $v;
230 26         66 $self }
231 17     17 1 90 sub unpack_short_bytes { my ( $self ) = @_;
232 17         36 my $l = $self->unpack_short;
233 17         67 substr $$self, 0, $l, "" }
234              
235             =head2 $frame->pack_inet( $v )
236              
237             =head2 $v = $frame->unpack_inet
238              
239             Add or remove an IPv4 or IPv6 address from or to a packed sockaddr string
240             (such as returned from C or C.
241              
242             =cut
243              
244 2     2 1 33 sub pack_inet { my ( $self, $v ) = @_;
245 2         9 my $family = Socket::sockaddr_family($v);
246 2 100       8 if ( $family == AF_INET ) { $$self .= "\x04"; $self->_pack_inet4( $v ) }
  1 50       3  
  1         5  
247 1         2 elsif( $family == AF_INET6 ) { $$self .= "\x10"; $self->_pack_inet6( $v ) }
  1         4  
248 0         0 else { croak "Expected AF_INET or AF_INET6 address" }
249 2         6 $self }
250 3     3 1 28 sub unpack_inet { my ( $self ) = @_;
251 3         11 my $addrlen = unpack "C", substr $$self, 0, 1, "";
252 3 100       11 if ( $addrlen == 4 ) { $self->_unpack_inet4 }
  2 50       8  
253 1         3 elsif( $addrlen == 16 ) { $self->_unpack_inet6 }
254 0         0 else { croak "Expected address length 4 or 16" } }
255              
256             # AF_INET
257 1     1   2 sub _pack_inet4 { my ( $self, $v ) = @_;
258 1         11 my ( $port, $addr ) = Socket::unpack_sockaddr_in( $v );
259 1         3 $$self .= $addr; $self->pack_int( $port ) }
  1         4  
260 2     2   4 sub _unpack_inet4 { my ( $self ) = @_;
261 2         7 my $addr = substr $$self, 0, 4, "";
262 2         12 Socket::pack_sockaddr_in( $self->unpack_int, $addr ) }
263              
264             # AF_INET6
265 1     1   2 sub _pack_inet6 { my ( $self, $v ) = @_;
266 1         4 my ( $port, $addr ) = Socket::unpack_sockaddr_in6( $v );
267 1         2 $$self .= $addr; $self->pack_int( $port ) }
  1         2  
268 1     1   2 sub _unpack_inet6 { my ( $self ) = @_;
269 1         2 my $addr = substr $$self, 0, 16, "";
270 1         3 Socket::pack_sockaddr_in6( $self->unpack_int, $addr ) }
271              
272             =head2 $frame->pack_string_map( $v )
273              
274             =head2 $v = $frame->unpack_string_map
275              
276             Add or remove a string map from or to a HASH of strings.
277              
278             =cut
279              
280             # Don't strictly need to sort the keys but it's nice for unit testing
281 4     4 1 15 sub pack_string_map { my ( $self, $v ) = @_;
282 4         45 $self->pack_short( scalar keys %$v );
283 4         47 $self->pack_string( $_ ), $self->pack_string( $v->{$_} ) for sort keys %$v;
284 4         35 $self }
285 1     1 1 6 sub unpack_string_map { my ( $self ) = @_;
286 1         3 my $n = $self->unpack_short;
287 1         4 +{ map { $self->unpack_string => $self->unpack_string } 1 .. $n } }
  2         20  
288              
289             =head2 $frame->pack_string_multimap( $v )
290              
291             =head2 $v = $frame->unpack_string_multimap
292              
293             Add or remove a string multimap from or to a HASH of ARRAYs of strings.
294              
295             =cut
296              
297 1     1 1 9 sub pack_string_multimap { my ( $self, $v ) = @_;
298 1         5 $self->pack_short( scalar keys %$v );
299 1         5 $self->pack_string( $_ ), $self->pack_string_list( $v->{$_} ) for sort keys %$v;
300 1         3 $self }
301 2     2 1 8 sub unpack_string_multimap { my ( $self ) = @_;
302 2         8 my $n = $self->unpack_short;
303 2         7 +{ map { $self->unpack_string => $self->unpack_string_list } 1 .. $n } }
  3         25  
304              
305             =head1 SPONSORS
306              
307             This code was paid for by
308              
309             =over 2
310              
311             =item *
312              
313             Perceptyx L
314              
315             =item *
316              
317             Shadowcat Systems L
318              
319             =back
320              
321             =head1 AUTHOR
322              
323             Paul Evans
324              
325             =cut
326              
327             0x55AA;