File Coverage

lib/DR/Tnt/Proto.pm
Criterion Covered Total %
statement 131 161 81.3
branch 45 80 56.2
condition 5 16 31.2
subroutine 24 29 82.7
pod 0 14 0.0
total 205 300 68.3


line stmt bran cond sub pod time code
1 8     8   150490 use utf8;
  8         28  
  8         42  
2 8     8   195 use strict;
  8         10  
  8         118  
3 8     8   28 use warnings;
  8         13  
  8         320  
4              
5             package DR::Tnt::Proto;
6 8     8   39 use base qw(Exporter);
  8         37  
  8         1248  
7             our @EXPORT_OK = qw(
8             call_lua
9             response
10             insert
11             replace
12             del
13             update
14             select
15             auth
16             handshake
17             ping
18             );
19 8     8   45 use Carp;
  8         41  
  8         517  
20             $Carp::Internal{ (__PACKAGE__) }++;
21 8     8   2542 use DR::Tnt::Msgpack;
  8         58  
  8         474  
22 8     8   3894 use Digest::SHA;
  8         21835  
  8         478  
23 8     8   95 use Scalar::Util 'looks_like_number';
  8         18  
  8         345  
24 8     8   38 use Digest::SHA 'sha1';
  8         9  
  8         391  
25 8     8   3236 use MIME::Base64;
  8         4194  
  8         402  
26 8     8   4065 use Data::Dumper;
  8         45894  
  8         1700  
27              
28             sub parse_greeting {
29 0     0 0 0 my ($str) = @_;
30 0 0 0     0 croak "strlen is not 128 bytes" unless $str and 128 == length $str;
31              
32 0   0     0 my $salt = eval { substr decode_base64(substr $str, 64, 44), 0, 20; } || undef;
33 0         0 my $grstr = substr $str, 0, 64;
34              
35 0         0 my ($title, $v, $pt, $uid) = split /\s+/, $grstr, 5;
36              
37             return {
38 0         0 salt => $salt,
39             gr => $grstr,
40             title => $title,
41             version => $v,
42             uuid => $uid,
43             proto => $pt,
44             }
45             }
46              
47             my (%resolve, %tresolve);
48              
49             my %iter = (
50             EQ => 0,
51             REQ => 1,
52             ALL => 2,
53             LT => 3,
54             LE => 4,
55             GE => 5,
56             GT => 6,
57             BITS_ALL_SET => 7,
58             BITS_ANY_SET => 8,
59             BITS_ALL_NOT_SET => 9
60             );
61              
62             my %riter = reverse %iter;
63              
64             BEGIN {
65 8     8   75 my %types = (
66             IPROTO_SELECT => 1,
67             IPROTO_INSERT => 2,
68             IPROTO_REPLACE => 3,
69             IPROTO_UPDATE => 4,
70             IPROTO_DELETE => 5,
71             IPROTO_CALL => 6,
72             IPROTO_AUTH => 7,
73             IPROTO_EVAL => 8,
74             IPROTO_PING => 64,
75             );
76 8         123 my %attrs = (
77             IPROTO_CODE => 0x00,
78             IPROTO_SYNC => 0x01,
79             IPROTO_SERVER_ID => 0x02,
80             IPROTO_LSN => 0x03,
81             IPROTO_TIMESTAMP => 0x04,
82             IPROTO_SCHEMA_ID => 0x05,
83             IPROTO_SPACE_ID => 0x10,
84             IPROTO_INDEX_ID => 0x11,
85             IPROTO_LIMIT => 0x12,
86             IPROTO_OFFSET => 0x13,
87             IPROTO_ITERATOR => 0x14,
88             IPROTO_KEY => 0x20,
89             IPROTO_TUPLE => 0x21,
90             IPROTO_FUNCTION_NAME => 0x22,
91             IPROTO_USER_NAME => 0x23,
92             IPROTO_EXPRESSION => 0x27,
93             IPROTO_DATA => 0x30,
94             IPROTO_ERROR => 0x31,
95             );
96              
97 8     8   60 use constant;
  8         16  
  8         947  
98 8         59 while (my ($n, $v) = each %types) {
99 72         1363 constant->import($n => $v);
100 72         211 $n =~ s/^IPROTO_//;
101 72         265 $tresolve{$v} = $n;
102             }
103 8         48 while (my ($n, $v) = each %attrs) {
104 144         2062 constant->import($n => $v);
105 144         671 $n =~ s/^IPROTO_//;
106 144         12988 $resolve{$v} = $n;
107             }
108             }
109              
110              
111             sub raw_response($$) {
112 172     172 0 175 my ($response, $utf8) = @_;
113              
114 172         145 my $len;
115             {
116 172 50       165 return unless defined $response;
  172         196  
117 172 100       279 my $lenheader = length $response > 10 ?
118             substr $response, 0, 10 : $response;
119 172 100       210 return unless my $lenlen = DR::Tnt::Msgpack::msgunpack_check $lenheader;
120              
121 165         202 $len = DR::Tnt::Msgpack::msgunpack $lenheader;
122 165 50       199 croak 'Unexpected msgpack object ' . ref($len) if ref $len;
123 165         212 $len += $lenlen;
124             }
125            
126 165 100       236 return if length $response < $len;
127              
128 7         9 my @r;
129 7         8 my $off = 0;
130              
131 7         13 for (1 .. 3) {
132 21 100       43 my $sp = $off ? substr $response, $off : $response;
133 21         33 my $len_item = DR::Tnt::Msgpack::msgunpack_check $sp;
134 21 50 33     78 croak sprintf('Broken %s section of response', $_)
135             unless $len_item and $len_item + $off <= length $response;
136 21 50       28 if ($utf8) {
137 0         0 push @r => DR::Tnt::Msgpack::msgunpack_utf8 $sp;
138             } else {
139 21         60 push @r => DR::Tnt::Msgpack::msgunpack $sp;
140             }
141 21         20 $off += $len_item;
142              
143 21 50 66     64 if ($_ eq 2 and $off == length $response) {
144 0         0 push @r => {};
145 0         0 last;
146             }
147             }
148              
149 7 50       22 croak 'Broken response header' unless 'HASH' eq ref $r[1];
150 7 50       13 croak 'Broken response body' unless 'HASH' eq ref $r[2];
151              
152 7         40 return [ $r[1], $r[2] ], substr $response, $off;
153             }
154              
155             sub response($;$) {
156              
157 172     172 0 58609 my ($buffer, $utf8) = @_;
158 172         203 my ($resp, $tail) = raw_response($buffer => $utf8);
159 172 100       265 return unless $resp;
160 7         10 my ($h, $b) = @$resp;
161              
162 7         11 my $res = {};
163              
164 7         26 while(my ($k, $v) = each %$h) {
165 14         30 my $name = $resolve{$k};
166 14 50       20 $name = $k unless defined $name;
167 14         40 $res->{$name} = $v;
168             }
169 7         22 while(my ($k, $v) = each %$b) {
170 17         22 my $name = $resolve{$k};
171 17 50       21 $name = $k unless defined $name;
172 17         38 $res->{$name} = $v;
173             }
174              
175 7 50       15 if (defined $res->{CODE}) {
176 7         17 my $n = $tresolve{ $res->{CODE} };
177 7 50       14 $res->{CODE} = $n if defined $n;
178             }
179              
180 7 100       12 if (defined $res->{ITERATOR}) {
181 1         3 my $n = $riter{ $res->{ITERATOR} };
182 1 50       2 $res->{ITERATOR} = $n if defined $n;
183             }
184              
185 7         29 return $res, $tail;
186            
187             }
188              
189             sub request($$) {
190 7     7 0 13 my ($header, $body) = @_;
191 7         20 my $pkt = msgpack($header) . msgpack($body);
192              
193 7         17 return join '',
194             msgpack(length $pkt),
195             $pkt;
196             }
197              
198             sub _mk_header($$$) {
199 7     7   14 my ($code, $sync, $schema_id) = @_;
200            
201              
202             return {
203 7 50       53 IPROTO_SYNC, $sync,
204             IPROTO_CODE, $code,
205             } unless defined $schema_id;
206              
207             return {
208 0         0 IPROTO_SYNC, $sync,
209             IPROTO_CODE, $code,
210             IPROTO_SCHEMA_ID, $schema_id
211             }
212             }
213              
214             sub _call_lua($$$$) {
215 0     0   0 my ($sync, $schema_id, $proc, $tuple) = @_;
216 0         0 request
217             _mk_header(IPROTO_CALL, $sync, $schema_id),
218             {
219             IPROTO_FUNCTION_NAME, $proc,
220             IPROTO_TUPLE, $tuple,
221             }
222             ;
223             }
224              
225             sub call_lua($$$@) {
226 0     0 0 0 my ($sync, $schema_id, $proc, @args) = @_;
227 0         0 return _call_lua($sync, $schema_id, $proc, \@args);
228             }
229              
230              
231             sub eval_lua($$$@) {
232 1     1 0 237 my ($sync, $schema_id, $lua, @args) = @_;
233 1         3 request
234             _mk_header(IPROTO_EVAL, $sync, $schema_id),
235             {
236             IPROTO_EXPRESSION, $lua,
237             IPROTO_TUPLE, \@args,
238             }
239             ;
240             }
241              
242             sub insert($$$$) {
243 1     1 0 288 my ($sync, $schema_id, $space, $tuple) = @_;
244              
245 1 50       4 $tuple = [ $tuple ] unless ref $tuple;
246 1 50       4 croak "Cant convert HashRef to tuple" if 'HASH' eq ref $tuple;
247              
248 1 50       4 if (looks_like_number $space) {
249 1         4 return request
250             _mk_header(IPROTO_INSERT, $sync, $schema_id),
251             {
252             IPROTO_SPACE_ID, $space,
253             IPROTO_TUPLE, $tuple,
254             }
255             ;
256             }
257              
258             # HACK
259 0         0 _call_lua($sync, $schema_id, "box.space.$space:insert", $tuple);
260             }
261              
262             sub replace($$$$) {
263 1     1 0 232 my ($sync, $schema_id, $space, $tuple) = @_;
264              
265 1 50       4 $tuple = [ $tuple ] unless ref $tuple;
266 1 50       3 croak "Cant convert HashRef to tuple" if 'HASH' eq ref $tuple;
267              
268 1 50       4 if (looks_like_number $space) {
269 1         3 return request
270             _mk_header(IPROTO_REPLACE, $sync, $schema_id),
271             {
272             IPROTO_SPACE_ID, $space,
273             IPROTO_TUPLE, $tuple,
274             }
275             ;
276             }
277             # HACK
278 0         0 _call_lua($sync, $schema_id, "box.space.$space:replace", $tuple);
279             }
280             sub del($$$$) {
281 1     1 0 251 my ($sync, $schema_id, $space, $key) = @_;
282              
283 1 50       5 $key = [ $key ] unless ref $key;
284 1 50       4 croak "Cant convert HashRef to key" if 'HASH' eq ref $key;
285              
286 1 50       5 if (looks_like_number $space) {
287 1         2 return request
288             _mk_header(IPROTO_DELETE, $sync, $schema_id),
289             {
290             IPROTO_SPACE_ID, $space,
291             IPROTO_KEY, $key,
292             }
293             ;
294             }
295             # HACK
296 0         0 _call_lua($sync, $schema_id, "box.space.$space:delete", $key);
297             }
298              
299              
300             sub update($$$$$) {
301 1     1 0 283 my ($sync, $schema_id, $space, $key, $ops) = @_;
302 1 50       4 croak 'Oplist must be Arrayref' unless 'ARRAY' eq ref $ops;
303 1 50       4 $key = [ $key ] unless ref $key;
304 1 50       4 croak "Cant convert HashRef to key" if 'HASH' eq ref $key;
305              
306 1 50       3 if (looks_like_number $space) {
307 1         3 return request
308             _mk_header(IPROTO_UPDATE, $sync, $schema_id),
309             {
310             IPROTO_SPACE_ID, $space,
311             IPROTO_KEY, $key,
312             IPROTO_TUPLE, $ops,
313             }
314             ;
315             }
316             # HACK
317 0         0 _call_lua($sync, $schema_id, "box.space.$space:update", [ $key, $ops ]);
318             }
319              
320             sub select($$$$$;$$$) {
321 1     1 0 1918 my ($sync, $schema_id, $space, $index, $key, $limit, $offset, $iterator) = @_;
322 1 50       5 $iterator = 'EQ' unless defined $iterator;
323 1   50     4 $offset ||= 0;
324 1 50       3 $limit = 0xFFFF_FFFF unless defined $limit;
325 1 50       3 $key = [ $key ] unless ref $key;
326 1 50       4 croak "Cant convert HashRef to key" if 'HASH' eq ref $key;
327              
328 1 50       8 unless(looks_like_number $iterator) {
329 1         5 my $i = $iter{$iterator};
330 1 50       3 croak "Wrong iterator type: $iterator" unless defined $i;
331 1         3 $iterator = $i;
332             }
333              
334 1 50 33     10 if (looks_like_number $space and looks_like_number $index) {
335 1         4 return request
336             _mk_header(IPROTO_SELECT, $sync, $schema_id),
337             {
338             IPROTO_KEY, $key,
339             IPROTO_SPACE_ID, $space,
340             IPROTO_OFFSET, $offset,
341             IPROTO_INDEX_ID, $index,
342             IPROTO_LIMIT, $limit,
343             IPROTO_ITERATOR, $iterator,
344             }
345             ;
346             }
347              
348             # HACK
349 0         0 _call_lua($sync, $schema_id, "box.space.$space.index.$index:select", [
350             $key,
351             {
352             offset => $offset,
353             limit => $limit,
354             iterator => $iterator
355             }
356             ]
357             );
358             }
359              
360             sub ping($$) {
361 1     1 0 157 my ($sync, $schema_id) = @_;
362 1         4 request
363             _mk_header(IPROTO_PING, $sync, $schema_id),
364             {
365             }
366             ;
367             }
368              
369              
370             sub strxor($$) {
371 0     0 0   my ($x, $y) = @_;
372              
373 0           my @x = unpack 'C*', $x;
374 0           my @y = unpack 'C*', $y;
375 0           $x[$_] ^= $y[$_] for 0 .. $#x;
376 0           return pack 'C*', @x;
377             }
378              
379             sub auth($$$$$) {
380 0     0 0   my ($sync, $schema_id, $user, $password, $salt) = @_;
381              
382 0           my $hpasswd = sha1 $password;
383 0           my $hhpasswd = sha1 $hpasswd;
384 0           my $scramble = sha1 $salt . $hhpasswd;
385              
386 0           my $hash = strxor $hpasswd, $scramble;
387 0           request
388             _mk_header(IPROTO_AUTH, $sync, $schema_id),
389             {
390             IPROTO_USER_NAME, $user,
391             IPROTO_TUPLE, [ 'chap-sha1', $hash ],
392             }
393             ;
394             }
395              
396             1;