File Coverage

XS.xs
Criterion Covered Total %
statement 194 270 71.8
branch 118 222 53.1
condition n/a
subroutine n/a
pod n/a
total 312 492 63.4


line stmt bran cond sub pod time code
1             #define PERL_NO_GET_CONTEXT
2             #include "EXTERN.h"
3             #include "perl.h"
4             #include "XSUB.h"
5             #include
6              
7             /* Back-compatibility */
8             #ifndef G_LIST
9             #define G_LIST G_ARRAY
10             #endif
11              
12             #ifndef PERL_ARGS_ASSERT_AV_COUNT
13             /* Taken from inline.h, was added in 5.33.something */
14             #define PERL_ARGS_ASSERT_AV_COUNT \
15             assert(av)
16             #endif
17              
18             #ifndef av_count
19             /* Taken from inline.h, was added in 5.33.something */
20             PERL_STATIC_INLINE Size_t
21 480           Perl_av_count(pTHX_ AV *av)
22             {
23             PERL_ARGS_ASSERT_AV_COUNT;
24             assert(SvTYPE(av) == SVt_PVAV);
25              
26 480 50         return AvFILL(av) + 1;
27             }
28             #define av_count(a) Perl_av_count(aTHX_ a)
29             #endif
30              
31             /* Types of data structures that can nest */
32             enum PendingStackType {
33             array, map, set, attribute, push
34             };
35             /* Container representation - basic stack of nested lists */
36             struct pending_stack {
37             AV *data;
38             struct pending_stack *prev;
39             long expected;
40             enum PendingStackType type;
41             };
42              
43             void
44 150           add_value(struct pending_stack *target, SV *v)
45             {
46 150 50         if(!target)
47 0           return;
48              
49 150           av_push(
50             target->data,
51             v
52             );
53             }
54              
55             MODULE = Net::Async::Redis::XS PACKAGE = Net::Async::Redis::XS
56              
57             PROTOTYPES: DISABLE
58              
59             AV *
60             decode_buffer(SV *this, SV *p)
61             PPCODE:
62             /* Plain bytestring required: no magic, no UTF-8, no nonsense */
63 105 100         if(!SvPOK(p))
64 2           croak("expected a string");
65 103 50         if(SvUTF8(p))
66 0           sv_utf8_downgrade(p, true);
67              
68             STRLEN len;
69 103 50         const char *in = SvPVbyte(p, len);
70 103           const char *ptr = in;
71 103           const char *end = in + len;
72 103           struct pending_stack *ps = NULL;
73 103           AV *results = (AV *) sv_2mortal((SV *) newAV());
74 103           bool extracted_item = false;
75 103           SV *extracted = &PL_sv_undef;
76             /* Perl strings _should_ guarantee this, so perhaps better as an assert? */
77 103 50         if(*end != '\0') {
78 0           croak("no trailing null?");
79             }
80              
81             /* The shortest command is a single-character null, which has the
82             * type `_` followed by CRLF terminator, so that's at least 3
83             * characters for a valid command.
84             */
85 103 100         if(len >= 3) { // && *(end - 1) == '\x0A' && *(end - 2) == '\x0D') {
86 508 100         while(*ptr && ptr < end) {
    50          
87             /* First step is to check the data type and extract it if we can */
88 477           switch(*ptr++) {
89             case '~': /* set */
90             case '*': { /* array */
91 276           int n = 0;
92             /* We effectively want grok_atoUV behaviour, but without having a full UV */
93 546 100         while(*ptr >= '0' && *ptr <= '9') {
    50          
94 270           n = (n * 10) + (*ptr - '0');
95 270           ++ptr;
96             }
97 276 100         if(ptr + 2 > end) {
98 20           goto end_parsing;
99             }
100 256 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
101 0           croak("protocol violation - array length not followed by CRLF");
102             }
103 256           ptr += 2;
104              
105 256           AV *x = (AV *) sv_2mortal((SV *)newAV());
106 256 100         if(n > 0) {
107 253           av_extend(x, n);
108             }
109 256           struct pending_stack *pn = Newx(pn, 1, struct pending_stack);
110 256           *pn = (struct pending_stack) {
111             .data = x,
112             .prev = ps,
113             .expected = n,
114             .type = array
115             };
116 256           ps = pn;
117 256           break;
118             }
119             case '>': { /* push (pubsub) */
120 1           int n = 0;
121 2 100         while(*ptr >= '0' && *ptr <= '9') {
    50          
122 1           n = (n * 10) + (*ptr - '0');
123 1           ++ptr;
124             }
125 1 50         if(ptr + 2 > end) {
126 0           goto end_parsing;
127             }
128 1 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
129 0           croak("protocol violation - push length not followed by CRLF");
130             }
131 1           ptr += 2;
132              
133 1           AV *x = (AV *) sv_2mortal((SV *)newAV());
134 1 50         if(n > 0) {
135 1           av_extend(x, n);
136             }
137 1           struct pending_stack *pn = Newx(pn, 1, struct pending_stack);
138 1           *pn = (struct pending_stack) {
139             .data = x,
140             .prev = ps,
141             .expected = n,
142             .type = push
143             };
144 1           ps = pn;
145 1           break;
146             }
147             case '%': { /* hash */
148 1           int n = 0;
149 2 100         while(*ptr >= '0' && *ptr <= '9') {
    50          
150 1           n = (n * 10) + (*ptr - '0');
151 1           ++ptr;
152             }
153 1 50         if(ptr + 2 > end) {
154 0           goto end_parsing;
155             }
156              
157             /* Hash of key/value pairs */
158 1           n = n * 2;
159 1 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
160 0           croak("protocol violation - number of hash entries not followed by CRLF");
161             }
162 1           ptr += 2;
163 1           AV *x = (AV *) sv_2mortal((SV *)newAV());
164 1 50         if(n > 0) {
165 0           av_extend(x, n);
166             }
167 1           struct pending_stack *pn = Newx(pn, 1, struct pending_stack);
168 1           *pn = (struct pending_stack) {
169             .data = x,
170             .prev = ps,
171             .expected = n,
172             .type = map
173             };
174 1           ps = pn;
175 1           break;
176             }
177             case ':': { /* integer */
178 63           int n = 0;
179 63           int negative = 0;
180 63 100         if(*ptr == '-') {
181 1           negative = 1;
182 1           ++ptr;
183             }
184 126 100         while(*ptr >= '0' && *ptr <= '9' && ptr < end) {
    50          
    50          
185 63           n = (n * 10) + (*ptr - '0');
186 63           ++ptr;
187             }
188 63 100         if(negative) {
189 1           n = -n;
190             }
191 63 100         if(ptr + 2 > end) {
192 6           goto end_parsing;
193             }
194 57 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
195 0           croak("protocol violation - integer not followed by CRLF\n");
196             }
197 57           ptr += 2;
198 57           SV *v = newSViv(n);
199 57 100         if(ps) {
200 52           add_value(ps, v);
201             } else {
202 5           av_push(results, v);
203 5           extracted_item = true;
204             }
205 57           break;
206             }
207             case ',': { /* decimal floating-point */
208 5           char *terminator = strchr(ptr, '\x0D');
209 5 50         if(terminator == NULL) {
210 0           goto end_parsing;
211             }
212             SV *v;
213             #ifdef my_strtod
214             v = newSVnv(my_strtod(ptr, &terminator));
215             #else
216             /* Taken from numeric.c in perl5 source */
217             {
218 5           const char * const s = ptr;
219 5           char **e = &terminator;
220             NV result;
221             char * end_ptr;
222              
223 5           end_ptr = my_atof2(s, &result);
224 5 50         if (e) {
225 5           *e = end_ptr;
226             }
227              
228 5 50         if (! end_ptr) {
229 0           result = 0.0;
230             }
231              
232 5           v = newSVnv(result);
233             }
234             #endif
235              
236 5           ptr = terminator;
237 5 50         if(ptr + 2 > end) {
238 0           goto end_parsing;
239             }
240 5 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
241 0           croak("protocol violation - decimal number not followed by CRLF\n");
242             }
243 5           ptr += 2;
244 5 50         if(ps) {
245 0           add_value(ps, v);
246             } else {
247 5           av_push(results, v);
248 5           extracted_item = true;
249             }
250 5           break;
251             }
252             case '=': /* verbatim string - fall through */
253             case '$': { /* bulk string */
254 0           int n = 0;
255             SV *v;
256 0 0         if(ptr[0] == '-' && ptr[1] == '1') {
    0          
257 0 0         if(ptr + 4 > end) {
258 0           goto end_parsing;
259             }
260             // null
261 0           ptr += 2;
262 0           v = &PL_sv_undef;
263             } else {
264 0 0         while(*ptr >= '0' && *ptr <= '9' && ptr < end) {
    0          
    0          
265 0           n = (n * 10) + (*ptr - '0');
266 0           ++ptr;
267             }
268 0 0         if(ptr + n + 4 > end) {
269 0           goto end_parsing;
270             }
271 0 0         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    0          
272 0           croak("protocol violation - bulk string length not followed by CRLF");
273             }
274 0           ptr += 2;
275 0           v = newSVpvn(ptr, n);
276 0           ptr += n;
277             }
278 0 0         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    0          
279 0           croak("protocol violation - bulk string not terminated by CRLF");
280             }
281 0           ptr += 2;
282 0 0         if(ps) {
283 0           add_value(ps, v);
284             } else {
285 0           av_push(results, v);
286 0           extracted_item = true;
287             }
288 0           break;
289             }
290             case '(': /* big number - treat as a string for now */
291             case '+': { /* string */
292 129           const char *start = ptr;
293 260 100         while(*ptr && (ptr[0] != '\x0D' && ptr[1] != '\x0A' && ptr < end)) {
    100          
    50          
    50          
294 131           ++ptr;
295             }
296 129 100         if(ptr + 2 > end) {
297 30           goto end_parsing;
298             }
299 99 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
300 0           croak("protocol violation - string not terminated by CRLF");
301             }
302 99           int n = ptr - start;
303 99           SV *v = newSVpvn(start, n);
304 99           ptr += 2;
305 99 100         if(ps) {
306 98           add_value(ps, v);
307             } else {
308 1           av_push(results, v);
309 1           extracted_item = true;
310             }
311 99           break;
312             }
313             case '-': { /* error */
314 2           const char *start = ptr;
315 12 50         while(*ptr && (ptr[0] != '\x0D' && ptr[1] != '\x0A' && ptr < end)) {
    100          
    50          
    50          
316 10           ++ptr;
317             }
318 2 50         if(ptr + 2 > end) {
319 0           goto end_parsing;
320             }
321 2 50         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    50          
322 0           croak("protocol violation - error not terminated by CRLF");
323             }
324 2           int n = ptr - start;
325 2           SV *v = newSVpvn(start, n);
326 2           ptr += 2;
327 2           SV *rv = SvRV(this);
328              
329             /* Remove anything we processed - we're doing this _before_ the call,
330             * since it may throw an exception */
331 2           sv_chop(p, ptr);
332 2 50         ptr = SvPVbyte(p, len);
333 2           end = ptr + len - 1;
334 2 50         if(hv_exists((HV *) rv, "error", 5)) {
335 2           SV **cv_ptr = hv_fetchs((HV *) rv, "error", 0);
336 2 50         if(cv_ptr) {
337 2           CV *cv = (CV *) *cv_ptr;
338 2           dSP;
339 2           ENTER;
340 2           SAVETMPS;
341 2 50         PUSHMARK(SP);
342 2 50         EXTEND(SP, 1);
343 2           PUSHs(sv_2mortal(v));
344 2           PUTBACK;
345 2           call_sv((SV *) cv, G_VOID | G_DISCARD);
346 2 50         FREETMPS;
347 2           LEAVE;
348             } else {
349 2           warn("no CV for ->{error}");
350             }
351             } else {
352 0           warn("no ->{error} handler");
353             }
354             /* Note that we are _not_ setting extracted_item here, because there
355             * were no items to put in results, and we've already updated the buffer
356             * to move past the error item. */
357 2           break;
358             }
359             case '_': { /* single-character null */
360 0           int n = 0;
361 0           SV *v = &PL_sv_undef;
362 0 0         if(ptr + 2 > end) {
363 0           goto end_parsing;
364             }
365 0 0         if(ptr[0] != '\x0D' || ptr[1] != '\x0A') {
    0          
366 0           croak("protocol violation - single-character null not followed by CRLF");
367             }
368 0           ptr += 2;
369 0 0         if(ps) {
370 0           add_value(ps, v);
371             } else {
372 0           av_push(results, v);
373 0           extracted_item = true;
374             }
375 0           break;
376             }
377             default:
378 0           croak("Unknown type %d, bail out", ptr[-1]);
379             }
380              
381             /* Do some housekeeping after parsing: see if we've accumulated enough
382             * data to fill the requirements for one or more levels of our nested container
383             * handling.
384             */
385 439 100         while(ps && av_count(ps->data) >= ps->expected) {
    100          
386 18           AV *data = ps->data;
387 18           struct pending_stack *orig = ps;
388 18           ps = orig->prev;
389 18 100         if(ps) {
390 10           SV *value_ref = newRV((SV *) data);
391 10           av_push(
392             ps->data,
393             value_ref
394             );
395             } else {
396 8           switch(orig->type) {
397             case push: { /* pub/sub is treated as an out-of-bound message */
398 1           SV *rv = SvRV(this);
399 1 50         if(hv_exists((HV *) rv, "pubsub", 6)) {
400 1           SV **cv_ptr = hv_fetchs((HV *) rv, "pubsub", 0);
401 1 50         if(cv_ptr) {
402 1           CV *cv = (CV *) *cv_ptr;
403 1           dSP;
404 1           ENTER;
405 1           SAVETMPS;
406 1 50         PUSHMARK(SP);
407 1           long count = av_count(data);
408 1 50         if(count) {
409 1 50         EXTEND(SP, count);
    50          
410 2 100         for(int i = 0; i < count; ++i) {
411 1           mPUSHs(av_shift(data));
412             }
413             }
414 1           PUTBACK;
415 1           call_sv((SV *) cv, G_VOID | G_DISCARD);
416 1 50         FREETMPS;
417 1           LEAVE;
418             } else {
419 1           warn("no CV for ->{pubsub}");
420             }
421             } else {
422 0           warn("no ->{pubsub} handler");
423             }
424 1           break;
425             }
426             case attribute: {
427             /* attributes aren't used much: for now, we emit as an event,
428             * but eventually would aim to apply this to the response,
429             * by returning a blessed object for example
430             */
431 0           SV *rv = SvRV(this);
432 0           SV *value_ref = newRV((SV *) data);
433 0 0         if(hv_exists((HV *) rv, "attribute", 9)) {
434 0           SV **cv_ptr = hv_fetchs((HV *) rv, "attribute", 0);
435 0 0         if(cv_ptr) {
436 0           CV *cv = (CV *) *cv_ptr;
437 0           dSP;
438 0           ENTER;
439 0           SAVETMPS;
440 0 0         PUSHMARK(SP);
441 0 0         EXTEND(SP, 1);
442 0           PUSHs(sv_2mortal(value_ref));
443 0           PUTBACK;
444 0           call_sv((SV *) cv, G_VOID | G_DISCARD);
445 0 0         FREETMPS;
446 0           LEAVE;
447             } else {
448 0           warn("no CV for ->{attribute}");
449             }
450             } else {
451 0           warn("no ->{attribute} handler");
452             }
453 0           break;
454             }
455             default: {
456             /* Yes, we fall through as a default for map and array: unless the
457             * hashrefs option is set, we want to map all key/value pairs to plain
458             * arrays anyway.
459             */
460 7           SV *value_ref = newRV((SV *) data);
461 7           av_push(
462             results,
463             value_ref
464             );
465 7           break;
466             }
467             }
468 8           extracted_item = true;
469             }
470 18           Safefree(orig);
471             }
472              
473             /* Every time we reach the end of a complete item, we update
474             * our parsed string progress and add to our list of things to
475             * return.
476             */
477 421 100         if(extracted_item) {
478             /* Remove anything we processed */
479 19           sv_chop(p, ptr);
480 19 50         ptr = SvPVbyte(p, len);
481 19           end = ptr + len;
482 19           extracted_item = false;
483             /* ... and our "list" is only ever going to be a single item if we're in scalar context */
484 19 50         if (GIMME_V == G_SCALAR && av_count(results) > 0) {
    100          
    50          
485 10           extracted = av_shift(results);
486 10           break;
487             }
488             }
489             }
490             }
491             end_parsing:
492             /* Clean up our temporary parse stack */
493 343 100         while(ps) {
494 240           struct pending_stack *orig = ps;
495 240           ps = ps->prev;
496 240           Safefree(orig);
497             }
498              
499             /* Flatten our results back into scalars for return */
500 103 50         if (GIMME_V == G_LIST) {
    100          
501 51           long count = av_count(results);
502 51 100         if(count) {
503 8 50         EXTEND(SP, count);
    50          
504 59 100         for(int i = 0; i < count; ++i) {
505 8           mPUSHs(av_shift(results));
506             }
507             }
508 52 50         } else if (GIMME_V == G_SCALAR) {
    50          
509 52 50         mXPUSHs(extracted);
510             }