diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f925ad..e14bded 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +env: + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer + jobs: build: name: Build and test diff --git a/.gitignore b/.gitignore index 73be72d..01af9b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ libs/ build_cmake/ +sdkconfig.old diff --git a/CMakeLists.txt b/CMakeLists.txt index 81baf92..7079ed3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ else() -Werror -Wall -Wpedantic + -Wno-assume # Lots of bogus "argument to '__builtin_assume' has side effects" -Wno-unknown-pragmas -Wno-unknown-warning-option ) @@ -110,31 +111,37 @@ add_library( LibCrouton STATIC src/Coroutine.cc src/Error.cc src/Future.cc - src/Logging.cc src/Scheduler.cc src/Select.cc - src/io/AddrInfo.cc - src/io/FileStream.cc - src/io/Filesystem.cc src/io/HTTPConnection.cc src/io/HTTPHandler.cc src/io/HTTPParser.cc src/io/ISocket.cc src/io/IStream.cc - src/io/Pipe.cc src/io/Process.cc - src/io/Stream.cc - src/io/TCPServer.cc - src/io/TCPSocket.cc src/io/URL.cc - src/io/UVBase.cc src/io/WebSocket.cc src/io/mbed/TLSSocket.cc + src/io/uv/AddrInfo.cc + src/io/uv/FileStream.cc + src/io/uv/Filesystem.cc + src/io/uv/Pipe.cc + src/io/uv/Stream.cc + src/io/uv/TCPServer.cc + src/io/uv/TCPSocket.cc + + src/io/uv/UVBase.cc + src/support/Backtrace.cc + src/support/Backtrace+Unix.cc + src/support/Backtrace+Windows.cc + src/support/betterassert.cc src/support/Memoized.cc + src/support/Logging.cc + src/support/MiniFormat.cc src/support/StringUtils.cc ) diff --git a/ESP32/hello_world/pytest_hello_world.py b/ESP32/hello_world/pytest_hello_world.py new file mode 100644 index 0000000..3cb161c --- /dev/null +++ b/ESP32/hello_world/pytest_hello_world.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +from typing import Callable + +import pytest +from pytest_embedded_idf.dut import IdfDut +from pytest_embedded_qemu.dut import QemuDut + + +@pytest.mark.supported_targets +@pytest.mark.generic +def test_hello_world(dut: IdfDut, log_minimum_free_heap_size: Callable[..., None]) -> None: + dut.expect('Hello world!') + log_minimum_free_heap_size() + + +@pytest.mark.esp32 # we only support qemu on esp32 for now +@pytest.mark.host_test +@pytest.mark.qemu +def test_hello_world_host(dut: QemuDut) -> None: + dut.expect('Hello world!') diff --git a/Makefile b/Makefile index 2647c30..7013a40 100644 --- a/Makefile +++ b/Makefile @@ -30,3 +30,9 @@ xcode_deps: debug release mkdir -p build_cmake/release/xcodedeps cp build_cmake/release/vendor/libuv/libuv*.a build_cmake/release/xcodedeps/ cp build_cmake/release/vendor/mbedtls/library/libmbed*.a build_cmake/release/xcodedeps/ + +esp: + cd tests/ESP32 && idf.py build + +esptest: + cd tests/ESP32 && idf.py build flash monitor diff --git a/README.md b/README.md index 734cdf2..7189b15 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Crouton -Crouton is a C++20 coroutine runtime library that provides some general purpose utilities, as well as cross-platform event loops, I/O and networking based on the [libuv][LIBUV], [mbedTLS][MBEDTLS] and [llhttp][LLHTTP] libraries. (On Apple platforms it can also use the system Network.framework.) +**Crouton** is a C++20 coroutine runtime library that provides some general purpose concurrency utilities, as well as cross-platform event loops, I/O and networking that work the same way on Mac, Linux, Windows and ESP32 microcontrollers. + +The cross-platform support is based on the widely-used [libuv][LIBUV], [mbedTLS][MBEDTLS] and [llhttp][LLHTTP] libraries. (On Apple platforms it can also use the system Network.framework. On [ESP32][ESP32], where libuv is not supported, it uses lwip and FreeRTOS APIs instead.) A **Coroutine** is _a function that can return partway through, and then later be resumed where it left off._ Knuth wrote about them in the 1960s and they remained a curiosity for a long time, but they've since become widely used under the hood of the "async / await" concurrency model used in languages like JavaScript, C#, Rust, Nim, and Swift. Crouton brings this to C++. @@ -53,15 +55,15 @@ How is that better than threads? It's safer and easier to reason about. The only * Core classes & APIs: * General-purpose `Error` and `Result` types - * Logging, a thin wrapper around [spdlog][SPDLOG] + * Logging uses either a thin wrapper around [spdlog][SPDLOG], or a smaller compatible library I wrote. * Cross-Platform: * macOS (builds and passes tests) * iOS? ("It's still Darwin…") * Linux (builds and passes test) * Android? ("It's still Linux…") + * [ESP32][ESP32] embedded CPUs (builds and passes tests. File APIs not available yet.) * Windows (sometimes builds; not yet tested; help wanted!) - * Would very much like to support some embedded platforms like ESP32 (help wanted!) ## Example @@ -86,11 +88,13 @@ cout << endl; See also [demo_server.cc](tests/demo_server.cc), a simple HTTP and WebSocket server. +An example embedded app is at [tests/ESP32](tests/ESP32/README.md). + ## Status: ☠️EXPERIMENTAL☠️ [![Build](https://github.com/couchbaselabs/crouton/actions/workflows/build.yml/badge.svg)](https://github.com/couchbaselabs/crouton/actions/workflows/build.yml) -This is new code, under heavy development! So far, it builds with Clang (Xcode 14) on macOS, GCC 12 on Ubuntu, and Visual Studio 17 2022 on Windows. +This is new code, under heavy development! So far, it builds with Clang (Xcode 15) on macOS, GCC 12 on Ubuntu, Visual Studio 17 2022 on Windows, and ESP-IDF 5.0. The tests run regularly on macOS, and occasionally on Ubuntu (though not in CI.) Test coverage is very limited. @@ -104,12 +108,12 @@ APIs are still in flux. Things get refactored a lot. ### Prerequisites: - CMake -- Clang 15, Xcode 14, or GCC 12 +- Clang 15, Xcode 15, or GCC 12 - zlib (aka libz) #### on macOS: -- Install Xcode 14 or later, or at least the command-line tools. +- Install Xcode 15 or later, or at least the command-line tools. - Install CMake; this is most easily done with [HomeBrew](https://brew.sh), by running `brew install cmake` #### on Ubuntu Linux @@ -123,7 +127,7 @@ APIs are still in flux. Things get refactored a lot. The library is `libCrouton`, in either the `build_cmake/debug/` or `build_cmake/release/` directory. -### Building With Xcode +### Building With Xcode 15+ **Before first building with Xcode**, you must use CMake to build libuv and mbedTLS: @@ -136,6 +140,9 @@ Then: - Select the `Tests` scheme and Run. - To locate the binaries, choose Product > Show Build Folder In Finder +### Building For ESP32 Embedded Systems + +The ESP-IDF component is at `src/io/esp32`; please see the [README](src/io/esp32/README.md) for details. A demo app is at [tests/ESP32](tests/ESP32/README.md). ## License(s) @@ -171,3 +178,4 @@ Then: [BAKER]: https://lewissbaker.github.io/2022/08/27/understanding-the-compiler-transform [BSL]: src/io/blip/licences/BSL.txt [BLIP]: src/io/blip/README.md +[ESP32]: https://www.espressif.com diff --git a/crouton.xcodeproj/project.pbxproj b/crouton.xcodeproj/project.pbxproj index c409917..1a5595c 100644 --- a/crouton.xcodeproj/project.pbxproj +++ b/crouton.xcodeproj/project.pbxproj @@ -43,6 +43,11 @@ 275EB4372ACDCC5D0022ED53 /* BLIPIO.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27B330502AB2763B0066C8DA /* BLIPIO.cc */; }; 275EB4402ACDCD660022ED53 /* libBLIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 275EB43F2ACDCC5D0022ED53 /* libBLIP.a */; }; 275EB4412ACDCD760022ED53 /* libBLIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 275EB43F2ACDCC5D0022ED53 /* libBLIP.a */; }; + 277B652C2AD8B5D8006F053D /* betterassert.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277B652B2AD8B5D8006F053D /* betterassert.cc */; }; + 277B652E2AD9FCDC006F053D /* Backtrace+Unix.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277B652D2AD9FCDC006F053D /* Backtrace+Unix.cc */; }; + 277B6C172ADDB6B7006F053D /* ESPTCPSocket.hh in Headers */ = {isa = PBXBuildFile; fileRef = 277B6C152ADDB6B7006F053D /* ESPTCPSocket.hh */; }; + 277B6C292AE06CDA006F053D /* MiniFormat.hh in Headers */ = {isa = PBXBuildFile; fileRef = 277B6C272AE06CDA006F053D /* MiniFormat.hh */; }; + 277B6C2A2AE06CDA006F053D /* MiniFormat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277B6C282AE06CDA006F053D /* MiniFormat.cc */; }; 277E8A412ABA4AA200E78CB1 /* CoCondition.hh in Headers */ = {isa = PBXBuildFile; fileRef = 277E8A3F2ABA4AA200E78CB1 /* CoCondition.hh */; }; 277E8A422ABA4AA200E78CB1 /* CoCondition.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277E8A402ABA4AA200E78CB1 /* CoCondition.cc */; }; 277E8A442ABA50FB00E78CB1 /* LinkedList.hh in Headers */ = {isa = PBXBuildFile; fileRef = 277E8A432ABA50FB00E78CB1 /* LinkedList.hh */; }; @@ -331,6 +336,26 @@ 275EB43F2ACDCC5D0022ED53 /* libBLIP.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBLIP.a; sourceTree = BUILT_PRODUCTS_DIR; }; 277B65272AD739B1006F053D /* Producer.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Producer.hh; sourceTree = ""; }; 277B65282AD766EF006F053D /* Relation.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Relation.hh; sourceTree = ""; }; + 277B652A2AD8B5CA006F053D /* betterassert.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = betterassert.hh; sourceTree = ""; }; + 277B652B2AD8B5D8006F053D /* betterassert.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = betterassert.cc; sourceTree = ""; }; + 277B652D2AD9FCDC006F053D /* Backtrace+Unix.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "Backtrace+Unix.cc"; sourceTree = ""; }; + 277B652F2AD9FD47006F053D /* Backtrace+Windows.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "Backtrace+Windows.cc"; sourceTree = ""; }; + 277B65302AD9FECA006F053D /* Backtrace+ESP32.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "Backtrace+ESP32.cc"; sourceTree = ""; }; + 277B65332ADA07E0006F053D /* sdkconfig */ = {isa = PBXFileReference; lastKnownFileType = text; path = sdkconfig; sourceTree = ""; }; + 277B65362ADA07E0006F053D /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 277B6C122ADA07E3006F053D /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 277B6C132ADA07E3006F053D /* main.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cc; sourceTree = ""; }; + 277B6C142ADA0873006F053D /* ESPEventLoop.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ESPEventLoop.cc; sourceTree = ""; }; + 277B6C152ADDB6B7006F053D /* ESPTCPSocket.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ESPTCPSocket.hh; sourceTree = ""; }; + 277B6C162ADDB6B7006F053D /* ESPTCPSocket.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ESPTCPSocket.cc; sourceTree = ""; }; + 277B6C1A2ADDB904006F053D /* ESPAddrInfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ESPAddrInfo.cc; sourceTree = ""; }; + 277B6C1D2ADDBE82006F053D /* ESPBase.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ESPBase.hh; sourceTree = ""; }; + 277B6C1E2ADDDF13006F053D /* ESPBase.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ESPBase.cc; sourceTree = ""; }; + 277B6C212ADDEEF3006F053D /* Kconfig.projbuild */ = {isa = PBXFileReference; lastKnownFileType = text; path = Kconfig.projbuild; sourceTree = ""; }; + 277B6C232ADF2632006F053D /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 277B6C242ADF2EC4006F053D /* Misc.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Misc.hh; sourceTree = ""; }; + 277B6C272AE06CDA006F053D /* MiniFormat.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MiniFormat.hh; sourceTree = ""; }; + 277B6C282AE06CDA006F053D /* MiniFormat.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MiniFormat.cc; sourceTree = ""; }; 277E8A3F2ABA4AA200E78CB1 /* CoCondition.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CoCondition.hh; sourceTree = ""; }; 277E8A402ABA4AA200E78CB1 /* CoCondition.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CoCondition.cc; sourceTree = ""; }; 277E8A432ABA50FB00E78CB1 /* LinkedList.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = LinkedList.hh; sourceTree = ""; }; @@ -585,6 +610,8 @@ 27B330822AB911470066C8DA /* demo_blipclient.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = demo_blipclient.cc; sourceTree = ""; }; 27B330842AB912D30066C8DA /* BLIP.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BLIP.hh; sourceTree = ""; }; 27BA40742AA502DD0011BB86 /* Result.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Result.hh; sourceTree = ""; }; + 27BCD2CE2AE73844009DFCED /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 27BCD2CF2AE738DF009DFCED /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 27E98EDE2AC2099E002F3D35 /* test_generator.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_generator.cc; sourceTree = ""; }; 27E98EE02AC21940002F3D35 /* Awaitable.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Awaitable.hh; sourceTree = ""; }; 27ED1C702AABF47200ADC971 /* CoroLifecycle.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CoroLifecycle.hh; sourceTree = ""; }; @@ -596,7 +623,7 @@ 27F49CD22A9E48A100BFB24C /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 27F49CD42A9E4E8E00BFB24C /* Backtrace.hh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Backtrace.hh; sourceTree = ""; }; 27F49CD52A9E4E8E00BFB24C /* Backtrace.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Backtrace.cc; sourceTree = ""; }; - 27F49CD82A9E593700BFB24C /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; usesTabs = 1; }; + 27F49CD82A9E593700BFB24C /* Makefile */ = {isa = PBXFileReference; indentWidth = 8; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; usesTabs = 1; }; 27F603022A9EB826006FA1D0 /* IStream.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IStream.hh; sourceTree = ""; }; 27F603032A9EB826006FA1D0 /* IStream.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IStream.cc; sourceTree = ""; }; 27F603062A9FA366006FA1D0 /* NWConnection.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = NWConnection.hh; sourceTree = ""; }; @@ -692,8 +719,8 @@ 279D5D5C2A93BB5C005C3066 /* EventLoop.hh */, 272728FF2A8D6B43000CCA22 /* Future.hh */, 272729002A8D6B43000CCA22 /* Generator.hh */, + 277B6C242ADF2EC4006F053D /* Misc.hh */, 277B65272AD739B1006F053D /* Producer.hh */, - 278F7F222AAA98FE005B12F2 /* Logging.hh */, 2788E3582AC4F8BA00254A88 /* PubSub.hh */, 27B3306C2AB3C3B40066C8DA /* Queue.hh */, 27BA40742AA502DD0011BB86 /* Result.hh */, @@ -752,6 +779,7 @@ 2711C18C2A9965B600361566 /* demo_client.cc */, 27B330822AB911470066C8DA /* demo_blipclient.cc */, 279D5D5D2A951599005C3066 /* Info.plist */, + 277B65322ADA07E0006F053D /* ESP32 */, ); path = tests; sourceTree = ""; @@ -765,7 +793,6 @@ 277E8A472ABB537D00E78CB1 /* Error.cc */, 272730542A8ED3D1000CCA22 /* Future.cc */, 278F7F282AAB9171005B12F2 /* Internal.hh */, - 278F7F232AAA98FE005B12F2 /* Logging.cc */, 272730472A8D8A73000CCA22 /* Scheduler.cc */, 2788E3532AC38B5E00254A88 /* Select.cc */, 2788E3572AC4E34100254A88 /* io */, @@ -1023,13 +1050,77 @@ isa = PBXGroup; children = ( 278F7E5D2AAA69B5005B12F2 /* Base.hh */, + 277B652A2AD8B5CA006F053D /* betterassert.hh */, 27B3306A2AB391F30066C8DA /* Bytes.hh */, 277E8A432ABA50FB00E78CB1 /* LinkedList.hh */, + 278F7F222AAA98FE005B12F2 /* Logging.hh */, 277B65282AD766EF006F053D /* Relation.hh */, + 277B6C272AE06CDA006F053D /* MiniFormat.hh */, ); path = util; sourceTree = ""; }; + 277B65322ADA07E0006F053D /* ESP32 */ = { + isa = PBXGroup; + children = ( + 27BCD2CF2AE738DF009DFCED /* README.md */, + 277B65332ADA07E0006F053D /* sdkconfig */, + 277B65362ADA07E0006F053D /* CMakeLists.txt */, + 277B6C112ADA07E3006F053D /* main */, + ); + path = ESP32; + sourceTree = ""; + }; + 277B6C112ADA07E3006F053D /* main */ = { + isa = PBXGroup; + children = ( + 277B6C122ADA07E3006F053D /* CMakeLists.txt */, + 277B6C212ADDEEF3006F053D /* Kconfig.projbuild */, + 277B6C132ADA07E3006F053D /* main.cc */, + ); + path = main; + sourceTree = ""; + }; + 277B6C222ADF25CD006F053D /* esp32 */ = { + isa = PBXGroup; + children = ( + 27BCD2CE2AE73844009DFCED /* README.md */, + 277B6C232ADF2632006F053D /* CMakeLists.txt */, + 277B6C1D2ADDBE82006F053D /* ESPBase.hh */, + 277B6C1E2ADDDF13006F053D /* ESPBase.cc */, + 277B65302AD9FECA006F053D /* Backtrace+ESP32.cc */, + 277B6C142ADA0873006F053D /* ESPEventLoop.cc */, + 277B6C1A2ADDB904006F053D /* ESPAddrInfo.cc */, + 277B6C152ADDB6B7006F053D /* ESPTCPSocket.hh */, + 277B6C162ADDB6B7006F053D /* ESPTCPSocket.cc */, + ); + path = esp32; + sourceTree = ""; + }; + 277B6C252ADF318A006F053D /* uv */ = { + isa = PBXGroup; + children = ( + 279D5D412A8FE08D005C3066 /* AddrInfo.cc */, + 272730512A8EC61D000CCA22 /* FileStream.cc */, + 278F7E4E2AA2ADFD005B12F2 /* Filesystem.cc */, + 279D5D642A952DD1005C3066 /* Pipe.cc */, + 279D5D602A952986005C3066 /* Stream.cc */, + 279D5D462A902588005C3066 /* TCPServer.cc */, + 2727303F2A8D6F35000CCA22 /* TCPSocket.cc */, + 2727304D2A8E9D99000CCA22 /* UVBase.cc */, + 272730492A8D99B0000CCA22 /* UVInternal.hh */, + ); + path = uv; + sourceTree = ""; + }; + 277B6C262ADF3787006F053D /* uv */ = { + isa = PBXGroup; + children = ( + 2727304C2A8E9D48000CCA22 /* UVBase.hh */, + ); + path = uv; + sourceTree = ""; + }; 2788E3562AC4E31700254A88 /* io */ = { isa = PBXGroup; children = ( @@ -1047,10 +1138,10 @@ 279D5D452A902588005C3066 /* TCPServer.hh */, 2727303E2A8D6F35000CCA22 /* TCPSocket.hh */, 272A85242A96DCB30083D947 /* URL.hh */, - 2727304C2A8E9D48000CCA22 /* UVBase.hh */, 272A852E2A982DE50083D947 /* WebSocket.hh */, 278F7E362AA1168A005B12F2 /* apple */, 278F7E372AA11696005B12F2 /* mbed */, + 277B6C262ADF3787006F053D /* uv */, ); path = io; sourceTree = ""; @@ -1058,27 +1149,20 @@ 2788E3572AC4E34100254A88 /* io */ = { isa = PBXGroup; children = ( - 279D5D412A8FE08D005C3066 /* AddrInfo.cc */, - 272730512A8EC61D000CCA22 /* FileStream.cc */, - 278F7E4E2AA2ADFD005B12F2 /* Filesystem.cc */, 272A85292A97B2090083D947 /* HTTPConnection.cc */, 278F7E592AA93D5A005B12F2 /* HTTPHandler.cc */, 278F7E392AA1489B005B12F2 /* HTTPParser.cc */, 278F7E562AA7EDBF005B12F2 /* ISocket.cc */, 27F603032A9EB826006FA1D0 /* IStream.cc */, - 279D5D642A952DD1005C3066 /* Pipe.cc */, 278F7E522AA7D1BC005B12F2 /* Process.cc */, - 279D5D602A952986005C3066 /* Stream.cc */, - 279D5D462A902588005C3066 /* TCPServer.cc */, - 2727303F2A8D6F35000CCA22 /* TCPSocket.cc */, 272A85252A96DCB30083D947 /* URL.cc */, - 2727304D2A8E9D99000CCA22 /* UVBase.cc */, - 272730492A8D99B0000CCA22 /* UVInternal.hh */, 272A852F2A982DE50083D947 /* WebSocket.cc */, 278F7E4B2AA28642005B12F2 /* WebSocketProtocol.hh */, 278F7E352AA1165C005B12F2 /* apple */, 27B3304C2AB2763B0066C8DA /* blip */, + 277B6C222ADF25CD006F053D /* esp32 */, 278F7E292A9FF3DA005B12F2 /* mbed */, + 277B6C252ADF318A006F053D /* uv */, ); path = io; sourceTree = ""; @@ -1395,11 +1479,16 @@ isa = PBXGroup; children = ( 27F49CD52A9E4E8E00BFB24C /* Backtrace.cc */, + 277B652D2AD9FCDC006F053D /* Backtrace+Unix.cc */, + 277B652F2AD9FD47006F053D /* Backtrace+Windows.cc */, 27F49CD42A9E4E8E00BFB24C /* Backtrace.hh */, + 277B652B2AD8B5D8006F053D /* betterassert.cc */, 2727304B2A8E811B000CCA22 /* Defer.hh */, 27B330692AB388960066C8DA /* Endian.hh */, + 278F7F232AAA98FE005B12F2 /* Logging.cc */, 275A5F132AAFB3B1009791E8 /* Memoized.hh */, 275A5F142AAFB3B1009791E8 /* Memoized.cc */, + 277B6C282AE06CDA006F053D /* MiniFormat.cc */, 278F7E3E2AA24FEE005B12F2 /* StringUtils.hh */, 27B330572AB278870066C8DA /* StringUtils.cc */, ); @@ -1440,6 +1529,7 @@ 277E8A442ABA50FB00E78CB1 /* LinkedList.hh in Headers */, 27B330632AB373430066C8DA /* MessageOut.hh in Headers */, 27B330522AB2763B0066C8DA /* Message.hh in Headers */, + 277B6C172ADDB6B7006F053D /* ESPTCPSocket.hh in Headers */, 27B3305D2AB36F1D0066C8DA /* MessageBuilder.hh in Headers */, 279D5D652A952DD1005C3066 /* Pipe.hh in Headers */, 27F603082A9FA366006FA1D0 /* NWConnection.hh in Headers */, @@ -1453,6 +1543,7 @@ 27B330732AB8FF790066C8DA /* BLIPConnection.hh in Headers */, 277E8A462ABA7A1800E78CB1 /* Error.hh in Headers */, 27F49CD62A9E4E8E00BFB24C /* Backtrace.hh in Headers */, + 277B6C292AE06CDA006F053D /* MiniFormat.hh in Headers */, 27B330532AB2763B0066C8DA /* BLIPIO.hh in Headers */, 27B330682AB384880066C8DA /* Codec.hh in Headers */, 275A5F152AAFB3B1009791E8 /* Memoized.hh in Headers */, @@ -1678,7 +1769,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which missing_includes.rb >/dev/null\nthen\n cd \"$SRCROOT\"\n missing_includes.rb -w --base include/util/Base.hh include src\nelse\n echo \"Script missing_includes.rb not found.\"\n echo \"You can get it from https://gist.github.com/snej/2672fe996d39752e23c471f6ed789958\"\nfi\n"; + shellScript = "if which missing_includes.rb >/dev/null\nthen\n cd \"$SRCROOT\"\n missing_includes.rb -w --base include/util/Base.hh --ignore cassert include src\nelse\n echo \"Script missing_includes.rb not found.\"\n echo \"You can get it from https://gist.github.com/snej/2672fe996d39752e23c471f6ed789958\"\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -1719,9 +1810,12 @@ 2788E3542AC38B5E00254A88 /* Select.cc in Sources */, 278F7F202AAA9212005B12F2 /* bundled_fmtlib_format.cpp in Sources */, 278F7E5B2AA93D5A005B12F2 /* HTTPHandler.cc in Sources */, + 277B652E2AD9FCDC006F053D /* Backtrace+Unix.cc in Sources */, 272730412A8D6F35000CCA22 /* TCPSocket.cc in Sources */, + 277B6C2A2AE06CDA006F053D /* MiniFormat.cc in Sources */, 27F603092A9FA366006FA1D0 /* NWConnection.cc in Sources */, 272730482A8D8A73000CCA22 /* Scheduler.cc in Sources */, + 277B652C2AD8B5D8006F053D /* betterassert.cc in Sources */, 272730532A8EC61D000CCA22 /* FileStream.cc in Sources */, 272A852B2A97B2090083D947 /* HTTPConnection.cc in Sources */, 2727304E2A8E9D99000CCA22 /* UVBase.cc in Sources */, diff --git a/crouton.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/crouton.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 5a43d4b..7b1ec2f 100644 --- a/crouton.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/crouton.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -64,7 +64,11 @@ isEnabled = "YES"> + + @@ -72,7 +76,7 @@ + isEnabled = "NO"> @@ -51,21 +51,21 @@ namespace crouton { bool startNew(coro_handle h) const { if (_scheduler.isCurrent()) { if (_activeCoro == nullptr) { - spdlog::info("Actor {} immediately starting {}", (void*)this, logCoro{h}); + Log->info("Actor {} immediately starting {}", (void*)this, minifmt::write(logCoro{h})); _activeCoro = h; return true; } else { - spdlog::info("Actor {} queued {}", (void*)this, logCoro{h}); + Log->info("Actor {} queued {}", (void*)this, minifmt::write(logCoro{h})); _queue.push_back(h); } } else { _scheduler.onEventLoop([this,h]{ if (_activeCoro == nullptr) { _activeCoro = h; - spdlog::info("Actor {} scheduled ", (void*)this, logCoro{h}); + Log->info("Actor {} scheduled ", (void*)this, minifmt::write(logCoro{h})); _scheduler.schedule(h); } else { - spdlog::info("Actor {} queued ", (void*)this, logCoro{h}); + Log->info("Actor {} queued ", (void*)this, minifmt::write(logCoro{h})); _queue.push_back(h); } }); @@ -82,7 +82,7 @@ namespace crouton { } else { _activeCoro = _queue.front(); _queue.pop_front(); - spdlog::info("Actor {} scheduled ", (void*)this, logCoro{h}); + Log->info("Actor {} scheduled ", (void*)this, minifmt::write(logCoro{h})); _scheduler.schedule(_activeCoro); } } @@ -101,7 +101,7 @@ namespace crouton { explicit ActorMethodImpl(crouton::Actor const& actor, ...) :_actor(const_cast(actor).shared_from_this()) { - spdlog::info("Created ActorMethodImpl {} on const Actor {}", (void*)this, (void*)&actor); + Log->info("Created ActorMethodImpl {} on const Actor {}", (void*)this, (void*)&actor); } explicit ActorMethodImpl(crouton::Actor const* actor, ...) :ActorMethodImpl(*actor) { } diff --git a/include/CoCondition.hh b/include/CoCondition.hh index 752d119..ae60ff5 100644 --- a/include/CoCondition.hh +++ b/include/CoCondition.hh @@ -20,6 +20,7 @@ #include "util/LinkedList.hh" #include "Scheduler.hh" +#include #include #include @@ -32,9 +33,9 @@ namespace crouton { class CoCondition { public: CoCondition() = default; - CoCondition(CoCondition&&) = default; - CoCondition& operator=(CoCondition&&) = default; - ~CoCondition() {assert(_awaiters.empty());} + CoCondition(CoCondition&&) noexcept = default; + CoCondition& operator=(CoCondition&&) noexcept = default; + ~CoCondition() {precondition(_awaiters.empty());} void notifyOne(); @@ -66,6 +67,46 @@ namespace crouton { #pragma mark - BLOCKER: + // base class of Blocker + class BlockerBase { + public: + bool await_ready() noexcept { + return _state.load() == Ready; + } + + coro_handle await_suspend(coro_handle h) noexcept { + _suspension = Scheduler::current().suspend(h); + State curState = Initial; + if (!_state.compare_exchange_strong(curState, Waiting)) { + assert(curState == Ready); + _suspension.wakeUp(); + } + return lifecycle::suspendingTo(h, CRTN_TYPEID(*this), this); + } + + void await_resume() noexcept { + assert(_state.load() == Ready); + } + + void notify() { + State prevState = _state.exchange(Ready); + if (prevState == Waiting) + _suspension.wakeUp(); + //return prevState != Ready; + } + + void reset() { + _state.store(Initial); + } + + protected: + enum State { Initial, Waiting, Ready }; + + Suspension _suspension; + std::atomic _state = Initial; + }; + + /** A simpler way to await a future event. A coroutine that `co_await`s a Blocker will block until something calls the Blocker's `notify` method. This provides an easy way to turn a completion-callback based API into a coroutine-based one: create a Blocker, start @@ -77,16 +118,10 @@ namespace crouton { Blocker supports only one waiting coroutine. If you need more, use a CoCondition. */ template - class Blocker { + class Blocker : public BlockerBase { public: - bool await_ready() noexcept {return _value.has_value();} - - coro_handle await_suspend(coro_handle h) noexcept { - _suspension = Scheduler::current().suspend(h); - return lifecycle::suspendingTo(h, typeid(*this), this); - } - T await_resume() noexcept { + assert(_state == Ready); T result = std::move(_value).value(); _value = std::nullopt; return result; @@ -96,33 +131,15 @@ namespace crouton { void notify(U&& val) { assert(!_value); _value.emplace(std::forward(val)); - _suspension.wakeUp(); + BlockerBase::notify(); } private: - Suspension _suspension; std::optional _value; }; template <> - class Blocker { - public: - bool await_ready() noexcept {return _hasValue;} - - coro_handle await_suspend(coro_handle h) noexcept { - assert(!_suspension); - _suspension = Scheduler::current().suspend(h); - return lifecycle::suspendingTo(h, typeid(*this), this); - } - - void await_resume() noexcept {_hasValue = false;} - - void notify() {_hasValue = true; _suspension.wakeUp();} - - private: - Suspension _suspension; - bool _hasValue = false; - }; + class Blocker : public BlockerBase { }; } diff --git a/include/CoroLifecycle.hh b/include/CoroLifecycle.hh index e017625..d251bb9 100644 --- a/include/CoroLifecycle.hh +++ b/include/CoroLifecycle.hh @@ -20,6 +20,10 @@ #include "util/Base.hh" #include +#if defined(ESP_PLATFORM) && !defined(CROUTON_LIFECYCLES) +#define CROUTON_LIFECYCLES 0 +#endif + // Enable lifecycle tracking in debug builds, by default. Override by defining CROUTON_LIFECYCLES=0 #if !defined(CROUTON_LIFECYCLES) && !defined(NDEBUG) #define CROUTON_LIFECYCLES 1 @@ -41,12 +45,13 @@ namespace crouton { coro_handle suspendingTo(coro_handle cur, coro_handle awaiting, coro_handle next); - coro_handle yieldingTo(coro_handle cur, coro_handle next); + coro_handle yieldingTo(coro_handle cur, coro_handle next, bool isCall); coro_handle finalSuspend(coro_handle cur, coro_handle next); void resume(coro_handle); // calls h.resume() void threw(coro_handle); void returning(coro_handle); void ended(coro_handle); + void destroy(coro_handle); void ignoreInCount(coro_handle); size_t count(); @@ -66,13 +71,16 @@ namespace crouton { inline coro_handle suspendingTo(coro_handle cur, coro_handle awaiting, coro_handle next) {return next;} - inline coro_handle yieldingTo(coro_handle cur, coro_handle next) {return next;} + inline coro_handle yieldingTo(coro_handle cur, coro_handle next, bool) {return next;} inline coro_handle finalSuspend(coro_handle cur, coro_handle next) {return next;} - inline void resume(coro_handle h) {h.resume();} inline void threw(coro_handle) { } inline void returning(coro_handle) { } inline void ended(coro_handle) { } + // These two do something: + inline void resume(coro_handle h) {h.resume();} + inline void destroy(coro_handle h) {h.destroy();} + inline void ignoreInCount(coro_handle) { } inline size_t count() {return 0;} inline size_t stackDepth() {return 0;} @@ -107,5 +115,9 @@ namespace crouton { } #if CROUTON_LIFECYCLES +/** A useful function to call from a debugger: lists all coroutines, their states and owners. */ void dumpCoros(); +/** A useful function to call from a debugger: shows the virtual "stacks" of coroutines, + i.e. which coroutine is blocked awaiting which other coroutine or Awaitable object. */ +void dumpCoroStacks(); #endif diff --git a/include/Coroutine.hh b/include/Coroutine.hh index 521ca1a..af08cf1 100644 --- a/include/Coroutine.hh +++ b/include/Coroutine.hh @@ -32,12 +32,12 @@ namespace crouton { class Coroutine { public: // movable, but not copyable. - Coroutine(Coroutine&& c) :_handle(c._handle) {c._handle = {};} + Coroutine(Coroutine&& c) noexcept :_handle(c._handle) {c._handle = {};} using promise_type = IMPL; // The name `promise_type` is magic here /// Returns my Impl object. - IMPL& impl() {return _handle.promise();} + IMPL& impl() Pure {return _handle.promise();} protected: using handle_type = CORO_NS::coroutine_handle; @@ -57,12 +57,11 @@ namespace crouton { + /** To be returned from a CoroutineImpl's `initial_suspend` method. */ template struct SuspendInitial : public CORO_NS::suspend_always { - SuspendInitial(coro_handle h) :_handle(h) { }; constexpr bool await_ready() const noexcept { return !SUS; } - private: - coro_handle _handle; + void await_suspend(coro_handle cur) const noexcept {lifecycle::suspendInitial(cur);} }; /** To be returned from a CoroutineImpl's `final_suspend` method. @@ -73,7 +72,7 @@ namespace crouton { void await_suspend(coro_handle cur) const noexcept { lifecycle::finalSuspend(cur, nullptr); if constexpr (Destroy) - cur.destroy(); + lifecycle::destroy(cur); } }; @@ -85,7 +84,7 @@ namespace crouton { coro_handle await_suspend(coro_handle cur) const noexcept { auto target = lifecycle::finalSuspend(cur, _target); if constexpr (Destroy) - cur.destroy(); + lifecycle::destroy(cur); return target; } coro_handle _target; @@ -96,7 +95,12 @@ namespace crouton { class CoroutineImplBase { public: CoroutineImplBase() = default; - ~CoroutineImplBase() {lifecycle::ended(_handle);} + + ~CoroutineImplBase() { + // FYI, a coroutine impl (`promise_type`) is destructed when its coroutine handle's + // `destroy` method is called. + lifecycle::ended(_handle); + } coro_handle handle() const {assert(_handle); return _handle;} @@ -141,12 +145,12 @@ namespace crouton { handle_type typedHandle() { auto h = handle_type::from_promise((SELF&)*this); - if (!_handle) registerHandle(h, EAGER, typeid(SELF)); + if (!_handle) registerHandle(h, EAGER, CRTN_TYPEID(SELF)); return h; } // Determines whether the coroutine starts suspended when created, or runs immediately. - SuspendInitial initial_suspend() {return {handle()};} + SuspendInitial initial_suspend() {return {};} }; @@ -165,7 +169,7 @@ namespace crouton { explicit YielderTo() :YielderTo(CORO_NS::noop_coroutine()) { } coro_handle await_suspend(coro_handle cur) noexcept { - return lifecycle::yieldingTo(cur, _consumer); + return lifecycle::yieldingTo(cur, _consumer, true); } private: diff --git a/include/Crouton.hh b/include/Crouton.hh index 51f6f82..3c822f2 100644 --- a/include/Crouton.hh +++ b/include/Crouton.hh @@ -17,14 +17,13 @@ // #pragma once -#include "Actor.hh" -#include "util/Bytes.hh" #include "CoCondition.hh" #include "Error.hh" #include "EventLoop.hh" #include "Future.hh" #include "Generator.hh" -#include "Logging.hh" +#include "Misc.hh" +#include "PubSub.hh" #include "Queue.hh" #include "Result.hh" #include "Scheduler.hh" @@ -32,12 +31,14 @@ #include "Task.hh" #include "io/AddrInfo.hh" -#include "io/FileStream.hh" -#include "io/Filesystem.hh" #include "io/HTTPConnection.hh" #include "io/HTTPHandler.hh" +#include "io/ISocket.hh" #include "io/Process.hh" -#include "io/TCPSocket.hh" -#include "io/TCPServer.hh" #include "io/URL.hh" #include "io/WebSocket.hh" + +#ifndef ESP_PLATFORM +#include "io/FileStream.hh" +#include "io/Filesystem.hh" +#endif diff --git a/include/Error.hh b/include/Error.hh index 729736a..6d548e1 100644 --- a/include/Error.hh +++ b/include/Error.hh @@ -21,12 +21,16 @@ #include #include +#include #include #include #include namespace crouton { + #define SOURCE_LOC std::source_location const& sourceLoc = std::source_location::current() + + /// Numeric base type of error codes. However, Error only uses 18 bits to store the code, /// giving a maximum range of ±131072. /// @note I wanted to use `int16_t`, but some libraries use error codes outside that range, @@ -51,7 +55,7 @@ namespace crouton { template concept ErrorDomain = requires { std::is_enum_v; - requires std::same_as, errorcode_t>; + requires std::convertible_to, errorcode_t>; {ErrorDomainInfo::name} -> std::convertible_to; {ErrorDomainInfo::description} -> std::convertible_to; }; @@ -65,6 +69,15 @@ namespace crouton { /// The default no-error value. Available as the constant `noerror`. constexpr Error() = default; +#if CROUTON_RTTI + using DomainInfo = std::type_info const&; + #define ErrorDomainTypeID(T) typeid(T) +#else + // Without RTTI, identify error domains by the address of their associated description fns. + using DomainInfo = const void*; + #define ErrorDomainTypeID(T) crouton::Error::DomainInfo(&crouton::ErrorDomainInfo::description) +#endif + /// Constructs an Error from an enum value. template Error(D d) :Error(errorcode_t(d), domainID()) { } @@ -81,13 +94,13 @@ namespace crouton { explicit Error(std::exception_ptr); /// The error's code as a plain integer. - errorcode_t code() const {return _code;} + errorcode_t code() const Pure {return _code;} /// The name of the error domain, which comes from `ErrorDomainInfo::name`. - string_view domain() const; + string_view domain() const Pure; /// The `type_info` metadata of the original enum type. - std::type_info const& typeInfo() const; + DomainInfo typeInfo() const Pure; /// A human-readable description of the error. /// First calls `ErrorDomainInfo::description` where `D` is the domain enum. @@ -101,34 +114,40 @@ namespace crouton { friend std::ostream& operator<< (std::ostream&, Error const&); /// True if there is an error, false if none. - explicit operator bool() const {return _code != 0;} + explicit operator bool() const Pure {return _code != 0;} /// True if the error is of type D. template - bool is() const {return typeInfo() == typeid(D);} + Pure bool is() const {return typeInfo() == ErrorDomainTypeID(D);} /// Converts the error code back into a D, if it is one. /// If its type isn't D, returns `D{0}`. template - D as() const {return D{is() ? _code : errorcode_t{0}};} + Pure D as() const {return D{is() ? _code : errorcode_t{0}};} /// Compares two Errors. Their domains and codes must match. friend bool operator== (Error const& a, Error const& b) = default; /// Compares an Error to an ErrorDomain enum value. friend bool operator== (Error const& err, ErrorDomain auto d) { - return err.typeInfo() == typeid(d) && err.code() == errorcode_t(d); + return err.typeInfo() == ErrorDomainTypeID(decltype(d)) && err.code() == errorcode_t(d); } /// Throws the error as an Exception. - [[noreturn]] void raise(string_view logMessage = "") const; + [[noreturn]] void raise(string_view logMessage = "", SOURCE_LOC) const; /// Throws the error as an Exception, if there is one. - void raise_if(string_view logMessage = "") const {if (*this) raise(logMessage);} + void raise_if(string_view logMessage = "", SOURCE_LOC) const + { + if (*this) raise(logMessage, sourceLoc); + } /// Convenience that directly throws an Exception from an ErrorDomain enum. template - [[noreturn]] static void raise(D d, string_view msg = "") {Error(d).raise(msg);} + [[noreturn]] static void raise(D d, string_view msg = "", SOURCE_LOC) + { + Error(d).raise(msg, sourceLoc); + } private: Error(errorcode_t code, uint8_t domain) :_code(code), _domain(domain) {assert(_code == code);} @@ -144,16 +163,16 @@ namespace crouton { template static uint8_t domainID() { - static uint8_t id = _registerDomain(typeid(T), + static uint8_t id = _registerDomain(ErrorDomainTypeID(T), ErrorDomainInfo::name, ErrorDomainInfo::description); return id; } - static uint8_t _registerDomain(std::type_info const&, string_view, ErrorDescriptionFunc); + static uint8_t _registerDomain(DomainInfo, string_view, ErrorDescriptionFunc); struct DomainMeta { - std::type_info const* type {nullptr}; // The C++ type_info of the enum + const void* type {nullptr}; // The C++ type_info of the enum string_view name; // The name of the domain ErrorDescriptionFunc description {nullptr}; // Function mapping codes to names }; @@ -184,6 +203,7 @@ namespace crouton { LogicError, // Something impossible happened due to a bug ParseError, // Syntax error parsing something, like an HTTP stream. Timeout, // Operation failed because it took too long + EndOfData, // Read past end of data in a stream Unimplemented, // Unimplemented functionality or abstract-by-convention method }; @@ -226,4 +246,5 @@ namespace crouton { static string description(errorcode_t); }; +#undef ErrorDomainTypeID } diff --git a/include/EventLoop.hh b/include/EventLoop.hh index 3779e3a..cfea768 100644 --- a/include/EventLoop.hh +++ b/include/EventLoop.hh @@ -22,10 +22,9 @@ #include #include -struct uv_timer_s; - namespace crouton { template class Future; + class Timer; /** Abstract event loop class, owned by a Scheduler. @@ -54,6 +53,8 @@ namespace crouton { protected: virtual ~EventLoop(); + inline void fireTimer(Timer* t); + bool _running = false; }; @@ -85,10 +86,13 @@ namespace crouton { staticASYNC sleep(double delaySecs); private: + friend class EventLoop; void _start(double delaySecs, double repeatSecs); + void _fire(); std::function _fn; - uv_timer_s* _handle = nullptr; + EventLoop* _eventLoop = nullptr; + void* _impl = nullptr; bool _deleteMe = false; }; @@ -108,4 +112,7 @@ namespace crouton { RETURN std::move(result.value()); } + + void EventLoop::fireTimer(Timer* t) {t->_fire();} + } diff --git a/include/Future.hh b/include/Future.hh index cde33cf..1bb9730 100644 --- a/include/Future.hh +++ b/include/Future.hh @@ -74,6 +74,10 @@ namespace crouton { /// @note In `Future`, this constructor takes no parameters. Future(nonvoidT&& v) requires (!std::is_void_v) {_state->setResult(std::move(v));} + /// Creates an already-ready `Future`. + /// @note In `Future`, this constructor takes no parameters. + Future(nonvoidT const& v) requires (!std::is_void_v) {_state->setResult(v);} + /// Creates an already-ready `Future`. Future() requires (std::is_void_v) {_state->setResult();} @@ -116,7 +120,7 @@ namespace crouton { if (this->handle()) return lifecycle::suspendingTo(coro, this->handle(), _state->suspend(coro)); else - return lifecycle::suspendingTo(coro, typeid(this), this, _state->suspend(coro)); + return lifecycle::suspendingTo(coro, CRTN_TYPEID(*this), this, _state->suspend(coro)); } [[nodiscard]] std::add_rvalue_reference_t await_resume() requires (!std::is_void_v) { return std::move(_state->resultValue()); @@ -196,6 +200,7 @@ namespace crouton { template void setResult(U&& value) requires (!std::is_void_v) { _result = std::forward(value); + assert(!_result.empty()); // A Future's result cannot be `noerror` _notify(); } @@ -222,12 +227,10 @@ namespace crouton { Result & result() & {return _result;} std::add_rvalue_reference_t resultValue() requires (!std::is_void_v) { - assert(hasResult()); return std::move(_result).value(); } void resultValue() requires (std::is_void_v) { - assert(hasResult()); _result.value(); } @@ -343,8 +346,8 @@ namespace crouton { template requires(!std::is_void_v) Future Future::then(FN fn) { return _state->template chain([fn](FutureStateBase& baseState, FutureStateBase& myBaseState) { - auto& state = dynamic_cast&>(baseState); - T&& result = dynamic_cast&>(myBaseState).resultValue(); + auto& state = static_cast&>(baseState); + T&& result = static_cast&>(myBaseState).resultValue(); if constexpr (std::is_void_v) { fn(std::move(result)); state.setResult(); diff --git a/include/Generator.hh b/include/Generator.hh index 7939590..0531b6c 100644 --- a/include/Generator.hh +++ b/include/Generator.hh @@ -44,13 +44,13 @@ namespace crouton { template class Generator : public Coroutine>, public ISeries { public: - Generator(Generator&&) = default; + Generator(Generator&&) noexcept = default; ~Generator() { if (auto h = this->handle()) { if (!h.done()) this->impl().stop(); - h.destroy(); + lifecycle::destroy(h); } } @@ -72,7 +72,7 @@ namespace crouton { coro_handle await_suspend(coro_handle cur) override { coro_handle next = this->impl().generateFor(cur); - return lifecycle::suspendingTo(cur, typeid(this), this, next); + return lifecycle::suspendingTo(cur, this->handle(), next); } Result await_resume() override {return this->impl().yieldedValue();} @@ -119,7 +119,7 @@ namespace crouton { void clear() {_yielded_value = noerror;} - bool isReady() const {return _ready;} + bool isReady() const Pure {return _ready;} // Implementation of the public Generator's next() method. Called by non-coroutine code. Result next() { @@ -154,9 +154,9 @@ namespace crouton { // Invoked by the coroutine's `co_yield`. Captures the value and transfers control. template > From> YielderTo yield_value(From&& value) { - assert(!_eof); + precondition(!_eof); _yielded_value = std::forward(value); - assert(!_yielded_value.empty()); + precondition(!_yielded_value.empty()); ready(); auto resumer = _consumer; if (resumer) @@ -200,7 +200,7 @@ namespace crouton { // Tells me which coroutine should resume after I co_yield the next value. coro_handle generateFor(coro_handle consumer) { - assert(!_consumer); // multiple awaiters not supported + precondition(!_consumer); // multiple awaiters not supported _consumer = consumer; clear(); return this->handle(); diff --git a/include/Logging.hh b/include/Logging.hh deleted file mode 100644 index 856c22a..0000000 --- a/include/Logging.hh +++ /dev/null @@ -1,60 +0,0 @@ -// -// Logging.hh -// -// Copyright 2023-Present Couchbase, Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#pragma once -#include "util/Base.hh" - -#include -#include // Makes custom types loggable via `operator <<` overloads - -namespace crouton { - - /* - You can configure the log level(s) by setting the environment variable `SPDLOG_LEVEL`. - For example: - - * Set global level to debug: - `export SPDLOG_LEVEL=debug` - * Turn off all logging except for logger1: - `export SPDLOG_LEVEL="*=off,logger1=debug"` - * Turn off all logging except for logger1 and logger2: - `export SPDLOG_LEVEL="off,logger1=debug,logger2=info"` - */ - - - /// Initializes spdlog logging, sets log levels and creates well-known loggers. - /// Called automatically by `MakeLogger` and `AddSink`. - /// Calling this multiple times has no effect. - void InitLogging(); - - - /// Well-known loggers: - extern std::shared_ptr - LCoro, // Coroutine lifecycle - LSched, // Scheduler - LLoop, // Event-loop - LNet; // Network I/O - - - /// Creates a new spdlog logger. - std::shared_ptr MakeLogger(string_view name, - spdlog::level::level_enum = spdlog::level::info); - - /// Creates a log destination. - void AddSink(spdlog::sink_ptr); -} diff --git a/include/Misc.hh b/include/Misc.hh new file mode 100644 index 0000000..80fa188 --- /dev/null +++ b/include/Misc.hh @@ -0,0 +1,27 @@ +// +// Misc.hh +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once +#include "util/Base.hh" + +namespace crouton { + + /// Writes cryptographically-secure random bytes to the destination buffer. + void Randomize(void* buf, size_t len); + +} diff --git a/include/Producer.hh b/include/Producer.hh index 1b3b6ab..13595a6 100644 --- a/include/Producer.hh +++ b/include/Producer.hh @@ -31,14 +31,14 @@ namespace crouton { template class SeriesProducer { public: - SeriesProducer() :_consumer(this) { } - SeriesProducer(SeriesProducer&&) = default; - SeriesProducer& operator=(SeriesProducer&&) = default; + SeriesProducer() noexcept :_consumer(this) { } + SeriesProducer(SeriesProducer&&) noexcept = default; + SeriesProducer& operator=(SeriesProducer&&) noexcept = default; ~SeriesProducer() {assert(!_consumer);} /// Creates the SeriesConsumer. Can only be called once. std::unique_ptr> make_consumer() { - assert(!_consumer); + precondition(!_consumer); return std::unique_ptr>(new SeriesConsumer(this)); } @@ -52,7 +52,7 @@ namespace crouton { coro_handle await_suspend(coro_handle cur) { assert(!self._suspension); self._suspension = Scheduler::current().suspend(cur); - return lifecycle::suspendingTo(cur, typeid(&self), &self, nullptr); + return lifecycle::suspendingTo(cur, CRTN_TYPEID(self), &self, nullptr); } [[nodiscard]] bool await_resume() { @@ -71,7 +71,7 @@ namespace crouton { /// **Must be awaited.** Suspends until the SeriesConsumer reads the previous value. /// `co_await` returns true if the SeriesConsumer still exists, false if it's been destroyed. [[nodiscard]] AwaitProduce produce(Result value) { - assert(!_consumer || !_consumer->_eof); + precondition(!_consumer || !_consumer->_eof); return AwaitProduce(this, std::move(value)); } @@ -88,17 +88,17 @@ namespace crouton { template class SeriesConsumer final : public ISeries { public: - SeriesConsumer(SeriesConsumer&&) = default; - SeriesConsumer& operator=(SeriesConsumer&&) = default; + SeriesConsumer(SeriesConsumer&&) noexcept = default; + SeriesConsumer& operator=(SeriesConsumer&&) noexcept = default; - bool await_ready() override { - assert(_producer); + bool await_ready() noexcept override { + precondition(_producer); return _hasValue; } coro_handle await_suspend(coro_handle cur) override { _suspension = Scheduler::current().suspend(cur); - return lifecycle::suspendingTo(cur, typeid(this), this, nullptr); + return lifecycle::suspendingTo(cur, CRTN_TYPEID(*this), this, nullptr); } [[nodiscard]] Result await_resume() override { diff --git a/include/PubSub.hh b/include/PubSub.hh index 09cb500..490bd95 100644 --- a/include/PubSub.hh +++ b/include/PubSub.hh @@ -6,7 +6,6 @@ #pragma once #include "Awaitable.hh" -#include "Defer.hh" #include "Generator.hh" #include "Producer.hh" #include "Queue.hh" @@ -79,8 +78,8 @@ namespace crouton::ps { /// Connects the subscriber to a Publisher. virtual void subscribeTo(std::shared_ptr> pub) { - assert(!_publisher); - assert(!_series); + precondition(!_publisher); + precondition(!_series); _publisher = std::move(pub); } @@ -94,7 +93,7 @@ namespace crouton::ps { if (!_task) { SeriesRef series = std::move(_series); if (!series) { - assert(_publisher); + precondition(_publisher); series = _publisher->publish(); } _task.emplace(run(std::move(series))); @@ -108,9 +107,14 @@ namespace crouton::ps { /// @note Before the Subscriber is done, this returns `noerror`. Error error() const {return _error;} - Subscriber(Subscriber&& s) {*this = std::move(s);} - Subscriber& operator=(Subscriber&& s) {assert(!_task); _publisher = std::move(s._publisher); return *this;} - virtual ~Subscriber() {assert(!_task || done());} + Subscriber(Subscriber&& s) noexcept {*this = std::move(s);} + virtual ~Subscriber() {precondition(!_task || done());} + + Subscriber& operator=(Subscriber&& s) noexcept { + precondition(!_task); + _publisher = std::move(s._publisher); + return *this; + } protected: /// Coroutine method that's the lifecycle of the Subscriber. @@ -431,7 +435,7 @@ namespace crouton::ps { bool inEof = !item.ok(); Result out = transform(std::move(item)); eof = !out.ok(); - assert(!inEof || eof); // EOF input has to produce EOF output + assert_always(!inEof || eof); // EOF input has to produce EOF output if (eof) this->handleEnd(out.error()); closed = ! AWAIT _queue.asyncPush(std::move(out)); @@ -458,7 +462,7 @@ namespace crouton::ps { Future timeout = Timer::sleep(_timeout); Select select {&timeout, &series}; select.enable(); - if ((AWAIT select) == 0) { + if (auto which = AWAIT select; which == 0) { this->produce(CroutonError::Timeout); RETURN; } diff --git a/include/Queue.hh b/include/Queue.hh index 9590569..70b0387 100644 --- a/include/Queue.hh +++ b/include/Queue.hh @@ -34,8 +34,8 @@ namespace crouton { class AsyncQueue { public: AsyncQueue() = default; - AsyncQueue(AsyncQueue&&) = default; - AsyncQueue& operator=(AsyncQueue&&) = default; + AsyncQueue(AsyncQueue&&) noexcept = default; + AsyncQueue& operator=(AsyncQueue&&) noexcept = default; virtual ~AsyncQueue() {close();} @@ -160,7 +160,7 @@ namespace crouton { /// Removes an item equal to the given `item`. virtual bool remove(T const& item) { - assert(_state == Open); + precondition(_state == Open); if (auto i = find(item); i != _queue.end()) { _queue.erase(i); return true; @@ -173,7 +173,7 @@ namespace crouton { /// Returns a Generator that will yield items from the queue until it closes. /// @warning Currently, this can only be called once. Generator generate() { - assert(!_generating); + precondition(!_generating); _generating = true; while (_state != Closed) { if (_queue.empty()) { @@ -211,7 +211,7 @@ namespace crouton { public: using super = AsyncQueue; - explicit BoundedAsyncQueue(size_t maxSize) :_maxSize(maxSize) {assert(maxSize > 0); } + explicit BoundedAsyncQueue(size_t maxSize) :_maxSize(maxSize) {precondition(maxSize > 0); } /// True if the queue is at capacity and no items can be pushed. bool full() const {return this->size() >= _maxSize;} diff --git a/include/Result.hh b/include/Result.hh index ddde562..9a9c4a9 100644 --- a/include/Result.hh +++ b/include/Result.hh @@ -32,8 +32,8 @@ namespace crouton { public: using TT = std::conditional_t, std::monostate, T>; - Result() :_value(noerror) { } - Result(Error err) :_value(err) { } + Result() noexcept :_value(noerror) { } + Result(Error err) noexcept :_value(err) { } template requires (!std::is_void_v && std::constructible_from) Result(U&& val) :_value(std::forward(val)) { } @@ -54,24 +54,24 @@ namespace crouton { void set() requires (std::is_void_v) {_value = TT();} /// True if there is a T value. - bool ok() const {return _value.index() == 0;} + bool ok() const noexcept Pure {return _value.index() == 0;} /// True if there is neither a value nor an error. - bool empty() const { + bool empty() const noexcept Pure { Error const* err = std::get_if(&_value); return err && !*err; } /// True if there is an error. - bool isError() const { + bool isError() const noexcept Pure { Error const* err = std::get_if(&_value); return err && *err; } /// True if there's a value, false if empty. If there's an error, raises it. explicit operator bool() const { - if (Error const* err = std::get_if(&_value)) { - if (*err) + if (Error const* err = std::get_if(&_value)) [[unlikely]] { + if (*err) [[unlikely]] err->raise(); return false; } else { @@ -80,7 +80,7 @@ namespace crouton { } /// Returns the error, if any, else `noerror`. - Error error() const { + Error error() const noexcept Pure { Error const* err = std::get_if(&_value); return err ? *err : noerror; } @@ -122,7 +122,7 @@ namespace crouton { private: void raise_if() const { - if (Error const* errp = std::get_if(&_value)) { + if (Error const* errp = std::get_if(&_value)) [[unlikely]] { Error err = *errp ? *errp : CroutonError::EmptyResult; err.raise(); } @@ -133,7 +133,7 @@ namespace crouton { -#if 0 +#if 0 //FIXME: `({...})` is a GCC extension, not standard C++, and MSVC doesn't support it :( /// Syntactic sugar to handle a `Result`, similar to Swift's `try`. /// - If R has a value, evaluates to its value. /// - If R has an error, `co_return`s the error from the current coroutine. @@ -141,9 +141,6 @@ namespace crouton { ({ auto _r_ = (R); \ if (_r_.isError()) co_return _r_.error(); \ std::move(_r_).value(); }) - //FIXME: MSVC doesn't support `({...})`, AFAIK - - /// Syntactic sugar to await a Future without throwing exceptions. /// - If the future returns a value, evaluates to that value. diff --git a/include/Scheduler.hh b/include/Scheduler.hh index 97aa26d..45c9882 100644 --- a/include/Scheduler.hh +++ b/include/Scheduler.hh @@ -91,12 +91,12 @@ namespace crouton { coro_handle await_suspend(coro_handle h) noexcept { _sched->suspend(h); - return lifecycle::suspendingTo(h, typeid(*_sched), _sched, + return lifecycle::suspendingTo(h, CRTN_TYPEID(*_sched), _sched, Scheduler::current().next()); } void await_resume() noexcept { - assert(_sched->isCurrent()); + precondition(_sched->isCurrent()); } private: Scheduler* _sched; @@ -146,7 +146,8 @@ namespace crouton { struct SuspensionImpl; friend class Suspension; - Scheduler() = default; + Scheduler(); + ~Scheduler(); static Scheduler& _create(); EventLoop* newEventLoop(); coro_handle eventLoopHandle(); @@ -163,8 +164,8 @@ namespace crouton { static inline thread_local Scheduler* sCurSched; // Current thread's instance std::deque _ready; // Coroutines that are ready to run - SuspensionMap _suspended; // Suspended/sleeping coroutines - EventLoop* _eventLoop; // My event loop + std::unique_ptr _suspended; // Suspended/sleeping coroutines + EventLoop* _eventLoop = nullptr; // My event loop coro_handle _eventLoopTask = nullptr; // EventLoop's coroutine handle std::atomic _woke = false; // True if a suspended is waking bool _ownsEventLoop = false; // True if I created _eventLoop @@ -178,13 +179,13 @@ namespace crouton { class Suspension { public: /// Default constructor creates an empty/null Suspension. - Suspension() :_impl(nullptr) { } + Suspension() :_impl(nullptr) { } - Suspension(Suspension&& s) :_impl(s._impl) {s._impl = nullptr;} - Suspension& operator=(Suspension&& s) {std::swap(_impl, s._impl); return *this;} - ~Suspension() {if (_impl) cancel();} + Suspension(Suspension&& s) noexcept :_impl(s._impl) {s._impl = nullptr;} + Suspension& operator=(Suspension&& s) noexcept {std::swap(_impl, s._impl); return *this;} + ~Suspension() {if (_impl) cancel();} - explicit operator bool() const {return _impl != nullptr;} + explicit operator bool() const Pure {return _impl != nullptr;} coro_handle handle() const; @@ -202,7 +203,7 @@ namespace crouton { private: friend class Scheduler; - explicit Suspension(Scheduler::SuspensionImpl* impl) :_impl(impl) { }; + explicit Suspension(Scheduler::SuspensionImpl* impl) noexcept :_impl(impl) { }; Suspension(Suspension const&) = delete; Scheduler::SuspensionImpl* _impl; @@ -215,7 +216,7 @@ namespace crouton { struct Yielder : public CORO_NS::suspend_always { coro_handle await_suspend(coro_handle h) noexcept { _handle = h; - return lifecycle::yieldingTo(h, Scheduler::current().yield(h)); + return lifecycle::yieldingTo(h, Scheduler::current().yield(h), false); } void await_resume() noexcept { diff --git a/include/Task.hh b/include/Task.hh index d52d454..296211e 100644 --- a/include/Task.hh +++ b/include/Task.hh @@ -68,6 +68,7 @@ namespace crouton { struct sus : public CORO_NS::suspend_always { void await_suspend(coro_handle h) { Scheduler::current().schedule(h); + lifecycle::suspendInitial(h); } }; return sus{}; diff --git a/include/io/AddrInfo.hh b/include/io/AddrInfo.hh index 5392a14..262746d 100644 --- a/include/io/AddrInfo.hh +++ b/include/io/AddrInfo.hh @@ -19,40 +19,50 @@ #pragma once #include "Future.hh" -struct addrinfo; -struct sockaddr; +#ifdef ESP_PLATFORM + struct ip_addr; +#else + struct addrinfo; + struct sockaddr; +#endif namespace crouton::io { /** An asynchronous DNS lookup. */ class AddrInfo { public: +#ifdef ESP_PLATFORM + using RawAddress = ::ip_addr; +#else + using RawAddress = ::sockaddr; +#endif /// Does a DNS lookup of the given hostname, returning an AddrInfo or an error. staticASYNC lookup(string hostname, uint16_t port =0); /// Returns the primary address, which may be either IPv4 or IPv6. - sockaddr const& primaryAddress() const; + RawAddress const& primaryAddress() const; - /// Returns the primary address of whichever address family you pass. - /// If there is none, throws `UV__EAI_ADDRFAMILY`. + /// Returns (a pointer to) the primary address of whichever address family you pass, + /// or nullptr if none. /// @note For convenience you can also pass 4 instead of AF_INET, or 6 instead of AF_INET6. - sockaddr const& primaryAddress(int af) const; + RawAddress const* primaryAddress(int af) const; /// The primary address converted to a numeric string. string primaryAddressString() const; - ~AddrInfo(); - AddrInfo(AddrInfo&& ai) :_info(ai._info) {ai._info = nullptr;} - AddrInfo& operator=(AddrInfo&& ai); - private: - AddrInfo(struct ::addrinfo* info) :_info(info) { } - AddrInfo(AddrInfo const&) = delete; - AddrInfo& operator=(AddrInfo const&) = delete; - sockaddr const* _primaryAddress(int af) const; +#ifdef ESP_PLATFORM + using addrinfo = ::ip_addr; + using deleter = std::default_delete; +#else + using addrinfo = ::addrinfo; + struct deleter { void operator() (addrinfo*); }; +#endif + + explicit AddrInfo(addrinfo*); - struct ::addrinfo* _info; + std::unique_ptr _info; }; } diff --git a/include/io/FileStream.hh b/include/io/FileStream.hh index 09b5614..d9c03d7 100644 --- a/include/io/FileStream.hh +++ b/include/io/FileStream.hh @@ -20,9 +20,10 @@ #include "io/IStream.hh" #include "Generator.hh" - -namespace crouton::io { +namespace crouton { struct Buffer; +} +namespace crouton::io { /** Asynchronous file I/O. @@ -36,12 +37,12 @@ namespace crouton::io { /// Constructs a FileStream; next, call open(). FileStream(string const& path, int flags = ReadOnly, int mode = 0644); - FileStream(FileStream&& fs); - FileStream& operator=(FileStream&& fs); + FileStream(FileStream&& fs) noexcept; + FileStream& operator=(FileStream&& fs) noexcept; ~FileStream(); /// True if the file is open. - bool isOpen() const override {return _fd >= 0;} + bool isOpen() const override Pure {return _fd >= 0;} /// Resolves once the stream has opened. ASYNC open() override; diff --git a/include/io/HTTPConnection.hh b/include/io/HTTPConnection.hh index 8a3ebd5..4fb91b2 100644 --- a/include/io/HTTPConnection.hh +++ b/include/io/HTTPConnection.hh @@ -81,13 +81,13 @@ namespace crouton::io::http { Response& operator=(Response&&) noexcept; /// The HTTP status code. - Status status() const {return _parser.status;} + Status status() const noexcept Pure {return _parser.status;} /// The HTTP status message. - string const& statusMessage() const {return _parser.statusMessage;} + string const& statusMessage() const noexcept Pure {return _parser.statusMessage;} /// The response headers. - Headers const& headers() const {return _parser.headers;} + Headers const& headers() const noexcept Pure {return _parser.headers;} ASYNC open() override {return _parser.readHeaders();} bool isOpen() const override {return _parser.status != Status::Unknown;} @@ -106,8 +106,8 @@ namespace crouton::io::http { private: Connection* _connection; Parser _parser; - string _buf; - size_t _bufUsed = 0; + string _buf; + size_t _bufUsed = 0; }; } diff --git a/include/io/HTTPParser.hh b/include/io/HTTPParser.hh index 3060e08..4647e5e 100644 --- a/include/io/HTTPParser.hh +++ b/include/io/HTTPParser.hh @@ -123,10 +123,10 @@ namespace crouton::io::http { bool parseData(ConstBytes); /// Returns true if the entire request has been read. - bool complete() const {return _messageComplete;} + bool complete() const noexcept Pure {return _messageComplete;} /// Returns true if the connection has been upgraded to another protocol. - bool upgraded() const {return _upgraded;} + bool upgraded() const noexcept Pure {return _upgraded;} //---- Metadata diff --git a/include/io/ISocket.hh b/include/io/ISocket.hh index 48d2924..8387977 100644 --- a/include/io/ISocket.hh +++ b/include/io/ISocket.hh @@ -28,12 +28,12 @@ namespace crouton::io { class ISocket { public: - /// Creates a new ISocket instance of a default subclass. + /// Factory method: Creates a new ISocket instance of a default subclass. static std::unique_ptr newSocket(bool useTLS); /// Specifies the address and port to connect to, and whether to use TLS. virtual void bind(string const& address, uint16_t port) { - assert(!_binding); + precondition(!_binding); _binding.reset(new binding{address, port}); } diff --git a/include/io/IStream.hh b/include/io/IStream.hh index f42cde2..00f9c02 100644 --- a/include/io/IStream.hh +++ b/include/io/IStream.hh @@ -72,7 +72,7 @@ namespace crouton::io { /// Will always read the full number of bytes unless it hits EOF. ASYNC readString(size_t maxLen); - /// Reads exactly `len` bytes; on eof, throws UVError(UV_EOF). + /// Reads exactly `len` bytes; on EOF, returns/throws an error. ASYNC readExactly(MutableBytes); /// Reads up through the first occurrence of the string `end`, diff --git a/include/io/Process.hh b/include/io/Process.hh index c0e64fe..ec2f1be 100644 --- a/include/io/Process.hh +++ b/include/io/Process.hh @@ -34,23 +34,56 @@ namespace crouton::io { int Main(int argc, const char* argv[], Task (*fn)()); - /** Convenience for defining the program's `main` function. */ +#ifndef ESP_PLATFORM + /** Convenience for defining the program's `main` function. + `FUNC` should be the name of a coroutine function returning `Task` or `Future`. */ #define CROUTON_MAIN(FUNC) \ int main(int argc, const char* argv[]) {return crouton::io::Main(argc, argv, FUNC);} +#else + /** Convenience for defining the firmware's `app_main` function. + `FUNC` should be the name of a coroutine function returning `Task` or `Future`. */ + #define CROUTON_MAIN(FUNC) \ + extern "C" void app_main() {crouton::io::Main(0, nullptr, FUNC);} +#endif - + /** Simple wrapper around command-line arguments. */ class Args : public std::vector { public: + /** The first argument, if any. */ std::optional first() const; + /** Removes and returns the first arg, if any. */ std::optional popFirst(); + /** Removes and returns the first arg, but only if it starts with "-". */ std::optional popFlag(); }; - /// Process arguments, as captured by Main. + + /// The command-line arguments, as captured by `Main`. /// Make a copy if you want to use methods like `popFirst`. extern const Args& MainArgs(); + + /** Information about an output device; currently just color support. */ + struct TTY { + static const TTY out; ///< TTY instance for stdout + static const TTY err; ///< TTY instance for stderr + + explicit TTY(int fd); + + const bool color; ///< True if device supports ANSI color escapes + + // ANSI escape sequences, or empty if no color support: + const char* const bold; + const char* const dim; + const char* const italic; + const char* const underline; + const char* const red; + const char* const yellow; + const char* const green; + const char* const reset; + }; + } diff --git a/include/io/Stream.hh b/include/io/Stream.hh index 6e560fc..aefe243 100644 --- a/include/io/Stream.hh +++ b/include/io/Stream.hh @@ -22,9 +22,10 @@ #include struct uv_stream_s; - -namespace crouton::io { +namespace crouton { struct Buffer; +} +namespace crouton::io { /** An asynchronous bidirectional stream. Abstract base class of Pipe and TCPSocket. */ class Stream : public IStream { @@ -32,7 +33,7 @@ namespace crouton::io { virtual ~Stream(); /// Returns true while the stream is open. - bool isOpen() const override {return _stream != nullptr;} + bool isOpen() const override Pure {return _stream != nullptr;} /// Closes the write stream, leaving the read stream open until the peer closes it. ASYNC closeWrite() override; @@ -43,10 +44,10 @@ namespace crouton::io { //---- READING /// True if the stream has data available to read. - bool isReadable() const; + bool isReadable() const noexcept Pure; /// The number of bytes known to be available without blocking. - size_t bytesAvailable() const; + size_t bytesAvailable() const noexcept Pure; ASYNC readNoCopy(size_t maxLen = 65536) override; ASYNC peekNoCopy() override; @@ -56,7 +57,7 @@ namespace crouton::io { //---- WRITING /// True if the stream has buffer space available to write to. - bool isWritable() const; + bool isWritable() const noexcept Pure; /// Writes as much as possible immediately, without blocking. /// @return Number of bytes written, which may be 0 if the write buffer is full. diff --git a/include/io/TCPServer.hh b/include/io/TCPServer.hh index a6c9cb9..167219d 100644 --- a/include/io/TCPServer.hh +++ b/include/io/TCPServer.hh @@ -17,14 +17,13 @@ // #pragma once -#include "util/Base.hh" +#include "io/TCPSocket.hh" #include struct uv_tcp_s; namespace crouton::io { - class TCPSocket; class TCPServer { public: diff --git a/include/io/TCPSocket.hh b/include/io/TCPSocket.hh index 8beb62e..404f96f 100644 --- a/include/io/TCPSocket.hh +++ b/include/io/TCPSocket.hh @@ -26,7 +26,7 @@ struct uv_tcp_s; namespace crouton::io { /** A TCP socket. (For TLS connections, use TLSSocket or NWConnection.) */ - class TCPSocket : public Stream, public ISocket { + class TCPSocket : public ISocket, public Stream { public: TCPSocket(); @@ -35,7 +35,7 @@ namespace crouton::io { bool isOpen() const override {return Stream::isOpen();} IStream& stream() override {return *this;} - ASYNC close() override {return Stream::close();} + ASYNC close() override {return Stream::close();} protected: friend class TCPServer; diff --git a/include/io/URL.hh b/include/io/URL.hh index 588e936..9c72212 100644 --- a/include/io/URL.hh +++ b/include/io/URL.hh @@ -30,17 +30,17 @@ namespace crouton::io { class URLRef { public: URLRef() = default; - explicit URLRef(const char* str) {parse(str);} + explicit URLRef(const char* str) {parse(str);} explicit URLRef(string const& str) :URLRef(str.c_str()) { } URLRef(string_view scheme, string_view hostname, uint16_t port = 0, string_view path = "/", - string_view query = ""); + string_view query = "") noexcept; /// Parses a URL, updating the properties. Returns false on error. - [[nodiscard]] bool tryParse(const char*); + [[nodiscard]] bool tryParse(const char*) noexcept; /// Parses a URL, updating the properties. Throws CroutonError::InvalidURL on error. void parse(const char*); @@ -58,7 +58,7 @@ namespace crouton::io { string unescapedPath() const {return unescape(path);} /// Returns the value for a key in the query, or "" if not found. - string_view queryValueForKey(string_view key); + string_view queryValueForKey(string_view key) noexcept Pure; /// Recombines the parts back into a URL. Useful if you've changed them. string reencoded() const; @@ -79,7 +79,7 @@ namespace crouton::io { public: explicit URL(string&& str) :URLRef(), _str(std::move(str)) {parse(_str.c_str());} explicit URL(string_view str) :URL(string(str)) { } - explicit URL(const char* str) :URL(string(str)) { } + explicit URL(const char* str) :URL(string(str)) { } URL(string_view scheme, string_view hostname, @@ -87,11 +87,13 @@ namespace crouton::io { string_view path = "/", string_view query = ""); - URL(URL const& url) :URL(url.asString()) { } + URL(URL const& url) :URL(url._str) { } URL& operator=(URL const& url) {_str = url._str; parse(_str.c_str()); return *this;} + URL(URL&& url) noexcept :URL(std::move(url._str)) { } + URL& operator=(URL&& url) {_str = std::move(url._str); parse(_str.c_str()); return *this;} - string const& asString() const {return _str;} - operator string() const {return _str;} + string const& asString() const noexcept Pure {return _str;} + operator string() const {return _str;} void reencode() { _str = reencoded(); diff --git a/include/io/mbed/TLSSocket.hh b/include/io/mbed/TLSSocket.hh index f44beda..fdd9825 100644 --- a/include/io/mbed/TLSSocket.hh +++ b/include/io/mbed/TLSSocket.hh @@ -21,7 +21,7 @@ #include "io/IStream.hh" -namespace crouton::io { +namespace crouton { struct Buffer; } diff --git a/include/io/UVBase.hh b/include/io/uv/UVBase.hh similarity index 81% rename from include/io/UVBase.hh rename to include/io/uv/UVBase.hh index ee1adcd..3753aa5 100644 --- a/include/io/UVBase.hh +++ b/include/io/uv/UVBase.hh @@ -19,17 +19,15 @@ #pragma once #include "Error.hh" -namespace crouton::io { +namespace crouton::io::uv { /** Enum for using libuv errors with Error. */ enum class UVError : errorcode_t { }; - /// Writes cryptographically-secure random bytes to the destination buffer. - void Randomize(void* buf, size_t len); } namespace crouton { - template <> struct ErrorDomainInfo { + template <> struct ErrorDomainInfo { static constexpr string_view name = "libuv"; static string description(errorcode_t); }; diff --git a/include/util/Base.hh b/include/util/Base.hh index fb58fce..6859201 100644 --- a/include/util/Base.hh +++ b/include/util/Base.hh @@ -22,7 +22,7 @@ #include #include #include -#include +#include "util/betterassert.hh" // Find in std or experimental. // Always use `CORO_NS` instead of `std` for coroutine types @@ -46,16 +46,30 @@ #define __has_attribute(x) 0 #endif -#if __has_attribute(__unused__) -#define __unused __attribute__((__unused__)) +#ifndef __unused +#if __has_attribute(unused) +#define __unused __attribute__((unused)) #else #define __unused #endif +#endif + +#if __has_attribute(pure) +#define Pure __attribute__((pure)) +#else +#define Pure +#endif + +#if __cpp_rtti +# define CROUTON_RTTI 1 +#else +# define CROUTON_RTTI 0 +#endif -#if __has_attribute(__pure__) -#define pure __attribute__((__pure__)) +#if CROUTON_RTTI +# define CRTN_TYPEID(T) typeid(T) #else -#define pure +# define CRTN_TYPEID(T) (*(std:: type_info*)-1L) // just a placeholder #endif diff --git a/include/util/Bytes.hh b/include/util/Bytes.hh index 898acf9..ad7d787 100644 --- a/include/util/Bytes.hh +++ b/include/util/Bytes.hh @@ -39,16 +39,18 @@ namespace crouton { using super::span; // inherits all of std::span's constructors using super::operator=; - Bytes(string& str) :super((T*)str.data(), str.size()) { } + Bytes(string& str) noexcept :super((T*)str.data(), str.size()) { } - explicit operator string_view() const {return {(const char*)this->data(), this->size()};} + explicit operator string_view() const noexcept Pure { + return {(const char*)this->data(), this->size()}; + } - Self first(size_t n) const {return Self(super::first(n));} - Self last(size_t n) const {return Self(super::last(n));} - Self without_first(size_t n) const {return last(this->size() - n);} - Self without_last(size_t n) const {return first(this->size() - n);} + Self first(size_t n) const noexcept Pure {return Self(super::first(n));} + Self last(size_t n) const noexcept Pure {return Self(super::last(n));} + Self without_first(size_t n) const noexcept Pure {return last(this->size() - n);} + Self without_last(size_t n) const noexcept Pure {return first(this->size() - n);} - T* endByte() const {return this->data() + this->size();} + T* endByte() const noexcept Pure {return this->data() + this->size();} }; @@ -71,7 +73,7 @@ namespace crouton { ConstBytes(const void* begin, const void* end) :ConstBytes(begin, uintptr_t(end) - uintptr_t(begin)) { } ConstBytes(uv_buf_t); - explicit operator uv_buf_t() const; + explicit operator uv_buf_t() const noexcept; [[nodiscard]] size_t read(void *dstBuf, size_t dstSize) noexcept { size_t n = std::min(dstSize, size()); @@ -112,16 +114,47 @@ namespace crouton { MutableBytes(uv_buf_t); - operator uv_buf_t() const; + operator uv_buf_t() const noexcept Pure; - [[nodiscard]] size_t write(const void* src, size_t len) { + [[nodiscard]] size_t write(const void* src, size_t len) noexcept { size_t n = std::min(len, this->size()); ::memcpy(data(), src, n); *this = without_first(n); return n; } - [[nodiscard]] size_t write(ConstBytes bytes) {return write(bytes.data(), bytes.size());} + [[nodiscard]] size_t write(ConstBytes bytes) noexcept { + return write(bytes.data(), bytes.size()); + } + }; + + + /** A data buffer used by stream_wrapper and Stream. */ + struct Buffer { + static constexpr size_t kCapacity = 65536 - 2 * sizeof(uint32_t); + + uint32_t size = 0; ///< Length of valid data + uint32_t used = 0; ///< Number of bytes consumed (from start of data) + std::byte data[kCapacity]; ///< The data itself + + size_t available() const noexcept Pure {return size - used;} + bool empty() const noexcept Pure {return size == used;} + + ConstBytes bytes() const noexcept Pure {return {data + used, size - used};} + + ConstBytes read(size_t maxLen) { + size_t n = std::min(maxLen, available()); + ConstBytes result(data + used, n); + used += n; + return result; + } + + void unRead(size_t len) { + assert(len <= used); + used -= len; + } }; + using BufferRef = std::unique_ptr; + } diff --git a/include/util/LinkedList.hh b/include/util/LinkedList.hh index dfddac1..49b8b55 100644 --- a/include/util/LinkedList.hh +++ b/include/util/LinkedList.hh @@ -26,12 +26,12 @@ namespace crouton::util { If it's a _private_ base class, you'll need to declare class `LinkList` as a friend. */ class Link { public: - Link() = default; - ~Link() {remove();} + Link() noexcept = default; + ~Link() noexcept {remove();} - Link(Link&& old) {replace(std::move(old));} + Link(Link&& old) noexcept {replace(std::move(old));} - Link& operator=(Link&& old) { + Link& operator=(Link&& old) noexcept { if (this != &old) { remove(); replace(std::move(old)); @@ -40,11 +40,11 @@ namespace crouton::util { } /// True if this Link is in a list. - bool inList() const {return _next != nullptr;} + bool inList() const noexcept Pure {return _next != nullptr;} protected: /// Removes the Link from whatever list it's in. - void remove() { + void remove() noexcept { if (_prev) _prev->_next = _next; if (_next) @@ -55,11 +55,11 @@ namespace crouton::util { private: friend class LinkList; - Link(Link const&) = default; - void clear() {_prev = _next = nullptr;} - void clearHead() {_prev = _next = this;} + Link(Link const&) noexcept = default; + void clear() noexcept {_prev = _next = nullptr;} + void clearHead() noexcept {_prev = _next = this;} - void replace(Link&& old) { + void replace(Link&& old) noexcept { assert(!_prev && !_next); if (old._prev) { _prev = old._prev; @@ -70,7 +70,7 @@ namespace crouton::util { } } - void insertAfter(Link* other) { + void insertAfter(Link* other) noexcept { remove(); _prev = other; _next = other->_next; @@ -78,23 +78,24 @@ namespace crouton::util { _next->_prev = this; } - Link *_prev = nullptr, *_next = nullptr; + Link* _prev = nullptr; + Link* _next = nullptr; }; // Base class of `LinkedList` class LinkList { public: - LinkList() {_head.clearHead();} + LinkList() noexcept {_head.clearHead();} - LinkList(LinkList&& other) { + LinkList(LinkList&& other) noexcept { if (other.empty()) _head.clearHead(); else mvHead(other); } - LinkList& operator=(LinkList&& other) { + LinkList& operator=(LinkList&& other) noexcept { if (&other != this) { clear(); if (!other.empty()) @@ -103,24 +104,24 @@ namespace crouton::util { return *this; } - bool empty() const {return _head._next == &_head;} + bool empty() const noexcept Pure {return _head._next == &_head;} - Link& front() {assert(!empty()); return *_head._next;} - Link& back() {assert(!empty()); return *_head._prev;} + Link& front() noexcept Pure {assert(!empty()); return *_head._next;} + Link& back() noexcept Pure {assert(!empty()); return *_head._prev;} - void push_front(Link& link) {link.insertAfter(&_head);} - void push_back(Link& link) {link.insertAfter(_head._prev);} + void push_front(Link& link) noexcept {link.insertAfter(&_head);} + void push_back(Link& link) noexcept {link.insertAfter(_head._prev);} - Link& pop_front() { - assert(!empty()); + Link& pop_front() noexcept { + precondition(!empty()); auto link = _head._next; link->remove(); return *link; } - void erase(Link& link) {link.remove();} + void erase(Link& link) noexcept {link.remove();} - void clear() { + void clear() noexcept { Link* next; for (Link* link = _head._next; link != &_head; link = next) { next = link->_next; @@ -129,26 +130,26 @@ namespace crouton::util { _head.clearHead(); } - ~LinkList() {clear();} + ~LinkList() noexcept {clear();} - static Link* next(Link* link) {return link->_next;} + static Link* next(Link* link) noexcept {return link->_next;} protected: - Link* _begin() {return _head._next;} - Link* _end() {return &_head;} - Link const* _begin() const {return _head._next;} - Link const* _end() const {return &_head;} + Link* _begin() noexcept Pure {return _head._next;} + Link* _end() noexcept Pure {return &_head;} + Link const* _begin() const noexcept Pure {return _head._next;} + Link const* _end() const noexcept Pure {return &_head;} template - static LINK& downcast(Link& link) {return static_cast(link);} + Pure static LINK& downcast(Link& link) noexcept {return static_cast(link);} template - static Link& upcast(LINK& link) {return static_cast(link);} + Pure static Link& upcast(LINK& link) noexcept {return static_cast(link);} private: LinkList(LinkList const&) = delete; LinkList& operator=(LinkList const&) = delete; - void mvHead(LinkList& other) { + void mvHead(LinkList& other) noexcept { assert(!other.empty()); _head = std::move(other._head); other._head.clearHead(); @@ -165,50 +166,50 @@ namespace crouton::util { public: LinkedList() = default; - LinkedList(LinkedList&& other) :LinkList(std::move(other)) { } + LinkedList(LinkedList&& other) noexcept :LinkList(std::move(other)) { } - LinkedList& operator=(LinkedList&& other) { + LinkedList& operator=(LinkedList&& other) noexcept { LinkList::operator=(std::move(other)); return *this; } - bool empty() const {return LinkList::empty();} + bool empty() const noexcept Pure {return LinkList::empty();} - LINK& front() {return downcast(LinkList::front());} - LINK& back() {return downcast(LinkList::back());} + LINK& front() noexcept Pure {return downcast(LinkList::front());} + LINK& back() noexcept Pure {return downcast(LinkList::back());} - void push_front(LINK& link) {LinkList::push_front(upcast(link));} - void push_back(LINK& link) {LinkList::push_back(upcast(link));} + void push_front(LINK& link) noexcept {LinkList::push_front(upcast(link));} + void push_back(LINK& link) noexcept {LinkList::push_back(upcast(link));} - LINK& pop_front() {return downcast(LinkList::pop_front());} + LINK& pop_front() noexcept {return downcast(LinkList::pop_front());} - void erase(LINK& link) {LinkList::erase(upcast(link));} + void erase(LINK& link) noexcept {LinkList::erase(upcast(link));} - void clear() {LinkList::clear();} + void clear() noexcept {LinkList::clear();} template class Iterator { public: - T& operator*() const {return LinkList::downcast(*_link);} - T* operator->() const {return &LinkList::downcast(*_link);} - Iterator& operator++() {_link = next(_link); return *this;} + T& operator*() const noexcept Pure {return LinkList::downcast(*_link);} + T* operator->() const noexcept Pure {return &LinkList::downcast(*_link);} + Iterator& operator++() noexcept {_link = next(_link); return *this;} - friend bool operator==(Iterator const& a, Iterator const& b) { + friend bool operator==(Iterator const& a, Iterator const& b) noexcept Pure { return a._link == b._link; } private: friend class LinkedList; - explicit Iterator(Link const* link) :_link(const_cast(link)) { } + explicit Iterator(Link const* link) noexcept :_link(const_cast(link)) { } Link* _link; }; using iterator = Iterator; using const_iterator = Iterator; - iterator begin() {return iterator(_begin());} - iterator end() {return iterator(_end());} - const_iterator begin() const {return const_iterator(_begin());} - const_iterator end() const {return const_iterator(_end());} + iterator begin() noexcept Pure {return iterator(_begin());} + iterator end() noexcept Pure {return iterator(_end());} + const_iterator begin() const noexcept Pure {return const_iterator(_begin());} + const_iterator end() const noexcept Pure {return const_iterator(_end());} }; } diff --git a/include/util/Logging.hh b/include/util/Logging.hh new file mode 100644 index 0000000..cc8f461 --- /dev/null +++ b/include/util/Logging.hh @@ -0,0 +1,137 @@ +// +// Logging.hh +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once +#include "util/Base.hh" +#include "util/MiniFormat.hh" + +// By default, Crouton uses its own small logging library. +// To make it use spdlog instead, pre-define the macro `CROUTON_USE_SPDLOG` to `1`. +#ifndef CROUTON_USE_SPDLOG +#define CROUTON_USE_SPDLOG 0 +#endif + +#if CROUTON_USE_SPDLOG +#include +#include // Makes custom types loggable via `operator <<` overloads +#endif + +namespace crouton { + +#if CROUTON_USE_SPDLOG + using LoggerRef = spdlog::logger*; + namespace LogLevel = spdlog::level; + using LogLevelType = spdlog::level::level_enum; +#else + namespace LogLevel { + enum level_enum { + trace, debug, info, warn, err, critical, off + }; + }; + using LogLevelType = LogLevel::level_enum; + + class Logger { + public: + Logger(string name, LogLevelType level) :_name(std::move(name)), _level(level) { } + + LogLevelType level() const Pure {return _level;} + void set_level(LogLevelType level) {_level = level;} + bool should_log(LogLevelType level) const Pure {return level >= _level;} + + template + void log(LogLevelType lvl, string_view fmt, Args &&...args) { + if (should_log(lvl)) [[unlikely]] + _log(lvl, fmt, minifmt::FmtIDs::ids, minifmt::passArg(args)...); + } + + void log(LogLevelType lvl, string_view msg); + + template + void trace(string_view fmt, Args &&...args) { + log(LogLevel::trace, fmt, std::forward(args)...); + } + template + void debug(string_view fmt, Args &&...args) { + log(LogLevel::debug, fmt, std::forward(args)...); + } + template + void info(string_view fmt, Args &&...args) { + log(LogLevel::info, fmt, std::forward(args)...); + } + template + void warn(string_view fmt, Args &&...args) { + log(LogLevel::warn, fmt, std::forward(args)...); + } + template + void error(string_view fmt, Args &&...args) { + log(LogLevel::err, fmt, std::forward(args)...); + } + template + void critical(string_view fmt, Args &&...args) { + log(LogLevel::critical, fmt, std::forward(args)...); + } + + private: + void _log(LogLevelType, string_view fmt, minifmt::FmtIDList, ...); + void _writeHeader(LogLevelType); + + string const _name; + LogLevelType _level; + }; + + using LoggerRef = Logger*; +#endif + + /* + You can configure the log level(s) by setting the environment variable `SPDLOG_LEVEL`. + For example: + + * Set global level to debug: + `export SPDLOG_LEVEL=debug` + * Turn off all logging except for logger1: + `export SPDLOG_LEVEL="*=off,logger1=debug"` + * Turn off all logging except for logger1 and logger2: + `export SPDLOG_LEVEL="off,logger1=debug,logger2=info"` + + For much more information about spdlog, see docs at https://github.com/gabime/spdlog/ + */ + + + /// Initializes spdlog logging, sets log levels and creates well-known loggers. + /// Called automatically by `MakeLogger` and `AddSink`. + /// Calling this multiple times has no effect. + void InitLogging(); + + + /// Well-known loggers: + extern LoggerRef + Log, // Default logger + LCoro, // Coroutine lifecycle + LSched, // Scheduler + LLoop, // Event-loop + LNet; // Network I/O + + + /// Creates a new spdlog logger. + LoggerRef MakeLogger(string_view name, LogLevelType = LogLevel::info); + +#if CROUTON_USE_SPDLOG + /// Creates a log destination. + void AddSink(spdlog::sink_ptr); +#endif +} diff --git a/include/util/MiniFormat.hh b/include/util/MiniFormat.hh new file mode 100644 index 0000000..800ea14 --- /dev/null +++ b/include/util/MiniFormat.hh @@ -0,0 +1,147 @@ +// +// MiniFormat.hh +// +// +// + +#pragma once +#include +#include +#include +#include +#include +#include + +/* + A string formatting API somewhat compatible with `std::format`, but optimized for small code size. + */ + +namespace crouton::minifmt { + using std::string; + using std::string_view; + + class write; + + + namespace i { + // Enumeration identifying all formattable types. + enum class FmtID : uint8_t { + None = 0, + Bool, + Char, + Int, + UInt, + Long, + ULong, + LongLong, + ULongLong, + Double, + CString, + Pointer, + String, + StringView, + Write, + }; + + using enum FmtID; + + // This maps types to FmtID values. Every formattable type needs an entry here. + template struct Formatting { }; + template<> struct Formatting { static constexpr FmtID id = Bool; }; + template<> struct Formatting { static constexpr FmtID id = Char; }; + template<> struct Formatting { static constexpr FmtID id = UInt; }; + template<> struct Formatting { static constexpr FmtID id = Int; }; + template<> struct Formatting { static constexpr FmtID id = UInt; }; + template<> struct Formatting { static constexpr FmtID id = Int; }; + template<> struct Formatting { static constexpr FmtID id = UInt; }; + template<> struct Formatting { static constexpr FmtID id = Long; }; + template<> struct Formatting { static constexpr FmtID id = ULong; }; + template<> struct Formatting { static constexpr FmtID id = LongLong; }; + template<> struct Formatting{ static constexpr FmtID id = ULongLong; }; + template<> struct Formatting { static constexpr FmtID id = Double; }; + template<> struct Formatting { static constexpr FmtID id = Double; }; + template<> struct Formatting { static constexpr FmtID id = CString; }; + template<> struct Formatting { static constexpr FmtID id = CString; }; + template<> struct Formatting { static constexpr FmtID id = Pointer; }; + template<> struct Formatting { static constexpr FmtID id = String; }; + template<> struct Formatting { static constexpr FmtID id = StringView; }; + template<> struct Formatting { static constexpr FmtID id = Write; }; + + // Returns the FmtID value corresponding to the type of its argument. + template + consteval FmtID getFmtID(T&&) { return Formatting>::id; } + } + + // Transforms args before they're passed to `format`. + // Makes sure non-basic types, like `std::string`, are passed by pointer. + template auto passArg(T t) {return t;} + template auto passArg(T t) {return t;} + template auto passArg(T const* t) {return t;} + template auto passArg(T* t) {return t;} + template auto passArg(T const& t) + requires (!std::integral && !std::floating_point) {return &t;} + + + /** The concept `Formattable` defines what types can be passed as args to `format`. */ + template + concept Formattable = requires { i::Formatting>::id; }; + + // FmtIDs<...>::ids is a C array of the FmtIDs corresponding to the template argument types. + template + struct FmtIDs { + static constexpr i::FmtID ids[] {i::Formatting>::id... , i::FmtID::None}; + }; + using FmtIDList = i::FmtID const*; + + void format_types(std::ostream&, string_view fmt, FmtIDList types, ...); + void vformat_types(std::ostream&, string_view fmt, FmtIDList types, va_list); + string format_types(string_view fmt, FmtIDList types, ...); + string vformat_types(string_view fmt, FmtIDList types, va_list); + + + /** Writes formatted output to an ostream. + @param out The stream to write to. + @param fmt Format string, with `{}` placeholders for args. + @param args Arguments; any type satisfying `Formattable`. */ + template + void format(std::ostream& out, string_view fmt, Args &&...args) { + format_types(out, fmt, FmtIDs::ids, passArg(args)...); + } + + + /** Returns a formatted string.. + @param fmt Format string, with `{}` placeholders for args. + @param args Arguments; any type satisfying `Formattable`. */ + template + string format(string_view fmt, Args &&...args) { + return format_types(fmt, FmtIDs::ids, passArg(args)...); + } + + + /** Struct that can be wrapped around an argument to `format()`. + Works with any type that can be written to an ostream with `<<`. */ + class write { + public: + template + write(T &&value) + :_ptr(reinterpret_cast(&value)) + ,_write(&writeFn>) + { } + + // for compatibility with std::fmt or spdlog, this object can itself be written + friend std::ostream& operator<< (std::ostream& out, write const& f) { + f._write(out, f._ptr); + return out; + } + private: + template + static void writeFn(std::ostream& out, const void* ptr) { + out << *(const T*)ptr; + } + + const void* _ptr; // address of value -- type-erased T* + void (*_write)(std::ostream&, const void*); // pointer to writeFn() + }; + + +} diff --git a/include/util/Relation.hh b/include/util/Relation.hh index 812abb0..bcae300 100644 --- a/include/util/Relation.hh +++ b/include/util/Relation.hh @@ -27,16 +27,16 @@ namespace crouton::util { template class Child { public: - explicit Child(Self* self) + explicit Child(Self* self) noexcept :_selfOffset(unsigned(uintptr_t(this) - uintptr_t(self))) { assert((void*)this >= self && (void*)(this + 1) <= self + 1); } - Self* self() {return (Self*)(uintptr_t(this) - _selfOffset);} + Self* self() noexcept Pure {return (Self*)(uintptr_t(this) - _selfOffset);} private: - unsigned const _selfOffset; // My byte offset within my Self instance + unsigned const _selfOffset; // My byte offset within my Self instance }; @@ -53,17 +53,17 @@ namespace crouton::util { class OneToOne : private Child { public: /// Initializes an unconnected OneToOne. This should be a member initializer of Self. - explicit OneToOne(Self* self) :Child(self) { } + explicit OneToOne(Self* self) noexcept :Child(self) { } /// Initializes a connected OneToOne. This should be a member initializer of Self. - OneToOne(Self* self, OneToOne* other) + OneToOne(Self* self, OneToOne* other) noexcept :OneToOne(self) { _other = other; hookup(); } - OneToOne(OneToOne&& old) + OneToOne(OneToOne&& old) noexcept :Child(old) ,_other(old._other) { @@ -71,14 +71,14 @@ namespace crouton::util { hookup(); } - OneToOne& operator=(OneToOne&& old) { + OneToOne& operator=(OneToOne&& old) noexcept { _other = old._other; old._other = nullptr; hookup(); } /// Connects to an `Other` object, or none. Breaks any existing link. - OneToOne& operator= (OneToOne* b) { + OneToOne& operator= (OneToOne* b) noexcept { if (b != _other) { unhook(); _other = b; @@ -88,12 +88,12 @@ namespace crouton::util { } /// A pointer to the target `Other` object. May be nullptr. - Other* other() const {return _other ? _other->self() : nullptr;} + Other* other() const noexcept Pure {return _other ? _other->self() : nullptr;} - operator Other*() const {return other();} - Other* operator->() const {return other();} + operator Other*() const noexcept Pure {return other();} + Other* operator->() const noexcept Pure {return other();} - ~OneToOne() {unhook();} + ~OneToOne() noexcept {unhook();} private: friend class OneToOne; @@ -101,12 +101,12 @@ namespace crouton::util { OneToOne(OneToOne const&) = delete; OneToOne& operator=(OneToOne const&) = delete; - void hookup() { + void hookup() noexcept { if (_other) _other->_other = this; } - void unhook() { + void unhook() noexcept { if (_other) { assert(_other->_other == this); _other->_other = nullptr; @@ -130,15 +130,15 @@ namespace crouton::util { using super = LinkedList>; /// Initializes an unconnected Child. This should be a member initializer of Self. - explicit ToMany(Self* self) :Child(self) { } + explicit ToMany(Self* self) noexcept :Child(self) { } - ToMany(ToMany&& other) + ToMany(ToMany&& other) noexcept :super(std::move(other)) { adopt(); } - ToMany& operator=(ToMany&& other) { + ToMany& operator=(ToMany&& other) noexcept { if (&other != this) { super::operator=(std::move(other)); adopt(); @@ -150,34 +150,34 @@ namespace crouton::util { class iterator { public: - explicit iterator(typename super::iterator i) :_i(i) { } - Other& operator*() const {return *(_i->self());} - Other* operator->() const {return _i->self();} - iterator& operator++() {++_i; return *this;} - friend bool operator==(iterator const& a, iterator const& b) {return a._i == b._i;} + explicit iterator(typename super::iterator i) noexcept :_i(i) { } + Other& operator*() const noexcept Pure {return *(_i->self());} + Other* operator->() const noexcept Pure {return _i->self();} + iterator& operator++() noexcept {++_i; return *this;} + friend bool operator==(iterator const& a, iterator const& b) Pure {return a._i == b._i;} private: typename super::iterator _i; }; - iterator begin() {return iterator(super::begin());} - iterator end() {return iterator(super::end());} + iterator begin() noexcept Pure {return iterator(super::begin());} + iterator end() noexcept Pure {return iterator(super::end());} - void push_front(ToOne& link) {super::push_front(link); link._parent = this;} - void push_back(ToOne& link) {super::push_back(link); link._parent = this;} - void erase(ToOne& link) {super::erase(link); link._parent = nullptr;} + void push_front(ToOne& link) noexcept {super::push_front(link); link._parent = this;} + void push_back(ToOne& link) noexcept {super::push_back(link); link._parent = this;} + void erase(ToOne& link) noexcept {super::erase(link); link._parent = nullptr;} - void clear() {deAdopt(); super::clear();} + void clear() noexcept {deAdopt(); super::clear();} - ~ToMany() {deAdopt();} + ~ToMany() noexcept {deAdopt();} private: friend ToOne; - void adopt() { + void adopt() noexcept { for (ToOne& child : (super&)*this) child._parent = this; } - void deAdopt() { + void deAdopt() noexcept { for (ToOne& child : (super&)*this) child._parent = nullptr; } @@ -195,10 +195,10 @@ namespace crouton::util { class ToOne : private Link, private Child { public: /// Initializes an unconnected instance. This should be a member initializer of Self. - explicit ToOne(Self* self) :Child(self) { } + explicit ToOne(Self* self) noexcept :Child(self) { } /// Initializes a connected instance. This should be a member initializer of Self. - ToOne(Self* self, ToMany* other) + ToOne(Self* self, ToMany* other) noexcept :Child(self) ,_parent(other) { @@ -206,7 +206,7 @@ namespace crouton::util { _parent->push_back(*this); } - ToOne(ToOne&& old) + ToOne(ToOne&& old) noexcept :Link(std::move(old)) ,Child(old) ,_parent(old._parent) @@ -214,14 +214,14 @@ namespace crouton::util { old._parent = nullptr; } - ToOne& operator=(ToOne&& old) { + ToOne& operator=(ToOne&& old) noexcept { this->Link::operator=(std::move(old)); _parent = old._parent; old._parent = nullptr; } /// Connects to an `Other` object, or none. Breaks any existing link. - ToOne& operator= (ToMany* parent) { + ToOne& operator= (ToMany* parent) noexcept { if (parent != _parent) { remove(); _parent = parent; @@ -232,7 +232,7 @@ namespace crouton::util { } /// A pointer to the target `Other` object. May be nullptr. - Other* other() const {return _parent ? _parent->self() : nullptr;} + Other* other() const noexcept Pure {return _parent ? _parent->self() : nullptr;} private: friend class ToMany; diff --git a/include/util/betterassert.hh b/include/util/betterassert.hh new file mode 100644 index 0000000..576556b --- /dev/null +++ b/include/util/betterassert.hh @@ -0,0 +1,80 @@ +// +// betterassert.hh +// +// Copyright © 2018 Couchbase. All rights reserved. +// Updated Sept 2022 by Jens Alfke to remove exception throwing +// + +// This is an alternate implementation of `assert()` that produces a nicer message that includes +// the function name, and calls `std::terminate` instead of `abort`. +// NOTE: If / is included later than this header, it will overwrite this +// definition of assert() with the usual one. (And vice versa.) + +// `assert_always()`, `precondition()`, and `postcondition()` do basically the same thing: +// if the boolean parameter is false, they log a message (to stderr) and terminate the process. +// They differ only in the message logged. +// +// * `precondition()` should be used at the start of a function/method to test its parameters +// or initial state. A failure should be interpreted as a bug in the method's _caller_. +// * `postcondition()` should be used at the end of a function/method to test its return value +// or final state. A failure should be interpreted as a bug in the _method_. +// * `assert_always()` can be used in between to test intermediate state or results. +// A failure may be a bug in the method, or in something it called. +// +// These are enabled in all builds regardless of the `NDEBUG` flag. + +#ifndef assert_always + #include + + #ifndef __has_attribute + #define __has_attribute(x) 0 + #endif + #ifndef __has_builtin + #define __has_builtin(x) 0 + #endif + + #if __has_attribute(noinline) + #define NOINLINE __attribute((noinline)) + #else + #define NOINLINE + #endif + + #define assert_always(e) \ + do { if (!(e)) [[unlikely]] ::crouton::_assert_failed (#e); } while (0) + #define precondition(e) \ + do { if (!(e)) [[unlikely]] ::crouton::_precondition_failed (#e); } while (0) + #define postcondition(e) \ + do { if (!(e)) [[unlikely]] ::crouton::_postcondition_failed (#e); } while (0) + + namespace crouton { + [[noreturn]] NOINLINE void _assert_failed(const char *cond, + std::source_location const& = std::source_location::current()) noexcept; + [[noreturn]] NOINLINE void _precondition_failed(const char *cond, + std::source_location const& = std::source_location::current()) noexcept; + [[noreturn]] NOINLINE void _postcondition_failed(const char *cond, + std::source_location const& = std::source_location::current()) noexcept; + + extern void (*assert_failed_hook)(const char *message); + } +#endif // assert_always + +// `assert()`, `assert_precondition()`, and `assert_postcondition()` are just like the macros +// above, except that they are disabled when `NDEBUG` is defined. They should be used when the +// evaluation of the expression would hurt performance in a release build. + +#undef assert +#undef assert_precondition +#undef assert_postcondition +#ifdef NDEBUG +# if __has_builtin(__builtin_assume) +# define assert(e) __builtin_assume(bool(e)) +# else +# define assert(e) (void(0)) +# endif +# define assert_precondition(e) assert(e) +# define assert_postcondition(e) assert(e) +#else +# define assert(e) assert_always(e) +# define assert_precondition(e) precondition(e) +# define assert_postcondition(e) postcondition(e) +#endif //NDEBUG diff --git a/src/CoCondition.cc b/src/CoCondition.cc index d0c37b9..9e10d83 100644 --- a/src/CoCondition.cc +++ b/src/CoCondition.cc @@ -17,7 +17,7 @@ // #include "CoCondition.hh" -#include "Logging.hh" +#include "util/Logging.hh" namespace crouton { @@ -37,15 +37,15 @@ namespace crouton { coro_handle CoCondition::awaiter::await_suspend(coro_handle h) noexcept { _suspension = Scheduler::current().suspend(h); LSched->debug("CoCondition {}: suspending {}", - (void*)this, logCoro{h}); + (void*)this, minifmt::write(logCoro{h})); _cond->_awaiters.push_back(*this); - return lifecycle::suspendingTo(h, typeid(*_cond), _cond); + return lifecycle::suspendingTo(h, CRTN_TYPEID(*_cond), _cond); } void CoCondition::awaiter::wakeUp() { LSched->debug("CoCondition {}: waking {}", - (void*)_cond, logCoro{_suspension.handle()}); + (void*)_cond, minifmt::write(logCoro{_suspension.handle()})); _suspension.wakeUp(); } diff --git a/src/CoroLifecycle.cc b/src/CoroLifecycle.cc index b72e544..7f37d08 100644 --- a/src/CoroLifecycle.cc +++ b/src/CoroLifecycle.cc @@ -17,10 +17,11 @@ // #include "CoroLifecycle.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "Memoized.hh" #include "Scheduler.hh" #include +#include #include #include #include @@ -40,6 +41,11 @@ namespace crouton::lifecycle { static constexpr const char* kStateNames[] = {"born", "active", "awaiting", "yielding", "ending"}; + // If true, the sCoros table will remember destroyed/deleted coroutines, so if a destroyed + // coroutine handle is accessed it can tell you its former sequence number and owner. + // The downside is that the table uses more memory over time since it never shrinks. + static constexpr bool kRememberDestroyedCoros = true; + static unsigned sLastSequence = 0; static string_view getCoroTypeName(std::type_info const& type) { @@ -62,9 +68,12 @@ namespace crouton::lifecycle { { } coroInfo(coroInfo const&) = delete; + coroInfo& operator=(coroInfo const&) = delete; + coroInfo(coroInfo&&) = default; + coroInfo& operator=(coroInfo&&) = default; void setState(coroState s) { - if (state == coroState::awaiting) { + if (state == coroState::awaiting || state == coroState::yielding) { awaitingCoro = nullptr; awaiting = nullptr; awaitingType = nullptr; @@ -76,7 +85,7 @@ namespace crouton::lifecycle { return out << "¢" << info.sequence; } - coro_handle handle; // Its handle + coro_handle handle; // Its handle; nullptr if tombstone coroState state = coroState::born; // Current state coroInfo* caller = nullptr; // Caller, if on stack coro_handle awaitingCoro; // Coro it's awaiting @@ -105,15 +114,22 @@ namespace crouton::lifecycle { static unordered_map sCoros; // Maps coro_handle -> coroInfo /// Gets a coro_handle's coroInfo. Mutex must be locked. - static coroInfo& _getInfo(coro_handle h) { + static auto _getInfoIter(coro_handle h) { auto i = sCoros.find(h.address()); if (i == sCoros.end()) { - LCoro->critical("Unknown coroutine_handle {}", h.address()); + LCoro->critical("FATAL: Unknown coroutine_handle {}", h.address()); + abort(); + } else if (i->second.handle == nullptr) { + LCoro->critical("FATAL: Using destroyed coroutine_handle {}; formerly ¢{} [{} {}]", + h.address(), i->second.sequence, i->second.typeName, i->second.name); abort(); + } else { + return i; } - return i->second; } + static coroInfo& _getInfo(coro_handle h) {return _getInfoIter(h)->second;} + /// Gets a coro_handle's coroInfo. static coroInfo& getInfo(coro_handle h) { unique_lock lock(sCorosMutex); @@ -134,7 +150,7 @@ namespace crouton::lifecycle { static size_t _count() { size_t n = 0; for (auto &[addr, info] : sCoros) { - if (!info.ignoreInCount) + if (info.handle && !info.ignoreInCount) ++n; } return n; @@ -156,7 +172,7 @@ namespace crouton::lifecycle { /// Logs the coroutines in the current thread's stack. static void logStack() { - if (LCoro->should_log(spdlog::level::trace)) { + if (LCoro->should_log(LogLevel::trace)) { stringstream out; for (auto c = tCurrent; c; c = c->caller) out << ' ' << *c; @@ -167,7 +183,7 @@ namespace crouton::lifecycle { /// Returns the coroutine this one called. static coroInfo* _currentCalleeOf(coroInfo &caller) { for (auto &[h, info] : sCoros) { - if (info.caller == &caller) + if (info.caller == &caller && info.handle) return &info; } return nullptr; @@ -176,7 +192,7 @@ namespace crouton::lifecycle { /// Asserts that this is the current coroutine (of this thread.) static void assertCurrent(coroInfo &h) { if (tCurrent != &h) { - LCoro->warn("??? Expected {} to be current, not {}", *tCurrent, h); + LCoro->warn("??? Expected {} to be current, not {}", minifmt::write(*tCurrent), minifmt::write(h)); assert(tCurrent == &h); } } @@ -225,31 +241,41 @@ namespace crouton::lifecycle { void created(coro_handle h, bool ready, std::type_info const& implType) { + precondition(h); unique_lock lock(sCorosMutex); auto [i, added] = sCoros.try_emplace(h.address(), h, implType); - assert(added); + if (kRememberDestroyedCoros) { + if (!added) { + // Reusing a tombstone when a new coro is allocated at the same address: + assert(!i->second.handle); + i->second = coroInfo{h, implType}; + } + } else { + assert(added); + } lock.unlock(); + if (ready) { i->second.setState(coroState::active); - LCoro->debug("{} created and starting", verbose{i->second}); + LCoro->debug("{} created and starting", minifmt::write(verbose{i->second})); pushCurrent(i->second); } else { - LCoro->debug("{} created", verbose{i->second}); + LCoro->debug("{} created", minifmt::write(verbose{i->second})); } } void suspendInitial(coro_handle cur) { auto& curInfo = getInfo(cur); - LCoro->trace("{} initially suspended", curInfo); - - popCurrent(curInfo); - assert(curInfo.state == coroState::active); - curInfo.setState(coroState::born); + LCoro->trace("{} initially suspended", minifmt::write(curInfo)); + if (curInfo.state == coroState::active) { + popCurrent(curInfo); + curInfo.setState(coroState::born); + } } void ready(coro_handle h) { auto& curInfo = getInfo(h); - LCoro->trace("{} starting", curInfo); + LCoro->trace("{} starting", minifmt::write(curInfo)); assert(curInfo.state == coroState::born); curInfo.setState(coroState::active); @@ -259,7 +285,7 @@ namespace crouton::lifecycle { static coro_handle switching(coroInfo& curInfo, coro_handle next) { if (next && !isNoop(next)) { auto& nextInfo = getInfo(next); - LCoro->trace("{} resuming", nextInfo); + LCoro->trace("{} resuming", minifmt::write(nextInfo)); assert(nextInfo.state != coroState::active); nextInfo.setState(coroState::active); nextInfo.awaiting = nullptr; @@ -278,7 +304,7 @@ namespace crouton::lifecycle { return next; if (to) { - LCoro->trace("{} awaiting {} {}", curInfo, GetTypeName(toType), to); + LCoro->trace("{} awaiting {} {}", minifmt::write(curInfo), GetTypeName(toType), minifmt::write(to)); } assert(curInfo.state == coroState::active); curInfo.setState(coroState::awaiting); @@ -297,7 +323,7 @@ namespace crouton::lifecycle { if (cur == next) return next; - LCoro->trace("{} awaiting {}", curInfo, logCoro{awaitingCoro}); + LCoro->trace("{} awaiting {}", minifmt::write(curInfo), minifmt::write(logCoro{awaitingCoro})); assert(curInfo.state == coroState::active); curInfo.setState(coroState::awaiting); curInfo.awaitingCoro = awaitingCoro; @@ -305,15 +331,17 @@ namespace crouton::lifecycle { return switching(curInfo, next); } - coro_handle yieldingTo(coro_handle cur, coro_handle next) { + coro_handle yieldingTo(coro_handle cur, coro_handle next, bool isCall) { auto& curInfo = getInfo(cur); assertCurrent(curInfo); if (cur == next) return next; - LCoro->trace("{} yielded to {}", curInfo, logCoro{next}); + LCoro->trace("{} yielded to {}", minifmt::write(curInfo), minifmt::write(logCoro{next})); assert(curInfo.state == coroState::active); curInfo.setState(coroState::yielding); + if (isCall && !isNoop(next)) + curInfo.awaitingCoro = next; return switching(curInfo, next); } @@ -323,7 +351,7 @@ namespace crouton::lifecycle { assertCurrent(curInfo); if (curInfo.state < coroState::ending) { - LCoro->trace("{} finished", curInfo); + LCoro->trace("{} finished", minifmt::write(curInfo)); curInfo.setState(coroState::ending); } @@ -332,7 +360,7 @@ namespace crouton::lifecycle { void resume(coro_handle h) { auto& curInfo = getInfo(h); - LCoro->trace("{}.resume() ...", curInfo); + LCoro->trace("{}.resume() ...", minifmt::write(curInfo)); pushCurrent(curInfo); @@ -348,21 +376,20 @@ namespace crouton::lifecycle { void threw(coro_handle h) { auto& curInfo = getInfo(h); assertCurrent(curInfo); - LCoro->error("{} threw an exception.", curInfo); + LCoro->error("{} threw an exception.", minifmt::write(curInfo)); curInfo.setState(coroState::ending); } void returning(coro_handle h) { auto& curInfo = getInfo(h); assertCurrent(curInfo); - LCoro->trace("{} returned.", curInfo); + LCoro->trace("{} returned.", minifmt::write(curInfo)); curInfo.setState(coroState::ending); } void ended(coro_handle h) { unique_lock lock(sCorosMutex); - auto i = sCoros.find(h.address()); - assert(i != sCoros.end()); + auto i = _getInfoIter(h); auto& info = i->second; assert(&info != tCurrent); @@ -371,10 +398,18 @@ namespace crouton::lifecycle { assert(other.second.caller != &info); if (info.state < coroState::ending) - LCoro->warn("{} destructed before returning or throwing", info); - LCoro->debug("{} destructed. ({} left)", info, _count() - 1); - - sCoros.erase(i); + LCoro->warn("{} destructed before returning or throwing", minifmt::write(info)); + LCoro->debug("{} destructed. ({} left)", minifmt::write(info), _count() - 1); + + if (kRememberDestroyedCoros) { + i->second.handle = nullptr; // mark as tombstone + } else { + sCoros.erase(i); + } + } + + void destroy(coro_handle h) { + h.destroy(); } @@ -383,37 +418,44 @@ namespace crouton::lifecycle { // logs all coroutines, in order they were created void logAll() { + using enum coroState; unique_lock lock(sCorosMutex); vector infos; - for (auto &[addr, info] : sCoros) - infos.emplace_back(&info); + for (auto &[addr, info] : sCoros) { + if (info.handle) + infos.emplace_back(&info); + } sort(infos.begin(), infos.end(), [](auto a, auto b) {return a->sequence < b->sequence;}); LCoro->info("{} Existing Coroutines:", infos.size()); for (auto info : infos) { switch (info->state) { - case coroState::born: - LCoro->info("\t{} [born]", verbose{*info}); + case born: + LCoro->info("\t{} [born]", minifmt::write(verbose{*info})); break; - case coroState::active: + case active: if (coroInfo* calling = _currentCalleeOf(*info)) - LCoro->info("\t{} -> calling ¢{}", verbose{*info}, calling->sequence); + LCoro->info("\t{} -> calling ¢{}", minifmt::write(verbose{*info}), calling->sequence); else - LCoro->info("\t{} **CURRENT**", verbose{*info}); + LCoro->info("\t{} **CURRENT**", minifmt::write(verbose{*info})); break; - case coroState::awaiting: + case awaiting: if (info->awaitingCoro) - LCoro->info("\t{} -> awaiting ¢{}", verbose{*info}, + LCoro->info("\t{} -> awaiting ¢{}", minifmt::write(verbose{*info}), _getInfo(info->awaitingCoro).sequence); else if (info->awaiting) - LCoro->info("\t{} -> awaiting {} {}", verbose{*info}, GetTypeName(*info->awaitingType), info->awaiting); + LCoro->info("\t{} -> awaiting {} {}", minifmt::write(verbose{*info}), GetTypeName(*info->awaitingType), minifmt::write(info->awaiting)); else - LCoro->info("\t{} -> awaiting ", verbose{*info}); + LCoro->info("\t{} -> awaiting ", minifmt::write(verbose{*info})); break; - case coroState::yielding: - LCoro->info("\t{} -- yielding", verbose{*info}); + case yielding: + if (info->awaitingCoro) + LCoro->info("\t{} -> yielding to ¢{}", minifmt::write(verbose{*info}), + _getInfo(info->awaitingCoro).sequence); + else + LCoro->info("\t{} -- yielding", minifmt::write(verbose{*info})); break; - case coroState::ending: - LCoro->info("\t{} [ending]", verbose{*info}); + case ending: + LCoro->info("\t{} [ending]", minifmt::write(verbose{*info})); break; } } @@ -421,30 +463,34 @@ namespace crouton::lifecycle { void logStacks() { + using enum coroState; unique_lock lock(sCorosMutex); - LCoro->info("{} Existing Coroutines, By Stack:", sCoros.size()); + size_t n = std::ranges::count_if(sCoros, [](auto &p) {return p.second.handle != nullptr;}); + LCoro->info("{} Existing Coroutines, By Stack:", n); unordered_set remaining; unordered_map next; // next[c] = the caller/awaiter of c for (auto &[h,c] : sCoros) { - remaining.insert(&c); - if (c.state == coroState::active && c.caller) { - next.insert({&c, c.caller}); - } else if (c.state == coroState::awaiting && c.awaitingCoro) { - coroInfo& other = _getInfo(c.awaitingCoro); - next.insert({&other, &c}); + if (c.handle) { + remaining.insert(&c); + if (c.state == active && c.caller) { + next.insert({&c, c.caller}); + } else if ((c.state == awaiting || c.state == yielding) && c.awaitingCoro) { + coroInfo& other = _getInfo(c.awaitingCoro); + next[&other] = &c; + } } } auto printStack = [&](coroInfo &c) { int depth = 1; - if (c.state == coroState::active) { - LCoro->info(" {}: {}", depth, verbose{c}); + if (c.state == active) { + LCoro->info(" {}: {}", depth, minifmt::write(verbose{c})); } else if (c.awaiting) { - LCoro->info(" {}: {} (awaiting {} {})", depth, verbose{c}, - GetTypeName(*c.awaitingType), c.awaiting); + LCoro->info(" {}: {} (awaiting {} {})", depth, minifmt::write(verbose{c}), + GetTypeName(*c.awaitingType), minifmt::write(c.awaiting)); } else { - LCoro->info(" {}: {} ({})", depth, verbose{c}, kStateNames[int(c.state)]); + LCoro->info(" {}: {} ({})", depth, minifmt::write(verbose{c}), kStateNames[int(c.state)]); } remaining.erase(&c); @@ -452,7 +498,7 @@ namespace crouton::lifecycle { while (true) { if (auto i = next.find(cur); i != next.end()) { cur = i->second; - LCoro->info(" {}: {} ({})", ++depth, verbose{*cur}, kStateNames[int(cur->state)]); + LCoro->info(" {}: {} ({})", ++depth, minifmt::write(verbose{*cur}), kStateNames[int(cur->state)]); remaining.erase(cur); } else { break; @@ -467,7 +513,7 @@ namespace crouton::lifecycle { for (auto &[h,c] : sCoros) { if (remaining.contains(&c) && next.contains(&c) - && (c.state == coroState::active || !c.awaitingCoro)) { + && (c.state == active || !c.awaitingCoro)) { LCoro->info(" Stack:"); printStack(c); } @@ -476,7 +522,7 @@ namespace crouton::lifecycle { if (!remaining.empty()) { LCoro->info(" Others:"); for (coroInfo *c : remaining) - LCoro->info(" -- {} ({})", verbose{*c}, kStateNames[int(c->state)]); + LCoro->info(" - {} ({})", minifmt::write(verbose{*c}), kStateNames[int(c->state)]); } } @@ -485,7 +531,6 @@ namespace crouton::lifecycle { #if CROUTON_LIFECYCLES -void dumpCoros() { - crouton::lifecycle::logAll(); -} +void dumpCoros() {crouton::lifecycle::logAll();} +void dumpCoroStacks() {crouton::lifecycle::logStacks();} #endif diff --git a/src/Coroutine.cc b/src/Coroutine.cc index 9daf77b..af7c225 100644 --- a/src/Coroutine.cc +++ b/src/Coroutine.cc @@ -18,10 +18,11 @@ #include "Coroutine.hh" #include "Memoized.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "Scheduler.hh" #include +#include namespace crouton { using namespace std; @@ -33,7 +34,7 @@ namespace crouton { coro_handle CoMutex::await_suspend(coro_handle h) noexcept { auto& sched = Scheduler::current(); _waiters.emplace_back(sched.suspend(h)); - return lifecycle::suspendingTo(h, typeid(this), this, sched.next()); + return lifecycle::suspendingTo(h, CRTN_TYPEID(*this), this, sched.next()); } void CoMutex::unlock() { diff --git a/src/Error.cc b/src/Error.cc index a157adf..7873c2f 100644 --- a/src/Error.cc +++ b/src/Error.cc @@ -19,9 +19,10 @@ #include "Error.hh" #include "Backtrace.hh" #include "Internal.hh" -#include "Logging.hh" -#include "UVInternal.hh" -#include "llhttp.h" +#include "util/Logging.hh" +#include +#include +#include #include namespace crouton { @@ -38,10 +39,16 @@ namespace crouton { } - type_info const& Error::typeInfo() const { + Error::DomainInfo Error::typeInfo() const { +#if CROUTON_RTTI if (_code != 0) - return *sDomains[_domain].type; + return *(std::type_info*)sDomains[_domain].type; return typeid(void); +#else + if (_code != 0) + return sDomains[_domain].type; + return nullptr; +#endif } @@ -67,10 +74,12 @@ namespace crouton { } - void Error::raise(string_view logMessage) const { - spdlog::error("*** Throwing crouton::Exception({}, {}): {} ({})", - domain(), _code, description(), logMessage); - assert(*this); // it's illegal to throw `noerror` + void Error::raise(string_view logMessage, std::source_location const& loc) const { + Log->error("*** Throwing crouton::Exception({}, {}): {} ({}) -- from {} [{}:{}]", + domain(), _code, description(), logMessage, + loc.function_name(), loc.file_name(), loc.line()); + fleece::Backtrace(1).writeTo(std::cerr); + precondition(*this); // it's illegal to throw `noerror` throw Exception(*this); } @@ -81,7 +90,7 @@ namespace crouton { std::array Error::sDomains; - uint8_t Error::_registerDomain(std::type_info const& type, + uint8_t Error::_registerDomain(DomainInfo typeRef, string_view name, ErrorDescriptionFunc description) { @@ -91,12 +100,18 @@ namespace crouton { static mutex sMutex; unique_lock lock(sMutex); - spdlog::info("Error: Registering domain {}", type.name()); + const void* type; +#if CROUTON_RTTI + type = &typeRef; +#else + type = typeRef; +#endif + Log->debug("Error: Registering domain {}", name); for (auto &d : sDomains) { - if (d.type == &type) { + if (d.type == type) { return uint8_t(&d - &sDomains[0]); } else if (d.type == nullptr ) { - d.type = &type; + d.type = type; d.name = name; d.description = description; return uint8_t(&d - &sDomains[0]); @@ -126,6 +141,7 @@ namespace crouton { {errorcode_t(LogicError), "internal error (logic error)"}, {errorcode_t(ParseError), "unreadable data"}, {errorcode_t(Timeout), "operation timed out"}, + {errorcode_t(EndOfData), "unexpected end of data"}, {errorcode_t(Unimplemented), "unimplemented operation"}, }; return NameEntry::lookup(code, names); @@ -171,20 +187,24 @@ namespace crouton { static CppError exceptionToCppError(std::exception const& x) { +#if CROUTON_RTTI string name = fleece::Unmangle(typeid(x)); for (auto &entry : kExceptionNames) { if (0 == strcmp(entry.name, name.c_str())) return CppError{entry.code}; } - spdlog::warn("No CppErr enum value matches exception class {}", name); + Log->warn("No CppErr enum value matches exception class {}", name); +#endif return exception; } Error::Error(std::exception const& x) { +#if CROUTON_RTTI if (auto exc = dynamic_cast(&x)) *this = exc->error(); else +#endif *this = exceptionToCppError(x); } diff --git a/src/Future.cc b/src/Future.cc index a774dc5..21eb7e0 100644 --- a/src/Future.cc +++ b/src/Future.cc @@ -17,7 +17,7 @@ // #include "Future.hh" -#include "Logging.hh" +#include "util/Logging.hh" namespace crouton { @@ -71,7 +71,7 @@ namespace crouton { //TODO: Make this fully thread-safe if (_suspension) { LCoro->info("Future dealloced with _suspension of {}", - logCoro{_suspension.handle()}); + minifmt::write(logCoro{_suspension.handle()})); State state = Waiting; _state.compare_exchange_strong(state, Empty); _suspension.cancel(); @@ -134,6 +134,7 @@ namespace crouton { // Updates the chained FutureState based on my `then` callback. void FutureStateBase::resolveChain() { + assert(_state == Ready); if (auto x = getError()) { _chainedFuture->setError(x); } else { diff --git a/src/Logging.cc b/src/Logging.cc deleted file mode 100644 index 913ab55..0000000 --- a/src/Logging.cc +++ /dev/null @@ -1,101 +0,0 @@ -// -// Logging.cc -// -// Copyright 2023-Present Couchbase, Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include "Logging.hh" - -#include -#include -#include - -#include -#include - -namespace crouton { - using namespace std; - - /// Defines the format of textual log output. - static constexpr const char* kLogPattern = "▣ %H:%M:%S.%f %^%L | <%n>%$ %v"; - - static vector sSinks; - static spdlog::sink_ptr sStderrSink; - - std::shared_ptr LCoro, LSched, LLoop, LNet; - - - static shared_ptr makeLogger(string_view name, - spdlog::level::level_enum level = spdlog::level::info) - { - string nameStr(name); - auto logger = spdlog::get(nameStr); - if (!logger) { - logger = make_shared(nameStr, sSinks.begin(), sSinks.end()); - spdlog::initialize_logger(logger); - // set default level, unless already customized by SPDLOG_LEVEL: - if (logger->level() == spdlog::level::info) - logger->set_level(level); - } - return logger; - } - - - static void addSink(spdlog::sink_ptr sink) { - sink->set_pattern(kLogPattern); - sSinks.push_back(sink); - spdlog::apply_all([&](std::shared_ptr logger) { - logger->sinks().push_back(sink); - }); - spdlog::set_level(std::min(sink->level(), spdlog::get_level())); - } - - - void InitLogging() { - static std::once_flag sOnce; - std::call_once(sOnce, []{ - // Configure log output: - spdlog::set_pattern(kLogPattern); - spdlog::flush_every(5s); - spdlog::flush_on(spdlog::level::warn); - - // Get the default stderr sink: - sStderrSink = spdlog::default_logger()->sinks().front(); - sSinks.push_back(sStderrSink); - - // Make the standard loggers: - LCoro = makeLogger("Coro"); - LSched = makeLogger("Sched"); - LLoop = makeLogger("Loop"); - LNet = makeLogger("Net"); - - // Set log levels from `SPDLOG_LEVEL` env var: - spdlog::cfg::load_env_levels(); - - spdlog::info("---------- Welcome to Crouton ----------"); - }); - } - - shared_ptr MakeLogger(string_view name, spdlog::level::level_enum level) { - InitLogging(); - return makeLogger(name, level); - } - - void AddSink(spdlog::sink_ptr sink) { - InitLogging(); - addSink(sink); - } - -} diff --git a/src/Scheduler.cc b/src/Scheduler.cc index dfbc66a..30e8c10 100644 --- a/src/Scheduler.cc +++ b/src/Scheduler.cc @@ -19,7 +19,7 @@ #include "Scheduler.hh" #include "EventLoop.hh" #include "Internal.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "Task.hh" namespace crouton { @@ -43,7 +43,7 @@ namespace crouton { assert(_visible); if (_wakeMe.test_and_set() == false) { _visible = false; - LSched->trace("{} unblocked", logCoro{_handle}); + LSched->trace("{} unblocked", minifmt::write(logCoro{_handle})); auto sched = _scheduler; assert(sched); _scheduler = nullptr; @@ -54,7 +54,7 @@ namespace crouton { /// Removes the associated coroutine from the suspended set. /// You must call this if the coroutine is destroyed while a Suspension exists. void cancel() { - LSched->trace("{} Suspension canceled -- forgetting it", logCoro{_handle}); + LSched->trace("{} Suspension canceled -- forgetting it", minifmt::write(logCoro{_handle})); assert(_visible); _handle = nullptr; if (_wakeMe.test_and_set() == false) { @@ -93,13 +93,20 @@ namespace crouton { #pragma mark - SCHEDULER: + + + Scheduler::Scheduler() + :_suspended(new SuspensionMap) + { } + + Scheduler::~Scheduler() = default; Scheduler& Scheduler::_create() { InitLogging(); assert(!sCurSched); sCurSched = new Scheduler(); - LSched->info("Created Scheduler {}", (void*)sCurSched); + LSched->debug("Created Scheduler {}", (void*)sCurSched); return *sCurSched; } @@ -116,7 +123,7 @@ namespace crouton { } bool Scheduler::isEmpty() const { - return isIdle() && _suspended.empty(); + return isIdle() && _suspended->empty(); } /// Returns true if there are no coroutines ready or suspended, except possibly for the one @@ -131,7 +138,7 @@ namespace crouton { return true; LSched->info("Scheduler::assertEmpty: Running event loop until {} ready and {} suspended coroutines finish...", - _ready.size(), _suspended.size()); + _ready.size(), _suspended->size()); int attempt = 0; const_cast(this)->runUntil([&] { return (isEmpty() && lifecycle::count() == 0) || ++attempt >= 10; @@ -147,17 +154,17 @@ namespace crouton { LSched->error("** On this Scheduler:"); for (auto &r : _ready) if (r != _eventLoopTask) { - LSched->info("ready: {}", logCoro{r}); + LSched->info("ready: {}", minifmt::write(logCoro{r})); } - for (auto &s : _suspended) { - LSched->info("\tsuspended: {}" , logCoro{s.second._handle}); + for (auto &s : *_suspended) { + LSched->info("\tsuspended: {}" , minifmt::write(logCoro{s.second._handle})); } return false; } EventLoop& Scheduler::eventLoop() { - assert(isCurrent()); + precondition(isCurrent()); if (!_eventLoop) { _eventLoop = newEventLoop(); _ownsEventLoop = true; @@ -166,7 +173,7 @@ namespace crouton { } void Scheduler::useEventLoop(EventLoop* loop) { - assert(isCurrent()); + precondition(isCurrent()); assert(!_eventLoop); _eventLoop = loop; _ownsEventLoop = false; @@ -221,8 +228,8 @@ namespace crouton { /// Adds a coroutine handle to the end of the ready queue, where at some point it will /// be returned from next(). void Scheduler::schedule(coro_handle h) { - LSched->debug("schedule {}", logCoro{h}); - assert(isCurrent()); + LSched->debug("schedule {}", minifmt::write(logCoro{h})); + precondition(isCurrent()); assert(!isWaiting(h)); if (!isReady(h)) _ready.push_back(h); @@ -235,13 +242,13 @@ namespace crouton { schedule(h); return nxt; } else { - LSched->debug("yield {} -- continue running", logCoro{h}); + LSched->debug("yield {} -- continue running", minifmt::write(logCoro{h})); return h; } } void Scheduler::resumed(coro_handle h) { - assert(isCurrent()); + precondition(isCurrent()); if (auto i = std::find(_ready.begin(), _ready.end(), h); i != _ready.end()) _ready.erase(i); } @@ -253,14 +260,14 @@ namespace crouton { /// Returns the coroutine that should be resumed, or else `dflt`. coro_handle Scheduler::nextOr(coro_handle dflt) { - assert(isCurrent()); + precondition(isCurrent()); scheduleWakers(); if (_ready.empty()) { return dflt; } else { coro_handle h = _ready.front(); _ready.pop_front(); - LSched->debug("resume {}", logCoro{h}); + LSched->debug("resume {}", minifmt::write(logCoro{h})); return h; } } @@ -268,8 +275,8 @@ namespace crouton { /// Returns the coroutine that should be resumed, /// or else the no-op coroutine that returns to the outer caller. coro_handle Scheduler::finished(coro_handle h) { - LSched->debug("finished {}", logCoro{h}); - assert(isCurrent()); + LSched->debug("finished {}", minifmt::write(logCoro{h})); + precondition(isCurrent()); assert(h.done()); assert(!isReady(h)); assert(!isWaiting(h)); @@ -281,22 +288,22 @@ namespace crouton { /// To make it runnable again, call the returned Suspension's `wakeUp` method /// from any thread. Suspension Scheduler::suspend(coro_handle h) { - LSched->debug("suspend {}", logCoro{h}); - assert(isCurrent()); + LSched->debug("suspend {}", minifmt::write(logCoro{h})); + precondition(isCurrent()); assert(!isReady(h)); - auto [i, added] = _suspended.try_emplace(h.address(), h, this); + auto [i, added] = _suspended->try_emplace(h.address(), h, this); i->second._visible = true; return Suspension(&i->second); } void Scheduler::destroying(coro_handle h) { - LSched->debug("destroying {}", logCoro{h}); - assert(isCurrent()); - if (auto i = _suspended.find(h.address()); i != _suspended.end()) { + LSched->debug("destroying {}", minifmt::write(logCoro{h})); + precondition(isCurrent()); + if (auto i = _suspended->find(h.address()); i != _suspended->end()) { SuspensionImpl& sus = i->second; if (sus._wakeMe.test_and_set()) { // The holder of the Suspension already tried to wake it, so it's OK to delete it: - _suspended.erase(i); + _suspended->erase(i); } else { assert(!sus._visible); sus._handle = nullptr; @@ -329,7 +336,7 @@ namespace crouton { } bool Scheduler::isWaiting(coro_handle h) const { - return _suspended.find(h.address()) != _suspended.end(); + return _suspended->find(h.address()) != _suspended->end(); } /// Changes a waiting coroutine's state to 'ready' and notifies the Scheduler to resume @@ -342,7 +349,7 @@ namespace crouton { bool Scheduler::hasWakers() const { if (_woke) { - for (auto &[h, sus] : _suspended) { + for (auto &[h, sus] :*_suspended) { if (sus._wakeMe.test() && sus._handle) return true; } @@ -355,15 +362,15 @@ namespace crouton { void Scheduler::scheduleWakers() { while (_woke.exchange(false) == true) { // Some waiting coroutine is now ready: - for (auto i = _suspended.begin(); i != _suspended.end();) { + for (auto i = _suspended->begin(); i != _suspended->end();) { if (i->second._wakeMe.test()) { if (i->second._handle) { - LSched->debug("scheduleWaker({})", logCoro{i->second._handle}); + LSched->debug("scheduleWaker({})", minifmt::write(logCoro{i->second._handle})); _ready.push_back(i->second._handle); } else { LSched->debug("cleaned up canceled Suspension {}", (void*)&i->second); } - i = _suspended.erase(i); + i = _suspended->erase(i); } else { ++i; } diff --git a/src/Select.cc b/src/Select.cc index 5a19dba..b7cb0c5 100644 --- a/src/Select.cc +++ b/src/Select.cc @@ -22,7 +22,7 @@ namespace crouton { Select::Select(std::initializer_list sources) { - assert(sources.size() <= kMaxSources); + precondition(sources.size() <= kMaxSources); size_t i = 0; for (ISelectable* source : sources) _sources[i++] = source; @@ -47,7 +47,7 @@ namespace crouton { coro_handle Select::await_suspend(coro_handle h) { _suspension = Scheduler::current().suspend(h); - return lifecycle::suspendingTo(h, typeid(*this), this); + return lifecycle::suspendingTo(h, CRTN_TYPEID(*this), this); } int Select::await_resume() { diff --git a/src/io/HTTPConnection.cc b/src/io/HTTPConnection.cc index a33310f..4a8775a 100644 --- a/src/io/HTTPConnection.cc +++ b/src/io/HTTPConnection.cc @@ -17,9 +17,8 @@ // #include "io/HTTPConnection.hh" -#include "io/TCPSocket.hh" -#include "io/mbed/TLSSocket.hh" -#include "UVInternal.hh" +#include "io/ISocket.hh" +#include "Internal.hh" #include "llhttp.h" #include @@ -100,7 +99,7 @@ namespace crouton::io::http { out << "Host: " << _url.hostname << "\r\n"; out << "Connection: close\r\n"; if (req.method != Method::GET && !req.bodyStream) { - assert(!req.headers.contains("Content-Length")); + assert_always(!req.headers.contains("Content-Length")); out << "Content-Length: " << req.body.size() << "\r\n"; } out << "\r\n"; diff --git a/src/io/HTTPHandler.cc b/src/io/HTTPHandler.cc index 7bdf251..4694a67 100644 --- a/src/io/HTTPHandler.cc +++ b/src/io/HTTPHandler.cc @@ -17,7 +17,7 @@ // #include "io/HTTPHandler.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include #include @@ -38,7 +38,7 @@ namespace crouton::io::http { auto uri = _parser.requestURI.value(); string path(uri.path); - LNet->info("HTTPHandler: Request is {} {}", _parser.requestMethod, string(uri)); + LNet->info("HTTPHandler: Request is {} {}", minifmt::write(_parser.requestMethod), string(uri)); Headers responseHeaders; responseHeaders.set("User-Agent", "Crouton"); @@ -117,7 +117,7 @@ namespace crouton::io::http { { } void Handler::Response::writeHeader(string_view name, string_view value) { - assert(!_sentHeaders); + precondition(!_sentHeaders); _headers.set(string(name), string(value)); } @@ -129,7 +129,7 @@ namespace crouton::io::http { Future Handler::Response::finishHeaders() { if (!_sentHeaders) { - LNet->info("HTTPHandler: Sending {} response", status); + LNet->info("HTTPHandler: Sending {} response", minifmt::write(status)); AWAIT _handler->writeHeaders(status, statusMessage, _headers); } _sentHeaders = true; diff --git a/src/io/HTTPParser.cc b/src/io/HTTPParser.cc index c8fc96f..2540f2b 100644 --- a/src/io/HTTPParser.cc +++ b/src/io/HTTPParser.cc @@ -106,7 +106,7 @@ namespace crouton::io::http { Future Parser::readHeaders() { - assert(_stream); + precondition(_stream); if (!_stream->isOpen()) AWAIT _stream->open(); @@ -119,7 +119,7 @@ namespace crouton::io::http { Future Parser::readBody() { - assert(_stream); + precondition(_stream); ConstBytes data; while (_body.empty() && !complete()) { data = AWAIT _stream->readNoCopy(); @@ -148,7 +148,7 @@ namespace crouton::io::http { if (err != HPE_OK) { if (err == HPE_PAUSED_UPGRADE) { // We have a (WebSocket) upgrade. Put any data after the request into _body. - assert(llhttp_get_upgrade(_parser.get()) != 0); + assert_always(llhttp_get_upgrade(_parser.get()) != 0); _upgraded = true; const char* end = llhttp_get_error_pos(_parser.get()); assert((byte*)end >= data.data() && (byte*)end <= data.data() + data.size()); @@ -170,7 +170,7 @@ namespace crouton::io::http { int Parser::addHeader(string value) { - assert(!_curHeaderName.empty()); + precondition(!_curHeaderName.empty()); this->headers.add(_curHeaderName, value); _curHeaderName = ""; return 0; diff --git a/src/io/ISocket.cc b/src/io/ISocket.cc index e76c091..d01260d 100644 --- a/src/io/ISocket.cc +++ b/src/io/ISocket.cc @@ -24,11 +24,20 @@ #include "io/apple/NWConnection.hh" #endif +#ifdef ESP_PLATFORM +#include "ESPTCPSocket.hh" +#endif + namespace crouton::io { std::unique_ptr ISocket::newSocket(bool useTLS) { -#ifdef __APPLE__ +#if defined(__APPLE__) return std::make_unique(useTLS); +#elif defined(ESP_PLATFORM) + if (useTLS) + return std::make_unique(); + else + return std::make_unique(); #else if (useTLS) return std::make_unique(); diff --git a/src/io/IStream.cc b/src/io/IStream.cc index f24fb45..8141699 100644 --- a/src/io/IStream.cc +++ b/src/io/IStream.cc @@ -17,7 +17,7 @@ // #include "io/IStream.hh" -#include "UVInternal.hh" +#include "util/Bytes.hh" #include // for memcpy namespace crouton::io { @@ -39,7 +39,7 @@ namespace crouton::io { Future IStream::readExactly(MutableBytes buf) { return read(buf).then([buf](size_t bytesRead) { if (bytesRead < buf.size()) - check(int(UV_EOF), "reading from the network"); + Error(CroutonError::ParseError).raise(); }); } @@ -64,8 +64,8 @@ namespace crouton::io { Future IStream::readUntil(string end, size_t maxLen) { - assert(!end.empty()); - assert(maxLen >= end.size()); + precondition(!end.empty()); + precondition(maxLen >= end.size()); string data; while (data.size() < maxLen) { auto dataLen = data.size(); diff --git a/src/io/Process.cc b/src/io/Process.cc index a04d792..4639615 100644 --- a/src/io/Process.cc +++ b/src/io/Process.cc @@ -17,9 +17,29 @@ // #include "io/Process.hh" -#include "UVInternal.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "Task.hh" +#include + +#ifndef ESP_PLATFORM +#include "uv/UVInternal.hh" +#endif + +#if defined(_MSC_VER) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# include +# define isatty _isatty +#elif defined(ESP_PLATFORM) +# include +# include +#else +# include +# include +#endif namespace crouton::io { using namespace std; @@ -29,7 +49,11 @@ namespace crouton::io { static void initArgs(int argc, const char * argv[]) { +#ifdef ESP_PLATFORM + auto nuArgv = argv; +#else auto nuArgv = uv_setup_args(argc, (char**)argv); +#endif sArgs.resize(argc); for (int i = 0; i < argc; ++i) sArgs[i] = nuArgv[i]; @@ -67,6 +91,9 @@ namespace crouton::io { int Main(int argc, const char * argv[], Future(*fn)()) { +#ifdef ESP_PLATFORM + std::thread([&] { +#endif try { initArgs(argc, argv); InitLogging(); @@ -74,27 +101,130 @@ namespace crouton::io { Scheduler::current().runUntil([&]{ return fut.hasResult(); }); return fut.result(); } catch (std::exception const& x) { - spdlog::error("*** Unexpected exception: {}" , x.what()); + Log->error("*** Unexpected exception: {}" , x.what()); return 1; } catch (...) { - spdlog::error("*** Unexpected exception"); + Log->error("*** Unexpected exception"); return 1; } +#ifdef ESP_PLATFORM + }).join(); + printf(" Restarting now.\n"); + fflush(stdout); + esp_restart(); +#endif } int Main(int argc, const char * argv[], Task(*fn)()) { +#ifdef ESP_PLATFORM + std::thread([&] { +#endif try { initArgs(argc, argv); + InitLogging(); Task task = fn(); Scheduler::current().run(); return 0; } catch (std::exception const& x) { - spdlog::error("*** Unexpected exception: {}", x.what()); + Log->error("*** Unexpected exception: {}", x.what()); return 1; } catch (...) { - spdlog::error("*** Unexpected exception"); + Log->error("*** Unexpected exception"); return 1; } +#ifdef ESP_PLATFORM + }).join(); + printf(" Restarting now.\n"); + fflush(stdout); + esp_restart(); +#endif + } + + +#pragma mark - TTY STUFF: + + +#ifdef _MSC_VER + typedef LONG NTSTATUS, *PNTSTATUS; +#define STATUS_SUCCESS (0x00000000) + + typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + + RTL_OSVERSIONINFOW GetRealOSVersion() { + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + if (hMod) { + RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); + if (fxPtr != nullptr) { + RTL_OSVERSIONINFOW rovi = { 0 }; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if ( STATUS_SUCCESS == fxPtr(&rovi) ) { + return rovi; + } + } + } + RTL_OSVERSIONINFOW rovi = { 0 }; + return rovi; + } +#endif + + + static bool isColor(int fd) { +#ifdef ESP_PLATFORM + #if CONFIG_LOG_COLORS + return true; + #else + return false; + #endif +#else + if (getenv("CLICOLOR_FORCE")) { + return true; + } + + if (!isatty(fd)) + return false; + + if (const char *term = getenv("TERM")) { + if (strstr(term,"ANSI") || strstr(term,"ansi") || strstr(term,"color")) + return true; + } + +#ifdef _MSC_VER +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + // Sick of this being missing for whatever reason +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif + if (GetRealOSVersion().dwMajorVersion >= 10) { + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD consoleMode; + if(GetConsoleMode(hConsole, &consoleMode)) { + SetConsoleMode(hConsole, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + return true; + } +#endif + + return false; +#endif } + + // + #define ANSI "\033[" + + TTY::TTY(int fd) + :color(isColor(fd)) + ,bold (color ? ANSI "1m" : "") + ,dim (color ? ANSI "2m" : "") + ,italic (color ? ANSI "3m" : "") + ,underline (color ? ANSI "4m" : "") + ,red (color ? ANSI "31m" : "") + ,yellow (color ? ANSI "33m" : "") + ,green (color ? ANSI "32m" : "") + ,reset (color ? ANSI "0m" : "") + { } + + + TTY const TTY::out(1); + TTY const TTY::err(2); + } diff --git a/src/io/URL.cc b/src/io/URL.cc index 062bffb..8216541 100644 --- a/src/io/URL.cc +++ b/src/io/URL.cc @@ -40,7 +40,7 @@ namespace crouton::io { static int tlsuv_parse_url(struct tlsuv_url_s *url, const char *urlstr); - bool URLRef::tryParse(const char* str) { + bool URLRef::tryParse(const char* str) noexcept { tlsuv_url_s url; if (tlsuv_parse_url(&url, str) != 0) return false; @@ -102,7 +102,7 @@ namespace crouton::io { string_view hostname_, uint16_t port_, string_view path_, - string_view query_) + string_view query_) noexcept :scheme(scheme_) ,hostname(hostname_) ,port(port_) @@ -139,7 +139,7 @@ namespace crouton::io { } - string_view URLRef::queryValueForKey(string_view key) { + string_view URLRef::queryValueForKey(string_view key) noexcept { string_view remaining = query; while (!remaining.empty()) { string_view q; diff --git a/src/io/WebSocket.cc b/src/io/WebSocket.cc index d7d510e..7675ef9 100644 --- a/src/io/WebSocket.cc +++ b/src/io/WebSocket.cc @@ -17,9 +17,10 @@ // #include "io/WebSocket.hh" -#include "Logging.hh" +#include "util/Logging.hh" +#include "Misc.hh" #include "StringUtils.hh" -#include "UVInternal.hh" +#include "Internal.hh" #include "WebSocketProtocol.hh" #include #include @@ -173,7 +174,7 @@ namespace crouton::io::ws { return close(); } else { // Peer is initiating a close; echo it: - LNet->warn("Peer sent {}; echoing it", msg); + LNet->warn("Peer sent {}; echoing it", minifmt::write(msg)); return send(std::move(msg)); } } diff --git a/src/io/WebSocketProtocol.hh b/src/io/WebSocketProtocol.hh index 08d9d73..1f9b710 100644 --- a/src/io/WebSocketProtocol.hh +++ b/src/io/WebSocketProtocol.hh @@ -323,7 +323,7 @@ namespace uWS { if ( !isServer ) { ((uint8_t*)dst)[1] |= 0x80; - crouton::io::Randomize(mask.data(), 4); + crouton::Randomize(mask.data(), 4); memcpy(dst + headerLength, mask.data(), 4); headerLength += 4; } diff --git a/src/io/apple/NWConnection.cc b/src/io/apple/NWConnection.cc index aec2b23..5fe52d3 100644 --- a/src/io/apple/NWConnection.cc +++ b/src/io/apple/NWConnection.cc @@ -19,7 +19,7 @@ #ifdef __APPLE__ #include "io/apple/NWConnection.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include #include diff --git a/src/io/blip/BLIPConnection.cc b/src/io/blip/BLIPConnection.cc index 2e49ce2..5d263af 100644 --- a/src/io/blip/BLIPConnection.cc +++ b/src/io/blip/BLIPConnection.cc @@ -19,6 +19,8 @@ #include "BLIPConnection.hh" #include "io/WebSocket.hh" #include "Task.hh" +#include +#include // Makes custom types loggable via `operator <<` overloads namespace crouton::io::blip { using namespace std; diff --git a/src/io/blip/BLIPIO.cc b/src/io/blip/BLIPIO.cc index e5aee20..26f84cf 100644 --- a/src/io/blip/BLIPIO.cc +++ b/src/io/blip/BLIPIO.cc @@ -14,11 +14,12 @@ #include "Error.hh" #include "Internal.hh" #include "MessageOut.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "StringUtils.hh" #include "Task.hh" #include #include +#include // Makes custom types loggable via `operator <<` overloads namespace crouton { string ErrorDomainInfo::description(errorcode_t code) { @@ -49,7 +50,7 @@ namespace crouton::io::blip { static constexpr size_t kOutboxCapacity = 10; /// Logger - shared_ptr LBLIP = MakeLogger("BLIP"); + LoggerRef LBLIP = MakeLogger("BLIP"); uint64_t readUVarint(ConstBytes& bytes) { @@ -109,7 +110,7 @@ namespace crouton::io::blip { } while (i != begin()); ++i; } - LBLIP->debug("Requeuing {} {}...", kMessageTypeNames[msg->type()], msg->number()); + LBLIP->debug("Requeuing {} {}...", kMessageTypeNames[msg->type()], minifmt::write(msg->number())); pushBefore(i, msg); // inserts _at_ position i, before message *i } @@ -192,7 +193,7 @@ namespace crouton::io::blip { /// Public API to send a new request. Future BLIPIO::sendRequest(MessageBuilder& mb) { auto message = make_shared(this, mb, MessageNo::None); - assert(message->type() == kRequestType); + precondition(message->type() == kRequestType); send(message); return message->onResponse(); } @@ -211,11 +212,11 @@ namespace crouton::io::blip { bool BLIPIO::_queueMessage(MessageOutRef msg) { if (!_sendOpen) { LBLIP->warn("Can't send {} {}; socket is closed for writes", - kMessageTypeNames[msg->type()], msg->number()); + kMessageTypeNames[msg->type()], minifmt::write(msg->number())); msg->disconnected(); return false; } - LBLIP->info("Sending {}", *msg); + LBLIP->info("Sending {}", minifmt::write(*msg)); _maxOutboxDepth = max(_maxOutboxDepth, _outbox.size() + 1); _totalOutboxDepth += _outbox.size() + 1; ++_countOutboxDepth; @@ -226,7 +227,7 @@ namespace crouton::io::blip { /** Adds an outgoing message to the icebox (until an ACK arrives.) */ void BLIPIO::freezeMessage(MessageOutRef msg) { - LBLIP->debug("Freezing {} {}", kMessageTypeNames[msg->type()], msg->number()); + LBLIP->debug("Freezing {} {}", kMessageTypeNames[msg->type()], minifmt::write(msg->number())); assert(!_outbox.contains(msg)); assert(ranges::find(_icebox, msg) == _icebox.end()); _icebox.push_back(msg); @@ -235,7 +236,7 @@ namespace crouton::io::blip { /** Removes an outgoing message from the icebox and re-queues it (after ACK arrives.) */ void BLIPIO::thawMessage(MessageOutRef msg) { - LBLIP->debug("Thawing {} {}", kMessageTypeNames[msg->type()], msg->number()); + LBLIP->debug("Thawing {} {}", kMessageTypeNames[msg->type()], minifmt::write(msg->number())); auto i = ranges::find(_icebox, msg); assert(i != _icebox.end()); _icebox.erase(i); @@ -292,9 +293,9 @@ namespace crouton::io::blip { *flagsPos = frameFlags; ConstBytes frame(frameBuf, out.data()); - if (LBLIP->should_log(spdlog::level::debug)) { + if (LBLIP->should_log(LogLevel::debug)) { LBLIP->debug(" Sending frame: {} {} {}{}{}{}, bytes {}--{}", - kMessageTypeNames[frameFlags & kTypeMask], msg->number(), + kMessageTypeNames[frameFlags & kTypeMask], minifmt::write(msg->number()), (frameFlags & kMoreComing ? 'M' : '-'), (frameFlags & kUrgent ? 'U' : '-'), (frameFlags & kNoReply ? 'N' : '-'), @@ -315,7 +316,7 @@ namespace crouton::io::blip { if (auto newMsg = _wayOutBox.maybePop()) _queueMessage(*newMsg); // Add response message to _pendingResponses: - LBLIP->debug("Sent last frame of {}", *msg); + LBLIP->debug("Sent last frame of {}", minifmt::write(*msg)); if (MessageIn* response = msg->createResponse()) _pendingResponses.emplace(response->number(), response); else @@ -338,7 +339,7 @@ namespace crouton::io::blip { FrameFlags flags = FrameFlags(f); LBLIP->debug("Received frame: {} {} {}{}{}{}, length {}", - kMessageTypeNames[flags & kTypeMask], msgNo, + kMessageTypeNames[flags & kTypeMask], minifmt::write(msgNo), (flags & kMoreComing ? 'M' : '-'), (flags & kUrgent ? 'U' : '-'), (flags & kNoReply ? 'N' : '-'), @@ -389,7 +390,7 @@ namespace crouton::io::blip { // Existing request: return it, and remove from _pendingRequests if the last frame: msg = i->second; if (!(flags & kMoreComing)) { - LBLIP->debug("REQ {} has reached the end of its frames", msgNo); + LBLIP->debug("REQ {} has reached the end of its frames", minifmt::write(msgNo)); _pendingRequests.erase(i); } } else if (msgNo == _numRequestsReceived + 1) { @@ -398,10 +399,10 @@ namespace crouton::io::blip { msg = make_shared(this, flags, msgNo); if (flags & kMoreComing) { _pendingRequests.emplace(msgNo, msg); - LBLIP->debug("REQ {} has more frames coming", msgNo); + LBLIP->debug("REQ {} has more frames coming", minifmt::write(msgNo)); } } else { - string err = fmt::format("Bad incoming REQ {} ({})", msgNo, + string err = fmt::format("Bad incoming REQ {} ({})", minifmt::write(msgNo), (msgNo <= _numRequestsReceived ? "already finished" : "too high")); Error::raise(BLIPError::InvalidFrame, err); } @@ -416,11 +417,11 @@ namespace crouton::io::blip { if (i != _pendingResponses.end()) { msg = i->second; if (!(flags & kMoreComing)) { - LBLIP->debug("RES {} has reached the end of its frames", msgNo); + LBLIP->debug("RES {} has reached the end of its frames", minifmt::write(msgNo)); _pendingResponses.erase(i); } } else { - string err = fmt::format("Bad incoming RES {} ({})", msgNo, + string err = fmt::format("Bad incoming RES {} ({})", minifmt::write(msgNo), (msgNo <= _lastMessageNo ? "no request waiting" : "too high")); Error::raise(BLIPError::InvalidFrame, err); } @@ -439,7 +440,7 @@ namespace crouton::io::blip { }); if (i == _icebox.end()) { LBLIP->debug("Received ACK of non-current message ({} {})", - (onResponse ? "RES" : "REQ"), msgNo); + (onResponse ? "RES" : "REQ"), minifmt::write(msgNo)); return; } msg = *i; diff --git a/src/io/blip/BLIPProtocol.hh b/src/io/blip/BLIPProtocol.hh index 8fa7693..93c940a 100644 --- a/src/io/blip/BLIPProtocol.hh +++ b/src/io/blip/BLIPProtocol.hh @@ -13,7 +13,7 @@ #pragma once #include "util/Bytes.hh" #include "Error.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include namespace crouton::io::blip { @@ -59,7 +59,7 @@ namespace crouton::io::blip { }; - extern std::shared_ptr LBLIP; + extern LoggerRef LBLIP; #pragma mark - VARINTS: diff --git a/src/io/blip/Codec.cc b/src/io/blip/Codec.cc index 16298c7..b065735 100644 --- a/src/io/blip/Codec.cc +++ b/src/io/blip/Codec.cc @@ -17,9 +17,11 @@ #include "Codec.hh" #include "BLIPProtocol.hh" #include "Endian.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include #include +#include +#include // Makes custom types loggable via `operator <<` overloads namespace crouton::io::blip { using namespace std; @@ -36,7 +38,7 @@ namespace crouton::io::blip { static constexpr int kZlibDeflateMemLevel = 9; - shared_ptr LZip = MakeLogger("Zip"); + LoggerRef LZip = MakeLogger("Zip"); Codec::Codec() :_checksum((uint32_t)crc32(0, nullptr, 0)) // the required initial value diff --git a/src/io/blip/Message.cc b/src/io/blip/Message.cc index 1391e74..4db124b 100644 --- a/src/io/blip/Message.cc +++ b/src/io/blip/Message.cc @@ -200,7 +200,7 @@ namespace crouton::io::blip { crouton::Error::raise(BLIPError::InvalidFrame, "message ends before end of properties"); _complete = true; state = kEnd; - LBLIP->info("Finished receiving {}", *this); + LBLIP->info("Finished receiving {}", minifmt::write(*this)); if (_onResponse) { auto ref = dynamic_pointer_cast(this->shared_from_this()); _onResponse->setResult(ref); diff --git a/src/io/blip/MessageBuilder.cc b/src/io/blip/MessageBuilder.cc index f557c88..b3931e1 100644 --- a/src/io/blip/MessageBuilder.cc +++ b/src/io/blip/MessageBuilder.cc @@ -30,7 +30,7 @@ namespace crouton::io::blip { MessageBuilder::MessageBuilder(MessageIn* inReplyTo) : MessageBuilder() { - assert(!inReplyTo->isResponse()); + precondition(inReplyTo && !inReplyTo->isResponse()); type = kResponseType; urgent = inReplyTo->urgent(); } @@ -48,7 +48,7 @@ namespace crouton::io::blip { void MessageBuilder::makeError(Message::Error err) { - assert(!err.domain.empty() && err.code != 0); + precondition(!err.domain.empty() && err.code != 0); type = kErrorType; addProperty("Error-Domain", err.domain); addProperty("Error-Code", err.code); @@ -66,13 +66,13 @@ namespace crouton::io::blip { void MessageBuilder::writeTokenizedString(ostream& out, string_view str) { - assert(str.find('\0') == string::npos); + precondition(str.find('\0') == string::npos); out << str << '\0'; } MessageBuilder& MessageBuilder::addProperty(string_view name, string_view value) { - assert(!_wroteProperties); + precondition(!_wroteProperties); writeTokenizedString(_properties, name); writeTokenizedString(_properties, value); return *this; diff --git a/src/io/blip/MessageOut.cc b/src/io/blip/MessageOut.cc index 83aa08c..2da415d 100644 --- a/src/io/blip/MessageOut.cc +++ b/src/io/blip/MessageOut.cc @@ -107,7 +107,7 @@ namespace crouton::io::blip { ASYNC MessageOut::onResponse() { - assert(!_onResponse); + precondition(!_onResponse); _onResponse = Future::provider(); return Future(_onResponse); } @@ -162,7 +162,7 @@ namespace crouton::io::blip { : _payload(std::move(payload)) ,_unsentPayload(_payload) { - assert(_payload.size() <= UINT32_MAX); + precondition(_payload.size() <= UINT32_MAX); } diff --git a/src/io/esp32/Backtrace+ESP32.cc b/src/io/esp32/Backtrace+ESP32.cc new file mode 100644 index 0000000..ba575a4 --- /dev/null +++ b/src/io/esp32/Backtrace+ESP32.cc @@ -0,0 +1,53 @@ +// +// Backtrace+ESP32.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "Backtrace.hh" +#include "StringUtils.hh" +#include +#include + +namespace fleece { + using namespace std; + + namespace internal { + + int backtrace(void** buffer, size_t max) { + esp_backtrace_frame_t frame; + frame.exc_frame = nullptr; + esp_backtrace_get_start(&frame.pc, &frame.sp, &frame.next_pc); + int n = 0; + do { + *buffer++ = (void*)frame.pc; + ++n; + } while (esp_backtrace_get_next_frame(&frame)); + return n; + } + + char* unmangle(const char *function) { + return (char*)function; + } + + } + + + bool Backtrace::writeTo(ostream &out) const { + esp_backtrace_print(10); //TODO + return true; + } + +} diff --git a/src/io/esp32/CMakeLists.txt b/src/io/esp32/CMakeLists.txt new file mode 100644 index 0000000..6e3f16a --- /dev/null +++ b/src/io/esp32/CMakeLists.txt @@ -0,0 +1,52 @@ +set(root "../../..") +set(src "../..") + +idf_component_register( + SRCS + "${src}/Coroutine.cc" + "${src}/CoCondition.cc" + "${src}/CoroLifecycle.cc" + "${src}/Coroutine.cc" + "${src}/Error.cc" + "${src}/Future.cc" + "${src}/Internal.hh" + "${src}/Scheduler.cc" + "${src}/Select.cc" + "${src}/io/HTTPHandler.cc" + "${src}/io/HTTPParser.cc" + "${src}/io/ISocket.cc" + "${src}/io/IStream.cc" + "${src}/io/Process.cc" + "${src}/io/URL.cc" + "${src}/io/WebSocket.cc" + "${src}/io/mbed/TLSSocket.cc" + "${src}/support/Backtrace.cc" + "${src}/support/betterassert.cc" + "${src}/support/Logging.cc" + "${src}/support/Memoized.cc" + "${src}/support/MiniFormat.cc" + "${src}/support/StringUtils.cc" + + "Backtrace+ESP32.cc" + "ESPBase.cc" + "ESPEventLoop.cc" + "ESPAddrInfo.cc" + "ESPTCPSocket.cc" + + INCLUDE_DIRS + "${root}/include/" + + PRIV_INCLUDE_DIRS + "${src}/" + "${src}/support/" + "${root}/vendor/llhttp/include/" + "." + + REQUIRES + lwip + mbedtls +) + +target_compile_options(${COMPONENT_LIB} PRIVATE + "-Wno-unknown-pragmas" # ignore Xcode `#pragma mark` in source code +) diff --git a/src/io/esp32/ESPAddrInfo.cc b/src/io/esp32/ESPAddrInfo.cc new file mode 100644 index 0000000..82eca10 --- /dev/null +++ b/src/io/esp32/ESPAddrInfo.cc @@ -0,0 +1,82 @@ +// +// ESPAddrInfo.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "io/AddrInfo.hh" +#include "ESPBase.hh" +#include "CoCondition.hh" +#include "util/Logging.hh" +#include + +namespace crouton::io { + using namespace std; + using namespace crouton::io::esp; + + + Future AddrInfo::lookup(string hostname, uint16_t port) { + Blocker blocker; + auto callback = [] (const char *name, const ip_addr_t *ipaddr, void *ctx) { + // Warning: Callback is called on the lwip thread + auto b = (Blocker*)ctx; + if (ipaddr) { + b->notify(*ipaddr); + } else { + b->notify(ip_addr_t{.u_addr = {}, .type = 0xFF}); + } + }; + + ip_addr addr; + switch (err_t err = dns_gethostbyname(hostname.c_str(), &addr, callback, &blocker)) { + case ERR_OK: + RETURN AddrInfo(&addr); + case ERR_INPROGRESS: + LNet->debug("Awaiting DNS lookup of {}", hostname); + addr = AWAIT blocker; + LNet->debug("DNS lookup {}", (addr.type != 0xFF ? "succeeded" : "failed")); + if (addr.type != 0xFF) + RETURN AddrInfo(&addr); + else + RETURN ESPError::HostNotFound; + default: + RETURN Error(LWIPError(err)); + } + } + + + AddrInfo::AddrInfo(ip_addr* addr) + :_info(make_unique(*addr)) + { } + + ip_addr const& AddrInfo::primaryAddress() const { + return *_info; + } + + ip_addr const* AddrInfo::primaryAddress(int af) const { + if (af == 4) + return _info.get(); + else + Error(ESPError::HostNotFound).raise(); + } + + /// The primary address converted to a numeric string. + string AddrInfo::primaryAddressString() const { + char buf[32]; + return string(ipaddr_ntoa_r(_info.get(), buf, sizeof(buf))); + } + + +} diff --git a/src/io/esp32/ESPBase.cc b/src/io/esp32/ESPBase.cc new file mode 100644 index 0000000..5cfebf9 --- /dev/null +++ b/src/io/esp32/ESPBase.cc @@ -0,0 +1,44 @@ +// +// ESPBase.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "ESPBase.hh" +#include "Misc.hh" +#include +#include + +namespace crouton { + using namespace std; + using namespace crouton::io::esp; + + void Randomize(void* buf, size_t len) { + esp_fill_random(buf, len); + } + + string ErrorDomainInfo::description(errorcode_t code) { + switch (ESPError(code)) { + case ESPError::HostNotFound: return "Host not found"; + default: break; + } + return "???"; + }; + + string ErrorDomainInfo::description(errorcode_t code) { + return string(lwip_strerr(code)); + }; + +} diff --git a/src/io/esp32/ESPBase.hh b/src/io/esp32/ESPBase.hh new file mode 100644 index 0000000..ce5987e --- /dev/null +++ b/src/io/esp32/ESPBase.hh @@ -0,0 +1,80 @@ +// +// ESPBase.hh +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once +#include "Error.hh" + +namespace crouton::io::esp { + + enum class ESPError : errorcode_t { + none = 0, + HostNotFound, + }; + + + /** Same as LWIP's `err_t` */ + enum class LWIPError : errorcode_t { + /** No error, everything OK. */ + ERR_OK = 0, + /** Out of memory error. */ + ERR_MEM = -1, + /** Buffer error. */ + ERR_BUF = -2, + /** Timeout. */ + ERR_TIMEOUT = -3, + /** Routing problem. */ + ERR_RTE = -4, + /** Operation in progress */ + ERR_INPROGRESS = -5, + /** Illegal value. */ + ERR_VAL = -6, + /** Operation would block. */ + ERR_WOULDBLOCK = -7, + /** Address in use. */ + ERR_USE = -8, + /** Already connecting. */ + ERR_ALREADY = -9, + /** Conn already established.*/ + ERR_ISCONN = -10, + /** Not connected. */ + ERR_CONN = -11, + /** Low-level netif error */ + ERR_IF = -12, + + /** Connection aborted. */ + ERR_ABRT = -13, + /** Connection reset. */ + ERR_RST = -14, + /** Connection closed. */ + ERR_CLSD = -15, + /** Illegal argument. */ + ERR_ARG = -16 + }; + +} + +namespace crouton { + template <> struct ErrorDomainInfo { + static constexpr string_view name = "ESP32"; + static string description(errorcode_t); + }; + template <> struct ErrorDomainInfo { + static constexpr string_view name = "LWIP"; + static string description(errorcode_t); + }; +} diff --git a/src/io/esp32/ESPEventLoop.cc b/src/io/esp32/ESPEventLoop.cc new file mode 100644 index 0000000..fbbe72a --- /dev/null +++ b/src/io/esp32/ESPEventLoop.cc @@ -0,0 +1,241 @@ +// +// ESPEventLoop.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "EventLoop.hh" +#include "Future.hh" +#include "util/Logging.hh" +#include "Task.hh" + +#include +#include +#include + +#include +#include +#include + +namespace crouton { + using namespace std; + + using AsyncFn = std::function; + + /** Size of FreeRTOS event queue, per task. */ + static constexpr size_t kQueueLength = 16; + + /** The struct that goes in the FreeRTOS event queue. + Can't use any fancy C++ RAII stuff because these structs get memcpy'd by FreeRTOS. */ + struct Event { + enum Type : uint8_t { + Interrupt, + Async, + TimerFired, + }; + Type type = Interrupt; + union { + AsyncFn* asyncFn; + Timer* timer; + } data; + }; + + + /** Implementation of EventLoop for ESP32. */ + class ESPEventLoop final : public EventLoop { + public: + ESPEventLoop() + :_queue(xQueueCreate(kQueueLength, sizeof(Event))) + { + if (!_queue) throw std::bad_alloc(); + LLoop->trace("Created ESPEventLoop"); + } + + void run() override { + do { + runOnce(); + } while (!_interrupt); + _interrupt = false; + } + + bool runOnce(bool waitForIO =true) override { + LLoop->trace("runOnce..."); + _interrupt = false; + Event event; + if (xQueueReceive(_queue, &event, waitForIO ? portMAX_DELAY : 0) == pdPASS) { + switch (event.type) { + case Event::Interrupt: + LLoop->trace(" received Interrupt event"); + _interrupt = true; + break; + case Event::Async: + LLoop->trace(" received AsyncFn event"); + try { + (*event.data.asyncFn)(); + } catch (...) { + LLoop->error("*** Unexpected exception in EventLoop::perform callback ***"); + } + delete event.data.asyncFn; + break; + case Event::TimerFired: + LLoop->trace(" received TimerFired event"); + fireTimer(event.data.timer); + break; + default: + LLoop->error("Received nknown event type {}", uint8_t(event.type)); + break; + } + } + LLoop->trace("...runOnce returning; {} msgs waiting", + uxQueueMessagesWaiting(_queue)); + return uxQueueMessagesWaiting(_queue) > 0; + } + + void stop(bool threadSafe) override { + LLoop->trace("stop!"); + post(Event{.type = Event::Interrupt, .data = {}}); + } + + void perform(std::function fn) override { + LLoop->trace("posting perform"); + post(Event{ + .type = Event::Async, + .data = {.asyncFn = new AsyncFn(std::move(fn))} + }); + } + + void post(Event const& event) { + if (xQueueSendToBack(_queue, &event, 0) != pdPASS) + throw runtime_error("Event queue full!"); + } + + private: + QueueHandle_t _queue; + bool _interrupt = false; + }; + + + EventLoop* Scheduler::newEventLoop() { + return new ESPEventLoop(); + } + + +#pragma mark - TIMER: + + + static unsigned ms(double secs){ + return unsigned(::round(max(secs, 0.0) * 1000.0)); + } + + static TickType_t ticks(double secs){ + return pdMS_TO_TICKS(ms(secs)); + } + + + Timer::Timer(std::function fn) + :_fn(std::move(fn)) + ,_eventLoop(&Scheduler::current().eventLoop()) + ,_impl(nullptr) + { } + + + Timer::~Timer() { + if (_impl) stop(); + } + + + void Timer::_start(double delaySecs, double repeatSecs) { + LLoop->trace("Timer::start({}, {})", delaySecs, repeatSecs); + precondition(!_impl && (repeatSecs == 0.0 || repeatSecs == delaySecs)); + auto callback = [](TimerHandle_t timer) { + // The timer callback runs on a special FreeRTOS task. + // From here we post an event to the target task's EventLoop: + auto self = (Timer*)pvTimerGetTimerID(timer); + ((ESPEventLoop*)self->_eventLoop)->post(Event{ + .type = Event::TimerFired, + .data = {.timer = self} + }); + }; + _impl = xTimerCreate("Timer", ticks(delaySecs), (repeatSecs > 0), this, callback); + if (!_impl) throw std::bad_alloc(); + xTimerStart(TimerHandle_t(_impl), portMAX_DELAY); + } + + + void Timer::_fire() { + LLoop->trace("Timer fired! Calling fn..."); + try { + _fn(); + LLoop->trace("...Timer fn returned"); + } catch (exception const& x) { + LLoop->error("*** Caught unexpected exception in Timer callback: {}", x.what()); + } + if (_deleteMe) + delete this; + } + + + void Timer::stop() { + if (_impl) { + LLoop->trace("Timer::stop"); + xTimerDelete(TimerHandle_t(_impl), portMAX_DELAY); + _impl = nullptr; + } + } + + + /*static*/ void Timer::after(double delaySecs, std::function fn) { + auto t = new Timer(std::move(fn)); + t->_deleteMe = true; + t->once(delaySecs); + } + + + Future Timer::sleep(double delaySecs) { + auto provider = Future::provider(); + Timer::after(delaySecs, [provider]{provider->setResult();}); + return Future(provider); + } + + +#pragma mark - ON BACKGROUND THREAD: + + + Future OnBackgroundThread(std::function fn) { + static Scheduler* sBGScheduler; + static std::thread bgThread([] { + sBGScheduler = &Scheduler::current(); + while (true) + sBGScheduler->run(); + }); + + while (!sBGScheduler) + vTaskDelay(10); + + auto provider = Future::provider(); + sBGScheduler->onEventLoop([provider, fn] { + try { + fn(); + provider->setResult(); + } catch (std::exception const& x) { + LLoop->error("*** Caught unexpected exception in OnBackgroundThread fn: {} ***", + x.what()); + provider->setError(Error(x)); + } + }); + return Future(provider); + } + +} diff --git a/src/io/esp32/ESPTCPSocket.cc b/src/io/esp32/ESPTCPSocket.cc new file mode 100644 index 0000000..afe952e --- /dev/null +++ b/src/io/esp32/ESPTCPSocket.cc @@ -0,0 +1,233 @@ +// +// ESPTCPSocket.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// + +#include "ESPTCPSocket.hh" +#include "CoCondition.hh" +#include "ESPBase.hh" +#include "Internal.hh" +#include "util/Logging.hh" +#include "io/AddrInfo.hh" + +#include +#include +#include + +namespace crouton::io::esp { + using namespace std; + using crouton::io::AddrInfo; + + + TCPSocket::TCPSocket() + :_tcp(tcp_new()) + { + if (!_tcp) throw std::bad_alloc(); + } + + TCPSocket::~TCPSocket() { + if (_tcp) { + LNet->info("Closing TCPSocket (destructor)"); + if (tcp_close(_tcp) != ERR_OK) + tcp_abort(_tcp); + _tcp = nullptr; + } + if (_readBufs) + pbuf_free(_readBufs); + } + + ASYNC TCPSocket::open() { + AddrInfo addr = AWAIT AddrInfo::lookup(_binding->address, _binding->port); + + LNet->info("Opening TCP connection to {}:{} ...", _binding->address, _binding->port); + Blocker block; + auto onConnect = [](void *arg, struct tcp_pcb *tpcb, err_t err) -> err_t { + // this is called on the lwip thread + ((Blocker*)arg)->notify(err); + return 0; + }; + tcp_arg(_tcp, &block); + err_t err = tcp_connect(_tcp, &addr.primaryAddress(), _binding->port, onConnect); + if (!err) + err = AWAIT block; + if (err) { + Error error(LWIPError{err}); + LNet->error("...TCP connection failed: {}", minifmt::write(error)); + RETURN error; + } + + // Initialize read/write callbacks: + _isOpen = true; + tcp_arg(_tcp, this); + tcp_sent(_tcp, [](void *arg, struct tcp_pcb *tpcb, u16_t len) -> err_t { + return ((TCPSocket*)arg)->_writeCallback(len); + }); + tcp_recv(_tcp, [](void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) -> err_t { + return ((TCPSocket*)arg)->_readCallback(p, err); + }); + RETURN noerror; + } + + + Future TCPSocket::close() { + LNet->info("Closing TCPSocket"); + precondition(_isOpen); + err_t err = tcp_close(_tcp); + _tcp = nullptr; + _isOpen = false; + if (_readBufs) { + pbuf_free(_readBufs); + _readBufs = nullptr; + } + postcondition(!err); + return Future{}; + } + + + Future TCPSocket::closeWrite() { + // TODO: Implement + return CroutonError::Unimplemented; + } + + +#pragma mark - READING: + + + size_t TCPSocket::bytesAvailable() const noexcept { + return !_inputBuf.empty() || _readBufs != nullptr; + } + + + bool TCPSocket::isReadable() const noexcept { + return _isOpen && bytesAvailable() > 0; + } + + + Future TCPSocket::readNoCopy(size_t maxLen) { + precondition(isOpen()); + if (!_inputBuf.empty()) { + return _inputBuf.read(maxLen); + } else { + return fillInputBuf().then([this,maxLen](ConstBytes bytes) -> ConstBytes { + return _inputBuf.read(maxLen); + }); + } + } + + + Future TCPSocket::peekNoCopy() { + precondition(isOpen()); + if (_inputBuf.empty()) + return fillInputBuf(); + else + return Future(_inputBuf); + } + + + /// Low-level read method that points `_inputBuf` to the next input buffer. + Future TCPSocket::fillInputBuf() { + precondition(isOpen() && _inputBuf.empty()); + //FIXME: Needs a mutex accessing _readBufs? + _readBlocker.reset(); + if (_readBufs) { + // Clean up the pbuf I just completed: + tcp_recved(_tcp, _readBufs->len); // Flow control: Acknowledge this pbuf has been read + pbuf* next = _readBufs->next; + if (next) + pbuf_ref(next); + pbuf_free(_readBufs); + _readBufs = next; + } + + if (!_readBufs && !_readErr) { + // Wait for buffers to arrive: + LNet->debug("TCPSocket: waiting to receive data..."); + AWAIT _readBlocker; + LNet->debug("...TCPSocket: received data"); + assert(_readBufs || _readErr); + } + + if (_readBufs) { + _inputBuf = {_readBufs->payload, _readBufs->len}; + RETURN _inputBuf; + } else if (_readErr == CroutonError::EndOfData) { + RETURN ConstBytes{}; + } else { + RETURN _readErr; + } + } + + + int TCPSocket::_readCallback(::pbuf *pb, int err) { + // Warning: This is called on the lwip thread. + //FIXME: Needs a mutex accessing _readBufs? + if (pb) { + LNet->debug("read completed, {} bytes", pb->tot_len); + if (_readBufs == nullptr) + _readBufs = pb; // Note: I take over the reference to pb + else + pbuf_cat(_readBufs, pb); + } else { + LNet->debug("read completed, LWIP error {}", err); + _readErr = err ? Error(LWIPError(err)) : Error(CroutonError::EndOfData); + } + assert(_readBufs || _readErr); + _readBlocker.notify(); + return ERR_OK; + } + + +#pragma mark - WRITING: + + + Future TCPSocket::write(ConstBytes data) { + precondition(isOpen()); + int flag = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE; + while (!data.empty()) { + auto nextData = data; + ConstBytes chunk = nextData.read(tcp_sndbuf(_tcp)); + LNet->info("TCPSocket::writing {} bytes", chunk.size()); + if (nextData.empty()) + flag &= ~TCP_WRITE_FLAG_MORE; + _writeBlocker.reset(); + switch (err_t err = tcp_write(_tcp, chunk.data(), chunk.size(), flag)) { + case ERR_OK: + data = nextData; + break; + case ERR_MEM: + LNet->debug("TCPSocket::write blocking...", chunk.size()); + AWAIT _writeBlocker; + LNet->debug("...TCPSocket::write unblocked", chunk.size()); + break; + default: + LNet->error("TCPSocket::write -- error {}", minifmt::write(err)); + RETURN LWIPError(err); + } + } + RETURN noerror; + } + + + int TCPSocket::_writeCallback(uint16_t len) { + // Warning: This is called on the lwip thread. + LNet->debug("write completed, {} bytes", len); + _writeBlocker.notify(); + return 0; + } + +} diff --git a/src/io/esp32/ESPTCPSocket.hh b/src/io/esp32/ESPTCPSocket.hh new file mode 100644 index 0000000..bba86e7 --- /dev/null +++ b/src/io/esp32/ESPTCPSocket.hh @@ -0,0 +1,80 @@ +// +// ESPTCPSocket.hh +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once +#include "CoCondition.hh" +#include "io/ISocket.hh" +#include "io/IStream.hh" +#include +#include + +struct pbuf; +struct tcp_pcb; +namespace crouton { + struct Buffer; +} + +namespace crouton::io::esp { + + class TCPSocket : public io::ISocket, public io::IStream { + public: + TCPSocket(); + ~TCPSocket(); + + /// Opens the socket to the bound address. Resolves once opened. + ASYNC open() override; + + bool isOpen() const override {return _isOpen;} + IStream& stream() override {return *this;} + ASYNC close() override; + + ASYNC closeWrite() override; + + /// True if the stream has data available to read. + bool isReadable() const noexcept Pure; + + /// The number of bytes known to be available without blocking. + size_t bytesAvailable() const noexcept Pure; + + ASYNC readNoCopy(size_t maxLen = 65536) override; + + ASYNC peekNoCopy() override; + + ASYNC write(ConstBytes) override; + + private: + using BufferRef = std::unique_ptr; + + int _writeCallback(uint16_t len); + int _readCallback(::pbuf *p, int err); + ASYNC _read(size_t len, void* dst); + ASYNC readBuf(); + ASYNC fillInputBuf(); + + tcp_pcb* _tcp; + bool _isOpen = false; + + ConstBytes _inputBuf; + pbuf* _readBufs = nullptr; + Error _readErr; + + Blocker _readBlocker; + Blocker _writeBlocker; + }; + +} diff --git a/src/io/esp32/README.md b/src/io/esp32/README.md new file mode 100644 index 0000000..1bbeb97 --- /dev/null +++ b/src/io/esp32/README.md @@ -0,0 +1,5 @@ +# Crouton ESP32 Component + +This directory is a Component for the ESP-IDF embedded framework. To use it in your IDF-based project, add it to the root `CMakeLists.txt`'s `EXTRA_COMPONENT_DIRS` property. + +For an example, see the `tests/ESP32` directory. diff --git a/src/io/mbed/TLSContext.hh b/src/io/mbed/TLSContext.hh index 30e103e..a409677 100644 --- a/src/io/mbed/TLSContext.hh +++ b/src/io/mbed/TLSContext.hh @@ -55,7 +55,7 @@ #pragma once #include "Error.hh" -#include "Logging.hh" +#include "util/Logging.hh" #if defined(_WIN32) # if !defined(_CRT_SECURE_NO_DEPRECATE) @@ -65,6 +65,11 @@ # include #endif +#ifdef ESP_PLATFORM +#include +#include +#endif + #include #include #include @@ -74,8 +79,10 @@ #include #include #include +#include #ifdef __APPLE__ + // macOS, iOS: #include #include #ifdef TARGET_OS_OSX @@ -83,8 +90,17 @@ #include #include #endif -#elif !defined(_WIN32) - // For Unix read_system_root_certs(): +#elif defined(ESP_PLATFORM) + // ESP32: + #include +#elif defined(_WIN32) + // Windows: + #include + #include + #pragma comment (lib, "crypt32.lib") + #pragma comment (lib, "cryptui.lib") +#else + // Other Unix; for read_system_root_certs(): #include #include #include @@ -92,13 +108,9 @@ #include #include #include -#else - #include - #include - #pragma comment (lib, "crypt32.lib") - #pragma comment (lib, "cryptui.lib") #endif + namespace crouton::io::mbed { /// Domain for mbedTLS error codes. enum class MbedError : errorcode_t { }; @@ -112,7 +124,7 @@ namespace crouton { mbedtls_strerror(code, msg, sizeof(msg)); //if (withCode) { size_t len = strlen(msg); - snprintf(msg + len, sizeof(msg) - len, " (-0x%04x)", -code); + snprintf(msg + len, sizeof(msg) - len, " (-0x%04x)", int(-code)); //} return string(msg); } @@ -142,32 +154,7 @@ namespace crouton::io::mbed { // - SPDLOG_LEVEL_CRITICAL 5 // - SPDLOG_LEVEL_OFF 6 - // Default log level is Warn because mbedTLS logging is very noisy at any higher level. - static shared_ptr LMbed = MakeLogger("mbedTLS", spdlog::level::warn); - - - // spdlog level values corresponding to ones used by mbedTLS - static constexpr int kSpdToMbedLogLevel[] = {4, 3, 2, 1, 1, 1, 0}; - - - static void mbedLogCallback(void *ctx, int level, const char *file, int line, const char *msg) { - using enum spdlog::level::level_enum; - static constexpr spdlog::level::level_enum kMbedToSpdLevel[] = { - off, err, info, debug, trace}; - - auto spdLevel = kMbedToSpdLevel[level]; - if (LMbed->should_log(spdLevel)) { - string_view msgStr(msg); - if (msgStr.ends_with('\n')) - msgStr = msgStr.substr(0, msgStr.size() - 1); - - // Strip parent directory names from filename: - if (auto lastSlash = strrchr(file, '/')) - file = lastSlash + 1; - - LMbed->log(spdlog::source_loc(file, line, "?"), spdLevel, msgStr); - } - } + LoggerRef LMbed; // Simple RAII helper for mbedTLS cert struct @@ -194,8 +181,7 @@ namespace crouton::io::mbed { /// @param endpoint Must be MBEDTLS_SSL_IS_CLIENT or MBEDTLS_SSL_IS_SERVER explicit TLSContext(int endpoint) { mbedtls_ssl_config_init(&_config); - mbedtls_ssl_conf_dbg(&_config, mbedLogCallback, this); - mbedtls_debug_set_threshold(kSpdToMbedLogLevel[LMbed->level()]); + setupLogging(); mbedtls_ssl_conf_rng(&_config, mbedtls_ctr_drbg_random, get_drbg_context()); check(mbedtls_ssl_config_defaults(&_config, endpoint, @@ -203,8 +189,12 @@ namespace crouton::io::mbed { MBEDTLS_SSL_PRESET_DEFAULT), "mbedtls_ssl_config_defaults"); +#ifdef ESP_PLATFORM + esp_crt_bundle_attach(&_config); +#else if (auto roots = get_system_root_certs()) mbedtls_ssl_conf_ca_chain(&_config, roots, nullptr); +#endif } ~TLSContext() { @@ -217,6 +207,51 @@ namespace crouton::io::mbed { private: + void setupLogging() { +#ifdef ESP_PLATFORM + #ifdef CONFIG_MBEDTLS_DEBUG + mbedtls_esp_enable_debug_log(_config, kSpdToMbedLogLevel[LMbed->level()]); + #endif +#else + static std::once_flag once; + call_once(once, [] { + // This logger is off by default, because mbedTLS logging is very noisy -- + // even in a successful handshake it will write several error-level logs. + LMbed = MakeLogger("mbedTLS", LogLevel::off); + }); + + // spdlog level values corresponding to ones used by mbedTLS + static constexpr int kSpdToMbedLogLevel[] = {4, 3, 2, 1, 1, 1, 0}; + + auto mbedLogCallback = [](void *ctx, int level, const char *file, int line, + const char *msg) { + using enum LogLevelType; + static constexpr LogLevelType kMbedToSpdLevel[] = { + off, err, info, debug, trace}; + + auto spdLevel = kMbedToSpdLevel[level]; + if (LMbed->should_log(spdLevel)) { + string_view msgStr(msg); + if (msgStr.ends_with('\n')) + msgStr = msgStr.substr(0, msgStr.size() - 1); + + // Strip parent directory names from filename: + if (auto lastSlash = strrchr(file, '/')) + file = lastSlash + 1; + +#if CROUTON_USE_SPDLOG + LMbed->log(spdlog::source_loc(file, line, "?"), spdLevel, msgStr); +#else + LMbed->log(spdLevel, msgStr); +#endif + } + }; + mbedtls_ssl_conf_dbg(&_config, mbedLogCallback, this); + mbedtls_debug_set_threshold(kSpdToMbedLogLevel[LMbed->level()]); +#endif + } + + // Returns a shared singleton mbedTLS random-number generator context. static mbedtls_ctr_drbg_context* get_drbg_context() { static const char* k_entropy_personalization = "Crouton"; @@ -255,6 +290,7 @@ namespace crouton::io::mbed { } +#if !defined(ESP_PLATFORM) // Returns the set of system trusted root CA certs. mbedtls_x509_crt* get_system_root_certs() { static once_flag once; @@ -267,6 +303,7 @@ namespace crouton::io::mbed { }); return s_system_root_certs; } +#endif // Parses a data blob containing one or many X.59 certs. @@ -340,7 +377,7 @@ namespace crouton::io::mbed { return certs.str(); } -#else +#elif !defined(ESP_PLATFORM) // Read system root CA certs on Linux using OpenSSL's cert directory static string read_system_root_certs() { #ifdef __ANDROID__ diff --git a/src/io/mbed/TLSSocket.cc b/src/io/mbed/TLSSocket.cc index c6d1dbc..cf0e5aa 100644 --- a/src/io/mbed/TLSSocket.cc +++ b/src/io/mbed/TLSSocket.cc @@ -17,9 +17,10 @@ // #include "io/mbed/TLSSocket.hh" -#include "io/TCPSocket.hh" +#include "io/ISocket.hh" +#include "Internal.hh" #include "TLSContext.hh" -#include "UVInternal.hh" +#include // Code adapted from Couchbase's fork of sockpp: // https://github.com/couchbasedeps/sockpp/blob/couchbase-master/src/mbedtls_context.cpp @@ -61,8 +62,9 @@ namespace crouton::io::mbed { class TLSSocket::Impl { public: - Impl(std::unique_ptr stream, TLSContext& context, string const& hostname) - :_stream(std::move(stream)) + Impl(std::unique_ptr socket, TLSContext& context, string const& hostname) + :_socket(std::move(socket)) + ,_stream(_socket->stream()) ,_context(context) { mbedtls_ssl_init(&_ssl); @@ -89,7 +91,7 @@ namespace crouton::io::mbed { // Coroutine that opens the underlying stream and then runs the TLS handshake. Future handshake() { - AWAIT _stream->open(); + AWAIT _stream.open(); _tcpOpen = true; int status; @@ -178,9 +180,9 @@ namespace crouton::io::mbed { _tlsOpen = false; if (fully) { _tcpOpen = _tlsOpen = false; - AWAIT _stream->close(); + AWAIT _stream.close(); } else { - AWAIT _stream->closeWrite(); + AWAIT _stream.closeWrite(); } _tcpOpen = false; //cerr << "TLSStream is now closed.\n"; @@ -200,7 +202,7 @@ namespace crouton::io::mbed { result = MBEDTLS_ERR_SSL_WANT_WRITE; // still busy with the last write else { //cerr << "[async write " << length << "] " << endl; - _pendingWrite.emplace(_stream->write(string((char*)buf, length))); + _pendingWrite.emplace(_stream.write(string((char*)buf, length))); result = int(length); } //cerr << "-> " << result << endl; @@ -227,7 +229,7 @@ namespace crouton::io::mbed { } else if (!_readEOF) { // We've read the entire buffer; time to read again: //cerr << "[async read] " << endl; - _pendingRead.emplace(_stream->readNoCopy(100000)); + _pendingRead.emplace(_stream.readNoCopy(100000)); _readBuf = ConstBytes{}; result = MBEDTLS_ERR_SSL_WANT_READ; } else { @@ -262,7 +264,8 @@ namespace crouton::io::mbed { private: - std::unique_ptr _stream; // The underlying TCP stream + std::unique_ptr _socket; // The underlying TCP socket + IStream& _stream; // The socket's stream TLSContext& _context; // The shared mbedTLS context mbedtls_ssl_context _ssl; // The mbedTLS SSL object optional> _pendingWrite; // In-progress write operation, if any @@ -290,9 +293,9 @@ namespace crouton::io::mbed { } Future TLSSocket::open() { - assert(!_impl); + precondition(!_impl); NotReentrant nr(_busy); - auto stream = make_unique(); + auto stream = ISocket::newSocket(false); stream->bind(_binding->address, _binding->port); stream->setNoDelay(_binding->noDelay); stream->keepAlive(_binding->keepAlive); diff --git a/src/io/AddrInfo.cc b/src/io/uv/AddrInfo.cc similarity index 78% rename from src/io/AddrInfo.cc rename to src/io/uv/AddrInfo.cc index 487025a..442bf47 100644 --- a/src/io/AddrInfo.cc +++ b/src/io/uv/AddrInfo.cc @@ -21,6 +21,7 @@ namespace crouton::io { using namespace std; + using namespace crouton::io::uv; #pragma mark - DNS LOOKUP: @@ -39,21 +40,13 @@ namespace crouton::io { }; - AddrInfo& AddrInfo::operator=(AddrInfo&& ai) { - uv_freeaddrinfo(_info); - _info = ai._info; - ai._info = nullptr; - return *this; - } - + void AddrInfo::deleter::operator()(addrinfo* info) {uv_freeaddrinfo(info);} - AddrInfo::~AddrInfo() { - uv_freeaddrinfo(_info); - } + AddrInfo::AddrInfo(addrinfo* info) :_info(info, deleter{}) { } Future AddrInfo::lookup(string hostName, uint16_t port) { - struct addrinfo hints = { + addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, @@ -76,7 +69,7 @@ namespace crouton::io { } - sockaddr const* AddrInfo::_primaryAddress(int ipv) const { + sockaddr const* AddrInfo::primaryAddress(int ipv) const { assert(_info); int af = ipv; switch (ipv) { @@ -84,28 +77,21 @@ namespace crouton::io { case 6: af = AF_INET6; break; } - for (auto i = _info; i; i = i->ai_next) { + for (auto i = _info.get(); i; i = i->ai_next) { if (i->ai_socktype == SOCK_STREAM && i->ai_protocol == IPPROTO_TCP && i->ai_family == af) return i->ai_addr; } return nullptr; } - sockaddr const& AddrInfo::primaryAddress(int af) const { - if (auto addr = _primaryAddress(af)) - return *addr; - else - Error::raise(UVError(UV__EAI_ADDRFAMILY), "getting address of hostname"); - } - sockaddr const& AddrInfo::primaryAddress() const { - auto addr = _primaryAddress(4); + auto addr = primaryAddress(4); if (!addr) - addr = _primaryAddress(6); + addr = primaryAddress(6); if (addr) return *addr; else - Error::raise(UVError(UV__EAI_ADDRFAMILY), "getting address of hostname"); + Error::raise(uv::UVError(UV__EAI_ADDRFAMILY), "getting address of hostname"); } string AddrInfo::primaryAddressString() const { diff --git a/src/io/FileStream.cc b/src/io/uv/FileStream.cc similarity index 94% rename from src/io/FileStream.cc rename to src/io/uv/FileStream.cc index ec2986d..eade9f3 100644 --- a/src/io/FileStream.cc +++ b/src/io/uv/FileStream.cc @@ -21,6 +21,7 @@ namespace crouton::io { using namespace std; + using namespace crouton::io::uv; const int FileStream::Append = O_APPEND; @@ -37,7 +38,7 @@ namespace crouton::io { void check(int r) { if (r < 0) - _futureP->setError(Error(UVError{r}, _what)); + _futureP->setError(Error(uv::UVError{r}, _what)); } static void callback(uv_fs_s *req) { @@ -71,8 +72,8 @@ namespace crouton::io { FileStream::FileStream(int fd) :_fd(fd) { } FileStream::~FileStream() {_close();} - FileStream::FileStream(FileStream&& fs) = default; - FileStream& FileStream::operator=(FileStream&& fs) = default; + FileStream::FileStream(FileStream&& fs) noexcept = default; + FileStream& FileStream::operator=(FileStream&& fs) noexcept = default; Future FileStream::open() { @@ -84,7 +85,7 @@ namespace crouton::io { // This is FileStream's primitive read operation. Future FileStream::_preadv(const MutableBytes bufs[], size_t nbufs, int64_t offset) { - assert(isOpen()); + precondition(isOpen()); static constexpr size_t kMaxBufs = 8; if (nbufs > kMaxBufs) return Error(CroutonError::InvalidArgument, "too many bufs"); @@ -144,7 +145,7 @@ namespace crouton::io { // This is FileStream's primitive write operation. Future FileStream::pwritev(const ConstBytes bufs[], size_t nbufs, int64_t offset) { NotReentrant nr(_busy); - assert(isOpen()); + precondition(isOpen()); _readBuf = nullptr; // because this write might invalidate it @@ -177,7 +178,7 @@ namespace crouton::io { void FileStream::_close() { if (isOpen()) { - assert(!_busy); + precondition(!_busy); _readBuf = nullptr; // Close synchronously, for simplicity uv_fs_t closeReq; diff --git a/src/io/Filesystem.cc b/src/io/uv/Filesystem.cc similarity index 99% rename from src/io/Filesystem.cc rename to src/io/uv/Filesystem.cc index a8718f5..907a2bc 100644 --- a/src/io/Filesystem.cc +++ b/src/io/uv/Filesystem.cc @@ -24,6 +24,7 @@ namespace crouton::io::fs { using namespace std; + using namespace crouton::io::uv; struct fs_request : public uv_fs_t { public: diff --git a/src/io/Pipe.cc b/src/io/uv/Pipe.cc similarity index 97% rename from src/io/Pipe.cc rename to src/io/uv/Pipe.cc index 99fbaa1..0d14abe 100644 --- a/src/io/Pipe.cc +++ b/src/io/uv/Pipe.cc @@ -21,6 +21,7 @@ namespace crouton::io { using namespace std; + using namespace crouton::io::uv; pair Pipe::createPair() { uv_file fds[2]; diff --git a/src/io/Stream.cc b/src/io/uv/Stream.cc similarity index 92% rename from src/io/Stream.cc rename to src/io/uv/Stream.cc index 5c5101d..8c548fe 100644 --- a/src/io/Stream.cc +++ b/src/io/uv/Stream.cc @@ -21,6 +21,7 @@ namespace crouton::io { using namespace std; + using namespace crouton::io::uv; using write_request = AwaitableRequest; @@ -40,7 +41,7 @@ namespace crouton::io { Future Stream::closeWrite() { - assert(isOpen()); + precondition(isOpen()); AwaitableRequest req("closing connection"); check( uv_shutdown(&req, _stream, req.callback), "closing connection"); @@ -50,7 +51,7 @@ namespace crouton::io { void Stream::_close() { - assert(!_readBusy); + precondition(!_readBusy); _inputBuf.reset(); closeHandle(_stream); } @@ -64,18 +65,18 @@ namespace crouton::io { #pragma mark - READING: - size_t Stream::bytesAvailable() const { + size_t Stream::bytesAvailable() const noexcept { return _inputBuf ? _inputBuf->available() : 0; } - bool Stream::isReadable() const { + bool Stream::isReadable() const noexcept { return _stream && (bytesAvailable() > 0 || uv_is_readable(_stream)); } Future Stream::readNoCopy(size_t maxLen) { - assert(isOpen()); + precondition(isOpen()); NotReentrant nr(_readBusy); if (_inputBuf && !_inputBuf->empty()) { // Advance _inputBuf->used and return the pointer: @@ -92,7 +93,7 @@ namespace crouton::io { Future Stream::peekNoCopy() { - assert(isOpen()); + precondition(isOpen()); NotReentrant nr(_readBusy); if (!_inputBuf || _inputBuf->empty()) return fillInputBuf(); @@ -103,8 +104,7 @@ namespace crouton::io { /// Low-level read method that ensures there is data to read in `_inputBuf`. Future Stream::fillInputBuf() { - assert(isOpen()); - assert(_readBusy); + precondition(isOpen() && _readBusy); if (_inputBuf && _inputBuf->available() == 0) { // Recycle the used-up buffer: _spare.emplace_back(std::move(_inputBuf)); @@ -120,8 +120,7 @@ namespace crouton::io { /// Reads once from the uv_stream and returns the result as a BufferRef. Future Stream::readBuf() { - assert(isOpen()); - assert(!_readFuture); + precondition(isOpen() && !_readFuture); if (!_input.empty()) { // We have an already-read buffer available; return it: Future result(std::move(_input[0])); @@ -133,7 +132,7 @@ namespace crouton::io { if (err == UV_EOF || err == UV_EINVAL) return BufferRef(); else - return Error(UVError(err), "reading from the network"); + return Error(uv::UVError(err), "reading from the network"); } else { // Start an async read: read_start(); @@ -199,7 +198,7 @@ namespace crouton::io { else if (err == UV_EOF || err == UV_EINVAL) _readFuture->setResult(nullptr); else - _readFuture->setError(Error(UVError{err}, "reading from the network")); + _readFuture->setError(Error(uv::UVError{err}, "reading from the network")); _readFuture = nullptr; } else { // If this is an unrequested read, queue it up for later: @@ -217,12 +216,12 @@ namespace crouton::io { #pragma mark - WRITING: - bool Stream::isWritable() const { + bool Stream::isWritable() const noexcept { return _stream && uv_is_writable(_stream); } Future Stream::write(const ConstBytes bufs[], size_t nbufs) { - assert(isOpen()); + precondition(isOpen()); static constexpr size_t kMaxBufs = 8; if (nbufs > kMaxBufs) diff --git a/src/io/TCPServer.cc b/src/io/uv/TCPServer.cc similarity index 97% rename from src/io/TCPServer.cc rename to src/io/uv/TCPServer.cc index 8f85312..ea1d739 100644 --- a/src/io/TCPServer.cc +++ b/src/io/uv/TCPServer.cc @@ -19,10 +19,11 @@ #include "io/TCPServer.hh" #include "io/TCPSocket.hh" #include "UVInternal.hh" -#include "Logging.hh" +#include "util/Logging.hh" namespace crouton::io { using namespace std; + using namespace crouton::io::uv; TCPServer::TCPServer(uint16_t port) diff --git a/src/io/TCPSocket.cc b/src/io/uv/TCPSocket.cc similarity index 94% rename from src/io/TCPSocket.cc rename to src/io/uv/TCPSocket.cc index 4a037b3..06bb433 100644 --- a/src/io/TCPSocket.cc +++ b/src/io/uv/TCPSocket.cc @@ -22,6 +22,7 @@ namespace crouton::io { using namespace std; + using namespace crouton::io::uv; using connect_request = AwaitableRequest; @@ -30,8 +31,8 @@ namespace crouton::io { Future TCPSocket::open() { - assert(!isOpen()); - assert(_binding); + precondition(!isOpen()); + precondition(_binding); int err; // Resolve the address/hostname: diff --git a/src/io/UVBase.cc b/src/io/uv/UVBase.cc similarity index 84% rename from src/io/UVBase.cc rename to src/io/uv/UVBase.cc index 0413f46..1a30f86 100644 --- a/src/io/UVBase.cc +++ b/src/io/uv/UVBase.cc @@ -16,22 +16,30 @@ // limitations under the License. // -#include "io/UVBase.hh" +#include "io/uv/UVBase.hh" #include "EventLoop.hh" -#include "Logging.hh" +#include "util/Logging.hh" +#include "Misc.hh" #include "Task.hh" #include "UVInternal.hh" #include #include namespace crouton { + using namespace crouton::io::uv; + + void Randomize(void* buf, size_t len) { + uv_random_t req; + check(uv_random(curLoop(), &req, buf, len, 0, nullptr), "generating random bytes"); + } + ConstBytes::ConstBytes(uv_buf_t buf) :Bytes((byte*)buf.base, buf.len) { } MutableBytes::MutableBytes(uv_buf_t buf) :Bytes((byte*)buf.base, buf.len) { } - ConstBytes::operator uv_buf_t() const { return uv_buf_init((char*)data(), unsigned(size())); } - MutableBytes::operator uv_buf_t() const { return uv_buf_init((char*)data(), unsigned(size())); } + ConstBytes::operator uv_buf_t() const noexcept { return uv_buf_init((char*)data(), unsigned(size())); } + MutableBytes::operator uv_buf_t() const noexcept { return uv_buf_init((char*)data(), unsigned(size())); } - string ErrorDomainInfo::description(errorcode_t code) { + string ErrorDomainInfo::description(errorcode_t code) { switch (code) { case UV__EAI_NONAME: return "unknown host"; default: return uv_strerror(code); @@ -39,7 +47,7 @@ namespace crouton { }; } -namespace crouton::io { +namespace crouton::io::uv { using namespace std; @@ -52,8 +60,6 @@ namespace crouton::io { void stop(bool threadSafe) override; void perform(std::function) override; - ASYNC sleep(double delaySecs); - void ensureWaits(); uv_loop_s* uvLoop() {return _loop.get();} private: @@ -140,20 +146,15 @@ namespace crouton::io { return ((UVEventLoop&)Scheduler::current().eventLoop()).uvLoop(); } - - void Randomize(void* buf, size_t len) { - uv_random_t req; - check(uv_random(curLoop(), &req, buf, len, 0, nullptr), "generating random bytes"); - } - } namespace crouton { using namespace std; + using namespace crouton::io::uv; EventLoop* Scheduler::newEventLoop() { - auto loop = new io::UVEventLoop(); + auto loop = new UVEventLoop(); loop->ensureWaits(); return loop; } @@ -166,36 +167,42 @@ namespace crouton { Timer::Timer(std::function fn) :_fn(std::move(fn)) - ,_handle(new uv_timer_t) + ,_impl(new uv_timer_t) { - uv_timer_init(io::curLoop(), _handle); - _handle->data = this; + uv_timer_init(curLoop(), ((uv_timer_t*)_impl)); + ((uv_timer_t*)_impl)->data = this; } Timer::~Timer() { - uv_timer_stop(_handle); - io::closeHandle(_handle); + auto handle = ((uv_timer_t*)_impl); + uv_timer_stop(handle); + closeHandle(handle); } void Timer::_start(double delaySecs, double repeatSecs) { auto callback = [](uv_timer_t *handle) noexcept { auto self = (Timer*)handle->data; - try { - self->_fn(); - } catch (...) { - LLoop->error("*** Caught unexpected exception in Timer callback ***"); - } - if (self->_deleteMe) - delete self; + self->_fire(); }; - uv_timer_start(_handle, callback, ms(delaySecs), ms(repeatSecs)); + uv_timer_start(((uv_timer_t*)_impl), callback, ms(delaySecs), ms(repeatSecs)); + } + + + void Timer::_fire() { + try { + _fn(); + } catch (...) { + LLoop->error("*** Caught unexpected exception in Timer callback ***"); + } + if (_deleteMe) + delete this; } void Timer::stop() { - uv_timer_stop(_handle); + uv_timer_stop(((uv_timer_t*)_impl)); } @@ -221,7 +228,7 @@ namespace crouton { Future OnBackgroundThread(std::function fn) { auto work = new QueuedWork{.fn = std::move(fn)}; - io::check(uv_queue_work(io::curLoop(), work, [](uv_work_t *req) noexcept { + check(uv_queue_work(curLoop(), work, [](uv_work_t *req) noexcept { auto work = static_cast(req); try { work->fn(); diff --git a/src/io/UVInternal.hh b/src/io/uv/UVInternal.hh similarity index 72% rename from src/io/UVInternal.hh rename to src/io/uv/UVInternal.hh index b115bf2..823e8d0 100644 --- a/src/io/UVInternal.hh +++ b/src/io/uv/UVInternal.hh @@ -21,9 +21,9 @@ #include "Coroutine.hh" #include "Internal.hh" #include "util/Bytes.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include "Scheduler.hh" -#include "io/UVBase.hh" +#include "io/uv/UVBase.hh" #include #include @@ -36,7 +36,7 @@ -namespace crouton::io { +namespace crouton::io::uv { /// Checks a libuv function result and throws a UVError exception if it's negative. static inline void check(std::signed_integral auto status, const char* what) { @@ -87,33 +87,4 @@ namespace crouton::io { }; - /** A data buffer used by stream_wrapper and Stream. */ - struct Buffer { - static constexpr size_t kCapacity = 65536 - 2 * sizeof(uint32_t); - - uint32_t size = 0; ///< Length of valid data - uint32_t used = 0; ///< Number of bytes consumed (from start of data) - std::byte data[kCapacity]; ///< The data itself - - size_t available() const {return size - used;} - bool empty() const {return size == used;} - - ConstBytes bytes() const {return {data + used, size - used};} - - ConstBytes read(size_t maxLen) { - size_t n = std::min(maxLen, available()); - ConstBytes result(data + used, n); - used += n; - return result; - } - - void unRead(size_t len) { - assert(len <= used); - used -= len; - } - }; - - using BufferRef = std::unique_ptr; - - } diff --git a/src/support/Backtrace+Unix.cc b/src/support/Backtrace+Unix.cc new file mode 100644 index 0000000..6693d96 --- /dev/null +++ b/src/support/Backtrace+Unix.cc @@ -0,0 +1,181 @@ +// +// Backtrace+Unix.cc +// +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef _WIN32 + +#include "Backtrace.hh" +#include "StringUtils.hh" +#include "util/betterassert.hh" +#include + +#include // dladdr() + +#ifndef __ANDROID__ + #define HAVE_EXECINFO + #include // backtrace(), backtrace_symbols() +#else + #include // free + #include // _Unwind_Backtrace(), etc. +#endif + +#ifndef __has_include +# define __has_include() 0 +#endif + +#if __has_include() || defined(__ANDROID__) + #include // abi::__cxa_demangle() + #define HAVE_UNMANGLE +#endif + + +namespace fleece { + using namespace std; + + + namespace internal { +#ifdef HAVE_EXECINFO + int backtrace(void** buffer, size_t max) { + return ::backtrace(buffer, int(max)); + } +#else + // Use libunwind to emulate backtrace(). This is limited in that it won't traverse any stack + // frames before a signal handler, so it's not useful for logging the stack upon a crash. + // Adapted from https://stackoverflow.com/a/28858941 + // See also https://stackoverflow.com/q/29559347 for more details & possible workarounds + + struct BacktraceState { + void** current; + void** end; + }; + + static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { + BacktraceState* state = static_cast(arg); + uintptr_t pc = _Unwind_GetIP(context); + if (pc) { + if (state->current == state->end) { + return _URC_END_OF_STACK; + } else { + *state->current++ = reinterpret_cast(pc); + } + } + return _URC_NO_REASON; + } + + int backtrace(void** buffer, size_t max) { + BacktraceState state = {buffer, buffer + max}; + _Unwind_Backtrace(unwindCallback, &state); + + return int(state.current - buffer); + } +#endif + + + char* unmangle(const char *function) { +#ifdef HAVE_UNMANGLE + int status; + size_t unmangledLen; + char *unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); + if (unmangled && status == 0) + return unmangled; + free(unmangled); +#endif + return (char*)function; + } + } + + + Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { + precondition(i < _addrs.size()); + frameInfo frame = { }; + Dl_info info; + if (dladdr(_addrs[i], &info)) { + frame.pc = _addrs[i]; + frame.offset = (size_t)frame.pc - (size_t)info.dli_saddr; + frame.function = info.dli_sname; + frame.library = info.dli_fname; + const char *slash = strrchr(frame.library, '/'); + if (slash) + frame.library = slash + 1; + } + return frame; + } + + + // If any of these strings occur in a backtrace, suppress further frames. + static constexpr const char* kTerminalFunctions[] = { + "_C_A_T_C_H____T_E_S_T_", + "Catch::TestInvokerAsFunction::invoke() const", + "litecore::actor::Scheduler::task(unsigned)", + "litecore::actor::GCDMailbox::safelyCall", + }; + + static constexpr struct {const char *old, *nuu;} kAbbreviations[] = { + {"(anonymous namespace)", "(anon)"}, + {"std::__1::", "std::"}, + {"std::basic_string, std::allocator >", + "string"}, + }; + + + bool Backtrace::writeTo(ostream &out) const { + for (unsigned i = 0; i < _addrs.size(); ++i) { + if (i > 0) + out << '\n'; + out << '\t'; + char *cstr = nullptr; + auto frame = getFrame(i); + int len; + bool stop = false; + if (frame.function) { + string name = Unmangle(frame.function); + // Stop when we hit a unit test, or other known functions: + for (auto fn : kTerminalFunctions) { + if (name.find(fn) != string::npos) + stop = true; + } + // Abbreviate some C++ verbosity: + for (auto &abbrev : kAbbreviations) + crouton::replaceStringInPlace(name, abbrev.old, abbrev.nuu); + len = asprintf(&cstr, "%2d %-25s %s + %zd", + i, frame.library, name.c_str(), frame.offset); + } else { + len = asprintf(&cstr, "%2d %p", i, _addrs[i]); + } + if (len < 0) + return false; + out.write(cstr, size_t(len)); + free(cstr); + + if (stop) { + out << "\n\t ... (" << (_addrs.size() - i - 1) << " more suppressed) ..."; + break; + } + } + return true; + } + + + std::string RawFunctionName(const void *pc) { + Dl_info info = {}; + dladdr(pc, &info); + return info.dli_sname; + } + +} + +#endif // _WIN32 diff --git a/src/support/Backtrace+Windows.cc b/src/support/Backtrace+Windows.cc new file mode 100644 index 0000000..24411b5 --- /dev/null +++ b/src/support/Backtrace+Windows.cc @@ -0,0 +1,128 @@ +// +// Backtrace+Windows.cc +// +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifdef _WIN32 + +#include "Backtrace.hh" +#include +#include +#include +#include +#include +#include +#include +#include "util/betterassert.hh" + +#pragma comment(lib, "Dbghelp.lib") +#include +#include +#include "asprintf.h" +#include +using namespace std; + +namespace fleece { + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + namespace internal { + int backtrace(void** buffer, size_t max) { + return (int)CaptureStackBackTrace(0, (DWORD)max, buffer, nullptr); + } + } + + bool Backtrace::writeTo(ostream &out) const { + const auto process = GetCurrentProcess(); + SYMBOL_INFO *symbol = nullptr; + IMAGEHLP_LINE64 *line = nullptr; + bool success = false; + SymInitialize(process, nullptr, TRUE); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + + symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO)+1023 * sizeof(TCHAR)); + if (!symbol) + goto exit; + symbol->MaxNameLen = 1024; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + DWORD displacement; + line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); + if (!line) + goto exit; + line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + for (unsigned i = 0; i < _addrs.size(); i++) { + if (i > 0) + out << "\r\n"; + out << '\t'; + const auto address = (DWORD64)_addrs[i]; + SymFromAddr(process, address, nullptr, symbol); + char* cstr = nullptr; + if (SymGetLineFromAddr64(process, address, &displacement, line)) { + asprintf(&cstr, "at %s in %s: line: %lu: address: 0x%0llX", + symbol->Name, line->FileName, line->LineNumber, symbol->Address); + } else { + asprintf(&cstr, "at %s, address 0x%0llX", + symbol->Name, symbol->Address); + } + if (!cstr) + goto exit; + out << cstr; + free(cstr); + } + success = true; + + exit: + free(symbol); + free(line); + SymCleanup(process); + return success; + } + +#else + // Windows Store apps cannot get backtraces + namespace internal { + int backtrace(void** buffer, size_t max) {return 0;} + } + + bool Backtrace::writeTo(std::ostream&) const {return false;} +#endif + + + namespace internal { + char* unmangle(const char *function) { + return (char*)function; + } + } + + + std::string RawFunctionName(const void *pc) { + const auto process = GetCurrentProcess(); + auto symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO)+1023 * sizeof(TCHAR)); + if (!symbol) + return ""; + symbol->MaxNameLen = 1024; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + SymFromAddr(process, (DWORD64)pc, nullptr, symbol); + std::string result(symbol->Name); + free(symbol); + return result; + } + +} + +#endif // _WIN32 diff --git a/src/support/Backtrace.cc b/src/support/Backtrace.cc index 6c0b330..690a468 100644 --- a/src/support/Backtrace.cc +++ b/src/support/Backtrace.cc @@ -24,277 +24,18 @@ #include #include #include -#include - - -#ifndef _MSC_VER -#pragma mark - UNIX IMPLEMENTATION: - -#include // dladdr() - -#ifndef __ANDROID__ - #define HAVE_EXECINFO - #include // backtrace(), backtrace_symbols() -#else - #include // free - #include // _Unwind_Backtrace(), etc. -#endif - -#ifndef __has_include -# define __has_include() 0 -#endif - -#if __has_include() || defined(__ANDROID__) - #include // abi::__cxa_demangle() - #define HAVE_UNMANGLE -#endif - - -namespace fleece { - using namespace std; - - -#ifndef HAVE_EXECINFO - // Use libunwind to emulate backtrace(). This is limited in that it won't traverse any stack - // frames before a signal handler, so it's not useful for logging the stack upon a crash. - // Adapted from https://stackoverflow.com/a/28858941 - // See also https://stackoverflow.com/q/29559347 for more details & possible workarounds - - struct BacktraceState { - void** current; - void** end; - }; - - static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { - BacktraceState* state = static_cast(arg); - uintptr_t pc = _Unwind_GetIP(context); - if (pc) { - if (state->current == state->end) { - return _URC_END_OF_STACK; - } else { - *state->current++ = reinterpret_cast(pc); - } - } - return _URC_NO_REASON; - } - - static int backtrace(void** buffer, size_t max) { - BacktraceState state = {buffer, buffer + max}; - _Unwind_Backtrace(unwindCallback, &state); - - return int(state.current - buffer); - } -#endif - - - static char* unmangle(const char *function) { -#ifdef HAVE_UNMANGLE - int status; - size_t unmangledLen; - char *unmangled = abi::__cxa_demangle(function, nullptr, &unmangledLen, &status); - if (unmangled && status == 0) - return unmangled; - free(unmangled); -#endif - return (char*)function; - } - - - Backtrace::frameInfo Backtrace::getFrame(unsigned i) const { - assert(i < _addrs.size()); - frameInfo frame = { }; - Dl_info info; - if (dladdr(_addrs[i], &info)) { - frame.pc = _addrs[i]; - frame.offset = (size_t)frame.pc - (size_t)info.dli_saddr; - frame.function = info.dli_sname; - frame.library = info.dli_fname; - const char *slash = strrchr(frame.library, '/'); - if (slash) - frame.library = slash + 1; - } - return frame; - } - - - // If any of these strings occur in a backtrace, suppress further frames. - static constexpr const char* kTerminalFunctions[] = { - "_C_A_T_C_H____T_E_S_T_", - "Catch::TestInvokerAsFunction::invoke() const", - "litecore::actor::Scheduler::task(unsigned)", - "litecore::actor::GCDMailbox::safelyCall", - }; - - static constexpr struct {const char *old, *nuu;} kAbbreviations[] = { - {"(anonymous namespace)", "(anon)"}, - {"std::__1::", "std::"}, - {"std::basic_string, std::allocator >", - "string"}, - }; - - - static void replace(string &str, string_view oldStr, string_view newStr) { - string::size_type pos = 0; - while (string::npos != (pos = str.find(oldStr, pos))) { - str.replace(pos, oldStr.size(), newStr); - pos += newStr.size(); - } - } - - - bool Backtrace::writeTo(ostream &out) const { - for (unsigned i = 0; i < _addrs.size(); ++i) { - if (i > 0) - out << '\n'; - out << '\t'; - char *cstr = nullptr; - auto frame = getFrame(i); - int len; - bool stop = false; - if (frame.function) { - string name = Unmangle(frame.function); - // Stop when we hit a unit test, or other known functions: - for (auto fn : kTerminalFunctions) { - if (name.find(fn) != string::npos) - stop = true; - } - // Abbreviate some C++ verbosity: - for (auto &abbrev : kAbbreviations) - replace(name, abbrev.old, abbrev.nuu); - len = asprintf(&cstr, "%2d %-25s %s + %zd", - i, frame.library, name.c_str(), frame.offset); - } else { - len = asprintf(&cstr, "%2d %p", i, _addrs[i]); - } - if (len < 0) - return false; - out.write(cstr, size_t(len)); - free(cstr); - - if (stop) { - out << "\n\t ... (" << (_addrs.size() - i - 1) << " more suppressed) ..."; - break; - } - } - return true; - } - - - std::string RawFunctionName(const void *pc) { - Dl_info info = {}; - dladdr(pc, &info); - return info.dli_sname; - } - -} - - -#else // _MSC_VER -#pragma mark - WINDOWS IMPLEMENTATION: - -#pragma comment(lib, "Dbghelp.lib") -#include -#include -#include "asprintf.h" -#include -using namespace std; - -namespace fleece { - -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) - - static inline int backtrace(void** buffer, size_t max) { - return (int)CaptureStackBackTrace(0, (DWORD)max, buffer, nullptr); - } - - - bool Backtrace::writeTo(ostream &out) const { - const auto process = GetCurrentProcess(); - SYMBOL_INFO *symbol = nullptr; - IMAGEHLP_LINE64 *line = nullptr; - bool success = false; - SymInitialize(process, nullptr, TRUE); - DWORD symOptions = SymGetOptions(); - symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; - SymSetOptions(symOptions); - - symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO)+1023 * sizeof(TCHAR)); - if (!symbol) - goto exit; - symbol->MaxNameLen = 1024; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - DWORD displacement; - line = (IMAGEHLP_LINE64*)malloc(sizeof(IMAGEHLP_LINE64)); - if (!line) - goto exit; - line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - for (unsigned i = 0; i < _addrs.size(); i++) { - if (i > 0) - out << "\r\n"; - out << '\t'; - const auto address = (DWORD64)_addrs[i]; - SymFromAddr(process, address, nullptr, symbol); - char* cstr = nullptr; - if (SymGetLineFromAddr64(process, address, &displacement, line)) { - asprintf(&cstr, "at %s in %s: line: %lu: address: 0x%0llX", - symbol->Name, line->FileName, line->LineNumber, symbol->Address); - } else { - asprintf(&cstr, "at %s, address 0x%0llX", - symbol->Name, symbol->Address); - } - if (!cstr) - goto exit; - out << cstr; - free(cstr); - } - success = true; - - exit: - free(symbol); - free(line); - SymCleanup(process); - return success; - } - -#else - // Windows Store apps cannot get backtraces - static inline int backtrace(void** buffer, size_t max) {return 0;} - bool Backtrace::writeTo(std::ostream&) const {return false;} -#endif - - - static char* unmangle(const char *function) { - return (char*)function; - } - - - std::string RawFunctionName(const void *pc) { - const auto process = GetCurrentProcess(); - auto symbol = (SYMBOL_INFO*)malloc(sizeof(SYMBOL_INFO)+1023 * sizeof(TCHAR)); - if (!symbol) - return ""; - symbol->MaxNameLen = 1024; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - SymFromAddr(process, (DWORD64)pc, nullptr, symbol); - std::string result(symbol->Name); - free(symbol); - return result; - } - -} - -#endif // _MSC_VER +#include "util/betterassert.hh" #pragma mark - COMMON CODE: namespace fleece { + using namespace std; string Unmangle(const char *name) { - auto unmangled = unmangle(name); + char* unmangled = internal::unmangle(name); string result = unmangled; if (unmangled != name) free(unmangled); @@ -330,7 +71,7 @@ namespace fleece { void Backtrace::_capture(unsigned skipFrames, unsigned maxFrames) { _addrs.resize(++skipFrames + maxFrames); // skip this frame - auto n = backtrace(&_addrs[0], skipFrames + maxFrames); + auto n = internal::backtrace(&_addrs[0], skipFrames + maxFrames); _addrs.resize(n); skip(skipFrames); } @@ -356,11 +97,15 @@ namespace fleece { try { rethrow_exception(xp); } catch(const exception& x) { +#if __cpp_rtti const char *name = typeid(x).name(); - char *unmangled = unmangle(name); + char *unmangled = internal::unmangle(name); out << unmangled << ": " << x.what() << "\n"; if (unmangled != name) free(unmangled); +#else + out << x.what() << "\n"; +#endif } catch (...) { out << "unknown exception type\n"; } diff --git a/src/support/Backtrace.hh b/src/support/Backtrace.hh index 620a182..cff855b 100644 --- a/src/support/Backtrace.hh +++ b/src/support/Backtrace.hh @@ -94,4 +94,9 @@ namespace fleece { /// The name will be unmangled, if possible. std::string FunctionName(const void *pc); + + namespace internal { + int backtrace(void** buffer, size_t max); + char* unmangle(const char*); + } } diff --git a/src/support/Endian.hh b/src/support/Endian.hh index 9a98182..ab8d58b 100644 --- a/src/support/Endian.hh +++ b/src/support/Endian.hh @@ -35,7 +35,7 @@ namespace crouton::endian { # error "unexpected define!" // freebsd may define these; probably just need to undefine them #endif - /* Define byte-swap functions, using fast processor-native built-ins where possible */ + /* Define byte-swap functions, using fast processor-native built-ins where possible */ #if defined(_MSC_VER) // needs to be first because msvc doesn't short-circuit after failing defined(__has_builtin) # define bswap16(x) _byteswap_ushort((x)) # define bswap32(x) _byteswap_ulong((x)) @@ -50,16 +50,16 @@ namespace crouton::endian { # define bswap64(x) __builtin_bswap64((x)) #else /* even in this case, compilers often optimize by using native instructions */ - static inline uint16_t bswap16(uint16_t x) { + Pure inline constexpr uint16_t bswap16(uint16_t x) { return ((( x >> 8 ) & 0xffu ) | (( x & 0xffu ) << 8 )); } - static inline uint32_t bswap32(uint32_t x) { + Pure inline constexpr uint32_t bswap32(uint32_t x) { return ((( x & 0xff000000u ) >> 24 ) | (( x & 0x00ff0000u ) >> 8 ) | (( x & 0x0000ff00u ) << 8 ) | (( x & 0x000000ffu ) << 24 )); } - static inline uint64_t bswap64(uint64_t x) { + Pure inline constexpr uint64_t bswap64(uint64_t x) { return ((( x & 0xff00000000000000ull ) >> 56 ) | (( x & 0x00ff000000000000ull ) >> 40 ) | (( x & 0x0000ff0000000000ull ) >> 24 ) | @@ -70,24 +70,24 @@ namespace crouton::endian { (( x & 0x00000000000000ffull ) << 56 )); } #endif - template constexpr T byteswap(T) noexcept; - template <> constexpr int16_t byteswap(int16_t i) noexcept {return bswap16(i);} - template <> constexpr uint16_t byteswap(uint16_t i) noexcept {return bswap16(i);} - template <> constexpr int32_t byteswap(int32_t i) noexcept {return bswap32(i);} - template <> constexpr uint32_t byteswap(uint32_t i) noexcept {return bswap32(i);} - template <> constexpr int64_t byteswap(int64_t i) noexcept {return bswap64(i);} - template <> constexpr uint64_t byteswap(uint64_t i) noexcept {return bswap64(i);} + template Pure constexpr T byteswap(T) noexcept; + template <> Pure constexpr int16_t byteswap(int16_t i) noexcept {return bswap16(i);} + template <> Pure constexpr uint16_t byteswap(uint16_t i) noexcept {return bswap16(i);} + template <> Pure constexpr int32_t byteswap(int32_t i) noexcept {return bswap32(i);} + template <> Pure constexpr uint32_t byteswap(uint32_t i) noexcept {return bswap32(i);} + template <> Pure constexpr int64_t byteswap(int64_t i) noexcept {return bswap64(i);} + template <> Pure constexpr uint64_t byteswap(uint64_t i) noexcept {return bswap64(i);} #endif - template constexpr T encodeBig(T i) noexcept { + template Pure constexpr T encodeBig(T i) noexcept { if constexpr (IsBig) return i; else return byteswap(i); } - template constexpr T encodeLittle(T i) noexcept { + template Pure constexpr T encodeLittle(T i) noexcept { if constexpr (IsLittle) return i; else return byteswap(i); } - template constexpr T decodeBig(T i) noexcept {return encodeBig(i);} - template constexpr T decodeLittle(T i) noexcept {return encodeLittle(i);} + template Pure constexpr T decodeBig(T i) noexcept {return encodeBig(i);} + template Pure constexpr T decodeLittle(T i) noexcept {return encodeLittle(i);} } diff --git a/src/support/Logging.cc b/src/support/Logging.cc new file mode 100644 index 0000000..c3ccaf0 --- /dev/null +++ b/src/support/Logging.cc @@ -0,0 +1,236 @@ +// +// Logging.cc +// +// Copyright 2023-Present Couchbase, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "util/Logging.hh" +#include "io/Process.hh" + +#if CROUTON_USE_SPDLOG +#include +#include +#include +#endif + +#ifdef ESP_PLATFORM +#include +#endif + +#include +#include +#include +#include +#include +#include + +namespace crouton { + using namespace std; + + LoggerRef Log, LCoro, LSched, LLoop, LNet; + +#if CROUTON_USE_SPDLOG + + /// Defines the format of textual log output. + static constexpr const char* kLogPattern = "▣ %H:%M:%S.%f %^%L | <%n>%$ %v"; + + static vector sSinks; + static spdlog::sink_ptr sStderrSink; + + + static LoggerRef makeLogger(string_view name, + spdlog::level::level_enum level = spdlog::level::info) + { + string nameStr(name); + auto logger = spdlog::get(nameStr); + if (!logger) { + logger = make_shared(nameStr, sSinks.begin(), sSinks.end()); + spdlog::initialize_logger(logger); + // set default level, unless already customized by SPDLOG_LEVEL: + if (logger->level() == spdlog::level::info) + logger->set_level(level); + } + return logger.get(); + } + + + static void addSink(spdlog::sink_ptr sink) { + sink->set_pattern(kLogPattern); + sSinks.push_back(sink); + spdlog::apply_all([&](std::shared_ptr logger) { + logger->sinks().push_back(sink); + }); + spdlog::set_level(std::min(sink->level(), spdlog::get_level())); + } + + + void AddSink(spdlog::sink_ptr sink) { + InitLogging(); + addSink(sink); + } + +#else // CROUTON_USE_SPDLOG + +#ifndef ESP_PLATFORM + static mutex sLogMutex; // makes logging thread-safe and prevents overlapping msgs + static std::time_t sTime; // Time in seconds that's formatted in sTimeBuf + static char sTimeBuf[30]; // Formatted timestamp, to second accuracy + + static constexpr const char* kLevelName[] = { + "trace", "debug", "info ", "WARN ", "ERR ", "CRITICAL", "" + }; + + void Logger::_writeHeader(LogLevelType lvl) { + // sLogMutex must be locked + timespec now; + timespec_get(&now, TIME_UTC); + if (now.tv_sec != sTime) { + sTime = now.tv_sec; + tm nowStruct; +#ifdef _MSC_VER + nowStruct = *localtime(&sTime); +#else + localtime_r(&sTime, &nowStruct); +#endif + strcpy(sTimeBuf, "▣ "); + strcat(sTimeBuf, io::TTY::err.dim); + size_t len = strlen(sTimeBuf); + strftime(sTimeBuf + len, sizeof(sTimeBuf) - len, "%H:%M:%S.", &nowStruct); + } + + const char* color = ""; + if (lvl >= LogLevel::err) + color = io::TTY::err.red; + else if (lvl == LogLevel::warn) + color = io::TTY::err.yellow; + + fprintf(stderr, "%s%06ld%s %s%s| <%s> ", + sTimeBuf, now.tv_nsec / 1000, io::TTY::err.reset, + color, kLevelName[int(lvl)], _name.c_str()); + } + + + void Logger::log(LogLevelType lvl, string_view msg) { + if (should_log(lvl)) { + unique_lock lock(sLogMutex); + _writeHeader(lvl); + cerr << msg << io::TTY::err.reset << std::endl; + } + } + + + void Logger::_log(LogLevelType lvl, string_view fmt, minifmt::FmtIDList types, ...) { + unique_lock lock(sLogMutex); + + _writeHeader(lvl); + va_list args; + va_start(args, types); + minifmt::vformat_types(cerr, fmt, types, args); + va_end(args); + cerr << io::TTY::err.reset << endl; + } + +#else + static constexpr esp_log_level_t kESPLevel[] = { + ESP_LOG_VERBOSE, ESP_LOG_DEBUG, ESP_LOG_INFO, ESP_LOG_WARN, ESP_LOG_ERROR, ESP_LOG_NONE + }; + static const char* kESPLevelChar = "TDIWE-"; + + + void Logger::log(LogLevelType lvl, string_view msg) { + if (should_log(lvl) && kESPLevel[lvl] <= esp_log_level_get("Crouton")) { + const char* color; + switch (lvl) { + case LogLevel::critical: + case LogLevel::err: color = io::TTY::err.red; break; + case LogLevel::warn: color = io::TTY::err.yellow; break; + case LogLevel::debug: + case LogLevel::trace: color = io::TTY::err.dim; break; + default: color = ""; break; + } +#if CONFIG_LOG_TIMESTAMP_SOURCE_RTOS + esp_log_write(kESPLevel[lvl], "Crouton", "%s%c (%4ld) <%s> %.*s%s\n", + color, + kESPLevelChar[lvl], + esp_log_timestamp(), + _name.c_str(), + int(msg.size()), msg.data(), + io::TTY::err.reset); +#else + esp_log_write(kESPLevel[lvl], "Crouton", "%s%c (%s) <%s> %.*s%s\n", + color, + kESPLevelChar[lvl], + esp_log_system_timestamp(), + _name.c_str(), + int(msg.size()), msg.data(), + io::TTY::err.reset); +#endif + } + } + + void Logger::_log(LogLevelType lvl, string_view fmt, minifmt::FmtIDList types, ...) { + va_list args; + va_start(args, types); + string message = minifmt::vformat_types(fmt, types, args); + va_end(args); + log(lvl, message); + } +#endif // ESP_PLATFORM + + static LoggerRef makeLogger(string_view name, LogLevelType level = LogLevel::info) { + return new Logger(string(name), level); + } +#endif // CROUTON_USE_SPDLOG + + + void InitLogging() { + static std::once_flag sOnce; + std::call_once(sOnce, []{ +#if CROUTON_USE_SPDLOG + // Configure log output: + spdlog::set_pattern(kLogPattern); + spdlog::flush_every(5s); + spdlog::flush_on(spdlog::level::warn); + + // Get the default stderr sink: + sStderrSink = spdlog::default_logger()->sinks().front(); + sSinks.push_back(sStderrSink); + Log = spdlog::default_logger().get(); +#else + Log = makeLogger(""); +#endif + // Make the standard loggers: + LCoro = makeLogger("Coro"); + LSched = makeLogger("Sched"); + LLoop = makeLogger("Loop"); + LNet = makeLogger("Net"); + +#if CROUTON_USE_SPDLOG + // Set log levels from `SPDLOG_LEVEL` env var: + spdlog::cfg::load_env_levels(); +#endif + assert_failed_hook = [](const char* message) { + Log->critical(message); + }; + Log->info("---------- Welcome to Crouton ----------"); + }); + } + + + LoggerRef MakeLogger(string_view name, LogLevelType level) { + InitLogging(); + return makeLogger(name, level); + } +} diff --git a/src/support/Memoized.cc b/src/support/Memoized.cc index c3a031d..bdf3d1c 100644 --- a/src/support/Memoized.cc +++ b/src/support/Memoized.cc @@ -18,6 +18,7 @@ #include "Memoized.hh" #include "Backtrace.hh" +#include "StringUtils.hh" namespace crouton { using namespace std; @@ -32,30 +33,42 @@ namespace crouton { } + static void cleanup(string &name) { + if (name.starts_with("crouton::")) + name = name.substr(9); + replaceStringInPlace(name, "std::__1::", "std::"); // probably libc++ specific + replaceStringInPlace(name, "std::basic_string, std::allocator>", "std::string"); + } + + string const& GetTypeName(type_info const& info) { +#if CROUTON_RTTI static Memoized sTypeNames([](const void* addr) -> string { string name = fleece::Unmangle(*(type_info*)addr); // Get unmangled name - auto bra = name.find('<'); - if (auto p = name.find_last_of(":", bra); p != string::npos) // Strip namespaces - name = name.substr(p + 1); + cleanup(name); return name; }); return sTypeNames.lookup(&info); +#else + static const string name = "???"; + return name; +#endif } string const& GetFunctionName(const void* addr) { static Memoized sFnNames([](const void* addr) -> string { - string symbol = fleece::FunctionName(addr); - if (symbol.ends_with(" (.resume)")) - symbol = symbol.substr(0, symbol.size() - 10); - else if (symbol.ends_with(" (.destroy)")) - symbol = symbol.substr(0, symbol.size() - 11); - if (symbol.starts_with("crouton::")) - symbol = symbol.substr(9); - if (auto pos = symbol.find('('); pos != string::npos) - symbol = symbol.substr(0, pos); - return symbol; + string name = fleece::FunctionName(addr); + if (name.ends_with(" (.resume)")) + name = name.substr(0, name.size() - 10); + else if (name.ends_with(" (.destroy)")) + name = name.substr(0, name.size() - 11); + if (name.starts_with("crouton::")) + name = name.substr(9); + if (auto pos = name.find('('); pos != string::npos) + name = name.substr(0, pos); + cleanup(name); + return name; }); return sFnNames.lookup(addr); } diff --git a/src/support/MiniFormat.cc b/src/support/MiniFormat.cc new file mode 100644 index 0000000..83ecf69 --- /dev/null +++ b/src/support/MiniFormat.cc @@ -0,0 +1,97 @@ +// +// MiniFormat.cc +// +// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "util/MiniFormat.hh" +#include +#include + +namespace crouton::minifmt { + using namespace std; + using namespace i; + + void vformat_types(ostream& out, string_view fmt, FmtIDList types, va_list args) { + auto itype = types; + size_t pos; + while (string::npos != (pos = fmt.find_first_of("{}"))) { + if (pos > 0) + out << fmt.substr(0, pos); + + if (fmt[pos] == '}') [[unlikely]] { + // (The only reason to pay attention to "}" is that the std::format spec says + // "}}" is an escape and should be emitted as "}". Otherwise a "} is a syntax + // error, but let's just emit it as-is. + out << '}'; + if (pos < fmt.size() - 1 && fmt[pos + 1] == '}') + ++pos; + fmt = fmt.substr(pos + 1); + } else if (pos < fmt.size() - 1 && fmt[pos + 1] == '{') { + // "{{" is an escape; emit "{". + out << '{'; + fmt = fmt.substr(pos + 2); + } else { + pos = fmt.find('}', pos + 1); + //TODO: Pay attention to at least some formatting specs + fmt = fmt.substr(pos + 1); + switch( *(itype++) ) { + case FmtID::None: out << "{{{TOO FEW ARGS}}}"; return; + case FmtID::Bool: out << (va_arg(args, int) ? "true" : "false"); break; + case FmtID::Char: out << char(va_arg(args, int)); break; + case FmtID::Int: out << va_arg(args, int); break; + case FmtID::UInt: out << va_arg(args, unsigned int); break; + case FmtID::Long: out << va_arg(args, long); break; + case FmtID::ULong: out << va_arg(args, unsigned long); break; + case FmtID::LongLong: out << va_arg(args, long long); break; + case FmtID::ULongLong: out << va_arg(args, unsigned long long); break; + case FmtID::Double: out << va_arg(args, double); break; + case FmtID::CString: out << va_arg(args, const char*); break; + case FmtID::Pointer: out << va_arg(args, const void*); break; + case FmtID::String: out << *va_arg(args, const string*); break; + case FmtID::StringView: out << *va_arg(args, const string_view*); break; + case FmtID::Write: out << *va_arg(args, const write*); break; + } + } + } + + if (!fmt.empty()) + out << fmt; + if (*itype != FmtID::None) + out << "{{{TOO FEW PLACEHOLDERS}}}"; + } + + void format_types(ostream& out, string_view fmt, FmtIDList types, ...) { + va_list args; + va_start(args, types); + vformat_types(out, fmt, types, args); + va_end(args); + } + + string vformat_types(string_view fmt, FmtIDList types, va_list args) { + stringstream out; + vformat_types(out, fmt, types, args); + return out.str(); + } + + string format_types(string_view fmt, FmtIDList types, ...) { + va_list args; + va_start(args, types); + string result = vformat_types(fmt, types, args); + va_end(args); + return result; + } + +} diff --git a/src/support/StringUtils.cc b/src/support/StringUtils.cc index c2d7c19..2882017 100644 --- a/src/support/StringUtils.cc +++ b/src/support/StringUtils.cc @@ -21,7 +21,7 @@ namespace crouton { - bool equalIgnoringCase(string_view a, string_view b) { + bool equalIgnoringCase(string_view a, string_view b) noexcept { size_t len = a.size(); if (len != b.size()) return false; @@ -56,7 +56,7 @@ namespace crouton { std::pair - split(string_view str, char c) { + split(string_view str, char c) noexcept { if (auto p = str.find(c); p != string::npos) return {str.substr(0, p), str.substr(p + 1)}; else @@ -65,7 +65,7 @@ namespace crouton { std::pair - splitAt(string_view str, size_t pos, size_t delimSize) { + splitAt(string_view str, size_t pos, size_t delimSize) noexcept { assert(pos + delimSize <= str.size()); return {str.substr(0, pos), str.substr(pos + delimSize)}; } diff --git a/src/support/StringUtils.hh b/src/support/StringUtils.hh index 12c0b22..7c1dd0f 100644 --- a/src/support/StringUtils.hh +++ b/src/support/StringUtils.hh @@ -20,37 +20,36 @@ #include "util/Base.hh" #include -#include namespace crouton { class ConstBytes; /// Plain-ASCII version of `tolower`, with no nonsense about locales or ints. - inline char toLower(char c) { + Pure inline char toLower(char c) noexcept { if (c >= 'A' && c <= 'Z') c += 32; return c; } /// Plain-ASCII version of `toupper`, with no nonsense about locales or ints. - inline char toUpper(char c) { + Pure inline char toUpper(char c) noexcept { if (c >= 'a' && c <= 'z') c -= 32; return c; } - inline bool isAlphanumeric(char c) { + Pure inline bool isAlphanumeric(char c) noexcept { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } - inline bool isHexDigit(char c) { + Pure inline bool isHexDigit(char c) noexcept { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } /// Converts an ASCII hex digit to its numeric value. - inline int hexDigitToInt(char c) { - if (c < 'A') + Pure inline int hexDigitToInt(char c) noexcept { + if (c < 'A') return c - '0'; if (c < 'a') return c - 'A' + 10; @@ -58,7 +57,7 @@ namespace crouton { } /// Returns a number 0..15 converted to an ASCII hex digit. - inline char asHexDigit(int n) { + Pure inline char asHexDigit(int n) noexcept { assert(n >= 0 && n < 16); if (n < 10) return '0' + char(n); @@ -66,7 +65,7 @@ namespace crouton { } /// True if a character can safely be used in a URL without escaping. - inline bool isURLSafe(char c) { + Pure inline bool isURLSafe(char c) noexcept { return isAlphanumeric(c) || strchr("-_.~", c) != nullptr; } @@ -84,17 +83,18 @@ namespace crouton { string decodeHexString(string_view); /// Case-insensitive equality comparison (ASCII only!) - bool equalIgnoringCase(string_view a, string_view b); + Pure bool equalIgnoringCase(string_view a, string_view b) noexcept; /// Splits a string around the first occurrence of `c`; /// if there is none, assumes it's at the end, i.e. returns `{str, ""}`. - std::pair split(string_view str, char c); + Pure std::pair split(string_view str, char c) noexcept; /// Splits a string at an index. /// @param str The string to split /// @param pos The index at which to split /// @param delimSize The length of the delimiter being split around. - std::pair splitAt(string_view str, size_t pos, size_t delimSize = 0); + Pure std::pair + splitAt(string_view str, size_t pos, size_t delimSize = 0) noexcept; /// Replaces all occurrences of `substring` with `replacement`, in-place. void replaceStringInPlace(string &str, diff --git a/src/support/betterassert.cc b/src/support/betterassert.cc new file mode 100644 index 0000000..cb4afd5 --- /dev/null +++ b/src/support/betterassert.cc @@ -0,0 +1,93 @@ +// +// betterassert.cc +// +// Copyright © 2018 Couchbase. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "util/betterassert.hh" +#include // for std::terminate() +#include +#include + +#ifdef _MSC_VER +#include "asprintf.h" +#endif + +#ifndef __cold +#define __cold +#endif + +namespace crouton { + + __cold + static const char* filename(const char *file) { + const char *slash = strrchr(file, '/'); + if (!slash) + slash = strrchr(file, '\\'); + if (slash) + file = slash + 1; + return file; + } + + + __cold + static void default_assert_failed_hook(const char *msg) { + fprintf(stderr, "\n***%s\n", msg); + } + + void (*assert_failed_hook)(const char *message) = &default_assert_failed_hook; + + + __cold + static const char* log(const char *format, const char *cond, std::source_location const& loc) + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + char *msg; + if (asprintf(&msg, format, cond, loc.function_name(), filename(loc.file_name()), loc.line()) > 0) { + assert_failed_hook(msg); + // (Yes, this leaks 'msg'. Under the circumstances, not an issue.) + return msg; + } else { + // Best we can do if even malloc has failed us: + assert_failed_hook(format); + return format; + } +#pragma GCC diagnostic pop + } + + + __cold + void _assert_failed(const char *cond, std::source_location const& loc) noexcept { + log("FATAL: FAILED ASSERTION `%s` in %s (at %s line %d)", + cond, loc); + std::terminate(); + } + + __cold + void _precondition_failed(const char *cond, std::source_location const& loc) noexcept { + log("FATAL: FAILED PRECONDITION: `%s` not true when calling %s (at %s line %d)", + cond, loc); + std::terminate(); + } + + __cold + void _postcondition_failed(const char *cond, std::source_location const& loc) noexcept { + log("FATAL: FAILED POSTCONDITION: `%s` not true at end of %s (at %s line %d)", + cond, loc); + std::terminate(); + } + +} diff --git a/tests/ESP32/CMakeLists.txt b/tests/ESP32/CMakeLists.txt new file mode 100644 index 0000000..e67d7d3 --- /dev/null +++ b/tests/ESP32/CMakeLists.txt @@ -0,0 +1,13 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# This example uses the `protocol_examples_common` component for Wi-Fi setup, +# as well as the Crouton component (of course). +set(EXTRA_COMPONENT_DIRS + $ENV{IDF_PATH}/examples/common_components/protocol_examples_common + ../../src/io/esp32 +) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hello_crouton) diff --git a/tests/ESP32/README.md b/tests/ESP32/README.md new file mode 100644 index 0000000..7a5dbc6 --- /dev/null +++ b/tests/ESP32/README.md @@ -0,0 +1,5 @@ +# Crouton ESP32 Tests / Example + +This directory is an example project for using Crouton with the ESP-IDF embedded framework. You can build and run it with the usual `idf.py build flash monitor` invocation. + +You may need to run `idf.py menuconfig` once to change settings in the `sdkconfig` file, such as details of the CPU type or serial port speed, if your board is different enough from the ESP32-S3 I use. diff --git a/tests/ESP32/main/CMakeLists.txt b/tests/ESP32/main/CMakeLists.txt new file mode 100644 index 0000000..fc3329e --- /dev/null +++ b/tests/ESP32/main/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register( + SRCS + "main.cc" +) + +target_compile_options(${COMPONENT_LIB} PRIVATE + "-UNDEBUG" + "-Wno-unknown-pragmas" # ignore Xcode `#pragma mark` in source code +) diff --git a/tests/ESP32/main/Kconfig.projbuild b/tests/ESP32/main/Kconfig.projbuild new file mode 100644 index 0000000..a1f5cea --- /dev/null +++ b/tests/ESP32/main/Kconfig.projbuild @@ -0,0 +1,51 @@ +menu "Example Configuration" + + choice EXAMPLE_IP_MODE + prompt "IP Version" + depends on EXAMPLE_SOCKET_IP_INPUT_STRING + help + Example can use either IPV4 or IPV6. + + config EXAMPLE_IPV4 + bool "IPV4" + + config EXAMPLE_IPV6 + bool "IPV6" + + endchoice + + config EXAMPLE_IPV4_ADDR + string "IPV4 Address" + default "192.168.0.165" + depends on EXAMPLE_IPV4 + help + The example will connect to this IPV4 address. + + config EXAMPLE_IPV6_ADDR + string "IPV6 Address" + default "FE80::30AD:E57B:C212:68AD" + depends on EXAMPLE_IPV6 + help + The example will connect to this IPV6 address. + + config EXAMPLE_PORT + int "Port" + range 0 65535 + default 3333 + help + The remote port to which the client example will connect to. + + choice EXAMPLE_SOCKET_IP_INPUT + prompt "Socket example source" + default EXAMPLE_SOCKET_IP_INPUT_STRING + help + Selects the input source of the IP used in the example. + + config EXAMPLE_SOCKET_IP_INPUT_STRING + bool "From string" + + config EXAMPLE_SOCKET_IP_INPUT_STDIN + bool "From stdin" + endchoice + +endmenu diff --git a/tests/ESP32/main/main.cc b/tests/ESP32/main/main.cc new file mode 100644 index 0000000..2cad4ee --- /dev/null +++ b/tests/ESP32/main/main.cc @@ -0,0 +1,144 @@ +#include "Crouton.hh" +#include "util/Logging.hh" + +#include "sdkconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static void initialize(); + +using namespace std; +using namespace crouton; + + +static Generator fibonacci(int64_t limit, bool slow = false) { + int64_t a = 1, b = 1; + YIELD a; + while (b <= limit) { + YIELD b; + tie(a, b) = pair{b, a + b}; + if (slow) + AWAIT Timer::sleep(0.1); + } +} + + +Task mainTask() { + initialize(); + + printf("---------- TESTING CROUTON ----------\n\n"); + esp_log_level_set("Crouton", ESP_LOG_DEBUG); +// LCoro->set_level(LogLevel::trace); +// LLoop->set_level(LogLevel::trace); +// LSched->set_level(LogLevel::trace); + LNet->set_level(LogLevel::trace); + + Log->info("Testing Generator"); + { + Generator fib = fibonacci(100, true); + vector results; + Result result; + while ((result = AWAIT fib)) { + printf("%lld ", result.value()); + results.push_back(result.value()); + } + printf("\n"); + } + + Log->info("Testing AddrInfo -- looking up example.com"); + { + io::AddrInfo addr = AWAIT io::AddrInfo::lookup("example.com"); + printf("Addr = %s\n", addr.primaryAddressString().c_str()); + auto ip4addr = addr.primaryAddress(); + postcondition(ip4addr.type == IPADDR_TYPE_V4); + postcondition(addr.primaryAddressString() == "93.184.216.34"); + } + + Log->info("Testing TCPSocket with TLS"); + { + auto socket = io::ISocket::newSocket(true); + AWAIT socket->connect("example.com", 443); + + Log->info("-- Connected! Test Writing..."); + AWAIT socket->stream().write(string_view("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")); + + Log->info("-- Test Reading..."); + string result = AWAIT socket->stream().readAll(); + + Log->info("Got HTTP response"); + printf("%s\n", result.c_str()); + postcondition(result.starts_with("HTTP/1.1 ")); + postcondition(result.size() > 1000); + postcondition(result.size() < 2000); + } + + Log->info("End of tests"); + postcondition(Scheduler::current().assertEmpty()); + + printf("\n---------- END CROUTON TESTS ----------\n"); + + + printf("Minimum heap space was %ld bytes\n", esp_get_minimum_free_heap_size()); + printf("Restarting in 100 seconds..."); + fflush(stdout); + for (int i = 99; i >= 0; i--) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + printf(" %d ...", i); + fflush(stdout); + } + RETURN; +} + +CROUTON_MAIN(mainTask) + + +// entry point of the `protocol_examples_common` component +extern "C" esp_err_t example_connect(void); + +// Adapted from ESP-IDF "hello world" example +static void initialize() { + printf("Hello world!\n"); + + /* Print chip information */ + esp_chip_info_t chip_info; + uint32_t flash_size; + esp_chip_info(&chip_info); + printf("This is %s chip with %d CPU core(s), WiFi%s%s, ", + CONFIG_IDF_TARGET, + chip_info.cores, + (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", + (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); + unsigned major_rev = chip_info.revision / 100; + unsigned minor_rev = chip_info.revision % 100; + printf("silicon revision v%d.%d, ", major_rev, minor_rev); + if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { + printf("Get flash size failed"); + return; + } + printf("%luMB %s flash\n", flash_size / (1024 * 1024), + (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); + printf("Heap space: %ld bytes ... internal %ld bytes\n", + esp_get_free_heap_size(), esp_get_free_internal_heap_size()); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); +} + + diff --git a/tests/ESP32/sdkconfig b/tests/ESP32/sdkconfig new file mode 100644 index 0000000..d3537af --- /dev/null +++ b/tests/ESP32/sdkconfig @@ -0,0 +1,1797 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) Project Configuration +# +CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +CONFIG_SOC_LEDC_TIMER_BIT_WIDE_NUM=14 +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +CONFIG_SOC_ADC_SUPPORTED=y +CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_WIFI_SUPPORTED=y +CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_LCDCAM_SUPPORTED=y +CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +CONFIG_SOC_CACHE_SUPPORT_WRAP=y +CONFIG_SOC_ULP_SUPPORTED=y +CONFIG_SOC_RISCV_COPROC_SUPPORTED=y +CONFIG_SOC_BT_SUPPORTED=y +CONFIG_SOC_USB_OTG_SUPPORTED=y +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y +CONFIG_SOC_RTC_MEM_SUPPORTED=y +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_XT_WDT_SUPPORTED=y +CONFIG_SOC_I2S_SUPPORTED=y +CONFIG_SOC_RMT_SUPPORTED=y +CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_SYSTIMER_SUPPORTED=y +CONFIG_SOC_SUPPORT_COEXISTENCE=y +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +CONFIG_SOC_AES_SUPPORTED=y +CONFIG_SOC_MPI_SUPPORTED=y +CONFIG_SOC_SHA_SUPPORTED=y +CONFIG_SOC_HMAC_SUPPORTED=y +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +CONFIG_SOC_MEMPROT_SUPPORTED=y +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_XTAL_SUPPORT_40M=y +CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_ARBITER_SUPPORTED=y +CONFIG_SOC_ADC_FILTER_SUPPORTED=y +CONFIG_SOC_ADC_MONITOR_SUPPORTED=y +CONFIG_SOC_ADC_DMA_SUPPORTED=y +CONFIG_SOC_ADC_PERIPH_NUM=2 +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 +CONFIG_SOC_ADC_ATTEN_NUM=4 +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +CONFIG_SOC_ADC_PATT_LEN_MAX=24 +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +CONFIG_SOC_RTC_SLOW_CLOCK_SUPPORT_8MD256=y +CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y +CONFIG_SOC_APB_BACKUP_DMA=y +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1 +CONFIG_SOC_CPU_CORES_NUM=2 +CONFIG_SOC_CPU_INTR_NUM=32 +CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINT_SIZE=64 +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +CONFIG_SOC_GDMA_GROUPS=y +CONFIG_SOC_GDMA_PAIRS_PER_GROUP=5 +CONFIG_SOC_GDMA_SUPPORT_PSRAM=y +CONFIG_SOC_GDMA_PSRAM_MIN_ALIGN=16 +CONFIG_SOC_GPIO_PORT=1 +CONFIG_SOC_GPIO_PIN_COUNT=49 +CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF +CONFIG_SOC_GPIO_SUPPORT_SLP_SWITCH=y +CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_OUT_AUTO_ENABLE=y +CONFIG_SOC_I2C_NUM=2 +CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y +CONFIG_SOC_I2C_SUPPORT_XTAL=y +CONFIG_SOC_I2C_SUPPORT_RTC=y +CONFIG_SOC_I2S_NUM=2 +CONFIG_SOC_I2S_HW_VERSION_2=y +CONFIG_SOC_I2S_SUPPORTS_PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_SUPPORTS_PDM_CODEC=y +CONFIG_SOC_I2S_SUPPORTS_TDM=y +CONFIG_SOC_MCPWM_GROUPS=2 +CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 +CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 +CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 +CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y +CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 +CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +CONFIG_SOC_PCNT_GROUPS=y +CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 +CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 +CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 +CONFIG_SOC_RMT_GROUPS=1 +CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y +CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_APB=y +CONFIG_SOC_RMT_SUPPORT_DMA=y +CONFIG_SOC_LCD_I80_SUPPORTED=y +CONFIG_SOC_LCD_RGB_SUPPORTED=y +CONFIG_SOC_LCD_I80_BUSES=1 +CONFIG_SOC_LCD_RGB_PANELS=1 +CONFIG_SOC_LCD_I80_BUS_WIDTH=16 +CONFIG_SOC_LCD_RGB_DATA_WIDTH=16 +CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549 +CONFIG_SOC_RTC_CNTL_TAGMEM_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTCIO_PIN_COUNT=22 +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_SDM_GROUPS=y +CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SPI_PERIPH_NUM=3 +CONFIG_SOC_SPI_MAX_CS_NUM=6 +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +CONFIG_SOC_SPI_SUPPORT_OCT=y +CONFIG_SOC_MEMSPI_SRC_FREQ_120M=y +CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y +CONFIG_SOC_SPIRAM_SUPPORTED=y +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +CONFIG_SOC_TIMER_GROUPS=2 +CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 +CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 +CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y +CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y +CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_TOUCH_VERSION_2=y +CONFIG_SOC_TOUCH_SENSOR_NUM=15 +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y +CONFIG_SOC_TOUCH_PAD_THRESHOLD_MAX=0x1FFFFF +CONFIG_SOC_TOUCH_PAD_MEASURE_WAIT_MAX=0xFF +CONFIG_SOC_UART_NUM=3 +CONFIG_SOC_UART_FIFO_LEN=128 +CONFIG_SOC_UART_BITRATE_MAX=5000000 +CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +CONFIG_SOC_UART_SUPPORT_APB_CLK=y +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +CONFIG_SOC_UART_REQUIRE_CORE_RESET=y +CONFIG_SOC_USB_PERIPH_NUM=y +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +CONFIG_SOC_SHA_SUPPORT_DMA=y +CONFIG_SOC_SHA_SUPPORT_RESUME=y +CONFIG_SOC_SHA_GDMA=y +CONFIG_SOC_SHA_SUPPORT_SHA1=y +CONFIG_SOC_SHA_SUPPORT_SHA224=y +CONFIG_SOC_SHA_SUPPORT_SHA256=y +CONFIG_SOC_SHA_SUPPORT_SHA384=y +CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +CONFIG_SOC_AES_SUPPORT_DMA=y +CONFIG_SOC_AES_GDMA=y +CONFIG_SOC_AES_SUPPORT_AES_128=y +CONFIG_SOC_AES_SUPPORT_AES_256=y +CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_BT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_CPU_PD=y +CONFIG_SOC_PM_SUPPORT_TAGMEM_PD=y +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +CONFIG_SOC_MEMPROT_CPU_PREFETCH_PAD_SIZE=16 +CONFIG_SOC_MEMPROT_MEM_ALIGN_SIZE=256 +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192 +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_OPI_MODE=y +CONFIG_SOC_SPI_MEM_SUPPORT_TIME_TUNING=y +CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y +CONFIG_SOC_COEX_HW_PTI=y +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +CONFIG_SOC_SDMMC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y +CONFIG_SOC_WIFI_HW_TSF=y +CONFIG_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SOC_BLE_SUPPORTED=y +CONFIG_SOC_BLE_MESH_SUPPORTED=y +CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_BRP_MAX=16384 +CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TARGET_ARCH_XTENSA=y +CONFIG_IDF_TARGET_ARCH="xtensa" +CONFIG_IDF_TARGET="esp32s3" +CONFIG_IDF_TARGET_ESP32S3=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# Bootloader config +# +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=3 +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 +# end of Application manager + +CONFIG_ESP_ROM_HAS_CRC_LE=y +CONFIG_ESP_ROM_HAS_CRC_BE=y +CONFIG_ESP_ROM_HAS_JPEG_DECODE=y +CONFIG_ESP_ROM_SUPPORT_MULTIPLE_UART=y +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=4 +CONFIG_ESP_ROM_HAS_ERASE_0_REGION_BUG=y +CONFIG_ESP_ROM_GET_CLK_FREQ=y +CONFIG_ESP_ROM_HAS_HAL_WDT=y +CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# CONFIG_ESPTOOLPY_NO_STUB is not set +# CONFIG_ESPTOOLPY_OCT_FLASH is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="8MB" +CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp_large.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Example Configuration +# +CONFIG_EXAMPLE_IPV4=y +# CONFIG_EXAMPLE_IPV6 is not set +CONFIG_EXAMPLE_IPV4_ADDR="192.168.0.165" +CONFIG_EXAMPLE_PORT=3333 +CONFIG_EXAMPLE_SOCKET_IP_INPUT_STRING=y +# CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN is not set +# end of Example Configuration + +# +# Example Connection Configuration +# +CONFIG_ENV_GPIO_RANGE_MIN=0 +CONFIG_ENV_GPIO_RANGE_MAX=48 +CONFIG_ENV_GPIO_IN_RANGE_MAX=48 +CONFIG_ENV_GPIO_OUT_RANGE_MAX=48 +CONFIG_EXAMPLE_CONNECT_WIFI=y +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y +CONFIG_EXAMPLE_WIFI_CONN_MAX_RETRY=6 +# CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST is not set +CONFIG_EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL=y + +# +# WiFi Scan threshold +# +CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD=-127 +CONFIG_EXAMPLE_WIFI_AUTH_OPEN=y +# CONFIG_EXAMPLE_WIFI_AUTH_WEP is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA_PSK is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA2_PSK is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA3_PSK is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK is not set +# CONFIG_EXAMPLE_WIFI_AUTH_WAPI_PSK is not set +# end of WiFi Scan threshold + +CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL=y +# CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY is not set +# CONFIG_EXAMPLE_CONNECT_ETHERNET is not set +CONFIG_EXAMPLE_CONNECT_IPV6=y +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK=y +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL is not set +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL is not set +# CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL is not set +# end of Example Connection Configuration + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +# end of Compiler options + +# +# Component config +# + +# +# Application Level Tracing +# +# CONFIG_APPTRACE_DEST_JTAG is not set +CONFIG_APPTRACE_DEST_NONE=y +# CONFIG_APPTRACE_DEST_UART1 is not set +# CONFIG_APPTRACE_DEST_UART2 is not set +# CONFIG_APPTRACE_DEST_USB_CDC is not set +CONFIG_APPTRACE_DEST_UART_NONE=y +CONFIG_APPTRACE_UART_TASK_PRIO=1 +CONFIG_APPTRACE_LOCK_ENABLE=y +# end of Application Level Tracing + +# +# Bluetooth +# +# CONFIG_BT_ENABLED is not set +# end of Bluetooth + +# +# Driver Configurations +# + +# +# Legacy ADC Configuration +# +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set + +# +# Legacy ADC Calibration Configuration +# +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Configuration + +# +# SPI Configuration +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of SPI Configuration + +# +# TWAI Configuration +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +# end of TWAI Configuration + +# +# Temperature sensor Configuration +# +# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# end of Temperature sensor Configuration + +# +# UART Configuration +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of UART Configuration + +# +# GPIO Configuration +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of GPIO Configuration + +# +# Sigma Delta Modulator Configuration +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of Sigma Delta Modulator Configuration + +# +# GPTimer Configuration +# +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of GPTimer Configuration + +# +# PCNT Configuration +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of PCNT Configuration + +# +# RMT Configuration +# +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# end of RMT Configuration + +# +# MCPWM Configuration +# +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of MCPWM Configuration + +# +# I2S Configuration +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of I2S Configuration +# end of Driver Configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# ADC and ADC Calibration +# +# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set +# end of ADC and ADC Calibration + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# Ethernet +# +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_SPI_ETHERNET=y +# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set +# CONFIG_ETH_SPI_ETHERNET_W5500 is not set +# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set +# CONFIG_ETH_USE_OPENETH is not set +# CONFIG_ETH_TRANSMIT_MUTEX is not set +# end of Ethernet + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# GDB Stub +# +# end of GDB Stub + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=512 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +# CONFIG_HTTPD_WS_SUPPORT is not set +# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +# end of HTTP Server + +# +# ESP HTTPS OTA +# +# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set +# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +# end of ESP HTTPS OTA + +# +# ESP HTTPS server +# +# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +# end of ESP HTTPS server + +# +# Hardware Settings +# + +# +# MAC Config +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 +# end of MAC Config + +# +# Sleep Config +# +# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set +CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLK_SRC_INT_RC=y +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_RTC_CLOCK_BBPLL_POWER_ON_WITH_USB=y +# end of RTC Clock Config + +# +# Peripheral Control +# +# CONFIG_PERIPH_CTRL_FUNC_IN_IRAM is not set +# end of Peripheral Control + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# GDMA Configuration +# +# CONFIG_GDMA_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# end of GDMA Configuration + +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config +# end of Hardware Settings + +# +# LCD and Touch Panel +# + +# +# LCD Touch Drivers are maintained in the IDF Component Registry +# + +# +# LCD Peripheral Configuration +# +CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32 +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set +# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set +# end of LCD Peripheral Configuration +# end of LCD and Touch Panel + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +# CONFIG_ESP_NETIF_L2_TAP is not set +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# end of ESP NETIF Adapter + +# +# PHY +# +CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP_PHY_MAX_TX_POWER=20 +CONFIG_ESP_PHY_REDUCE_TX_POWER=y +CONFIG_ESP_PHY_ENABLE_USB=y +# end of PHY + +# +# Power Management +# +# CONFIG_PM_ENABLE is not set +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y +# end of Power Management + +# +# ESP PSRAM +# +# CONFIG_SPIRAM is not set +# end of ESP PSRAM + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP System Settings +# +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 + +# +# Cache config +# +CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y +# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y +CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 +# CONFIG_ESP32S3_DATA_CACHE_16KB is not set +CONFIG_ESP32S3_DATA_CACHE_32KB=y +# CONFIG_ESP32S3_DATA_CACHE_64KB is not set +CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 +# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set +CONFIG_ESP32S3_DATA_CACHE_8WAYS=y +CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y +# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 +# end of Cache config + +# +# Memory +# +# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set +# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set +# end of Memory + +# +# Trace memory +# +# CONFIG_ESP32S3_TRAX is not set +CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 +# end of Trace memory + +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# Memory protection +# +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_USB_CDC is not set +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_UART=y +CONFIG_ESP_CONSOLE_MULTIPLE_UART=y +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +# end of Brownout Detector + +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_TASK_STACK_SIZE=1280 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# High resolution timer (esp_timer) +# +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of High resolution timer (esp_timer) + +# +# Wi-Fi +# +CONFIG_ESP32_WIFI_ENABLED=y +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +# CONFIG_ESP_WIFI_FTM_ENABLE is not set +# CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE is not set +# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set +# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set +# end of Wi-Fi + +# +# Core dump +# +# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set +# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set +CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y +# end of Core dump + +# +# FAT Filesystem support +# +CONFIG_FATFS_VOLUME_COUNT=2 +# CONFIG_FATFS_SECTOR_512 is not set +# CONFIG_FATFS_SECTOR_1024 is not set +# CONFIG_FATFS_SECTOR_2048 is not set +CONFIG_FATFS_SECTOR_4096=y +CONFIG_FATFS_SECTORS_PER_CLUSTER_1=y +# CONFIG_FATFS_SECTORS_PER_CLUSTER_2 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_4 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_8 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_16 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_32 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_64 is not set +# CONFIG_FATFS_SECTORS_PER_CLUSTER_128 is not set +# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set +CONFIG_FATFS_CODEPAGE_437=y +# CONFIG_FATFS_CODEPAGE_720 is not set +# CONFIG_FATFS_CODEPAGE_737 is not set +# CONFIG_FATFS_CODEPAGE_771 is not set +# CONFIG_FATFS_CODEPAGE_775 is not set +# CONFIG_FATFS_CODEPAGE_850 is not set +# CONFIG_FATFS_CODEPAGE_852 is not set +# CONFIG_FATFS_CODEPAGE_855 is not set +# CONFIG_FATFS_CODEPAGE_857 is not set +# CONFIG_FATFS_CODEPAGE_860 is not set +# CONFIG_FATFS_CODEPAGE_861 is not set +# CONFIG_FATFS_CODEPAGE_862 is not set +# CONFIG_FATFS_CODEPAGE_863 is not set +# CONFIG_FATFS_CODEPAGE_864 is not set +# CONFIG_FATFS_CODEPAGE_865 is not set +# CONFIG_FATFS_CODEPAGE_866 is not set +# CONFIG_FATFS_CODEPAGE_869 is not set +# CONFIG_FATFS_CODEPAGE_932 is not set +# CONFIG_FATFS_CODEPAGE_936 is not set +# CONFIG_FATFS_CODEPAGE_949 is not set +# CONFIG_FATFS_CODEPAGE_950 is not set +CONFIG_FATFS_AUTO_TYPE=y +# CONFIG_FATFS_FAT12 is not set +# CONFIG_FATFS_FAT16 is not set +CONFIG_FATFS_CODEPAGE=437 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set +CONFIG_FATFS_FS_LOCK=0 +CONFIG_FATFS_TIMEOUT_MS=10000 +CONFIG_FATFS_PER_FILE_CACHE=y +# CONFIG_FATFS_USE_FASTSEEK is not set +# end of FAT Filesystem support + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_SMP is not set +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=100 +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# end of Kernel + +# +# Port +# +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH is not set +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +# end of Port + +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +CONFIG_HAL_WDT_USE_ROM_IMPL=y +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS=y +# end of Heap memory debugging + +# +# Log output +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 +CONFIG_LOG_COLORS=y +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Log output + +# +# LWIP +# +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +# CONFIG_LWIP_NETIF_API is not set +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 +CONFIG_LWIP_TCP_WND_DEFAULT=5744 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +# CONFIG_LWIP_TCP_SACK_OUT is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_LWIP_PPP_SUPPORT is not set +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +# end of SNTP + +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y +# CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v3.x related +# +# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y +# end of mbedTLS v3.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +# CONFIG_MBEDTLS_CMAC_C is not set +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA512_C=y +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +# CONFIG_MBEDTLS_DHM_C is not set +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +# CONFIG_MBEDTLS_SECURITY_RISKS is not set +# end of mbedTLS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# Newlib +# +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set +# end of Newlib + +# +# NVS +# +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# end of NVS + +# +# OpenThread +# +# CONFIG_OPENTHREAD_ENABLED is not set +# end of OpenThread + +# +# Protocomm +# +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +# end of Protocomm + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=16384 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +CONFIG_SPI_FLASH_HAS_ROM_IMPL=y +# CONFIG_SPI_FLASH_ROM_IMPL is not set +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_SHARE_SPI1_BUS is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# SPIFFS Configuration +# +CONFIG_SPIFFS_MAX_PARTITIONS=3 + +# +# SPIFFS Cache Configuration +# +CONFIG_SPIFFS_CACHE=y +CONFIG_SPIFFS_CACHE_WR=y +# CONFIG_SPIFFS_CACHE_STATS is not set +# end of SPIFFS Cache Configuration + +CONFIG_SPIFFS_PAGE_CHECK=y +CONFIG_SPIFFS_GC_MAX_RUNS=10 +# CONFIG_SPIFFS_GC_STATS is not set +CONFIG_SPIFFS_PAGE_SIZE=256 +CONFIG_SPIFFS_OBJ_NAME_LEN=32 +# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set +CONFIG_SPIFFS_USE_MAGIC=y +CONFIG_SPIFFS_USE_MAGIC_LENGTH=y +CONFIG_SPIFFS_META_LENGTH=4 +CONFIG_SPIFFS_USE_MTIME=y + +# +# Debug Configuration +# +# CONFIG_SPIFFS_DBG is not set +# CONFIG_SPIFFS_API_DBG is not set +# CONFIG_SPIFFS_GC_DBG is not set +# CONFIG_SPIFFS_CACHE_DBG is not set +# CONFIG_SPIFFS_CHECK_DBG is not set +# CONFIG_SPIFFS_TEST_VISUALISATION is not set +# end of Debug Configuration +# end of SPIFFS Configuration + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# Ultra Low Power (ULP) Co-processor +# +# CONFIG_ULP_COPROC_ENABLED is not set +# end of Ultra Low Power (ULP) Co-processor + +# +# Unity unit testing library +# +CONFIG_UNITY_ENABLE_FLOAT=y +CONFIG_UNITY_ENABLE_DOUBLE=y +# CONFIG_UNITY_ENABLE_64BIT is not set +# CONFIG_UNITY_ENABLE_COLOR is not set +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y +# CONFIG_UNITY_ENABLE_FIXTURE is not set +# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set +# end of Unity unit testing library + +# +# USB-OTG +# +CONFIG_USB_OTG_SUPPORTED=y +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_VFS_SUPPORT_TERMIOS=y + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) +# end of Virtual file system + +# +# Wear Levelling +# +# CONFIG_WL_SECTOR_SIZE_512 is not set +CONFIG_WL_SECTOR_SIZE_4096=y +CONFIG_WL_SECTOR_SIZE=4096 +# end of Wear Levelling + +# +# Wi-Fi Provisioning Manager +# +CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 +CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 +CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION=y +# end of Wi-Fi Provisioning Manager + +# +# Supplicant +# +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_SUITE_B_192 is not set +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_WPA_TESTING_OPTIONS is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# CONFIG_WPA_11R_SUPPORT is not set +# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set +# end of Supplicant +# end of Component config + +# Deprecated options for backward compatibility +# CONFIG_NO_BLOBS is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +CONFIG_CXX_EXCEPTIONS=y +CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +CONFIG_WARN_WRITE_STRINGS=y +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +# CONFIG_OTA_ALLOW_HTTP is not set +# CONFIG_ESP_SYSTEM_PD_FLASH is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +CONFIG_REDUCE_PHY_TX_POWER=y +CONFIG_ESP32_REDUCE_PHY_TX_POWER=y +CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y +# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5744 +CONFIG_TCP_WND_DEFAULT=5744 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_PPP_SUPPORT is not set +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=16384 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options diff --git a/tests/demo_server.cc b/tests/demo_server.cc index e355b29..6dab58c 100644 --- a/tests/demo_server.cc +++ b/tests/demo_server.cc @@ -17,6 +17,8 @@ // #include "Crouton.hh" +#include "io/TCPServer.hh" +#include "util/Logging.hh" #include #include #include @@ -49,11 +51,11 @@ staticASYNC serveWebSocket(http::Handler::Request const& req, http::Handle if (! AWAIT socket.connect(req, res)) RETURN noerror; - spdlog::info("-- Opened WebSocket"); + Log->info("-- Opened WebSocket"); Generator rcvr = socket.receive(); Result msg; while ((msg = AWAIT rcvr)) { - spdlog::info("\treceived {}", msg); + Log->info("\treceived {}", minifmt::write(msg)); switch (msg->type) { case ws::Message::Text: case ws::Message::Binary: @@ -66,7 +68,7 @@ staticASYNC serveWebSocket(http::Handler::Request const& req, http::Handle break; // WebSocket itself handles Ping and Pong } } - spdlog::info("-- Closing WebSocket"); + Log->info("-- Closing WebSocket"); AWAIT socket.close(); RETURN noerror; } @@ -79,16 +81,16 @@ static vector sRoutes = { static Task connectionTask(std::shared_ptr client) { - spdlog::info("-- Accepted connection"); + Log->info("-- Accepted connection"); http::Handler handler(client, sRoutes); AWAIT handler.run(); - spdlog::info("-- Done!\n"); + Log->info("-- Done!\n"); } static Task run() { static TCPServer server(kPort); - spdlog::info("Listening at http://localhost:{}/ and ws://localhost:{}/ws", kPort, kPort); + Log->info("Listening at http://localhost:{}/ and ws://localhost:{}/ws", kPort, kPort); server.listen([](std::shared_ptr client) { connectionTask(std::move(client)); }); diff --git a/tests/test_io.cc b/tests/test_io.cc index e798302..3687394 100644 --- a/tests/test_io.cc +++ b/tests/test_io.cc @@ -127,7 +127,8 @@ TEST_CASE("DNS lookup", "[uv]") { AddrInfo addr = AWAIT AddrInfo::lookup("example.com"); cerr << "Addr = " << addr.primaryAddressString() << endl; auto ip4addr = addr.primaryAddress(4); - CHECK(ip4addr.sa_family == AF_INET); + REQUIRE(ip4addr); + CHECK(ip4addr->sa_family == AF_INET); CHECK(addr.primaryAddressString() == "93.184.216.34"); RETURN noerror; }); @@ -137,15 +138,15 @@ TEST_CASE("DNS lookup", "[uv]") { TEST_CASE("Read a socket", "[uv]") { RunCoroutine([]() -> Future { - TCPSocket socket; + auto socket = ISocket::newSocket(false); cerr << "-- Test Connecting...\n"; - AWAIT socket.connect("example.com", 80); + AWAIT socket->connect("example.com", 80); cerr << "-- Connected! Test Writing...\n"; - AWAIT socket.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"); + AWAIT socket->stream().write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"); cerr << "-- Test Reading...\n"; - string result = AWAIT socket.readAll(); + string result = AWAIT socket->stream().readAll(); cerr << "HTTP response:\n" << result << endl; CHECK(result.starts_with("HTTP/1.1 ")); diff --git a/tests/tests.cc b/tests/tests.cc index fa84d94..33465c5 100644 --- a/tests/tests.cc +++ b/tests/tests.cc @@ -17,9 +17,12 @@ // #include "tests.hh" +#include "Actor.hh" +#include "Misc.hh" #include "Producer.hh" +#include "util/MiniFormat.hh" #include "util/Relation.hh" -#include "io/UVBase.hh" +#include "io/uv/UVBase.hh" void RunCoroutine(Future (*test)()) { @@ -35,7 +38,7 @@ TEST_CASE("Randomize") { uint8_t buf[10]; ::memset(buf, 0, sizeof(buf)); for (int pass = 0; pass < 5; ++pass) { - io::Randomize(buf, sizeof(buf)); + Randomize(buf, sizeof(buf)); for (int i = 0; i < 10; ++i) printf("%02x ", buf[i]); printf("\n"); @@ -234,6 +237,44 @@ TEST_CASE("Producer Consumer") { } +TEST_CASE("MiniFormat") { + CHECK(minifmt::format("No placeholders") == "No placeholders"); + CHECK(minifmt::format("Escaped {{... {}!", 7) == "Escaped {... 7!"); + CHECK(minifmt::format("Escaped {{{{... {}!", 7) == "Escaped {{... 7!"); + CHECK(minifmt::format("Escaped {{{}!", 7) == "Escaped {7!"); + CHECK(minifmt::format("{{Escaped ... {}!", 7) == "{Escaped ... 7!"); + CHECK(minifmt::format("Escaped ... {}!{{", 7) == "Escaped ... 7!{"); + CHECK(minifmt::format("Escaped {{... {}! ...}}", 7) == "Escaped {... 7! ...}"); + CHECK(minifmt::format("Escaped {{... {}! }", 7) == "Escaped {... 7! }"); + + CHECK(minifmt::format("{} {}", false, true) == "false true"); + CHECK(minifmt::format("char '{}', i16 {}, u16 {}, i32 {}, u32 {}, i {}, u {}", + 'X', int16_t(-1234), uint16_t(65432), int32_t(123456789), uint32_t(987654321), + int(-1234567), unsigned(7654321)) + == "char 'X', i16 -1234, u16 65432, i32 123456789, u32 987654321, i -1234567, u 7654321"); + CHECK(minifmt::format("long {}, ulong {}, i64 {}, u64 {}", + 12345678l, 87654321ul, int64_t(-12345678901234), uint64_t(12345678901234)) + == "long 12345678, ulong 87654321, i64 -12345678901234, u64 12345678901234"); + double d = 3.1415926; + CHECK(minifmt::format("float {}, double {}", 12345.125f, d) + == "float 12345.1, double 3.14159"); + + const char* cstr = "C string"; + string str = "C++ string"; + CHECK(minifmt::format("cstr '{}', C++ str '{}', string_view '{}'", cstr, str, string_view(str)) + == "cstr 'C string', C++ str 'C++ string', string_view 'C++ string'"); + + coro_handle h = std::noop_coroutine(); + CHECK(minifmt::format("{}", minifmt::write(logCoro{h})) + == "¢exit"); + + CHECK(minifmt::format("One {} two {} three", 1, 2, 3) + == "One 1 two 2 three{{{TOO FEW PLACEHOLDERS}}}"); + CHECK(minifmt::format("One {} two {} three {} four {}", 1, 2) + == "One 1 two 2 three {{{TOO FEW ARGS}}}"); +} + + #if 0 staticASYNC waitFor(chrono::milliseconds ms) { FutureProvider f; diff --git a/tests/tests.hh b/tests/tests.hh index 4aa5c34..152a288 100644 --- a/tests/tests.hh +++ b/tests/tests.hh @@ -17,7 +17,7 @@ // #include "Crouton.hh" -#include "Logging.hh" +#include "util/Logging.hh" #include #include "catch_amalgamated.hpp"