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