From 1f766a332fe6ade0666da5837f0a6fdfcdfe9e93 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 7 Aug 2021 07:05:19 -0700 Subject: [PATCH 1/7] Add function for creating hex string literals --- libc/stdio/appendd.c | 12 +++--- libc/stdio/appendf.c | 1 + libc/stdio/appendr.c | 41 ++++++++++--------- libc/stdio/appends.c | 1 + libc/stdio/appendw.c | 42 ++++++++++++++++--- libc/stdio/appendz.c | 7 ++++ libc/stdio/dumphexc.c | 52 ++++++++++++++++++++++++ libc/stdio/hex.internal.h | 11 +++++ libc/stdio/stdio.mk | 2 +- libc/stdio/vappendf.c | 6 ++- libc/testlib/bench.h | 29 +++++++------- test/libc/stdio/dumphexc_test.c | 34 ++++++++++++++++ test/libc/stdio/vappendf_test.c | 53 ++++++++++++++++++++---- tool/viz/dumphexc.c | 71 ++++++--------------------------- 14 files changed, 250 insertions(+), 112 deletions(-) create mode 100644 libc/stdio/dumphexc.c create mode 100644 libc/stdio/hex.internal.h create mode 100644 test/libc/stdio/dumphexc_test.c diff --git a/libc/stdio/appendd.c b/libc/stdio/appendd.c index ffffbcd9b..4a6c63775 100644 --- a/libc/stdio/appendd.c +++ b/libc/stdio/appendd.c @@ -39,15 +39,17 @@ * @param l is byte length of `s` * @return bytes appended (always `l`) or -1 if `ENOMEM` * @see appendz(b).i to get buffer length + * @note 20% faster than appends() */ ssize_t appendd(char **b, const void *s, size_t l) { char *p; + size_t n; struct appendz z; - assert(b); z = appendz((p = *b)); - if (ROUNDUP(z.i + l + 1, 8) + W > z.n) { + n = ROUNDUP(z.i + l + 1, 8) + W; + if (n > z.n) { if (!z.n) z.n = W * 2; - while (ROUNDUP(z.i + l + 1, 8) + W > z.n) z.n += z.n >> 1; + while (n > z.n) z.n += z.n >> 1; z.n = ROUNDUP(z.n, W); if ((p = realloc(p, z.n))) { z.n = malloc_usable_size(p); @@ -59,9 +61,7 @@ ssize_t appendd(char **b, const void *s, size_t l) { } *(char *)mempcpy(p + z.i, s, l) = 0; z.i += l; - if (!IsTiny() && W == 8) { - z.i |= (size_t)APPEND_COOKIE << 48; - } + if (!IsTiny() && W == 8) z.i |= (size_t)APPEND_COOKIE << 48; *(size_t *)(p + z.n - W) = z.i; return l; } diff --git a/libc/stdio/appendf.c b/libc/stdio/appendf.c index 3991d4a9a..aa1487313 100644 --- a/libc/stdio/appendf.c +++ b/libc/stdio/appendf.c @@ -27,6 +27,7 @@ * * @return bytes appended or -1 if `ENOMEM` * @see appendz(b).i to get buffer length + * @note O(1) amortized buffer growth */ ssize_t(appendf)(char **b, const char *fmt, ...) { int n; diff --git a/libc/stdio/appendr.c b/libc/stdio/appendr.c index c49b216a2..94af5e41d 100644 --- a/libc/stdio/appendr.c +++ b/libc/stdio/appendr.c @@ -20,6 +20,7 @@ #include "libc/dce.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" +#include "libc/nexgen32e/bsr.h" #include "libc/stdio/append.internal.h" #include "libc/str/str.h" @@ -37,35 +38,39 @@ * free(b); * * If `i` is greater than the current length then the extra bytes are - * filled with NUL characters. + * filled with NUL characters. If `i` is less than the current length + * then memory is released to the system. * * The resulting buffer is guarranteed to be NUL-terminated, i.e. - * `!b[appendz(b).i]` will be the case. + * `!b[appendz(b).i]` will be the case even if both params are 0. * * @return `i` or -1 if `ENOMEM` * @see appendz(b).i to get buffer length */ ssize_t appendr(char **b, size_t i) { char *p; + size_t n; struct appendz z; assert(b); z = appendz((p = *b)); - z.n = ROUNDUP(i + 1, 8) + W; - if ((p = realloc(p, z.n))) { - z.n = malloc_usable_size(p); - assert(!(z.n & (W - 1))); - *b = p; - } else { - return -1; + if (i != z.i || !p) { + n = ROUNDUP(i + 1, 8) + W; + if (n > z.n || bsrl(n) < bsrl(z.n)) { + if ((p = realloc(p, n))) { + n = malloc_usable_size(p); + assert(!(n & (W - 1))); + *b = p; + } else { + return -1; + } + } + if (i > z.i) { + memset(p + z.i, 0, i - z.i + 1); + } else { + p[i] = 0; + } + *(size_t *)(p + n - W) = + i | (!IsTiny() && W == 8 ? (size_t)APPEND_COOKIE << 48 : 0); } - if (i > z.i) { - memset(p, z.i, i - z.i); - } - z.i = i; - p[z.i] = 0; - if (!IsTiny() && W == 8) { - z.i |= (size_t)APPEND_COOKIE << 48; - } - *(size_t *)(p + z.n - W) = z.i; return i; } diff --git a/libc/stdio/appends.c b/libc/stdio/appends.c index e2fc22743..39755ffc0 100644 --- a/libc/stdio/appends.c +++ b/libc/stdio/appends.c @@ -31,6 +31,7 @@ * * @return bytes appended (always `strlen(s)`) or -1 if `ENOMEM` * @see appendz(b).i to get buffer length + * @note 2x faster than appendf() */ ssize_t appends(char **b, const char *s) { return appendd(b, s, strlen(s)); diff --git a/libc/stdio/appendw.c b/libc/stdio/appendw.c index 4ea9a1615..c85bf6b2b 100644 --- a/libc/stdio/appendw.c +++ b/libc/stdio/appendw.c @@ -16,18 +16,23 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/bits/bits.h" +#include "libc/macros.internal.h" #include "libc/nexgen32e/bsr.h" #include "libc/stdio/append.internal.h" +#define W sizeof(size_t) + /** * Appends character or word-encoded string to buffer. * * Up to eight characters can be appended. For example: * * appendw(&s, 'h'|'i'<<8); + * appendw(&s, READ64LE("hi\0\0\0\0\0")); * - * Is equivalent to: + * Are equivalent to: * * appends(&s, "hi"); * @@ -37,15 +42,40 @@ * * Will append a single NUL character. * + * This function is slightly faster than appendd() and appends(). Its + * main advantage is it improves code size in functions that call it. + * * The resulting buffer is guaranteed to be NUL-terminated, i.e. * `!b[appendz(b).i]` will be the case. * * @return bytes appended or -1 if `ENOMEM` + * @see appendz(b).i to get buffer length */ ssize_t appendw(char **b, uint64_t w) { - char t[8]; - unsigned n = 1; - WRITE64LE(t, w); - if (w) n += bsrl(w) >> 3; - return appendd(b, t, n); + size_t n; + unsigned l; + char *p, *q; + struct appendz z; + z = appendz((p = *b)); + l = w ? (bsrl(w) >> 3) + 1 : 1; + n = ROUNDUP(z.i + 8 + 1, 8) + W; + if (n > z.n) { + if (!z.n) z.n = W * 2; + while (n > z.n) z.n += z.n >> 1; + z.n = ROUNDUP(z.n, W); + if ((p = realloc(p, z.n))) { + z.n = malloc_usable_size(p); + assert(!(z.n & (W - 1))); + *b = p; + } else { + return -1; + } + } + q = p + z.i; + WRITE64LE(q, w); + q[8] = 0; + z.i += l; + if (!IsTiny() && W == 8) z.i |= (size_t)APPEND_COOKIE << 48; + *(size_t *)(p + z.n - W) = z.i; + return l; } diff --git a/libc/stdio/appendz.c b/libc/stdio/appendz.c index 12133dd68..f56d0da8c 100644 --- a/libc/stdio/appendz.c +++ b/libc/stdio/appendz.c @@ -26,6 +26,7 @@ /** * Returns size of append buffer. * + * @param p must be 0 or have been allocated by an append*() function * @return i is number of bytes stored in buffer * @return n is number of bytes in allocation */ @@ -36,6 +37,12 @@ struct appendz appendz(char *p) { assert(z.n >= W * 2 && !(z.n & (W - 1))); z.i = *(size_t *)(p + z.n - W); if (!IsTiny() && W == 8) { + /* + * This check should fail if an append*() function was passed a + * pointer that was allocated manually by malloc(). Append ptrs + * can be free()'d safely, but they need to be allocated by the + * append library, because we write a special value to the end. + */ assert((z.i >> 48) == APPEND_COOKIE); z.i &= 0x0000ffffffffffff; } diff --git a/libc/stdio/dumphexc.c b/libc/stdio/dumphexc.c new file mode 100644 index 000000000..c359e10ca --- /dev/null +++ b/libc/stdio/dumphexc.c @@ -0,0 +1,52 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/macros.internal.h" +#include "libc/stdio/append.internal.h" +#include "libc/stdio/hex.internal.h" +#include "libc/str/str.h" + +/** + * Turns data into "\x00..." string literal. + */ +char *DumpHexc(const char *p, size_t n, size_t *z) { + long o; + int i, m; + char A[128], *q, *s = 0; + if (n == -1) n = p ? strlen(p) : 0; + appendw(&s, '"' | '\\' << 8 | '\n' << 16); + for (o = 0; (m = MIN(16, n)); p += m, n -= m) { + q = A; + for (i = 0; i < m; ++i) { + *q++ = '\\'; + *q++ = 'x'; + *q++ = "0123456789abcdef"[(p[i] & 0xF0) >> 4]; + *q++ = "0123456789abcdef"[(p[i] & 0x0F) >> 0]; + } + if (o) appendw(&s, '\\' | '\n' << 8); + appendd(&s, A, q - A); + o += m; + } + if (appendw(&s, '"') != -1) { + if (z) *z = appendz(s).i; + return s; + } else { + free(s); + return 0; + } +} diff --git a/libc/stdio/hex.internal.h b/libc/stdio/hex.internal.h new file mode 100644 index 000000000..f4740417d --- /dev/null +++ b/libc/stdio/hex.internal.h @@ -0,0 +1,11 @@ +#ifndef COSMOPOLITAN_LIBC_STDIO_HEX_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_STDIO_HEX_INTERNAL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +char *DumpHex(const char *, size_t, size_t *); +char *DumpHexc(const char *, size_t, size_t *); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_STDIO_HEX_INTERNAL_H_ */ diff --git a/libc/stdio/stdio.mk b/libc/stdio/stdio.mk index ad5ae77ae..9c0b34f6d 100644 --- a/libc/stdio/stdio.mk +++ b/libc/stdio/stdio.mk @@ -57,7 +57,7 @@ o/$(MODE)/libc/stdio/fputc.o: \ o//libc/stdio/appendw.o: \ OVERRIDE_CFLAGS += \ - -O2 + -Os LIBC_STDIO_LIBS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x))) LIBC_STDIO_SRCS = $(foreach x,$(LIBC_STDIO_ARTIFACTS),$($(x)_SRCS)) diff --git a/libc/stdio/vappendf.c b/libc/stdio/vappendf.c index 531f7ca86..139d6ffeb 100644 --- a/libc/stdio/vappendf.c +++ b/libc/stdio/vappendf.c @@ -30,14 +30,16 @@ ssize_t(vappendf)(char **b, const char *f, va_list v) { char *p; int r, s; + size_t n; va_list w; struct appendz z; z = appendz((p = *b)); va_copy(w, v); if ((r = (vsnprintf)(p + z.i, z.n ? z.n - W - z.i : 0, f, v)) >= 0) { - if (ROUNDUP(z.i + r + 1, 8) + W > z.n) { + n = ROUNDUP(z.i + r + 1, 8) + W; + if (n > z.n) { if (!z.n) z.n = W * 2; - while (ROUNDUP(z.i + r + 1, 8) + W > z.n) z.n += z.n >> 1; + while (n > z.n) z.n += z.n >> 1; z.n = ROUNDUP(z.n, W); if ((p = realloc(p, z.n))) { z.n = malloc_usable_size(p); diff --git a/libc/testlib/bench.h b/libc/testlib/bench.h index c64f8c9f8..e0664e195 100644 --- a/libc/testlib/bench.h +++ b/libc/testlib/bench.h @@ -1,27 +1,28 @@ #ifndef COSMOPOLITAN_LIBC_BENCH_H_ #define COSMOPOLITAN_LIBC_BENCH_H_ +#include "libc/bits/safemacros.internal.h" #include "libc/nexgen32e/bench.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ /** - * @fileoverview Microbenchmarking tools. + * @fileoverview Microbenchmarking Toolz. */ #ifndef BENCHLOOP -#define BENCHLOOP(START, STOP, N, INIT, EXPR) \ - ({ \ - unsigned long Iter, Count; \ - double Average, Sample, Time1, Time2; \ - for (Average = 1, Iter = 1, Count = (N); Iter < Count; ++Iter) { \ - INIT; \ - Time1 = START(); \ - EXPR; \ - Time2 = STOP(); \ - Sample = Time2 - Time1; \ - Average += 1. / Iter * (Sample - Average); \ - } \ - Average; \ +#define BENCHLOOP(START, STOP, N, INIT, EXPR) \ + ({ \ + unsigned long Iter, Count; \ + uint64_t Time1, Time2; \ + double Average; \ + for (Average = 1, Iter = 1, Count = (N); Iter < Count; ++Iter) { \ + INIT; \ + Time1 = START(); \ + EXPR; \ + Time2 = STOP(); \ + Average += 1. / Iter * (unsignedsubtract(Time2, Time1) - Average); \ + } \ + Average; \ }) #endif /* BENCHLOOP */ diff --git a/test/libc/stdio/dumphexc_test.c b/test/libc/stdio/dumphexc_test.c new file mode 100644 index 000000000..d7bce6d58 --- /dev/null +++ b/test/libc/stdio/dumphexc_test.c @@ -0,0 +1,34 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/stdio/hex.internal.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" + +TEST(DumpHexc, test) { + EXPECT_STREQ("\"\\\n\ +\\x68\\x65\\x6c\\x6c\\x6f\\xe2\\x86\\x92\\x0a\\x01\\x02\\x74\\x68\\x65\\x65\\x72\\\n\ +\\x68\\x75\\x72\\x63\\x65\\x6f\\x61\\x68\\x72\\x63\\x75\\x6f\\x65\\x61\\x75\\x68\\\n\ +\\x63\\x72\"", + DumpHexc("hello→\n\1\2theerhurceoahrcuoeauhcr", -1, 0)); +} + +BENCH(DumpHexc, bench) { + EZBENCH2("dumphexc", donothing, free(DumpHexc(kHyperion, kHyperionSize, 0))); +} diff --git a/test/libc/stdio/vappendf_test.c b/test/libc/stdio/vappendf_test.c index e6632e62b..063ab43c4 100644 --- a/test/libc/stdio/vappendf_test.c +++ b/test/libc/stdio/vappendf_test.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" #include "libc/stdio/append.internal.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" @@ -87,8 +88,7 @@ TEST(appendw, test) { EXPECT_EQ(0, b[0]); EXPECT_EQ('h', b[1]); EXPECT_EQ(0, b[2]); - ASSERT_NE(-1, appendw(&b, 'e' | 'l' << 8 | 'l' << 16 | 'o' << 24 | - (uint64_t)'!' << 32)); + ASSERT_NE(-1, appendw(&b, READ64LE("ello!\0\0"))); EXPECT_EQ(7, appendz(b).i); EXPECT_EQ(0, b[0]); EXPECT_EQ('h', b[1]); @@ -101,7 +101,7 @@ TEST(appendw, test) { free(b); } -TEST(appendr, test) { +TEST(appendr, testShrink_nulTerminates) { char *b = 0; ASSERT_NE(-1, appends(&b, "hello")); EXPECT_EQ(5, appendz(b).i); @@ -121,15 +121,54 @@ TEST(appendr, test) { free(b); } +TEST(appendr, testExtend_zeroFills) { + char *b = 0; + ASSERT_NE(-1, appends(&b, "hello")); + EXPECT_EQ(5, appendz(b).i); + ASSERT_NE(-1, appendr(&b, 20)); + EXPECT_EQ(20, appendz(b).i); + ASSERT_BINEQ(u"hello                ", b); + ASSERT_NE(-1, appendr(&b, 20)); + EXPECT_EQ(20, appendz(b).i); + ASSERT_BINEQ(u"hello                ", b); + free(b); +} + +TEST(appendr, testAbsent_allocatesNul) { + char *b = 0; + ASSERT_NE(-1, appendr(&b, 0)); + EXPECT_EQ(0, appendz(b).i); + ASSERT_BINEQ(u" ", b); + free(b); +} + +TEST(appendd, testMemFail_doesntInitializePointer) { + char *b = 0; + ASSERT_EQ(-1, appendd(&b, 0, -1ull >> 8)); + EXPECT_EQ(0, b); +} + +TEST(appendd, testMemFail_doesntFreeExistingAllocation) { + char *b = 0; + ASSERT_NE(-1, appends(&b, "hello")); + EXPECT_STREQ("hello", b); + ASSERT_EQ(-1, appendd(&b, 0, -1ull >> 8)); + EXPECT_STREQ("hello", b); + free(b); +} + BENCH(vappendf, bench) { const char t[] = {0}; char *b = 0; - EZBENCH2("appendf", donothing, appendf(&b, "1")); + EZBENCH2("appendf", donothing, appendf(&b, "hello")); free(b), b = 0; - EZBENCH2("appends", donothing, appends(&b, "1")); + EZBENCH2("appends", donothing, appends(&b, "hello")); free(b), b = 0; - EZBENCH2("appendw", donothing, appendw(&b, 'B')); + EZBENCH2("appendw", donothing, + appendw(&b, 'h' | 'e' << 8 | 'l' << 16 | 'l' << 24 | + (uint64_t)'o' << 32)); free(b), b = 0; - EZBENCH2("appendd", donothing, appendd(&b, t, 1)); + EZBENCH2("appendd", donothing, appendd(&b, "hello", 5)); + EZBENCH2("appendr", donothing, appendr(&b, 0)); free(b), b = 0; } diff --git a/tool/viz/dumphexc.c b/tool/viz/dumphexc.c index e2d0ab94f..13d558121 100644 --- a/tool/viz/dumphexc.c +++ b/tool/viz/dumphexc.c @@ -18,75 +18,30 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/fmt.h" #include "libc/macros.internal.h" +#include "libc/stdio/append.internal.h" +#include "libc/stdio/hex.internal.h" #include "libc/stdio/stdio.h" -struct Append { - size_t i, n; - char *p; -}; - -int AppendFmt(struct Append *b, const char *fmt, ...) { - int n; - char *p; - va_list va, vb; - va_start(va, fmt); - va_copy(vb, va); - n = vsnprintf(b->p + b->i, b->n - b->i, fmt, va); - if (n >= b->n - b->i) { - do { - if (b->n) { - b->n += b->n >> 1; /* this is the important line */ - } else { - b->n = 16; - } - } while (b->i + n + 1 > b->n); - b->p = realloc(b->p, b->n); - vsnprintf(b->p + b->i, b->n - b->i, fmt, vb); - } - va_end(vb); - va_end(va); - b->i += n; - return n; -} - -char *DumpHexc(const char *data, size_t size, size_t *z) { - long o; - int b, i, n; - char A[128], *p; - struct Append buf; - if (size == -1) size = data ? strlen(data) : 0; - buf.i = 0; - buf.n = 256; - buf.p = calloc(1, 256); - AppendFmt(&buf, "\"\\\n"); - for (b = o = 0; (n = MIN(16, size)); data += n, size -= n) { - p = A; - for (i = 0; i < n; ++i) { - *p++ = '\\'; - *p++ = 'x'; - *p++ = "0123456789abcdef"[(data[i] & 0xF0) >> 4]; - *p++ = "0123456789abcdef"[(data[i] & 0x0F) >> 0]; - } - if (o) AppendFmt(&buf, "\\\n"); - AppendFmt(&buf, "%.*s", p - A, A); - o += n; - } - AppendFmt(&buf, "\""); - if (z) *z = buf.i; - return buf.p; -} +/** + * @fileoverview Hex String Literal Converter, e.g. + * + * $ echo hello | o/tool/viz/dumphexc.com + * "\ + * \x68\x65\x6c\x6c\x6f\x0a" + */ int main(int argc, char *argv[]) { char *p; size_t n, g; + char *b = 0; char buf[512]; - struct Append b = {0}; while ((g = fread(buf, 1, sizeof(buf), stdin))) { - AppendFmt(&b, "%.*s", g, buf); + appendd(&b, buf, g); } if (!ferror(stdin)) { - p = DumpHexc(b.p, b.i, &n); + p = DumpHexc(b, appendz(b).i, &n); fwrite(p, 1, n, stdout); + free(p); } printf("\n"); return ferror(stdin) || ferror(stdout) ? 1 : 0; From 0cdba6878bfecfd196df22672f601b1ec55b83f8 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 7 Aug 2021 13:22:35 -0700 Subject: [PATCH 2/7] Secure the testing infrastructure --- build/bootstrap/compile.com | Bin 77824 -> 110592 bytes third_party/mbedtls/config.h | 4 +- third_party/mbedtls/dhm.c | 3 +- third_party/mbedtls/ssl.h | 2 +- third_party/mbedtls/ssl_ciphersuites.c | 12 +- third_party/mbedtls/ssl_tls.c | 4 +- tool/build/build.mk | 1 + tool/build/compile.c | 3 + tool/build/lib/buildlib.mk | 1 + tool/build/lib/eztls.c | 185 +++++++++++++++++++++++++ tool/build/lib/eztls.h | 25 ++++ tool/build/lib/psk.c | 61 ++++++++ tool/build/lib/psk.h | 10 ++ tool/build/runit.c | 71 +++++----- tool/build/runitd.c | 43 +++--- 15 files changed, 354 insertions(+), 71 deletions(-) create mode 100644 tool/build/lib/eztls.c create mode 100644 tool/build/lib/eztls.h create mode 100644 tool/build/lib/psk.c create mode 100644 tool/build/lib/psk.h diff --git a/build/bootstrap/compile.com b/build/bootstrap/compile.com index c460b3c6001959d875565e2ea0600edfbe28186c..179038020255017be6976bfe10358d3db52e55a3 100755 GIT binary patch literal 110592 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%0AxhTAxo`D#fDI7%I>>h@5mrUNFPLC_7Uh+1=@< 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 diff --git a/third_party/mbedtls/config.h b/third_party/mbedtls/config.h index f7242ab26..a6aa5cb66 100644 --- a/third_party/mbedtls/config.h +++ b/third_party/mbedtls/config.h @@ -64,6 +64,7 @@ /* key exchange */ #define MBEDTLS_RSA_C #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED #ifndef TINY #define MBEDTLS_ECP_C #define MBEDTLS_ECDH_C @@ -72,13 +73,12 @@ #define MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED /*#define MBEDTLS_DHM_C*/ /*#define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED*/ /*#define MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED*/ /*#define MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED*/ -/*#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED*/ /*#define MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED*/ -/*#define MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED*/ /*#define MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED*/ #endif diff --git a/third_party/mbedtls/dhm.c b/third_party/mbedtls/dhm.c index 6574cfcf4..d91d9e01b 100644 --- a/third_party/mbedtls/dhm.c +++ b/third_party/mbedtls/dhm.c @@ -17,6 +17,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "third_party/mbedtls/asn1.h" +#include "third_party/mbedtls/bignum.h" #include "third_party/mbedtls/common.h" #include "third_party/mbedtls/dhm.h" #include "third_party/mbedtls/error.h" @@ -322,7 +323,7 @@ static int dhm_random_below( mbedtls_mpi *R, const mbedtls_mpi *M, MBEDTLS_MPI_CHK( mbedtls_mpi_fill_random( R, mbedtls_mpi_size( M ), f_rng, p_rng ) ); while( mbedtls_mpi_cmp_mpi( R, M ) >= 0 ) - mbedtls_mpi_shift_r( &R, 1 ); + mbedtls_mpi_shift_r( R, 1 ); if( count++ > 10 ) return( MBEDTLS_ERR_MPI_NOT_ACCEPTABLE ); diff --git a/third_party/mbedtls/ssl.h b/third_party/mbedtls/ssl.h index 6d9ae60a3..1c0ac6e34 100644 --- a/third_party/mbedtls/ssl.h +++ b/third_party/mbedtls/ssl.h @@ -1419,7 +1419,7 @@ int mbedtls_ssl_conf_dh_param_ctx( mbedtls_ssl_config *, mbedtls_dhm_context * ) int mbedtls_ssl_conf_dtls_srtp_protection_profiles( mbedtls_ssl_config *, const mbedtls_ssl_srtp_profile * ); int mbedtls_ssl_conf_max_frag_len( mbedtls_ssl_config *, unsigned char ); int mbedtls_ssl_conf_own_cert( mbedtls_ssl_config *, mbedtls_x509_crt *, mbedtls_pk_context * ); -int mbedtls_ssl_conf_psk( mbedtls_ssl_config *, const unsigned char *, size_t, const unsigned char *, size_t ); +int mbedtls_ssl_conf_psk( mbedtls_ssl_config *, const void *, size_t, const void *, size_t ); int mbedtls_ssl_context_load( mbedtls_ssl_context *, const unsigned char *, size_t ); int mbedtls_ssl_context_save( mbedtls_ssl_context *, unsigned char *, size_t, size_t * ); int mbedtls_ssl_get_ciphersuite_id( const char * ); diff --git a/third_party/mbedtls/ssl_ciphersuites.c b/third_party/mbedtls/ssl_ciphersuites.c index 3f3ecbe5d..fed76e55f 100644 --- a/third_party/mbedtls/ssl_ciphersuites.c +++ b/third_party/mbedtls/ssl_ciphersuites.c @@ -72,16 +72,20 @@ static const uint16_t ciphersuite_preference[] = #endif #ifdef MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED - MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, - MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, + /* TODO(jart): RFC8442 */ + /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, */ + /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, */ + /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, */ MBEDTLS_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256, MBEDTLS_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256, - MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, + MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384, + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256, MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, - MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, MBEDTLS_TLS_DHE_PSK_WITH_AES_256_CBC_SHA, MBEDTLS_TLS_DHE_PSK_WITH_AES_128_CBC_SHA, #endif diff --git a/third_party/mbedtls/ssl_tls.c b/third_party/mbedtls/ssl_tls.c index 2cf965c9f..3208b9e8a 100644 --- a/third_party/mbedtls/ssl_tls.c +++ b/third_party/mbedtls/ssl_tls.c @@ -4415,8 +4415,8 @@ static int ssl_conf_set_psk_identity( mbedtls_ssl_config *conf, * \return An \c MBEDTLS_ERR_SSL_XXX error code on failure. */ int mbedtls_ssl_conf_psk( mbedtls_ssl_config *conf, - const unsigned char *psk, size_t psk_len, - const unsigned char *psk_identity, + const void *psk, size_t psk_len, + const void *psk_identity, size_t psk_identity_len ) { int ret = MBEDTLS_ERR_THIS_CORRUPTION; diff --git a/tool/build/build.mk b/tool/build/build.mk index f9448be1b..d5820a54a 100644 --- a/tool/build/build.mk +++ b/tool/build/build.mk @@ -55,6 +55,7 @@ TOOL_BUILD_DIRECTDEPS = \ THIRD_PARTY_GETOPT \ THIRD_PARTY_STB \ THIRD_PARTY_XED \ + THIRD_PARTY_MBEDTLS \ THIRD_PARTY_ZLIB \ TOOL_BUILD_LIB diff --git a/tool/build/compile.c b/tool/build/compile.c index 43709fbe2..2c1cae0d7 100644 --- a/tool/build/compile.c +++ b/tool/build/compile.c @@ -117,6 +117,9 @@ struct Command command; const char *const kSafeEnv[] = { "ADDR2LINE", // needed by GetAddr2linePath + "HOME", // needed by ~/.runit.psk + "HOMEDRIVE", // needed by ~/.runit.psk + "HOMEPATH", // needed by ~/.runit.psk "MAKEFLAGS", // needed by IsRunningUnderMake "MODE", // needed by test scripts "PATH", // needed by clang diff --git a/tool/build/lib/buildlib.mk b/tool/build/lib/buildlib.mk index 6bb6634e3..a3133eb9e 100644 --- a/tool/build/lib/buildlib.mk +++ b/tool/build/lib/buildlib.mk @@ -46,6 +46,7 @@ TOOL_BUILD_LIB_A_DIRECTDEPS = \ LIBC_UNICODE \ LIBC_X \ THIRD_PARTY_COMPILER_RT \ + THIRD_PARTY_MBEDTLS \ THIRD_PARTY_XED TOOL_BUILD_LIB_A_DEPS := \ diff --git a/tool/build/lib/eztls.c b/tool/build/lib/eztls.c new file mode 100644 index 000000000..a1168fe8a --- /dev/null +++ b/tool/build/lib/eztls.c @@ -0,0 +1,185 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/iovec.h" +#include "libc/errno.h" +#include "libc/log/check.h" +#include "libc/log/log.h" +#include "libc/rand/rand.h" +#include "libc/sock/sock.h" +#include "libc/sysv/consts/sig.h" +#include "libc/x/x.h" +#include "third_party/mbedtls/ctr_drbg.h" +#include "third_party/mbedtls/ecp.h" +#include "third_party/mbedtls/error.h" +#include "third_party/mbedtls/ssl.h" +#include "tool/build/lib/eztls.h" +#include "tool/build/lib/psk.h" + +struct EzTlsBio ezbio; +mbedtls_ssl_config ezconf; +mbedtls_ssl_context ezssl; +mbedtls_ctr_drbg_context ezrng; + +static char *EzTlsError(int r) { + static char b[128]; + mbedtls_strerror(r, b, sizeof(b)); + return b; +} + +static wontreturn void EzTlsDie(const char *s, int r) { + if (IsTiny()) { + fprintf(stderr, "error: %s (-0x%04x %s)\n", s, -r, EzTlsError(r)); + } else { + fprintf(stderr, "error: %s (grep -0x%04x)\n", s, -r); + } + exit(1); +} + +static int EzGetEntropy(void *c, unsigned char *p, size_t n) { + CHECK_EQ(n, getrandom(p, n, 0)); + return 0; +} + +static void EzInitializeRng(mbedtls_ctr_drbg_context *r) { + volatile unsigned char b[64]; + mbedtls_ctr_drbg_init(r); + CHECK(getrandom(b, 64, 0) == 64); + CHECK(!mbedtls_ctr_drbg_seed(r, EzGetEntropy, 0, b, 64)); + mbedtls_platform_zeroize(b, 64); +} + +static ssize_t EzWritevAll(int fd, struct iovec *iov, int iovlen) { + int i; + ssize_t rc; + size_t wrote, total; + i = 0; + total = 0; + do { + if (i) { + while (i < iovlen && !iov[i].iov_len) ++i; + if (i == iovlen) break; + } + if ((rc = writev(fd, iov + i, iovlen - i)) != -1) { + wrote = rc; + total += wrote; + do { + if (wrote >= iov[i].iov_len) { + wrote -= iov[i++].iov_len; + } else { + iov[i].iov_base = (char *)iov[i].iov_base + wrote; + iov[i].iov_len -= wrote; + wrote = 0; + } + } while (wrote); + } else if (errno != EINTR) { + return total ? total : -1; + } + } while (i < iovlen); + return total; +} + +int EzTlsFlush(struct EzTlsBio *bio, const unsigned char *buf, size_t len) { + struct iovec v[2]; + if (len || bio->c > 0) { + v[0].iov_base = bio->u; + v[0].iov_len = MAX(0, bio->c); + v[1].iov_base = buf; + v[1].iov_len = len; + if (EzWritevAll(bio->fd, v, 2) != -1) { + if (bio->c > 0) bio->c = 0; + } else if (errno == EINTR) { + return MBEDTLS_ERR_NET_CONN_RESET; + } else if (errno == EAGAIN) { + return MBEDTLS_ERR_SSL_TIMEOUT; + } else if (errno == EPIPE || errno == ECONNRESET || errno == ENETRESET) { + return MBEDTLS_ERR_NET_CONN_RESET; + } else { + WARNF("EzTlsSend error %s", strerror(errno)); + return MBEDTLS_ERR_NET_SEND_FAILED; + } + } + return 0; +} + +static int EzTlsSend(void *ctx, const unsigned char *buf, size_t len) { + int rc; + struct iovec v[2]; + struct EzTlsBio *bio = ctx; + if (bio->c >= 0 && bio->c + len <= sizeof(bio->u)) { + memcpy(bio->u + bio->c, buf, len); + bio->c += len; + return len; + } + if ((rc = EzTlsFlush(bio, buf, len)) < 0) return rc; + return len; +} + +static int EzTlsRecvImpl(void *ctx, unsigned char *p, size_t n, uint32_t o) { + int r; + ssize_t s; + struct iovec v[2]; + struct EzTlsBio *bio = ctx; + if ((r = EzTlsFlush(bio, 0, 0)) < 0) return r; + if (bio->a < bio->b) { + r = MIN(n, bio->b - bio->a); + memcpy(p, bio->t + bio->a, r); + if ((bio->a += r) == bio->b) bio->a = bio->b = 0; + return r; + } + v[0].iov_base = p; + v[0].iov_len = n; + v[1].iov_base = bio->t; + v[1].iov_len = sizeof(bio->t); + while ((r = readv(bio->fd, v, 2)) == -1) { + if (errno == EINTR) { + return MBEDTLS_ERR_SSL_WANT_READ; + } else if (errno == EAGAIN) { + return MBEDTLS_ERR_SSL_TIMEOUT; + } else if (errno == EPIPE || errno == ECONNRESET || errno == ENETRESET) { + return MBEDTLS_ERR_NET_CONN_RESET; + } else { + WARNF("tls read() error %s", strerror(errno)); + return MBEDTLS_ERR_NET_RECV_FAILED; + } + } + if (r > n) bio->b = r - n; + return MIN(n, r); +} + +static int EzTlsRecv(void *ctx, unsigned char *buf, size_t len, uint32_t tmo) { + return EzTlsRecvImpl(ctx, buf, len, tmo); +} + +/* + * openssl s_client -connect 127.0.0.1:31337 \ + * -psk $(hex <~/.runit.psk) \ + * -psk_identity runit + */ + +void SetupPresharedKeySsl(int endpoint) { + xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0); + EzInitializeRng(&ezrng); + mbedtls_ssl_config_defaults(&ezconf, endpoint, MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_SUITEC); + mbedtls_ssl_conf_rng(&ezconf, mbedtls_ctr_drbg_random, &ezrng); + DCHECK_EQ(0, mbedtls_ssl_conf_psk(&ezconf, GetRunitPsk(), 32, "runit", 5)); + DCHECK_EQ(0, mbedtls_ssl_setup(&ezssl, &ezconf)); + mbedtls_ssl_set_bio(&ezssl, &ezbio, EzTlsSend, 0, EzTlsRecv); +} diff --git a/tool/build/lib/eztls.h b/tool/build/lib/eztls.h new file mode 100644 index 000000000..36a6b7d28 --- /dev/null +++ b/tool/build/lib/eztls.h @@ -0,0 +1,25 @@ +#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_EZTLS_H_ +#define COSMOPOLITAN_TOOL_BUILD_LIB_EZTLS_H_ +#include "third_party/mbedtls/ctr_drbg.h" +#include "third_party/mbedtls/ssl.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +struct EzTlsBio { + int fd, c; + unsigned a, b; + unsigned char t[4000]; + unsigned char u[1430]; +}; + +extern struct EzTlsBio ezbio; +extern mbedtls_ssl_config ezconf; +extern mbedtls_ssl_context ezssl; +extern mbedtls_ctr_drbg_context ezrng; + +void SetupPresharedKeySsl(int); +int EzTlsFlush(struct EzTlsBio *, const unsigned char *, size_t); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_EZTLS_H_ */ diff --git a/tool/build/lib/psk.c b/tool/build/lib/psk.c new file mode 100644 index 000000000..5c2d84186 --- /dev/null +++ b/tool/build/lib/psk.c @@ -0,0 +1,61 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" +#include "libc/dce.h" +#include "libc/fmt/fmt.h" +#include "libc/log/check.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/sysv/consts/o.h" +#include "tool/build/lib/psk.h" + +/** + * Returns preshared key for runit testing infrastructure. + */ +void *GetRunitPsk(void) { + int fd; + struct stat st; + const char *a, *b; + char *r, p[PATH_MAX + 1]; + if ((a = getenv("HOME"))) { + b = ""; + } else if (IsWindows()) { + a = getenv("HOMEDRIVE"); + b = getenv("HOMEPATH"); + if (!a || !b) { + a = "C:"; + b = ""; + } + } else { + fprintf(stderr, "need $HOME\n"); + exit(1); + } + snprintf(p, sizeof(p), "%s%s/.runit.psk", a, b); + if (stat(p, &st) == -1 || st.st_size != 32) { + fprintf(stderr, "need o//examples/getrandom.com -bn32 >~/.runit.psk\n"); + exit(1); + } + CHECK_NOTNULL((r = malloc(32))); + CHECK_NE(-1, (fd = open(p, O_RDONLY))); + CHECK_EQ(32, read(fd, r, 32)); + CHECK_NE(-1, close(fd)); + return r; +} diff --git a/tool/build/lib/psk.h b/tool/build/lib/psk.h new file mode 100644 index 000000000..1f06ecade --- /dev/null +++ b/tool/build/lib/psk.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_TOOL_BUILD_LIB_PSK_H_ +#define COSMOPOLITAN_TOOL_BUILD_LIB_PSK_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +void *GetRunitPsk(void); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_TOOL_BUILD_LIB_PSK_H_ */ diff --git a/tool/build/runit.c b/tool/build/runit.c index cdf58e524..ee41c8af7 100644 --- a/tool/build/runit.c +++ b/tool/build/runit.c @@ -16,63 +16,55 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/alg/alg.h" #include "libc/bits/bits.h" #include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" #include "libc/calls/sigbits.h" #include "libc/calls/struct/flock.h" -#include "libc/calls/struct/itimerval.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/stat.h" -#include "libc/calls/struct/timeval.h" -#include "libc/dce.h" #include "libc/dns/dns.h" -#include "libc/errno.h" #include "libc/fmt/conv.h" -#include "libc/fmt/fmt.h" #include "libc/limits.h" #include "libc/log/check.h" #include "libc/log/log.h" -#include "libc/mem/mem.h" +#include "libc/macros.internal.h" #include "libc/runtime/gc.internal.h" -#include "libc/runtime/runtime.h" #include "libc/sock/ipclassify.internal.h" -#include "libc/sock/sock.h" #include "libc/stdio/stdio.h" -#include "libc/str/str.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/ex.h" -#include "libc/sysv/consts/exit.h" -#include "libc/sysv/consts/f.h" -#include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/ipproto.h" #include "libc/sysv/consts/itimer.h" #include "libc/sysv/consts/lock.h" +#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" -#include "libc/sysv/consts/pr.h" -#include "libc/sysv/consts/shut.h" -#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sock.h" #include "libc/time/time.h" #include "libc/x/x.h" +#include "third_party/mbedtls/ssl.h" +#include "tool/build/lib/eztls.h" #include "tool/build/runit.h" /** * @fileoverview Remote test runner. * - * This is able to upload and run test binaries on remote operating - * systems with about 30 milliseconds of latency. It requires zero ops - * work too, since it deploys the ephemeral runit daemon via SSH upon - * ECONNREFUSED. That takes 10x longer (300 milliseconds). Further note - * there's no make -j race conditions here, thanks to SO_REUSEPORT. + * We want to scp .com binaries to remote machines and run them. The + * problem is that SSH is the slowest thing imaginable, taking about + * 300ms to connect to a host that's merely half a millisecond away. + * + * This program takes 17ms using elliptic curve diffie hellman exchange + * where we favor a 32-byte binary preshared key (~/.runit.psk) instead + * of certificates. It's how long it takes to connect, copy the binary, + * and run it. The remote daemon is deployed via SSH if it's not there. * * o/default/tool/build/runit.com \ * o/default/tool/build/runitd.com \ * o/default/test/libc/alg/qsort_test.com \ * freebsd.test.:31337:22 * + * APE binaries are hermetic and embed dependent files within their zip + * structure, which is why all we need is this simple test runner tool. * The only thing that needs to be configured is /etc/hosts or Bind, to * assign numbers to the officially reserved canned names. For example: * @@ -97,12 +89,7 @@ * iptables -I INPUT 1 -s 10.0.0.0/8 -p tcp --dport 31337 -j ACCEPT * iptables -I INPUT 1 -s 192.168.0.0/16 -p tcp --dport 31337 -j ACCEPT * - * If your system administrator blocks all ICMP, you'll likely encounter - * difficulties. Consider offering feedback to his/her manager and grand - * manager. - * - * Finally note this tool isn't designed for untrustworthy environments. - * It also isn't designed to process untrustworthy inputs. + * This tool may be used in zero trust environments. */ static const struct addrinfo kResolvHints = {.ai_family = AF_INET, @@ -301,7 +288,9 @@ TryAgain: void SendRequest(void) { int fd; - int64_t off; + char *p; + size_t i; + ssize_t rc; struct stat st; const char *name; unsigned char *hdr; @@ -309,6 +298,7 @@ void SendRequest(void) { DEBUGF("running %s on %s", g_prog, g_hostname); CHECK_NE(-1, (fd = open(g_prog, O_RDONLY))); CHECK_NE(-1, fstat(fd, &st)); + CHECK_NE(MAP_FAILED, (p = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0))); CHECK_LE((namesize = strlen((name = basename(g_prog)))), PATH_MAX); CHECK_LE((progsize = st.st_size), INT_MAX); CHECK_NOTNULL((hdr = gc(calloc(1, (hdrsize = 4 + 1 + 4 + 4 + namesize))))); @@ -326,25 +316,27 @@ void SendRequest(void) { hdr[9 + 2] = (unsigned char)((unsigned)progsize >> 010); hdr[9 + 3] = (unsigned char)((unsigned)progsize >> 000); memcpy(&hdr[4 + 1 + 4 + 4], name, namesize); - CHECK_EQ(hdrsize, write(g_sock, hdr, hdrsize)); - for (off = 0; off < progsize;) { - CHECK_GT(sendfile(g_sock, fd, &off, progsize - off), 0); + CHECK_EQ(hdrsize, mbedtls_ssl_write(&ezssl, hdr, hdrsize)); + for (i = 0; i < progsize; i += rc) { + CHECK_GT((rc = mbedtls_ssl_write(&ezssl, p + i, progsize - i)), 0); } - CHECK_NE(-1, shutdown(g_sock, SHUT_WR)); + CHECK_NE(-1, EzTlsFlush(&ezbio, 0, 0)); + CHECK_NE(-1, munmap(p, st.st_size)); + CHECK_NE(-1, close(fd)); } int ReadResponse(void) { int res; - uint32_t size; ssize_t rc; size_t n, m; + uint32_t size; unsigned char *p; enum RunitCommand cmd; static long backoff; static unsigned char msg[512]; res = -1; for (;;) { - if ((rc = recv(g_sock, msg, sizeof(msg), 0)) == -1) { + if ((rc = mbedtls_ssl_read(&ezssl, msg, sizeof(msg))) == -1) { CHECK_EQ(ECONNRESET, errno); usleep((backoff = (backoff + 1000) * 2)); break; @@ -369,7 +361,7 @@ int ReadResponse(void) { size = READ32BE(p), p += 4, n -= 4; while (size) { if (n) { - CHECK_NE(-1, (rc = write(STDERR_FILENO, p, min(n, size)))); + CHECK_NE(-1, (rc = write(STDERR_FILENO, p, MIN(n, size)))); CHECK_NE(0, (m = (size_t)rc)); p += m, n -= m, size -= m; } else { @@ -400,7 +392,11 @@ int RunOnHost(char *spec) { 1); if (!strchr(g_hostname, '.')) strcat(g_hostname, ".test."); do { + mbedtls_ssl_session_reset(&ezssl); Connect(); + ezbio.fd = g_sock; + CHECK_EQ(0, mbedtls_ssl_handshake(&ezssl)); + CHECK_NE(-1, EzTlsFlush(&ezbio, 0, 0)); SendRequest(); } while ((rc = ReadResponse()) == -1); return rc; @@ -464,6 +460,7 @@ int RunRemoteTestsInParallel(char *hosts[], int count) { int main(int argc, char *argv[]) { showcrashreports(); + SetupPresharedKeySsl(MBEDTLS_SSL_IS_CLIENT); /* __log_level = kLogDebug; */ if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { diff --git a/tool/build/runitd.c b/tool/build/runitd.c index 3094a0b00..63739e22c 100644 --- a/tool/build/runitd.c +++ b/tool/build/runitd.c @@ -17,49 +17,37 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/bits/bits.h" -#include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" #include "libc/calls/sigbits.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/stat.h" -#include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/conv.h" -#include "libc/fmt/fmt.h" #include "libc/log/check.h" #include "libc/log/log.h" -#include "libc/macros.internal.h" -#include "libc/nt/runtime.h" -#include "libc/paths.h" #include "libc/runtime/gc.internal.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" -#include "libc/stdio/temp.h" #include "libc/str/str.h" #include "libc/sysv/consts/af.h" -#include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" -#include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/inaddr.h" #include "libc/sysv/consts/ipproto.h" #include "libc/sysv/consts/itimer.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/shut.h" -#include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/so.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/w.h" -#include "libc/testlib/testlib.h" #include "libc/time/time.h" #include "libc/x/x.h" #include "third_party/getopt/getopt.h" +#include "third_party/mbedtls/ssl.h" +#include "tool/build/lib/eztls.h" #include "tool/build/runit.h" /** @@ -208,7 +196,7 @@ void StartTcpServer(void) { } } -void SendExitMessage(int sock, int rc) { +void SendExitMessage(int rc) { unsigned char msg[4 + 1 + 1]; msg[0 + 0] = (unsigned char)((unsigned)RUNITD_MAGIC >> 030); msg[0 + 1] = (unsigned char)((unsigned)RUNITD_MAGIC >> 020); @@ -216,11 +204,12 @@ void SendExitMessage(int sock, int rc) { msg[0 + 3] = (unsigned char)((unsigned)RUNITD_MAGIC >> 000); msg[4] = kRunitExit; msg[5] = (unsigned char)rc; - CHECK_EQ(sizeof(msg), send(sock, msg, sizeof(msg), 0)); + CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg))); + CHECK_NE(-1, EzTlsFlush(&ezbio, 0, 0)); } -void SendOutputFragmentMessage(int sock, enum RunitCommand kind, - unsigned char *buf, size_t size) { +void SendOutputFragmentMessage(enum RunitCommand kind, unsigned char *buf, + size_t size) { ssize_t rc; size_t sent; unsigned char msg[4 + 1 + 4]; @@ -233,13 +222,14 @@ void SendOutputFragmentMessage(int sock, enum RunitCommand kind, msg[5 + 1] = (unsigned char)((unsigned)size >> 020); msg[5 + 2] = (unsigned char)((unsigned)size >> 010); msg[5 + 3] = (unsigned char)((unsigned)size >> 000); - CHECK_EQ(sizeof(msg), send(sock, msg, sizeof(msg), 0)); + CHECK_EQ(sizeof(msg), mbedtls_ssl_write(&ezssl, msg, sizeof(msg))); while (size) { - CHECK_NE(-1, (rc = send(sock, buf, size, 0))); + CHECK_NE(-1, (rc = mbedtls_ssl_write(&ezssl, buf, size))); CHECK_LE((sent = (size_t)rc), size); size -= sent; buf += sent; } + CHECK_NE(-1, EzTlsFlush(&ezbio, 0, 0)); } void OnAlarm(int sig) { @@ -274,9 +264,12 @@ void HandleClient(void) { close(g_clifd); return; } + ezbio.fd = g_clifd; + CHECK_EQ(0, mbedtls_ssl_handshake(&ezssl)); + CHECK_NE(-1, EzTlsFlush(&ezbio, 0, 0)); addrstr = gc(DescribeAddress(&addr)); DEBUGF("%s %s %s", gc(DescribeAddress(&g_servaddr)), "accepted", addrstr); - got = recv(g_clifd, (p = &g_buf[0]), sizeof(g_buf), 0); + got = mbedtls_ssl_read(&ezssl, (p = &g_buf[0]), sizeof(g_buf)); CHECK_GE(got, kMinMsgSize); CHECK_LE(got, sizeof(g_buf)); CHECK_EQ(RUNITD_MAGIC, READ32BE(p)); @@ -304,7 +297,7 @@ void HandleClient(void) { remaining -= got; } while (remaining) { - CHECK_NE(-1, (got = recv(g_clifd, g_buf, sizeof(g_buf), 0))); + CHECK_NE(-1, (got = mbedtls_ssl_read(&ezssl, g_buf, sizeof(g_buf)))); CHECK_LE(got, remaining); if (!got) { LOGF("%s %s %,u/%,u %s", addrstr, "sent", remaining, filesize, @@ -351,7 +344,7 @@ void HandleClient(void) { break; } fwrite(g_buf, got, 1, stderr); - SendOutputFragmentMessage(g_clifd, kRunitStderr, g_buf, got); + SendOutputFragmentMessage(kRunitStderr, g_buf, got); } else { CHECK_EQ(EINTR, errno); } @@ -381,7 +374,8 @@ void HandleClient(void) { /* let client know how it went */ LOGIFNEG1(unlink(g_exepath)); - SendExitMessage(g_clifd, exitcode); + SendExitMessage(exitcode); + mbedtls_ssl_close_notify(&ezssl); LOGIFNEG1(close(g_clifd)); _exit(0); } @@ -442,6 +436,7 @@ void Daemonize(void) { int main(int argc, char *argv[]) { showcrashreports(); + SetupPresharedKeySsl(MBEDTLS_SSL_IS_SERVER); /* __log_level = kLogDebug; */ GetOpts(argc, argv); CHECK_NE(-1, (g_devnullfd = open("/dev/null", O_RDWR))); From 6bbb44c165af24644e677caa80107b41a2b77926 Mon Sep 17 00:00:00 2001 From: Paul Kulchenko Date: Sat, 7 Aug 2021 14:25:55 -0700 Subject: [PATCH 3/7] Add GetHostOs to redbean (#228) --- tool/net/help.txt | 3 +++ tool/net/redbean.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tool/net/help.txt b/tool/net/help.txt index c51893cc1..bc0516282 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -639,6 +639,9 @@ FUNCTIONS Returns host associated with request. This will be the Host header, if it's supplied. Otherwise it's the bind address. + GetHostOs() → str + Returns string that describes the host OS. + GetMonospaceWidth(str|char) → int Returns monospace display width of string. This is useful for fixed-width formatting. For example, CJK characters typically take diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 62a5ef794..44b8f59da 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -5035,6 +5035,32 @@ static int LuaGetComment(lua_State *L) { return 1; } + +static int LuaGetHostOs(lua_State *L) { + const char *s = NULL; + if (IsLinux()) { + s = "LINUX"; + } else if (IsMetal()) { + s = "METAL"; + } else if (IsWindows()) { + s = "WINDOWS"; + } else if (IsXnu()) { + s = "XNU"; + } else if (IsOpenbsd()) { + s = "OPENBSD"; + } else if (IsFreebsd()) { + s = "FREEBSD"; + } else if (IsNetbsd()) { + s = "NETBSD"; + } + if (s) { + lua_pushstring(L, s); + } else { + lua_pushnil(L); + } + return 1; +} + static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) { lua_pushinteger(L, v); lua_setfield(L, -2, k); @@ -5194,6 +5220,7 @@ static bool LuaRun(const char *path, bool mandatory) { } else { WARNF("%s %s", path, lua_tostring(L, -1)); } + lua_pop(L, 1); } free(code); } @@ -5236,6 +5263,7 @@ static const luaL_Reg kLuaFuncs[] = { {"GetHeader", LuaGetHeader}, // {"GetHeaders", LuaGetHeaders}, // {"GetHost", LuaGetHost}, // + {"GetHostOs", LuaGetHostOs}, // {"GetHttpReason", LuaGetHttpReason}, // {"GetHttpVersion", LuaGetHttpVersion}, // {"GetLastModifiedTime", LuaGetLastModifiedTime}, // From 2bc0901ce3afdd8afe979f57de91aa183826790b Mon Sep 17 00:00:00 2001 From: Paul Kulchenko Date: Sat, 7 Aug 2021 15:20:33 -0700 Subject: [PATCH 4/7] Revert adding lua array per discussion in #222 (#229) This reverts commit 55a15c204eb80f0ab53ce94d862cdc191b38800d. --- third_party/lua/lapi.c | 47 +------ third_party/lua/larray.c | 107 ---------------- third_party/lua/larray.h | 22 ---- third_party/lua/lbaselib.c | 15 +-- third_party/lua/ldebug.c | 2 +- third_party/lua/lgc.c | 4 - third_party/lua/lobject.h | 27 ---- third_party/lua/lparser.c | 37 ++---- third_party/lua/lstate.h | 2 - third_party/lua/ltable.c | 26 +--- third_party/lua/ltablib.c | 13 -- third_party/lua/ltm.c | 3 +- third_party/lua/lua.h | 7 +- third_party/lua/lvm.c | 39 +----- third_party/lua/lvm.h | 15 --- third_party/lua/test/arrays.lua | 211 -------------------------------- 16 files changed, 20 insertions(+), 557 deletions(-) delete mode 100644 third_party/lua/larray.c delete mode 100644 third_party/lua/larray.h delete mode 100644 third_party/lua/test/arrays.lua diff --git a/third_party/lua/lapi.c b/third_party/lua/lapi.c index 067f60a84..ce9ba51d1 100644 --- a/third_party/lua/lapi.c +++ b/third_party/lua/lapi.c @@ -18,7 +18,6 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" -#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" #include "third_party/lua/lundump.h" @@ -313,12 +312,6 @@ LUA_API int lua_isuserdata (lua_State *L, int idx) { } -LUA_API int lua_isarray (lua_State *L, int idx) { - const TValue *o = index2value(L, idx); - return (isvalid(L, o) && ttype(o) == LUA_TTABLE && hvalue(o)->truearray); -} - - LUA_API int lua_rawequal (lua_State *L, int index1, int index2) { const TValue *o1 = index2value(L, index1); const TValue *o2 = index2value(L, index2); @@ -766,41 +759,6 @@ LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { } -LUA_API void lua_createarray (lua_State *L, int narray) { - Array *a; - lua_lock(L); - a = luaA_new(L); - setavalue2s(L, L->top, a); - api_incr_top(L); - if (narray > 0) - luaA_resize(L, a, narray); - luaC_checkGC(L); - lua_unlock(L); -} - - -LUA_API void lua_resize (lua_State *L, int idx, unsigned int size) { - TValue *o; - Table *t; - unsigned int i, oldsize; - lua_lock(L); - o = index2value(L, idx); - api_check(L, ttistable(o), "table expected"); - t = hvalue(o); - oldsize = t->sizeused; - if (size > luaH_realasize(t)) { - luaH_resizearray(L, t, size); - } - lua_unlock(L); - /* set removed elements to nil when shrinking array size */ - for(i = size + 1; i <= oldsize; i++) { - lua_pushnil(L); - lua_seti(L, idx, i); - } - t->sizeused = size; -} - - LUA_API int lua_getmetatable (lua_State *L, int objindex) { const TValue *obj; Table *mt; @@ -909,10 +867,7 @@ LUA_API void lua_seti (lua_State *L, int idx, lua_Integer n) { lua_lock(L); api_checknelems(L, 1); t = index2value(L, idx); - if (ttisarray(t)) { - luaA_setint(L, avalue(t), n, s2v(L->top - 1)); - } - else if (luaV_fastgeti(L, t, n, slot)) { + if (luaV_fastgeti(L, t, n, slot)) { luaV_finishfastset(L, t, slot, s2v(L->top - 1)); } else { diff --git a/third_party/lua/larray.c b/third_party/lua/larray.c deleted file mode 100644 index 42252823c..000000000 --- a/third_party/lua/larray.c +++ /dev/null @@ -1,107 +0,0 @@ -/* -** Lua arrays -** See Copyright Notice in lua.h -*/ - -asm(".ident\t\"\\n\\n\ -lua-array (MIT License)\\n\ -Copyright 2018-2021 Petri Häkkinen\""); -asm(".include \"libc/disclaimer.inc\""); - -#define larray_c -#define LUA_CORE - -#include "third_party/lua/lprefix.h" - -#include -#include - -#include "third_party/lua/lua.h" - -#include "third_party/lua/ldebug.h" -#include "third_party/lua/ldo.h" -#include "third_party/lua/lgc.h" -#include "third_party/lua/lmem.h" -#include "third_party/lua/lobject.h" -#include "third_party/lua/lstate.h" -#include "third_party/lua/lstring.h" -#include "third_party/lua/ltable.h" -#include "third_party/lua/larray.h" -#include "third_party/lua/lvm.h" - -Array *luaA_new (lua_State *L) { - GCObject *o = luaC_newobj(L, LUA_TARRAY, sizeof(Array)); - Array *a = gco2a(o); - a->array = NULL; - a->alimit = 0; - return a; -} - - -void luaA_resize (lua_State *L, Array *a, unsigned int newsize) { - unsigned int i; - unsigned int oldsize = a->alimit; - TValue *newarray; - - /* allocate new array */ - newarray = luaM_reallocvector(L, a->array, oldsize, newsize, TValue); - if (newarray == NULL && newsize > 0) { /* allocation failed? */ - luaM_error(L); /* raise error (with array unchanged) */ - } - - /* allocation ok; initialize new part of the array */ - a->array = newarray; /* set new array part */ - a->alimit = newsize; - for (i = oldsize; i < newsize; i++) /* clear new slice of the array */ - setempty(&a->array[i]); -} - - -void luaA_free (lua_State *L, Array *a) { - luaM_freearray(L, a->array, a->alimit); - luaM_free(L, a); -} - - -const TValue *luaA_getint (lua_State *L, Array *a, lua_Integer key) { - /* (1 <= key && key <= t->alimit) */ - if (l_castS2U(key) - 1u < a->alimit) - return &a->array[key - 1]; - else - return luaO_nilobject; -} - -const TValue *luaA_get (lua_State *L, Array *a, const TValue *key) { - if (ttypetag(key) == LUA_VNUMINT) { - lua_Integer ikey = ivalue(key); - if (l_castS2U(ikey) - 1u < a->alimit) - return &a->array[ikey - 1]; - else - return luaO_nilobject; - } else { - return luaO_nilobject; - } -} - -void luaA_setint (lua_State *L, Array *a, lua_Integer key, TValue *value) { - if (l_castS2U(key) - 1u < a->alimit) { - /* set value! */ - TValue* val = &a->array[key - 1]; - val->value_ = value->value_; - val->tt_ = value->tt_; - checkliveness(L,val); - } else { - /* TODO: this error message could be improved! */ - luaG_runerror(L, "array index out of bounds"); - } -} - -void luaA_set (lua_State *L, Array *a, const TValue* key, TValue *value) { - if (ttypetag(key) == LUA_VNUMINT) { - lua_Integer ikey = ivalue(key); - luaA_setint(L, a, ikey, value); - } else { - /* TODO: the error message could be improved */ - luaG_runerror(L, "attempt to index array with a non-integer value"); - } -} diff --git a/third_party/lua/larray.h b/third_party/lua/larray.h deleted file mode 100644 index f8ebfc0a3..000000000 --- a/third_party/lua/larray.h +++ /dev/null @@ -1,22 +0,0 @@ -/* -** Lua arrays -** See Copyright Notice in lua.h -*/ - -#ifndef larray_h -#define larray_h - -#include "third_party/lua/lobject.h" - -#define luaO_nilobject (&G(L)->nilvalue) - -LUAI_FUNC Array *luaA_new (lua_State *L); -LUAI_FUNC void luaA_resize (lua_State *L, Array *a, unsigned int nsize); -LUAI_FUNC void luaA_free (lua_State *L, Array *a); - -LUAI_FUNC const TValue *luaA_getint (lua_State *L, Array *a, lua_Integer key); -LUAI_FUNC void luaA_setint (lua_State *L, Array *a, lua_Integer key, TValue *value); -LUAI_FUNC const TValue *luaA_get (lua_State *L, Array *a, const TValue *key); -LUAI_FUNC void luaA_set (lua_State *L, Array *a, const TValue *key, TValue *value); - -#endif diff --git a/third_party/lua/lbaselib.c b/third_party/lua/lbaselib.c index e9a5dcd41..25f37e902 100644 --- a/third_party/lua/lbaselib.c +++ b/third_party/lua/lbaselib.c @@ -254,12 +254,9 @@ static int luaB_next (lua_State *L) { } } -static int luaB_ipairs (lua_State *L); static int luaB_pairs (lua_State *L) { luaL_checkany(L, 1); - if (lua_isarray(L, 1)) - return luaB_ipairs(L); if (luaL_getmetafield(L, 1, "__pairs") == LUA_TNIL) { /* no metamethod? */ lua_pushcfunction(L, luaB_next); /* will return generator, */ lua_pushvalue(L, 1); /* state, */ @@ -282,16 +279,6 @@ static int ipairsaux (lua_State *L) { return (lua_geti(L, 1, i) == LUA_TNIL) ? 1 : 2; } -/* -** Traversal function for 'ipairs' specialized for arrays. -*/ -static int ipairsauxarray (lua_State *L) { - lua_Integer i = luaL_checkinteger(L, 2) + 1; - lua_pushinteger(L, i); - lua_geti(L, 1, i); - return (lua_rawlen(L, 1) == i-1) ? 1 : 2; -} - /* ** 'ipairs' function. Returns 'ipairsaux', given "table", 0. @@ -299,7 +286,7 @@ static int ipairsauxarray (lua_State *L) { */ static int luaB_ipairs (lua_State *L) { luaL_checkany(L, 1); - lua_pushcfunction(L, lua_isarray(L, 1) ? ipairsauxarray : ipairsaux); /* iteration function */ + lua_pushcfunction(L, ipairsaux); /* iteration function */ lua_pushvalue(L, 1); /* state */ lua_pushinteger(L, 0); /* initial value */ return 3; diff --git a/third_party/lua/ldebug.c b/third_party/lua/ldebug.c index 1657922d9..1f2c0785c 100644 --- a/third_party/lua/ldebug.c +++ b/third_party/lua/ldebug.c @@ -688,7 +688,7 @@ static const char *varinfo (lua_State *L, const TValue *o) { l_noret luaG_typeerror (lua_State *L, const TValue *o, const char *op) { const char *t = luaT_objtypename(L, o); - luaG_runerror(L, "attempt to %s a%s %s value%s", op, ttisarray(o) ? "n" : "", t, varinfo(L, o)); + luaG_runerror(L, "attempt to %s a %s value%s", op, t, varinfo(L, o)); } diff --git a/third_party/lua/lgc.c b/third_party/lua/lgc.c index 310c87133..da7894b72 100644 --- a/third_party/lua/lgc.c +++ b/third_party/lua/lgc.c @@ -17,7 +17,6 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" -#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" @@ -778,9 +777,6 @@ static void freeobj (lua_State *L, GCObject *o) { case LUA_VTABLE: luaH_free(L, gco2t(o)); break; - case LUA_TARRAY: - luaA_free(L, gco2a(o)); - break; case LUA_VTHREAD: luaE_freethread(L, gco2th(o)); break; diff --git a/third_party/lua/lobject.h b/third_party/lua/lobject.h index 717a2e563..33dbba4fb 100644 --- a/third_party/lua/lobject.h +++ b/third_party/lua/lobject.h @@ -722,8 +722,6 @@ typedef struct Table { lu_byte flags; /* 1<

'{' [ field { sep field } [sep] ] '}' sep -> ',' | ';' */ - - /* special case for arrays (when 'array' is true): - constructor -> '[' [ listfield { sep listfield } [sep] ] ']' - sep -> ',' | ';' */ - FuncState *fs = ls->fs; int line = ls->linenumber; int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0); @@ -923,32 +918,18 @@ static void constructor (LexState *ls, expdesc *t, int array) { init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */ luaK_reserveregs(fs, 1); init_exp(&cc.v, VVOID, 0); /* no value (yet) */ - luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */ - checknext(ls, (array ? '[' : '{')); + checknext(ls, '{'); do { lua_assert(cc.v.k == VVOID || cc.tostore > 0); - if (ls->t.token == (array ? ']' : '}')) break; + if (ls->t.token == '}') break; closelistfield(fs, &cc); - if (array) - listfield(ls, &cc); - else - field(ls, &cc); + field(ls, &cc); } while (testnext(ls, ',') || testnext(ls, ';')); - check_match(ls, array ? ']' : '}', array ? '[' : '{', line); + check_match(ls, '}', '{', line); lastlistfield(fs, &cc); luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh); - /* encode arrayness by setting B to max value (255) */ - if (array) { - /* make sure B is not already 255 */ - /* I don't this can happen in practice (max size is luaO_fb2int(255) = 3221225472), but let's be sure... */ - unsigned int b = GETARG_B(fs->f->code[pc]); - if (b == 255) - luaX_syntaxerror(fs->ls, "table too large"); - SETARG_B(fs->f->code[pc], 255); - } } - /* }====================================================================== */ @@ -1042,7 +1023,7 @@ static void funcargs (LexState *ls, expdesc *f, int line) { break; } case '{': { /* funcargs -> constructor */ - constructor(ls, &args, 0); + constructor(ls, &args); break; } case TK_STRING: { /* funcargs -> STRING */ @@ -1177,11 +1158,7 @@ static void simpleexp (LexState *ls, expdesc *v) { break; } case '{': { /* constructor */ - constructor(ls, v, 0); - return; - } - case '[': { /* array constructor */ - constructor(ls, v, 1); + constructor(ls, v); return; } case TK_FUNCTION: { diff --git a/third_party/lua/lstate.h b/third_party/lua/lstate.h index 04b444894..7b5aed28d 100644 --- a/third_party/lua/lstate.h +++ b/third_party/lua/lstate.h @@ -351,7 +351,6 @@ union GCUnion { struct Udata u; union Closure cl; struct Table h; - struct Array a; struct Proto p; struct lua_State th; /* thread */ struct UpVal upv; @@ -374,7 +373,6 @@ union GCUnion { #define gco2cl(o) \ check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl)) #define gco2t(o) check_exp((o)->tt == LUA_VTABLE, &((cast_u(o))->h)) -#define gco2a(o) check_exp((o)->tt == LUA_TARRAY, &((cast_u(o))->a)) #define gco2p(o) check_exp((o)->tt == LUA_VPROTO, &((cast_u(o))->p)) #define gco2th(o) check_exp((o)->tt == LUA_VTHREAD, &((cast_u(o))->th)) #define gco2upv(o) check_exp((o)->tt == LUA_VUPVAL, &((cast_u(o))->upv)) diff --git a/third_party/lua/ltable.c b/third_party/lua/ltable.c index 29f642026..4d021a141 100644 --- a/third_party/lua/ltable.c +++ b/third_party/lua/ltable.c @@ -565,7 +565,7 @@ void luaH_resize (lua_State *L, Table *t, unsigned int newasize, t->array = newarray; /* set new array part */ t->alimit = newasize; for (i = oldasize; i < newasize; i++) /* clear new slice of the array */ - setempty(&t->array[i]); + setempty(&t->array[i]); /* re-insert elements from old hash part into new parts */ reinsert(L, &newt, t); /* 'newt' now has the old hash */ freehash(L, &newt); /* free old hash part */ @@ -613,10 +613,8 @@ Table *luaH_new (lua_State *L) { Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(maskflags); /* table has no metamethod fields */ - t->truearray = 0; t->array = NULL; t->alimit = 0; - t->sizeused = 0; setnodevector(L, t, 0); return t; } @@ -654,25 +652,7 @@ void luaH_newkey (lua_State *L, Table *t, const TValue *key, TValue *value) { TValue aux; if (l_unlikely(ttisnil(key))) luaG_runerror(L, "table index is nil"); - else if (t->truearray) { - /* set new value to true array */ - int capacity; - int asize = luaH_realasize(t); - int idx = ivalue(key); /* TODO: does not handle numbers larger than fits into a 32-bit signed integer! */ - if (!ttisinteger(key) || idx < 1) - luaG_runerror(L, "invalid array index"); - /* enlarge capacity */ - if (asize < idx) { - capacity = asize + (asize >> 1); - if (capacity < idx) - capacity = idx; - luaH_resizearray(L, t, capacity); - } - t->sizeused = idx; // since this is guaranteed to be a new key, it exceeds t->sizeused - luaC_barrierback(L, obj2gco(t), key); - setobj2t(L, cast(TValue *, t->array + idx - 1), value); - return; - } else if (ttisfloat(key)) { + else if (ttisfloat(key)) { lua_Number f = fltvalue(key); lua_Integer k; if (luaV_flttointeger(f, &k, F2Ieq)) { /* does key fit in an integer? */ @@ -927,8 +907,6 @@ static unsigned int binsearch (const TValue *array, unsigned int i, */ lua_Unsigned luaH_getn (Table *t) { unsigned int limit = t->alimit; - if (t->truearray) - return t->sizeused; if (limit > 0 && isempty(&t->array[limit - 1])) { /* (1)? */ /* there must be a boundary before 'limit' */ if (limit >= 2 && !isempty(&t->array[limit - 2])) { diff --git a/third_party/lua/ltablib.c b/third_party/lua/ltablib.c index 76ef39665..553f2a7e6 100644 --- a/third_party/lua/ltablib.c +++ b/third_party/lua/ltablib.c @@ -401,18 +401,6 @@ static int sort (lua_State *L) { return 0; } - -static int resize (lua_State *L) { - /* reserve capacity of the array part -- useful when filling large tables/arrays */ - int size; - luaL_checktype(L, 1, LUA_TTABLE); - size = luaL_checkinteger(L, 2); - luaL_argcheck(L, size >= 0, 2, "invalid size"); - lua_resize(L, 1, (unsigned int)size); - return 0; -} - - /* }====================================================== */ @@ -424,7 +412,6 @@ static const luaL_Reg tab_funcs[] = { {"remove", tremove}, {"move", tmove}, {"sort", sort}, - {"resize", resize}, {NULL, NULL} }; diff --git a/third_party/lua/ltm.c b/third_party/lua/ltm.c index 45849eb35..75f67c5f6 100644 --- a/third_party/lua/ltm.c +++ b/third_party/lua/ltm.c @@ -26,7 +26,7 @@ static const char udatatypename[] = "userdata"; LUAI_DDEF const char *const luaT_typenames_[LUA_TOTALTYPES] = { "no value", "nil", "boolean", udatatypename, "number", - "string", "table", "function", udatatypename, "thread", "array", + "string", "table", "function", udatatypename, "thread", "upvalue", "proto" /* these last cases are used for tests only */ }; @@ -264,3 +264,4 @@ void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) { for (; i < wanted; i++) /* complete required results with nil */ setnilvalue(s2v(where + i)); } + diff --git a/third_party/lua/lua.h b/third_party/lua/lua.h index f13f63f57..4e9b825f2 100644 --- a/third_party/lua/lua.h +++ b/third_party/lua/lua.h @@ -60,8 +60,8 @@ typedef struct lua_State lua_State; #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8 -#define LUA_TARRAY 9 -#define LUA_NUMTYPES 10 + +#define LUA_NUMTYPES 9 @@ -173,7 +173,6 @@ LUA_API int (lua_isstring) (lua_State *L, int idx); LUA_API int (lua_iscfunction) (lua_State *L, int idx); LUA_API int (lua_isinteger) (lua_State *L, int idx); LUA_API int (lua_isuserdata) (lua_State *L, int idx); -LUA_API int (lua_isarray) (lua_State *L, int idx); LUA_API int (lua_type) (lua_State *L, int idx); LUA_API const char *(lua_typename) (lua_State *L, int tp); @@ -246,8 +245,6 @@ LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); -LUA_API void (lua_createarray) (lua_State *L, int narr); -LUA_API void (lua_resize) (lua_State *L, int idx, unsigned int size); LUA_API void *(lua_newuserdatauv) (lua_State *L, size_t sz, int nuvalue); LUA_API int (lua_getmetatable) (lua_State *L, int objindex); LUA_API int (lua_getiuservalue) (lua_State *L, int idx, int n); diff --git a/third_party/lua/lvm.c b/third_party/lua/lvm.c index 069275b2c..7780b974a 100644 --- a/third_party/lua/lvm.c +++ b/third_party/lua/lvm.c @@ -17,7 +17,6 @@ #include "third_party/lua/lstate.h" #include "third_party/lua/lstring.h" #include "third_party/lua/ltable.h" -#include "third_party/lua/larray.h" #include "third_party/lua/ltm.h" #include "third_party/lua/lua.h" #include "third_party/lua/lvm.h" @@ -284,11 +283,6 @@ void luaV_finishget (lua_State *L, const TValue *t, TValue *key, StkId val, for (loop = 0; loop < MAXTAGLOOP; loop++) { if (slot == NULL) { /* 't' is not a table? */ lua_assert(!ttistable(t)); - if (ttisarray(t)) { - /* get array value */ - setobj2s(L, val, luaA_get(L, avalue(t), key)); - return; - } tm = luaT_gettmbyobj(L, t, TM_INDEX); if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index"); /* no metamethod */ @@ -337,17 +331,12 @@ void luaV_finishset (lua_State *L, const TValue *t, TValue *key, if (tm == NULL) { /* no metamethod? */ luaH_finishset(L, h, key, slot, val); /* set new value */ invalidateTMcache(h); - /* enlarge array length when necessary */ - /* this should be quite fast as fields of h and key have already been loaded to CPU cache at this point */ - h->sizeused += (val_(key).i > h->sizeused) & ttisinteger(key) & 1; luaC_barrierback(L, obj2gco(h), val); return; } /* else will try the metamethod */ } else { /* not a table; check metamethod */ - if(ttisarray(t)) - luaG_typeerror(L, t, "set non-integer index of"); tm = luaT_gettmbyobj(L, t, TM_NEWINDEX); if (l_unlikely(notm(tm))) luaG_typeerror(L, t, "index"); @@ -693,11 +682,6 @@ void luaV_objlen (lua_State *L, StkId ra, const TValue *rb) { setivalue(s2v(ra), luaH_getn(h)); /* else primitive len */ return; } - case LUA_TARRAY: { - Array *a = avalue(rb); - setivalue(s2v(ra), a->alimit); - return; - } case LUA_VSHRSTR: { setivalue(s2v(ra), tsvalue(rb)->shrlen); return; @@ -1257,10 +1241,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; TValue *rb = vRB(i); int c = GETARG_C(i); - if(ttisarray(rb)) { - setobj2s(L, ra, luaA_getint(L, avalue(rb), c)); - } - else if (luaV_fastgeti(L, rb, c, slot)) { + if (luaV_fastgeti(L, rb, c, slot)) { setobj2s(L, ra, slot); } else { @@ -1313,10 +1294,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { const TValue *slot; int c = GETARG_B(i); TValue *rc = RKC(i); - if(ttisarray(s2v(ra))) { - luaA_setint(L, avalue(s2v(ra)), c, rc); - } - else if (luaV_fastgeti(L, s2v(ra), c, slot)) { + if (luaV_fastgeti(L, s2v(ra), c, slot)) { luaV_finishfastset(L, s2v(ra), slot, rc); } else { @@ -1341,8 +1319,6 @@ void luaV_execute (lua_State *L, CallInfo *ci) { vmcase(OP_NEWTABLE) { int b = GETARG_B(i); /* log2(hash size) + 1 */ int c = GETARG_C(i); /* array size */ - /* decode arrayness */ - int array = (b == 255); Table *t; if (b > 0) b = 1 << (b - 1); /* size is 2^(b - 1) */ @@ -1352,16 +1328,9 @@ void luaV_execute (lua_State *L, CallInfo *ci) { pc++; /* skip extra argument */ L->top = ra + 1; /* correct top in case of emergency GC */ t = luaH_new(L); /* memory allocation */ - t->truearray = array; - t->sizeused = c; sethvalue2s(L, ra, t); - if (b != 0 || c != 0) { - if (array) { - luaH_resizearray(L, t, c); /* idem */ - } else { - luaH_resize(L, t, c, b); /* idem */ - } - } + if (b != 0 || c != 0) + luaH_resize(L, t, c, b); /* idem */ checkGC(L, ra + 1); vmbreak; } diff --git a/third_party/lua/lvm.h b/third_party/lua/lvm.h index f07eacad4..8fd25ed22 100644 --- a/third_party/lua/lvm.h +++ b/third_party/lua/lvm.h @@ -88,13 +88,6 @@ typedef enum { : (slot = f(hvalue(t), k), /* else, do raw access */ \ !isempty(slot))) /* result not empty? */ -/* This one supports arrays as well as tables. */ -#define luaV_fastget2(L,t,k,slot,f) \ - (ttisarray(t) ? (slot = f(avalue(t), k), !isempty(slot)) : \ - (!ttistable(t) \ - ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ - : (slot = f(hvalue(t), k), /* else, do raw access */ \ - !isempty(slot)))) /* result not empty? */ /* ** Special case of 'luaV_fastget' for integers, inlining the fast case @@ -107,14 +100,6 @@ typedef enum { ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ !isempty(slot))) /* result not empty? */ -/* This one supports arrays as well as tables. */ -#define luaV_fastgeti2(L,t,k,slot) \ - (ttisarray(t) ? (slot = (l_castS2U(k) - 1u < avalue(t)->sizearray) ? &avalue(t)->array[k - 1] : luaO_nilobject, !isempty(slot)) : \ - (!ttistable(t) \ - ? (slot = NULL, 0) /* not a table; 'slot' is NULL and result is 0 */ \ - : (slot = (l_castS2U(k) - 1u < hvalue(t)->sizearray) \ - ? &hvalue(t)->array[k - 1] : luaH_getint(hvalue(t), k), \ - !isempty(slot)))) /* result not empty? */ /* ** Finish a fast set operation (when fast get succeeds). In that case, diff --git a/third_party/lua/test/arrays.lua b/third_party/lua/test/arrays.lua deleted file mode 100644 index 27517787a..000000000 --- a/third_party/lua/test/arrays.lua +++ /dev/null @@ -1,211 +0,0 @@ -local errors = false - -function assert(cond) - if not cond then - local line = debug.getinfo(2).currentline - print("test failed at line " .. line) - errors = true - end -end - --- test array constructor -do - local a = [1, 2, 3] - assert(a[1] == 1) - assert(a[2] == 2) - assert(a[3] == 3) - assert(#a == 3) -end - --- test nested array constructor -do - local a = [1, 2, 3, [4, 5]] - assert(a[1] == 1) - assert(a[2] == 2) - assert(a[3] == 3) - assert(a[4][1] == 4) - assert(a[4][2] == 5) - assert(#a == 4) -end - --- test array write -do - local a = [1, 2, 3] - a[1] = -1 - assert(a[1] == -1) - assert(#a == 3) -end - --- test array extend -do - local a = [1, 2, 3] - a[7] = 5 - assert(#a == 7) - assert(a[7] == 5) -end - --- test array extend 2 -do - local a = [] - for i=5,15 do - a[i] = i - assert(a[i] == i) - assert(#a == i) - end -end - --- test setting element to nil (should not affect array size) -do - local a = [1, 2, 3] - a[3] = nil - assert(a[3] == nil) - assert(#a == 3) -end - --- test array with holes -do - local a = [1, nil, 3] - assert(a[1] == 1) - assert(a[2] == nil) - assert(a[3] == 3) - assert(#a == 3) - a[1] = nil - assert(#a == 3) -end - --- test filling hole in array -do - local a = [1, nil, 3] - a[2] = 2 - assert(a[2] == 2) - assert(#a == 3) -end - --- test filling hole in array 2 -do - local a = [1, nil, 3] - local i = 2 - a[i] = 2 - assert(a[2] == 2) - assert(#a == 3) -end - --- test read out of bounds -do - local a = [1, 2, 3] - assert(#a == 3) - assert(a[0] == nil) - assert(a[4] == nil) - assert(#a == 3) -end - --- test array resize (array growing) -do - local a = [1, 2, 3] - table.resize(a, 1000) - assert(a[4] == nil) - assert(#a == 1000) - a[1] = 4 - a[10] = 10 - a[11] = 11 - assert(#a == 1000) -end - --- test array resize (array shrinking) -do - local a = [1, 2, 3, 4, 5] - table.resize(a, 3) - assert(a[1] == 1) - assert(a[2] == 2) - assert(a[3] == 3) - assert(a[4] == nil) - assert(a[5] == nil) - assert(#a == 3) -end - --- test non-const integer -do - local a = [] - local y = 3 - a[y] = 66 - assert(a[3] == 66) - assert(#a == 3) -end - --- test table.insert() -do - local a = [1, 2, 3] - table.insert(a, 1, "new") - assert(a[1] == "new") - assert(a[2] == 1) - assert(a[3] == 2) - assert(a[4] == 3) - assert(#a == 4) -end - --- test table.remove() -do - local a = [1, 2, 3] - table.remove(a, 1) - assert(a[1] == 2) - assert(a[2] == 3) - -- TODO: fix the implementation, as after upgrading to Lua 5.4.3 - -- it keeps the array size the same after table.remove() - -- assert(#a == 2) -end - --- test ipairs --- expected behavior: equivalent to for i=1,#a do print(i, a[i]) end -do - local a = [1, nil, 3, nil] - local cnt = 0 - for k,v in ipairs(a) do - assert(v == a[k]) - cnt = cnt + 1 - end - assert(cnt == #a) -end - --- test pairs --- expected behavior: same as ipairs? -do - local a = [1, nil, 3] - local cnt = 0 - for k,v in pairs(a) do - assert(v == a[k]) - cnt = cnt + 1 - end - assert(cnt == 3) -end - --- test normal insert/remove operations -local function test (a) - assert(not pcall(table.insert, a, 2, 20)); - table.insert(a, 10); - table.insert(a, 2, 20); - table.insert(a, 1, -1); - table.insert(a, 40); - table.insert(a, #a+1, 50) - table.insert(a, 2, -2) - assert(a[2] ~= undef) - assert(a["2"] == undef) - assert(not pcall(table.insert, a, 0, 20)); - assert(not pcall(table.insert, a, #a + 2, 20)); - assert(table.remove(a,1) == -1) - assert(table.remove(a,1) == -2) - assert(table.remove(a,1) == 10) - assert(table.remove(a,1) == 20) - assert(table.remove(a,1) == 40) - assert(table.remove(a,1) == 50) - assert(table.remove(a,1) == nil) - assert(table.remove(a) == nil) - assert(table.remove(a, #a) == nil) -end - -a = {n=0, [-7] = "ban"} -test(a) -assert(a.n == 0 and a[-7] == "ban") - -if not errors then - print("All tests passed!") -end From 4daafef63afad272bd683b79d817c74baa71f4c3 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 7 Aug 2021 16:43:00 -0700 Subject: [PATCH 5/7] Implement RFC8442 --- third_party/mbedtls/ssl_ciphersuites.c | 49 +++++++++++++++++++------- third_party/mbedtls/ssl_ciphersuites.h | 6 ++++ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/third_party/mbedtls/ssl_ciphersuites.c b/third_party/mbedtls/ssl_ciphersuites.c index fed76e55f..7a949bdaf 100644 --- a/third_party/mbedtls/ssl_ciphersuites.c +++ b/third_party/mbedtls/ssl_ciphersuites.c @@ -72,10 +72,9 @@ static const uint16_t ciphersuite_preference[] = #endif #ifdef MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED - /* TODO(jart): RFC8442 */ - /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, */ - /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, */ - /* MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, */ + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, + MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, MBEDTLS_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256, MBEDTLS_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256, MBEDTLS_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, @@ -169,17 +168,25 @@ static const uint16_t ciphersuite_preference[] = static const mbedtls_ssl_ciphersuite_t ciphersuite_definitions[] = { -#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA256_C) - { MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "ECDHE-ECDSA-AES128-GCM-SHA256", - MBEDTLS_CIPHER_AES_128_GCM, MBEDTLS_MD_SHA256, MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA, +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) && !defined(MBEDTLS_SHA512_NO_SHA384) + { MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "ECDHE-ECDSA-AES256-GCM-SHA384", + MBEDTLS_CIPHER_AES_256_GCM, MBEDTLS_MD_SHA384, MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, 0 }, #endif -#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) - { MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "ECDHE-ECDSA-AES256-GCM-SHA384", - MBEDTLS_CIPHER_AES_256_GCM, MBEDTLS_MD_SHA384, MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA, +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) && !defined(MBEDTLS_SHA512_NO_SHA384) + { MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "ECDHE-RSA-AES256-GCM-SHA384", + MBEDTLS_CIPHER_AES_256_GCM, MBEDTLS_MD_SHA384, MBEDTLS_KEY_EXCHANGE_ECDHE_RSA, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + 0 }, +#endif + +#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA256_C) + { MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "ECDHE-ECDSA-AES128-GCM-SHA256", + MBEDTLS_CIPHER_AES_128_GCM, MBEDTLS_MD_SHA256, MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, 0 }, @@ -193,9 +200,9 @@ static const mbedtls_ssl_ciphersuite_t ciphersuite_definitions[] = 0 }, #endif -#if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) - { MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "ECDHE-RSA-AES256-GCM-SHA384", - MBEDTLS_CIPHER_AES_256_GCM, MBEDTLS_MD_SHA384, MBEDTLS_KEY_EXCHANGE_ECDHE_RSA, +#if defined(MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) && defined(MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) && !defined(MBEDTLS_SHA512_NO_SHA384) + { MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384, "ECDHE-PSK-AES256-GCM-SHA384", + MBEDTLS_CIPHER_AES_256_GCM, MBEDTLS_MD_SHA384, MBEDTLS_KEY_EXCHANGE_ECDHE_PSK, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, 0 }, @@ -271,6 +278,22 @@ static const mbedtls_ssl_ciphersuite_t ciphersuite_definitions[] = MBEDTLS_SHA256_C && MBEDTLS_SSL_PROTO_TLS1_2 */ +#if defined(MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) && defined(MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_GCM_C) && defined(MBEDTLS_SHA512_C) + { MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256, "ECDHE-PSK-AES128-GCM-SHA256", + MBEDTLS_CIPHER_AES_128_GCM, MBEDTLS_MD_SHA256, MBEDTLS_KEY_EXCHANGE_ECDHE_PSK, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + 0 }, +#endif + +#if defined(MBEDTLS_KEY_EXCHANGE_PSK_ENABLED) && defined(MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_CCM_C) && defined(MBEDTLS_SHA512_C) + { MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256, "ECDHE-PSK-AES128-CCM-SHA256", + MBEDTLS_CIPHER_AES_128_CCM, MBEDTLS_MD_SHA256, MBEDTLS_KEY_EXCHANGE_ECDHE_PSK, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3, + 0 }, +#endif + #if defined(MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED) #if defined(MBEDTLS_AES_C) #if defined(MBEDTLS_SHA1_C) diff --git a/third_party/mbedtls/ssl_ciphersuites.h b/third_party/mbedtls/ssl_ciphersuites.h index 24d17b3ed..b61ff3407 100644 --- a/third_party/mbedtls/ssl_ciphersuites.h +++ b/third_party/mbedtls/ssl_ciphersuites.h @@ -255,6 +255,12 @@ COSMOPOLITAN_C_START_ #define MBEDTLS_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAD /**< TLS 1.2 */ #define MBEDTLS_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 0xCCAE /**< TLS 1.2 */ +/* RFC 8442 */ +#define MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 0xD001 /**< TLS 1.2 */ +#define MBEDTLS_TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 0xD002 /**< TLS 1.2 */ +#define MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 0xD003 /**< TLS 1.2 */ +#define MBEDTLS_TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 0xD005 /**< TLS 1.2 */ + /* Reminder: update mbedtls_ssl_premaster_secret when adding a new key exchange. * Reminder: update MBEDTLS_KEY_EXCHANGE__xxx below */ From d8faadf44e42421c3abcd81956056de756b1561f Mon Sep 17 00:00:00 2001 From: w13b3 Date: Sun, 8 Aug 2021 00:28:32 +0000 Subject: [PATCH 6/7] Fix redbean Log() in global scope (#230) --- tool/net/redbean.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 44b8f59da..e008ccc0e 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -4911,7 +4911,7 @@ static int LuaLog(lua_State *L) { level = luaL_checkinteger(L, 1); if (LOGGABLE(level)) { msg = luaL_checkstring(L, 2); - lua_getstack(L, 1, &ar); + lua_getstack(L, 0, &ar); lua_getinfo(L, "nSl", &ar); if (!strcmp(ar.name, "main")) { module = strndup(effectivepath.p, effectivepath.n); From 28a592f2d46231ccef1e3fc7e2a01cdada2e5919 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 7 Aug 2021 18:18:12 -0700 Subject: [PATCH 7/7] Clarify kLogFatal documentation Fixes #231 --- tool/net/help.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tool/net/help.txt b/tool/net/help.txt index bc0516282..1206e5000 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1104,6 +1104,8 @@ CONSTANTS kLogFatal Integer for fatal logging level, which is less than kLogError. + Logging anything at this level will result in a backtrace and + process exit. SEE ALSO