diff --git a/READMEs/README.fault-injection.md b/READMEs/README.fault-injection.md index 381d459a9..afeba402a 100644 --- a/READMEs/README.fault-injection.md +++ b/READMEs/README.fault-injection.md @@ -232,15 +232,26 @@ matches an object, the fault will be injected every time. It's also possible to make the fault inject itself at a random probability, or in a cyclic pattern, by giving additional information in brackets, eg -|Syntax|Meaning| -|---|---| -|`wsi/thefault`|Inject the fault every time| -|`wsi/thefault(10%)`|Randomly inject the fault at 10% probability| -|`wsi/thefault(.............X.X)`|Inject the fault on the 14th and 16th try, every 16 tries| +|Syntax|Used with|Meaning| +|---|---|---| +|`wsi/thefault`|lws_fi()|Inject the fault every time| +|`wsi/thefault(10%)`|lws_fi()|Randomly inject the fault at 10% probability| +|`wsi/thefault(.............X.X)`|lws_fi()|Inject the fault on the 14th and 16th try, every 16 tries| +|`wsi/thefault2(123..456)`|lws_fi_range()|Pick a number between 123 and 456| You must quote the strings containing these symbols, since they may otherwise be interpreted by your shell. +The last example above does not decide whether to inject the fault via `lws_fi()` +like the others. Instead you can use it via `lws_fi_range()` as part of the +fault processing, on a secondary fault injection name. For example you may have +a fault `myfault` you use with `lws_fi()` to decide when to inject the fault, +and then a second, related fault name `myfault_delay` to allow you to add code +to delay the fault action by some random amount of ms within an externally- +given range. You can get a pseudo-random number within the externally-given +range by calling `lws_fi_range()` on `myfault_delay`, and control the whole +thing by giving, eg, `"myfault(10%),myfault_delay(123..456)"` + ## Well-known fault names in lws |Scope|Namespc|Name|Fault effect| diff --git a/include/libwebsockets/lws-fault-injection.h b/include/libwebsockets/lws-fault-injection.h index 6f8e1aa81..e9133763e 100644 --- a/include/libwebsockets/lws-fault-injection.h +++ b/include/libwebsockets/lws-fault-injection.h @@ -72,6 +72,7 @@ enum { LWSFI_PROBABILISTIC, /* .pre % chance of injection */ LWSFI_PATTERN, /* use .count bits in .pattern after .pre */ LWSFI_PATTERN_ALLOC, /* as _PATTERN, but .pattern is malloc'd */ + LWSFI_RANGE /* pick a number between pre and count */ }; typedef struct lws_fi { @@ -108,6 +109,27 @@ typedef struct lws_fi_ctx { LWS_VISIBLE LWS_EXTERN int lws_fi(const lws_fi_ctx_t *fic, const char *fi_name); +/** + * lws_fi_range() - get a random number from a range + * + * \param fic: fault injection tracking context + * \param fi_name: name of fault injection + * \param result: points to uint64_t to be set to the result + * + * This lets you get a random number from an externally-set range, set using a + * fault injection syntax like "myfault(123..456)". That will cause us to + * return a number between those two inclusive, from the seeded PRNG. + * + * This is useful when you used lws_fi() with its own fault name to decide + * whether to inject the fault, and then the code to cause the fault needs + * additional constrained pseudo-random fuzzing for, eg, delays before issuing + * the fault. + * + * Returns 0 if \p *result is set, else nonzero for failure. + */ +LWS_VISIBLE LWS_EXTERN int +lws_fi_range(const lws_fi_ctx_t *fic, const char *name, uint64_t *result); + /** * lws_fi_add() - add an allocated copy of fault injection to a context * diff --git a/lib/system/fault-injection/fault-injection.c b/lib/system/fault-injection/fault-injection.c index 57082cc50..70c2a9f90 100644 --- a/lib/system/fault-injection/fault-injection.c +++ b/lib/system/fault-injection/fault-injection.c @@ -88,6 +88,30 @@ inject: return 1; } +int +lws_fi_range(const lws_fi_ctx_t *fic, const char *name, uint64_t *result) +{ + lws_fi_priv_t *pv; + uint64_t d; + + pv = lws_fi_lookup(fic, name); + + if (!pv) + return 1; + + if (pv->fi.type != LWSFI_RANGE) { + lwsl_err("%s: fault %s is not a 123..456 range\n", + __func__, name); + return 1; + } + + d = pv->fi.count - pv->fi.pre; + + *result = pv->fi.pre + (lws_xos((lws_xos_t *)&fic->xos) % d); + + return 0; +} + int _lws_fi_user_wsi_fi(struct lws *wsi, const char *name) { @@ -240,20 +264,30 @@ lws_fi_destroy(const lws_fi_ctx_t *fic) } lws_end_foreach_dll_safe(p, p1); } +/* + * We want to support these kinds of qualifier + * + * myfault true always + * myfault(10%) true 10% of the time + * myfault(....X X) true when X + * myfault2(20..3000) pick a number between 20 and 3000 + */ + enum { PARSE_NAME, PARSE_WHEN, PARSE_PC, - PARSE_ENDBR + PARSE_ENDBR, + PARSE_COMMA }; void lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers) { + int state = PARSE_NAME, m; struct lws_tokenize ts; lws_fi_t fi; char nm[64]; - int state = PARSE_NAME; /* * Go through the comma-separated list of faults @@ -284,17 +318,20 @@ lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers) memset(&fi, 0, sizeof(fi)); - lws_strnncpy(nm, ts.token, ts.token_len, sizeof(nm)); + lws_strnncpy(nm, ts.token, ts.token_len, + sizeof(nm)); fi.name = nm; fi.type = LWSFI_ALWAYS; - lwsl_notice("%s: name %.*s\n", __func__, (int)ts.token_len, ts.token); + lwsl_notice("%s: name %.*s\n", __func__, + (int)ts.token_len, ts.token); /* added later, potentially after (when) */ break; } if (state == PARSE_WHEN) { - /* it's either numeric or a pattern */ + /* it's either numeric (then % or ..num2), or + * .X pattern */ lwsl_notice("%s: when\n", __func__); @@ -306,7 +343,8 @@ lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers) * pattern... we need to allocate it */ fi.type = LWSFI_PATTERN_ALLOC; - pat = lws_zalloc((ts.token_len >> 3) + 1, __func__); + pat = lws_zalloc((ts.token_len >> 3) + 1, + __func__); if (!pat) return; fi.pattern = pat; @@ -315,16 +353,50 @@ lws_fi_deserialize(lws_fi_ctx_t *fic, const char *sers) for (n = 0; n < ts.token_len; n++) if (ts.token[n] == 'X') pat[n >> 3] = (uint8_t)( - pat[n >> 3] | (1 << (n & 7))); + pat[n >> 3] | + (1 << (n & 7))); - lwsl_hexdump_notice(pat, (ts.token_len >> 3) + 1); + lwsl_hexdump_notice(pat, + (ts.token_len >> 3) + 1); state = PARSE_ENDBR; break; } - fi.pre = (uint64_t)atoi(ts.token); - lwsl_notice("%s: prob %d%%\n", __func__, (int)fi.pre); + fi.pre = (uint64_t)atoll(ts.token); + + for (m = 0; m < (int)ts.token_len - 1; m++) + if (ts.token[m] < '0' || + ts.token[m] > '9') + break; + + /* + * We can understand num% or num..num + */ + + if (m != (int)ts.token_len && + ts.token[m] == '.' && + ts.token[m + 1] == '.') { + fi.count = (uint64_t)atoll( + &ts.token[m + 2]); + fi.type = LWSFI_RANGE; + state = PARSE_ENDBR; + + if (fi.pre >= fi.count) { + lwsl_err("%s: range must have " + "smaller first!\n", + __func__); + } + + lwsl_notice("%s: range %llx .." + "%llx\n", __func__, + (unsigned long long)fi.pre, + (unsigned long long)fi.count); + break; + } + + lwsl_notice("%s: prob %d%%\n", __func__, + (int)fi.pre); fi.type = LWSFI_PROBABILISTIC; state = PARSE_PC; break;