/* * lws-api-test-lws_cache * * Written in 2010-2021 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. */ #include static struct lws_context *cx; static int tests, fail; static int test_just_l1(void) { struct lws_cache_creation_info ci; struct lws_cache_ttl_lru *l1; int ret = 1; size_t size; char *po; lwsl_user("%s\n", __func__); tests++; /* just create a heap cache "L1" */ memset(&ci, 0, sizeof(ci)); ci.cx = cx; ci.ops = &lws_cache_ops_heap; ci.name = "L1"; l1 = lws_cache_create(&ci); if (!l1) goto cdone; /* add two items, a has 1s expiry and b has 2s */ if (lws_cache_write_through(l1, "a", (const uint8_t *)"is_a", 5, lws_now_usecs() + LWS_US_PER_SEC, NULL)) goto cdone; if (lws_cache_write_through(l1, "b", (const uint8_t *)"is_b", 5, lws_now_usecs() + LWS_US_PER_SEC * 2, NULL)) goto cdone; /* check they exist as intended */ if (lws_cache_item_get(l1, "a", (const void **)&po, &size) || size != 5 || strcmp(po, "is_a")) goto cdone; if (lws_cache_item_get(l1, "b", (const void **)&po, &size) || size != 5 || strcmp(po, "is_b")) goto cdone; /* wait for 1.2s to pass, working the event loop by hand */ lws_cancel_service(cx); if (lws_service(cx, 0) < 0) goto cdone; #if defined(WIN32) Sleep(1200); #else /* netbsd cares about < 1M */ usleep(999999); usleep(200001); #endif lws_cancel_service(cx); if (lws_service(cx, 0) < 0) goto cdone; lws_cancel_service(cx); if (lws_service(cx, 0) < 0) goto cdone; /* a only had 1s lifetime, he should be gone */ if (!lws_cache_item_get(l1, "a", (const void **)&po, &size)) { lwsl_err("%s: cache: a still exists after expiry\n", __func__); fail++; goto cdone; } /* that's ok then */ ret = 0; cdone: lws_cache_destroy(&l1); if (ret) lwsl_warn("%s: fail\n", __func__); return ret; } static int test_just_l1_limits(void) { struct lws_cache_creation_info ci; struct lws_cache_ttl_lru *l1; int ret = 1; size_t size; char *po; lwsl_user("%s\n", __func__); tests++; /* just create a heap cache "L1" */ memset(&ci, 0, sizeof(ci)); ci.cx = cx; ci.ops = &lws_cache_ops_heap; ci.name = "L1_lim"; ci.max_items = 1; /* ie, adding a second item destroys the first */ l1 = lws_cache_create(&ci); if (!l1) goto cdone; /* add two items, a has 1s expiry and b has 2s */ if (lws_cache_write_through(l1, "a", (const uint8_t *)"is_a", 5, lws_now_usecs() + LWS_US_PER_SEC, NULL)) goto cdone; if (lws_cache_write_through(l1, "b", (const uint8_t *)"is_b", 5, lws_now_usecs() + LWS_US_PER_SEC * 2, NULL)) goto cdone; /* only b should exit, since we limit to cache to just one entry */ if (!lws_cache_item_get(l1, "a", (const void **)&po, &size)) goto cdone; if (lws_cache_item_get(l1, "b", (const void **)&po, &size) || size != 5 || strcmp(po, "is_b")) goto cdone; /* that's ok then */ ret = 0; cdone: lws_cache_destroy(&l1); if (ret) lwsl_warn("%s: fail\n", __func__); return ret; } #if defined(LWS_WITH_CACHE_NSCOOKIEJAR) static const char *cookie1 = "host.com\tFALSE\t/\tTRUE\t4000000000\tmycookie\tmycookievalue", *tag_cookie1 = "host.com|/|mycookie", *cookie2 = "host.com\tFALSE\t/xxx\tTRUE\t4000000000\tmycookie\tmyxxxcookievalue", *tag_cookie2 = "host.com|/xxx|mycookie", *cookie3 = "host.com\tFALSE\t/\tTRUE\t4000000000\textra\tcookie3value", *tag_cookie3 = "host.com|/|extra", *cookie4 = "host.com\tFALSE\t/yyy\tTRUE\t4000000000\tnewcookie\tnewcookievalue", *tag_cookie4 = "host.com|/yyy|newcookie" ; static int test_nsc1(void) { struct lws_cache_creation_info ci; struct lws_cache_ttl_lru *l1 = NULL, *nsc; lws_cache_results_t cr; int n, ret = 1; size_t size; char *po; lwsl_user("%s\n", __func__); tests++; /* First create a netscape cookie cache object */ memset(&ci, 0, sizeof(ci)); ci.cx = cx; ci.ops = &lws_cache_ops_nscookiejar; ci.name = "NSC"; ci.u.nscookiejar.filepath = "./cookies.txt"; nsc = lws_cache_create(&ci); if (!nsc) goto cdone; /* Then a heap cache "L1" as a child of nsc */ ci.ops = &lws_cache_ops_heap; ci.name = "L1"; ci.parent = nsc; l1 = lws_cache_create(&ci); if (!l1) goto cdone; lws_cache_debug_dump(nsc); lws_cache_debug_dump(l1); lwsl_user("%s: add cookies to L1\n", __func__); /* add three cookies */ if (lws_cache_write_through(l1, tag_cookie1, (const uint8_t *)cookie1, strlen(cookie1), lws_now_usecs() + LWS_US_PER_SEC, NULL)) { lwsl_err("%s: write1 failed\n", __func__); goto cdone; } lws_cache_debug_dump(nsc); lws_cache_debug_dump(l1); if (lws_cache_write_through(l1, tag_cookie2, (const uint8_t *)cookie2, strlen(cookie2), lws_now_usecs() + LWS_US_PER_SEC * 2, NULL)) { lwsl_err("%s: write2 failed\n", __func__); goto cdone; } lws_cache_debug_dump(nsc); lws_cache_debug_dump(l1); if (lws_cache_write_through(l1, tag_cookie3, (const uint8_t *)cookie3, strlen(cookie3), lws_now_usecs() + LWS_US_PER_SEC * 2, NULL)) { lwsl_err("%s: write3 failed\n", __func__); goto cdone; } lws_cache_debug_dump(nsc); lws_cache_debug_dump(l1); lwsl_user("%s: check cookies in L1\n", __func__); /* confirm that the cookies are individually in L1 */ if (lws_cache_item_get(l1, tag_cookie1, (const void **)&po, &size) || size != strlen(cookie1) || memcmp(po, cookie1, size)) { lwsl_err("%s: L1 '%s' missing, size %llu, po %s\n", __func__, tag_cookie1, (unsigned long long)size, po); goto cdone; } if (lws_cache_item_get(l1, tag_cookie2, (const void **)&po, &size) || size != strlen(cookie2) || memcmp(po, cookie2, size)) { lwsl_err("%s: L1 '%s' missing\n", __func__, tag_cookie2); goto cdone; } if (lws_cache_item_get(l1, tag_cookie3, (const void **)&po, &size) || size != strlen(cookie3) || memcmp(po, cookie3, size)) { lwsl_err("%s: L1 '%s' missing\n", __func__, tag_cookie3); goto cdone; } /* confirm that the cookies are individually in L2 / NSC... normally * we don't do this but check via L1 so we can get it from there if * present. But as a unit test, we want to make sure it's in L2 / NSC */ lwsl_user("%s: check cookies written thru to NSC\n", __func__); if (lws_cache_item_get(nsc, tag_cookie1, (const void **)&po, &size) || size != strlen(cookie1) || memcmp(po, cookie1, size)) { lwsl_err("%s: NSC '%s' missing, size %llu, po %s\n", __func__, tag_cookie1, (unsigned long long)size, po); goto cdone; } if (lws_cache_item_get(nsc, tag_cookie2, (const void **)&po, &size) || size != strlen(cookie2) || memcmp(po, cookie2, size)) { lwsl_err("%s: NSC '%s' missing\n", __func__, tag_cookie2); goto cdone; } if (lws_cache_item_get(nsc, tag_cookie3, (const void **)&po, &size) || size != strlen(cookie3) || memcmp(po, cookie3, size)) { lwsl_err("%s: NSC '%s' missing\n", __func__, tag_cookie3); goto cdone; } /* let's do a lookup with no results */ lwsl_user("%s: nonexistant get must not pass\n", __func__); if (!lws_cache_item_get(l1, "x.com|y|z", (const void **)&po, &size)) { lwsl_err("%s: nonexistant found size %llu, po %s\n", __func__, (unsigned long long)size, po); goto cdone; } /* * let's try some url paths and check we get the right results set... * for / and any cookie, we expect only c1 and c3 to be listed */ lwsl_user("%s: wildcard lookup 1\n", __func__); n = lws_cache_lookup(l1, "host.com|/|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } lwsl_hexdump_notice(cr.ptr, size); if (cr.size != 53) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* * for /xxx and any cookie, we expect all 3 listed */ lwsl_user("%s: wildcard lookup 2\n", __func__); n = lws_cache_lookup(l1, "host.com|/xxx|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 84) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* * for /yyyy and any cookie, we expect only c1 and c3 */ lwsl_user("%s: wildcard lookup 3\n", __func__); n = lws_cache_lookup(l1, "host.com|/yyyy|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 53) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* * repeat the above test, results should come from cache */ lwsl_user("%s: wildcard lookup 4\n", __func__); n = lws_cache_lookup(l1, "host.com|/yyyy|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 53) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* now let's try deleting cookie 1 */ if (lws_cache_item_remove(l1, tag_cookie1)) goto cdone; lws_cache_debug_dump(nsc); lws_cache_debug_dump(l1); /* with c1 gone, we should only get c3 */ lwsl_user("%s: wildcard lookup 5\n", __func__); n = lws_cache_lookup(l1, "host.com|/|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 25) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* * let's add a fourth cookie (third in cache now we deleted one) */ if (lws_cache_write_through(l1, tag_cookie4, (const uint8_t *)cookie4, strlen(cookie4), lws_now_usecs() + LWS_US_PER_SEC * 2, NULL)) { lwsl_err("%s: write4 failed\n", __func__); goto cdone; } /* * for /yy and any cookie, we expect only c3 */ lwsl_user("%s: wildcard lookup 6\n", __func__); n = lws_cache_lookup(l1, "host.com|/yy|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 25) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* * for /yyy and any cookie, we expect c3 and c4 */ lwsl_user("%s: wildcard lookup 7\n", __func__); n = lws_cache_lookup(l1, "host.com|/yyy|*", (const void **)&cr.ptr, &cr.size); if (n) { lwsl_err("%s: lookup failed %d\n", __func__, n); goto cdone; } if (cr.size != 57) goto cdone; while (!lws_cache_results_walk(&cr)) lwsl_notice(" %s (%d)\n", (const char *)cr.tag, (int)cr.payload_len); /* that's ok then */ lwsl_user("%s: done\n", __func__); ret = 0; cdone: lws_cache_destroy(&nsc); lws_cache_destroy(&l1); if (ret) lwsl_warn("%s: fail\n", __func__); return ret; } #endif int main(int argc, const char **argv) { struct lws_context_creation_info info; memset(&info, 0, sizeof info); lws_cmdline_option_handle_builtin(argc, argv, &info); info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; lwsl_user("LWS API selftest: lws_cache\n"); cx = lws_create_context(&info); if (!cx) { lwsl_err("lws init failed\n"); return 1; } if (test_just_l1()) fail++; if (test_just_l1_limits()) fail++; #if defined(LWS_WITH_CACHE_NSCOOKIEJAR) if (test_nsc1()) fail++; #endif lws_context_destroy(cx); if (tests && !fail) lwsl_user("Completed: PASS\n"); else lwsl_err("Completed: FAIL %d / %d\n", fail, tests); return 0; }