1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 
3 #include "proxy.h"
4 
5 enum mcp_ins_type {
6     INS_REQ = 1,
7     INS_RES,
8 };
9 
10 enum mcp_ins_steptype {
11     mcp_ins_step_none = 0,
12     mcp_ins_step_sepkey,
13     mcp_ins_step_keybegin,
14     mcp_ins_step_keyis,
15     mcp_ins_step_hasflag,
16     mcp_ins_step_flagtoken,
17     mcp_ins_step_flagint,
18     mcp_ins_step_flagis,
19     mcp_ins_step_final, // not used.
20 };
21 
22 // START STEP STRUCTS
23 
24 struct mcp_ins_sepkey {
25     char sep;
26     int pos;
27     int mapref;
28 };
29 
30 struct mcp_ins_string {
31     unsigned int str; // arena offset for match string.
32     unsigned int len;
33 };
34 
35 struct mcp_ins_flag {
36     uint64_t bit; // flag converted for bitmask test
37     char f;
38 };
39 
40 // TODO: it might make more sense to flatten the structs into the ins_step
41 // struct. It wouldn't take much more space if we can be careful with
42 // alignment.
43 struct mcp_ins_flagstr {
44     unsigned int str;
45     unsigned int len;
46     uint64_t bit; // flag bit
47     char f;
48 };
49 
50 struct mcp_ins_step {
51     enum mcp_ins_steptype type;
52     union {
53         struct mcp_ins_sepkey sepkey;
54         struct mcp_ins_string string;
55         struct mcp_ins_flag flag;
56         struct mcp_ins_flagstr flagstr;
57     } c;
58 };
59 
60 // END STEP STRUCTS
61 
62 struct mcp_inspector {
63     enum mcp_ins_type type;
64     int scount;
65     unsigned int aused; // arena memory used
66     unsigned int rcount; // number of results to expect
67     char *arena; // string/data storage for steps
68     struct mcp_ins_step steps[];
69 };
70 
71 // PRIVATE INTERFACE
72 
73 #define res_buf(r) (r->cresp ? r->cresp->iov[0].iov_base : r->buf)
74 
75 // COMMON ARG HANDLERS
76 
77 // multiple step types only take 'flag' as an argument.
mcp_inspector_flag_c_g(lua_State * L,int tidx)78 static int mcp_inspector_flag_c_g(lua_State *L, int tidx) {
79     if (lua_getfield(L, tidx, "flag") != LUA_TNIL) {
80         size_t len = 0;
81         const char *flag = lua_tolstring(L, -1, &len);
82         if (len != 1) {
83             proxy_lua_ferror(L, "inspector step %d: 'flag' must be a single character", tidx);
84         }
85         if (mcp_is_flag_invalid(flag[0])) {
86             proxy_lua_ferror(L, "inspect step %d: 'flag' must be alphanumeric", tidx);
87         }
88     } else {
89         proxy_lua_ferror(L, "inspector step %d: must provide 'flag' argument", tidx);
90     }
91     lua_pop(L, 1); // val or nil
92     return 0;
93 }
94 
mcp_inspector_flag_i_g(lua_State * L,int tidx,int sc,struct mcp_inspector * ins)95 static int mcp_inspector_flag_i_g(lua_State *L, int tidx, int sc, struct mcp_inspector *ins) {
96     struct mcp_ins_step *s = &ins->steps[sc];
97     struct mcp_ins_flag *c = &s->c.flag;
98 
99     if (lua_getfield(L, tidx, "flag") != LUA_TNIL) {
100         const char *flag = lua_tostring(L, -1);
101         c->f = flag[0];
102         c->bit = (uint64_t)1 << (c->f - 65);
103     }
104     lua_pop(L, 1); // val or nil
105 
106     return 0;
107 }
108 
mcp_inspector_string_c_g(lua_State * L,int tidx)109 static int mcp_inspector_string_c_g(lua_State *L, int tidx) {
110     size_t len = 0;
111 
112     if (lua_getfield(L, tidx, "str") != LUA_TNIL) {
113         lua_tolstring(L, -1, &len);
114         if (len < 1) {
115             proxy_lua_ferror(L, "inspector step %d: 'str' must have nonzero length", tidx);
116         }
117     } else {
118         proxy_lua_ferror(L, "inspector step %d: must provide 'str' argument", tidx);
119     }
120     lua_pop(L, 1); // val or nil
121 
122     return len;
123 }
124 
mcp_inspector_string_i_g(lua_State * L,int tidx,int sc,struct mcp_inspector * ins)125 static int mcp_inspector_string_i_g(lua_State *L, int tidx, int sc, struct mcp_inspector *ins) {
126     struct mcp_ins_step *s = &ins->steps[sc];
127     struct mcp_ins_string *c = &s->c.string;
128     size_t len = 0;
129 
130     // store our match string in the arena space that we reserved before.
131     if (lua_getfield(L, tidx, "str") != LUA_TNIL) {
132         const char *str = lua_tolstring(L, -1, &len);
133         c->str = ins->aused;
134         c->len = len;
135         char *a = ins->arena + ins->aused;
136         memcpy(a, str, len);
137         ins->aused += len;
138     }
139     lua_pop(L, 1); // val or nil
140 
141     return len;
142 }
143 
144 // END COMMMON ARG HANDLERS
145 
mcp_inspector_sepkey_c(lua_State * L,int tidx)146 static int mcp_inspector_sepkey_c(lua_State *L, int tidx) {
147     if (lua_getfield(L, tidx, "sep") != LUA_TNIL) {
148         size_t len = 0;
149         lua_tolstring(L, -1, &len);
150         if (len != 1) {
151             proxy_lua_ferror(L, "inspector step %d: separator must be one character", tidx);
152         }
153     }
154     lua_pop(L, 1); // val or nil
155 
156     if (lua_getfield(L, tidx, "pos") != LUA_TNIL) {
157         luaL_checktype(L, -1, LUA_TNUMBER);
158     }
159     lua_pop(L, 1); // val or nil
160 
161     if (lua_getfield(L, tidx, "map") != LUA_TNIL) {
162         luaL_checktype(L, -1, LUA_TTABLE);
163     }
164     lua_pop(L, 1); // val or nil
165 
166     return 0;
167 }
168 
169 // initializer. arguments already checked, so just fill out the slot.
mcp_inspector_sepkey_i(lua_State * L,int tidx,int sc,struct mcp_inspector * ins)170 static int mcp_inspector_sepkey_i(lua_State *L, int tidx, int sc, struct mcp_inspector *ins) {
171     struct mcp_ins_step *s = &ins->steps[sc];
172     struct mcp_ins_sepkey *c = &s->c.sepkey;
173 
174     if (lua_getfield(L, tidx, "sep") != LUA_TNIL) {
175         const char *sep = lua_tostring(L, -1);
176         c->sep = sep[0];
177     } else {
178         // default separator
179         c->sep = '/';
180     }
181     lua_pop(L, 1); // val or nil
182 
183     if (lua_getfield(L, tidx, "pos") != LUA_TNIL) {
184         c->pos = lua_tointeger(L, -1);
185     } else {
186         c->pos = 1;
187     }
188     lua_pop(L, 1);
189 
190     if (lua_getfield(L, tidx, "map") != LUA_TNIL) {
191         c->mapref = luaL_ref(L, LUA_REGISTRYINDEX);
192     } else {
193         c->mapref = 0;
194         lua_pop(L, 1);
195     }
196     // ref was popped
197 
198     return 0;
199 }
200 
201 // TODO: abstract out the token-position-finder
mcp_inspector_sepkey_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)202 static int mcp_inspector_sepkey_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
203     mcp_request_t *rq = arg;
204     struct mcp_ins_sepkey *c = &s->c.sepkey;
205 
206     const char *key = MCP_PARSER_KEY(rq->pr);
207     const char *end = key + rq->pr.klen;
208     char sep = c->sep;
209     int pos = c->pos;
210 
211     // skip initial separators
212     while (key != end) {
213         if (*key == sep) {
214             key++;
215         } else {
216             break;
217         }
218     }
219     const char *token = key;
220     int tlen = 0;
221 
222     while (key != end) {
223         if (*key == sep) {
224             // measure token length and stop if at position.
225             if (--pos == 0) {
226                 tlen = key - token;
227                 break;
228             } else {
229                 // NOTE: this could point past the end of the key, but unless
230                 // it's the token we want we won't look at it.
231                 token = key+1;
232             }
233         }
234         key++;
235     }
236 
237     // either the separator was never found, or we ended before finding
238     // another one, which gives us an end token.
239     if (pos == 1) {
240         tlen = key - token;
241     }
242 
243     // now have *token and tlen
244     if (tlen != 0) {
245         if (c->mapref) {
246             // look up this string against the map.
247             // NOTE: this still ends up creating a garbage string. However,
248             // since the map is internal we can optimize this later by moving
249             // the map lookup op to C.
250             lua_rawgeti(L, LUA_REGISTRYINDEX, c->mapref);
251             lua_pushlstring(L, token, tlen);
252             lua_rawget(L, -2); // pops string.
253             lua_remove(L, -2); // removes map, shifts lookup result down.
254             // stack should be clean: just the result.
255         } else {
256             // no map, return the actual token.
257             lua_pushlstring(L, token, tlen);
258         }
259     } else {
260         lua_pushnil(L); // not found.
261     }
262 
263     return 1;
264 }
265 
mcp_inspector_keybegin_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)266 static int mcp_inspector_keybegin_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
267     mcp_request_t *rq = arg;
268     struct mcp_ins_string *c = &s->c.string;
269 
270     const char *key = MCP_PARSER_KEY(rq->pr);
271     int klen = rq->pr.klen;
272     const char *str = ins->arena + c->str;
273 
274     if (c->len < klen && strncmp(key, str, c->len) == 0) {
275         lua_pushboolean(L, 1);
276     } else {
277         lua_pushboolean(L, 0);
278     }
279 
280     return 1;
281 }
282 
mcp_inspector_keyis_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)283 static int mcp_inspector_keyis_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
284     mcp_request_t *rq = arg;
285     struct mcp_ins_string *c = &s->c.string;
286 
287     const char *key = MCP_PARSER_KEY(rq->pr);
288     int klen = rq->pr.klen;
289     const char *str = ins->arena + c->str;
290 
291     if (c->len == klen && strncmp(key, str, c->len) == 0) {
292         lua_pushboolean(L, 1);
293     } else {
294         lua_pushboolean(L, 0);
295     }
296 
297     return 1;
298 }
299 
mcp_inspector_hasflag_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)300 static int mcp_inspector_hasflag_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
301     struct mcp_ins_flag *c = &s->c.flag;
302     if (ins->type == INS_REQ) {
303         mcp_request_t *rq = arg;
304         // requests should always be tokenized, so we can just check the bit.
305         if (rq->pr.t.meta.flags & c->bit) {
306             lua_pushboolean(L, 1);
307         } else {
308             lua_pushboolean(L, 0);
309         }
310     } else {
311         mcp_resp_t *res = arg;
312         if (res->resp.type == MCMC_RESP_META) {
313             // result object may not be tokenized. this will do so if not
314             // already. any future hits agains the same object will use the
315             // cached tokenizer struct.
316             mcmc_tokenize_res(res_buf(res), res->resp.reslen, &res->tok);
317             if (mcmc_token_has_flag_bit(&res->tok, c->bit) == MCMC_OK) {
318                 lua_pushboolean(L, 1);
319             } else {
320                 lua_pushboolean(L, 0);
321             }
322         } else {
323             proxy_lua_error(L, "inspector error: response is not meta protocol");
324         }
325     }
326     return 1;
327 }
328 
329 // This mirrors `bool, (str|nil) = r:flag_token("T")`
mcp_inspector_flagtoken_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)330 static int mcp_inspector_flagtoken_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
331     struct mcp_ins_flag *c = &s->c.flag;
332     if (ins->type == INS_REQ) {
333         mcp_request_t *rq = arg;
334 
335         if (rq->pr.t.meta.flags & c->bit) {
336             lua_pushboolean(L, 1); // flag exists
337             const char *tok = NULL;
338             size_t tlen = 0;
339             mcp_request_find_flag_token(rq, c->f, &tok, &tlen);
340             lua_pushlstring(L, tok, tlen); // flag's token
341             return 2;
342         }
343     } else {
344         mcp_resp_t *res = arg;
345         if (res->resp.type == MCMC_RESP_META) {
346             mcmc_tokenize_res(res_buf(res), res->resp.reslen, &res->tok);
347             if (mcmc_token_has_flag_bit(&res->tok, c->bit) == MCMC_OK) {
348                 lua_pushboolean(L, 1); // flag exists
349                 int tlen = 0;
350                 const char *tok = mcmc_token_get_flag(res_buf(res), &res->tok, c->f, &tlen);
351                 lua_pushlstring(L, tok, tlen); // flag's token
352                 return 2;
353             }
354         }
355     }
356     lua_pushboolean(L, 0);
357     lua_pushnil(L);
358 
359     return 2;
360 }
361 
362 // TODO: flaguint variant?
363 // still stuck as signed in lua but would reject signed tokens
mcp_inspector_flagint_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)364 static int mcp_inspector_flagint_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
365     struct mcp_ins_flag *c = &s->c.flag;
366     if (ins->type == INS_REQ) {
367         mcp_request_t *rq = arg;
368 
369         if (rq->pr.t.meta.flags & c->bit) {
370             lua_pushboolean(L, 1); // flag exists
371             int64_t tok = 0;
372             if (mcp_request_find_flag_tokenint64(rq, c->f, &tok) == 0) {
373                 lua_pushinteger(L, tok);
374             } else {
375                 lua_pushnil(L);
376             }
377             return 2;
378         }
379     } else {
380         mcp_resp_t *res = arg;
381         if (res->resp.type == MCMC_RESP_META) {
382             mcmc_tokenize_res(res_buf(res), res->resp.reslen, &res->tok);
383             if (mcmc_token_has_flag_bit(&res->tok, c->bit) == MCMC_OK) {
384                 lua_pushboolean(L, 1); // flag exists
385                 int64_t tok = 0;
386                 if (mcmc_token_get_flag_64(res_buf(res), &res->tok, c->f, &tok) == MCMC_OK) {
387                     lua_pushinteger(L, tok);
388                 } else {
389                     lua_pushnil(L); // token couldn't be converted
390                 }
391                 return 2;
392             }
393         }
394     }
395     lua_pushboolean(L, 0);
396     lua_pushnil(L);
397 
398     return 2;
399 }
400 
mcp_inspector_flagstr_c(lua_State * L,int tidx)401 static int mcp_inspector_flagstr_c(lua_State *L, int tidx) {
402     mcp_inspector_flag_c_g(L, tidx);
403     int size = mcp_inspector_string_c_g(L, tidx);
404     return size;
405 }
406 
mcp_inspector_flagstr_i(lua_State * L,int tidx,int sc,struct mcp_inspector * ins)407 static int mcp_inspector_flagstr_i(lua_State *L, int tidx, int sc, struct mcp_inspector *ins) {
408     // TODO: if we never use mcp_ins_step we can remove it and just pass parts
409     // of the relevant structs down into these functions.
410     struct mcp_ins_step *s = &ins->steps[sc];
411     struct mcp_ins_flagstr *c = &s->c.flagstr;
412     size_t len = 0;
413 
414     if (lua_getfield(L, tidx, "flag") != LUA_TNIL) {
415         const char *flag = lua_tostring(L, -1);
416         c->f = flag[0];
417         c->bit = (uint64_t)1 << (c->f - 65);
418     }
419     lua_pop(L, 1); // val or nil
420 
421     if (lua_getfield(L, tidx, "str") != LUA_TNIL) {
422         const char *str = lua_tolstring(L, -1, &len);
423         c->str = ins->aused;
424         c->len = len;
425         char *a = ins->arena + ins->aused;
426         memcpy(a, str, len);
427         ins->aused += len;
428     }
429     lua_pop(L, 1); // val or nil
430 
431     return len;
432 }
433 
434 // FIXME: size_t vs int consistency for tlen would shorten the code.
mcp_inspector_flagis_r(lua_State * L,struct mcp_inspector * ins,struct mcp_ins_step * s,void * arg)435 static int mcp_inspector_flagis_r(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg) {
436     struct mcp_ins_flagstr *c = &s->c.flagstr;
437     const char *str = ins->arena + c->str;
438     if (ins->type == INS_REQ) {
439         mcp_request_t *rq = arg;
440 
441         if (rq->pr.t.meta.flags & c->bit) {
442             lua_pushboolean(L, 1); // flag exists
443             const char *tok = NULL;
444             size_t tlen = 0;
445             mcp_request_find_flag_token(rq, c->f, &tok, &tlen);
446             if (tlen == c->len && strncmp(tok, str, c->len) == 0) {
447                 lua_pushboolean(L, 1);
448             } else {
449                 lua_pushboolean(L, 0);
450             }
451             return 2;
452         }
453     } else {
454         mcp_resp_t *res = arg;
455         if (res->resp.type == MCMC_RESP_META) {
456             mcmc_tokenize_res(res_buf(res), res->resp.reslen, &res->tok);
457             if (mcmc_token_has_flag_bit(&res->tok, c->bit) == MCMC_OK) {
458                 lua_pushboolean(L, 1); // flag exists
459                 int tlen = 0;
460                 const char *tok = mcmc_token_get_flag(res_buf(res), &res->tok, c->f, &tlen);
461                 if (tlen == c->len && strncmp(tok, str, c->len) == 0) {
462                     lua_pushboolean(L, 1);
463                 } else {
464                     lua_pushboolean(L, 0);
465                 }
466                 return 2;
467             }
468         }
469     }
470     lua_pushboolean(L, 0);
471     lua_pushnil(L);
472 
473     return 2;
474 }
475 
476 // END STEPS
477 
478 typedef int (*mcp_ins_c)(lua_State *L, int tidx);
479 typedef int (*mcp_ins_i)(lua_State *L, int tidx, int sc, struct mcp_inspector *ins);
480 typedef int (*mcp_ins_r)(lua_State *L, struct mcp_inspector *ins, struct mcp_ins_step *s, void *arg);
481 
482 struct mcp_ins_entry {
483     const char *s; // string name
484     mcp_ins_c c;
485     mcp_ins_i i;
486     mcp_ins_r r;
487     unsigned int t; // allowed object types
488     int n; // number of results to expect
489 };
490 
491 static const struct mcp_ins_entry mcp_ins_entries[] = {
492     [mcp_ins_step_none] = {NULL, NULL, NULL, NULL, 0, 0},
493     [mcp_ins_step_sepkey] = {"sepkey", mcp_inspector_sepkey_c, mcp_inspector_sepkey_i, mcp_inspector_sepkey_r, INS_REQ, 1},
494     [mcp_ins_step_keybegin] = {"keybegin", mcp_inspector_string_c_g, mcp_inspector_string_i_g, mcp_inspector_keybegin_r, INS_REQ, 1},
495     [mcp_ins_step_keyis] = {"keyis", mcp_inspector_string_c_g, mcp_inspector_string_i_g, mcp_inspector_keyis_r, INS_REQ, 1},
496     [mcp_ins_step_hasflag] = {"hasflag", mcp_inspector_flag_c_g, mcp_inspector_flag_i_g, mcp_inspector_hasflag_r, INS_REQ|INS_RES, 1},
497     [mcp_ins_step_flagtoken] = {"flagtoken", mcp_inspector_flag_c_g, mcp_inspector_flag_i_g, mcp_inspector_flagtoken_r, INS_REQ|INS_RES, 2},
498     [mcp_ins_step_flagint] = {"flagint", mcp_inspector_flag_c_g, mcp_inspector_flag_i_g, mcp_inspector_flagint_r, INS_REQ|INS_RES, 2},
499     [mcp_ins_step_flagis] = {"flagis", mcp_inspector_flagstr_c, mcp_inspector_flagstr_i, mcp_inspector_flagis_r, INS_REQ|INS_RES, 2},
500 };
501 
502 // call with type string on top
mcp_inspector_steptype(lua_State * L)503 static enum mcp_ins_steptype mcp_inspector_steptype(lua_State *L) {
504     const char *type = luaL_checkstring(L, -1);
505     for (int x = 0; x < mcp_ins_step_final; x++) {
506         const struct mcp_ins_entry *e = &mcp_ins_entries[x];
507         if (e->s && strcmp(type, e->s) == 0) {
508             return x;
509         }
510     }
511     return mcp_ins_step_none;
512 }
513 
514 // - arguments given as list of tables:
515 //   { t = "type", arg = "bar", etc },
516 //   { etc }
517 //   - can take table-of-tables via: mcp.req_inspector_new(table.unpack(args))
518 // NOTES:
519 // - can we inline necessary strings/etc via extra allocated memory?
520 // - can we get mcp.inspector metatable into the upvalue of the _call and _gc
521 // funcs for fast-compare?
mcp_inspector_new(lua_State * L,enum mcp_ins_type type)522 static int mcp_inspector_new(lua_State *L, enum mcp_ins_type type) {
523     int argc = lua_gettop(L);
524     size_t size = 0;
525     int scount = 0;
526 
527     // loop argument tables once for validation and pre-calculations.
528     for (int x = 1; x <= argc; x++) {
529         luaL_checktype(L, x, LUA_TTABLE);
530         if (lua_getfield(L, x, "t") != LUA_TNIL) {
531             enum mcp_ins_steptype st = mcp_inspector_steptype(L);
532             const struct mcp_ins_entry *e = &mcp_ins_entries[st];
533             if (!(e->t & type)) {
534                 proxy_lua_ferror(L, "inspector step %d: step incompatible with inspector type", x);
535             }
536             if ((st == mcp_ins_step_none) || e->c == NULL) {
537                 proxy_lua_ferror(L, "inspector step %d: unknown step type", x);
538             }
539             size += e->c(L, x);
540         }
541         lua_pop(L, 1); // drop 't' or nil
542         scount++;
543     }
544 
545     // we now know the size and number of steps. allocate some flat memory.
546 
547     // TODO: we need memory for steps + arbitrary step data. (ie; string stems
548     // and the like)
549     // - now: single extra malloc, divvy out the buffer as requested
550     // - later: if alignment of the flexible step array can be reliably
551     // determined (C11 alignas or etc), inline memory can be used instead.
552     size_t extsize = sizeof(struct mcp_ins_step) * scount;
553     struct mcp_inspector *ins = lua_newuserdatauv(L, sizeof(*ins) + extsize, 1);
554     memset(ins, 0, sizeof(*ins));
555 
556     ins->arena = malloc(size);
557     if (ins->arena == NULL) {
558         proxy_lua_error(L, "mcp.req_inspector_new: failed to allocate memory");
559     }
560 
561     luaL_setmetatable(L, "mcp.inspector");
562     switch (type) {
563         case INS_REQ:
564             luaL_getmetatable(L, "mcp.request");
565             break;
566         case INS_RES:
567             luaL_getmetatable(L, "mcp.response");
568             break;
569     }
570     // set metatable to the upvalue for a fast comparison during __call
571     lua_setiuservalue(L, -2, 1);
572     ins->type = type;
573 
574     // loop the arg tables again to fill in the steps
575     // skip checks since we did that during the first loop.
576     scount = 0;
577     for (int x = 1; x <= argc; x++) {
578         if (lua_getfield(L, x, "t") != LUA_TNIL) {
579             enum mcp_ins_steptype st = mcp_inspector_steptype(L);
580             ins->steps[scount].type = st;
581             mcp_ins_entries[st].i(L, x, scount, ins);
582             ins->rcount += mcp_ins_entries[st].n;
583         }
584         lua_pop(L, 1); // drop t or nil
585         scount++;
586     }
587 
588     if (size != ins->aused) {
589         proxy_lua_error(L, "inspector failed to properly initialize, memory not filled correctly");
590     }
591     ins->scount = scount;
592 
593     return 1;
594 }
595 
mcp_ins_run(lua_State * L,struct mcp_inspector * ins,void * arg)596 static int mcp_ins_run(lua_State *L, struct mcp_inspector *ins, void *arg) {
597     int ret = 0;
598 
599     for (int x = 0; x < ins->scount; x++) {
600         struct mcp_ins_step *s = &ins->steps[x];
601         assert(s->type != mcp_ins_step_none);
602         ret += mcp_ins_entries[s->type].r(L, ins, s, arg);
603     }
604 
605     return ret;
606 }
607 
608 // PUBLIC INTERFACE
609 
mcplib_req_inspector_new(lua_State * L)610 int mcplib_req_inspector_new(lua_State *L) {
611     return mcp_inspector_new(L, INS_REQ);
612 }
613 
mcplib_res_inspector_new(lua_State * L)614 int mcplib_res_inspector_new(lua_State *L) {
615     return mcp_inspector_new(L, INS_RES);
616 }
617 
618 // walk each step and free references/memory/etc
mcplib_inspector_gc(lua_State * L)619 int mcplib_inspector_gc(lua_State *L) {
620     struct mcp_inspector *ins = lua_touserdata(L, 1);
621 
622     if (ins->arena) {
623         free(ins->arena);
624         ins->arena = NULL;
625     }
626 
627     for (int x = 0; x < ins->scount; x++) {
628         struct mcp_ins_step *s = &ins->steps[x];
629         switch (s->type) {
630             case mcp_ins_step_sepkey:
631                 if (s->c.sepkey.mapref) {
632                     luaL_unref(L, LUA_REGISTRYINDEX, s->c.sepkey.mapref);
633                     s->c.sepkey.mapref = 0;
634                 }
635                 break;
636             case mcp_ins_step_keybegin:
637             case mcp_ins_step_keyis:
638             case mcp_ins_step_hasflag:
639             case mcp_ins_step_flagtoken:
640             case mcp_ins_step_flagint:
641             case mcp_ins_step_flagis:
642             case mcp_ins_step_none:
643             case mcp_ins_step_final:
644                 break;
645         }
646     }
647 
648     return 0;
649 }
650 
651 // - iterate steps, call function callbacks with as context arg
652 // TODO:
653 // - second arg _may_ be a table: in which case we fill the results into this
654 //   table rather than return them directly.
655 //   - do this via a different run function that pops each step result?
mcplib_inspector_call(lua_State * L)656 int mcplib_inspector_call(lua_State *L) {
657     // since we're here from a __call, assume the type is correct.
658     struct mcp_inspector *ins = lua_touserdata(L, 1);
659     luaL_checktype(L, 2, LUA_TUSERDATA);
660     if (lua_checkstack(L, ins->rcount) == 0) {
661         proxy_lua_error(L, "inspector ran out of stack space for results");
662     }
663 
664     // luaL_checkudata() is slow. Trying a new method here where we pull the
665     // metatable from a reference then compare it against the meta table of
666     // the argument object.
667     lua_getmetatable(L, 2); // put arg metatable on stack
668     lua_getiuservalue(L, 1, 1); // put stashed metatable on stack
669     luaL_argcheck(L, lua_rawequal(L, -1, -2), 2,
670             "invalid argument to inspector object");
671     lua_pop(L, 2); // toss both metatables
672 
673     // we're valid now. run the steps
674     void *arg = lua_touserdata(L, 2);
675     return mcp_ins_run(L, ins, arg);
676 }
677