Description: <short summary of the patch>
 TODO: Put a short summary on the line above and replace this paragraph
 with a longer explanation of this change. Complete the meta-information
 with other relevant fields (see below for details). To make it easier, the
 information below has been extracted from the changelog. Adjust it or drop
 it.
 .
 wlmaker (0.3-1) unstable; urgency=medium
 .
   * Initial release. (Closes: #1074240)
Author: Alex Myczko <tar@debian.org>
Bug-Debian: https://bugs.debian.org/1074240

---
The information above should follow the Patch Tagging Guidelines, please
checkout https://dep.debian.net/deps/dep3/ to learn about the format. Here
are templates for supplementary fields that you might want to add:

Origin: (upstream|backport|vendor|other), (<patch-url>|commit:<commit-id>)
Bug: <upstream-bugtracker-url>
Bug-Debian: https://bugs.debian.org/<bugnumber>
Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
Forwarded: (no|not-needed|<patch-forwarded-url>)
Applied-Upstream: <version>, (<commit-url>|commit:<commid-id>)
Reviewed-By: <name and email of someone who approved/reviewed the patch>
Last-Update: 2024-07-05

--- /dev/null
+++ wlmaker-0.3/submodules/libbase/.github/workflows/build-for-linux.yml
@@ -0,0 +1,50 @@
+name: Build for Linux
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+jobs:
+  build_matrix:
+    strategy:
+      matrix:
+        compiler: [ "gcc", "clang" ]
+    runs-on: ubuntu-latest
+    container:
+      image: debian:bookworm
+
+    steps:
+    - name: Install package dependencies.
+      run: |
+        apt-get update
+        apt-get install -y \
+          clang \
+          cmake \
+          doxygen \
+          git \
+          libcairo2-dev \
+          libncurses-dev
+    
+    - name: Checkout code.
+      uses: actions/checkout@v3
+
+    - name: Configure libbase through CMake.
+      run: |
+        export CC="${{ matrix.compiler }}"
+        cmake -B ${{github.workspace}}/build
+
+    - name: Build libbase.
+      run: |
+        export CC="${{ matrix.compiler }}"
+        cmake --build ${{github.workspace}}/build
+
+    - name: Build documentation.
+      run: |
+        cmake --build ${{github.workspace}}/build --target doc
+
+    - name: Run tests.
+      run: |
+        ctest --test-dir ${{github.workspace}}/build --build-run-dir ${{github.workspace}}/build -V
+
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/CMakeLists.txt
@@ -0,0 +1,207 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+# https://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.
+
+CMAKE_MINIMUM_REQUIRED(VERSION 3.13)
+PROJECT(libbase VERSION 1.0
+  DESCRIPTION "A small standard library for C"
+  LANGUAGES C)
+
+# Requires out-of-source builds.
+FILE(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOCATION_PATH)
+IF(EXISTS "${LOCATION_PATH}")
+  MESSAGE(
+    FATAL_ERROR
+    "You cannot build into a source directory (containing a CMakeLists.txt file).\n"
+    "Please make a build subdirectory, for example:\n"
+    "cmake -B build\n"
+    "(cd build && make)")
+ENDIF()
+
+# Defaults to /usr/local/lib for installation.
+INCLUDE(GNUInstallDirs)
+
+# Configuration. Remove CMakeCache.txt to rerun...
+OPTION(config_DEBUG "Include debugging information" ON)
+OPTION(config_OPTIM "Optimizations" OFF)
+OPTION(config_COVERAGE "Build with test coverage" OFF)
+
+# Toplevel compile options, for Clang and GCC.
+IF(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU")
+  IF(config_DEBUG)
+    ADD_COMPILE_OPTIONS(-ggdb -DDEBUG)
+  ENDIF(config_DEBUG)
+
+  IF(config_OPTIM)
+    ADD_COMPILE_OPTIONS(-O2)
+  ELSE(config_OPTIM)
+    ADD_COMPILE_OPTIONS(-O0)
+  ENDIF(config_OPTIM)
+  ADD_COMPILE_OPTIONS(-Wall -Wextra -Werror)
+
+  # CMake provides absolute paths to GCC, hence the __FILE__ macro includes the
+  # full path. This option resets it to a path relative to project source.
+  ADD_COMPILE_OPTIONS(-fmacro-prefix-map=${PROJECT_SOURCE_DIR}=.)
+ENDIF()
+SET(CMAKE_C_STANDARD 11)
+
+FIND_PACKAGE(Curses REQUIRED)
+
+FIND_PACKAGE(PkgConfig REQUIRED)
+PKG_CHECK_MODULES(CAIRO REQUIRED IMPORTED_TARGET cairo>=1.16.0)
+
+SET(PUBLIC_HEADER_FILES
+  arg.h
+  assert.h
+  atomic.h
+  avltree.h
+  c2x_compat.h
+  def.h
+  dequeue.h
+  dllist.h
+  file.h
+  gfxbuf.h
+  gfxbuf_xpm.h
+  libbase.h
+  log.h
+  log_wrappers.h
+  ptr_set.h
+  ptr_stack.h
+  ptr_vector.h
+  sock.h
+  strutil.h
+  subprocess.h
+  test.h
+  thread.h
+  time.h
+  vector.h)
+
+SET(SOURCES
+  arg.c
+  atomic.c
+  avltree.c
+  c2x_compat.c
+  dequeue.c
+  dllist.c
+  file.c
+  gfxbuf.c
+  gfxbuf_xpm.c
+  log.c
+  ptr_set.c
+  ptr_stack.c
+  ptr_vector.c
+  sock.c
+  strutil.c
+  subprocess.c
+  test.c
+  thread.c
+  time.c)
+
+ADD_LIBRARY(base STATIC)
+TARGET_SOURCES(base PRIVATE ${SOURCES})
+TARGET_INCLUDE_DIRECTORIES(base PRIVATE ${CURSES_CURSES_INCLUDE_DIRS})
+TARGET_INCLUDE_DIRECTORIES(base PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
+TARGET_LINK_LIBRARIES(base PRIVATE ${CURSES_CURSES_LIBRARY})
+SET_TARGET_PROPERTIES(
+  base PROPERTIES
+  VERSION 1.0
+  PUBLIC_HEADER "${PUBLIC_HEADER_FILES}")
+
+IF(CAIRO_FOUND)
+  TARGET_COMPILE_DEFINITIONS(base PUBLIC HAVE_CAIRO)
+  TARGET_INCLUDE_DIRECTORIES(base PUBLIC ${CAIRO_INCLUDE_DIRS})
+  TARGET_LINK_LIBRARIES(base PUBLIC PkgConfig::CAIRO)
+ENDIF(CAIRO_FOUND)
+
+# Create pkg-config file from the template.
+CONFIGURE_FILE(libbase.pc.in ${CMAKE_BINARY_DIR}/libbase.pc @ONLY)
+
+# Add 'install' target, but only if we're the toplevel project.
+IF(CMAKE_PROJECT_NAME STREQUAL libbase)
+  INSTALL(
+    TARGETS base
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libbase")
+
+  INSTALL(
+    FILES ${CMAKE_BINARY_DIR}/libbase.pc
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
+  )
+ENDIF()
+
+# Add test target, but only if we're the toplevel project.
+IF(CMAKE_PROJECT_NAME STREQUAL libbase)
+  ADD_EXECUTABLE(libbase_test libbase_test.c)
+  TARGET_LINK_LIBRARIES(libbase_test base)
+  # Refer to the source path for relative testdata.
+  TARGET_COMPILE_DEFINITIONS(
+    libbase_test PUBLIC BS_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
+
+
+  ADD_EXECUTABLE(libbase_benchmark libbase_benchmark.c)
+  TARGET_LINK_LIBRARIES(libbase_benchmark base)
+
+  ADD_EXECUTABLE(subprocess_test_hang subprocess_test_hang.c)
+  ADD_EXECUTABLE(subprocess_test_failure subprocess_test_failure.c)
+  ADD_EXECUTABLE(subprocess_test_sigpipe subprocess_test_sigpipe.c)
+  ADD_EXECUTABLE(subprocess_test_success subprocess_test_success.c)
+
+  INCLUDE(CTest)
+  IF(BUILD_TESTING)
+    ADD_TEST(NAME libbase_test COMMAND libbase_test)
+  ENDIF()
+ENDIF()
+
+# Adds 'doc' target, if doxygen is installed.
+FIND_PACKAGE(Doxygen)
+IF(DOXYGEN_FOUND)
+  # Set input and output files.
+  SET(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
+  SET(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+
+  # Configure the file.
+  CONFIGURE_FILE(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
+  ADD_CUSTOM_TARGET(
+    libbase_doc
+    COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
+    DEPENDS ${DOXYGEN_OUT}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+    COMMENT "Generating API documentation with Doxygen."
+    VERBATIM)
+  MESSAGE(NOTICE "Doxygen available, adding libbase documentation to 'doc' target.")
+
+  # Add as dependency to an existing 'doc' target, or create it.
+  IF(TARGET doc)
+    ADD_DEPENDENCIES(doc libbase_doc)
+  ELSE(TARGET doc)
+    ADD_CUSTOM_TARGET(doc DEPENDS libbase_doc)
+  ENDIF(TARGET doc)
+
+ELSE(DOXYGEN_FOUND)
+  MESSAGE(NOTICE "Doxygen not found. Not adding 'doc' target to generate API documentation.")
+ENDIF(DOXYGEN_FOUND)
+
+# Adds 'coverage' target, if configured. Needs 'gcovr'.
+IF(config_COVERAGE)
+  FIND_PROGRAM(GCOVR_FOUND gcovr OPTIONAL)
+  IF(GCOVR_FOUND)
+    ADD_COMPILE_OPTIONS(--coverage)
+    ADD_LINK_OPTIONS(--coverage)
+
+    MESSAGE(NOTICE "Coverage configured, adding 'coverage' target.")
+    ADD_CUSTOM_TARGET(coverage
+      COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR} . --exclude _deps --print-summary --html-details --html-title "Unittest coverage" -o coverage.html)
+  ELSE(GCOVR_FOUND)
+    MESSAGE(WARNING "Coverage enabled, but 'govr' not found.")
+  ENDIF(GCOVR_FOUND)
+ENDIF()
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+*   Using welcoming and inclusive language
+*   Being respectful of differing viewpoints and experiences
+*   Gracefully accepting constructive criticism
+*   Focusing on what is best for the community
+*   Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+*   The use of sexualized language or imagery and unwelcome sexual attention or
+    advances
+*   Trolling, insulting/derogatory comments, and personal or political attacks
+*   Public or private harassment
+*   Publishing others' private information, such as a physical or electronic
+    address, without explicit permission
+*   Other conduct which could reasonably be considered inappropriate in a
+    professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the
+Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out to the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct/
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/CONTRIBUTING.md
@@ -0,0 +1,32 @@
+# How to Contribute
+
+We would love to accept your patches and contributions to this project.
+
+## Before you begin
+
+### Sign our Contributor License Agreement
+
+Contributions to this project must be accompanied by a
+[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
+You (or your employer) retain the copyright to your contribution; this simply
+gives us permission to use and redistribute your contributions as part of the
+project.
+
+If you or your current employer have already signed the Google CLA (even if it
+was for a different project), you probably don't need to do it again.
+
+Visit <https://cla.developers.google.com/> to see your current agreements or to
+sign a new one.
+
+### Review our Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google/conduct/).
+
+## Contribution process
+
+### Code Reviews
+
+All submissions, including submissions by project members, require review. We 
+use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests)
+for this purpose.
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/Doxyfile.in
@@ -0,0 +1,2614 @@
+# Doxyfile 1.9.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "My Project"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @PROJECT_BINARY_DIR@/doc
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines (in the resulting output). You can put ^^ in the value part of an
+# alias to insert a newline as if a physical newline was in the original file.
+# When you need a literal { or } or , in the value part of an alias you have to
+# escape them by means of a backslash (\), this can lead to conflicts with the
+# commands \{ and \} for these it is advised to use the version @{ and @} or use
+# a double escape (\\{ and \\})
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
+# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which efficively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation. If
+# EXTRACT_ALL is set to YES then this flag will automatically be disabled.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR          = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @PROJECT_SOURCE_DIR@
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
+# *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f18 \
+                         *.f \
+                         *.for \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf \
+                         *.ice
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to
+# YES then doxygen will add the directory of each input to the include path.
+# The default value is: YES.
+
+CLANG_ADD_INC_PATHS    = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see:
+# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment.
+# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = https://cdn.jsdelivr.net/npm/mathjax@2
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = YES
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = DOXYGEN_SHOULD_SKIP_THIS \
+                         __UNUSED__= \
+                         __ARG_PRINTF__()= \
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc and
+# plantuml temporary files.
+# The default value is: YES.
+
+DOT_CLEANUP            = YES
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/README.md
@@ -0,0 +1,35 @@
+# phkaeser libbase
+
+A small "standard library" for C, used for my personal projects.
+
+### To configure
+
+```bash
+cmake [-DCMAKE_INSTALL_PREFIX:PATH="${HOME}/.local"] -S . -B build
+```
+
+### To build
+
+```bash
+(cd build && make)
+```
+
+### To install
+
+```bash
+(cd build && make install)
+```
+
+## Contributing
+
+See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details, and [code of conduct](CODE_OF_CONDUCT.md) for more.
+
+## License
+
+Apache 2.0; see [`LICENSE`](LICENSE) for details.
+
+## Disclaimer
+
+This project is not an official Google project. It is not supported by
+Google and Google specifically disclaims all warranties as to its quality,
+merchantability, or fitness for a particular purpose.
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/arg.c
@@ -0,0 +1,1027 @@
+ /* ========================================================================= */
+/**
+ * @file arg.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "arg.h"
+#include "assert.h"
+#include "avltree.h"
+#include "c2x_compat.h"
+#include "log.h"
+#include "strutil.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+/* == Declarations ========================================================= */
+
+/** How the arg matches a bs_arg_t. */
+typedef enum {
+    /** No match at all. */
+    _BS_ARG_NO_MATCH = 0,
+    /** The value is "name=value", as one string. */
+    _BS_ARG_MATCH_WITH_EQUAL_SIGN = 1,
+    /** The value is "name", so the next arg is the value. */
+    _BS_ARG_MATCH_WITH_TWO_ARGS = 2,
+    /** It's a bool, an exact match. Doesn't take a value. */
+    _BS_ARG_MATCH_BOOL = 3,
+    /** It's a bool, with a 'no' prefix. Doesn't take a value. */
+    _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO = 4
+} bs_arg_match_t;
+
+static bs_arg_match_t get_match_type(const bs_arg_t *arg_ptr,
+                                     const char *argv, const char *next_argv,
+                                     const char **arg_value_ptr);
+static bool find_matching_arg(const bs_arg_t *arg_ptr,
+                              const char *argv, const char *next_argv,
+                              const bs_arg_t **matching_arg_ptr,
+                              const char **arg_value_ptr);
+static bool parse_arg(const bs_arg_t *arg_ptr, const char *value_ptr);
+static bool parse_bool(const bs_arg_bool_t *arg_bool_ptr,
+                       const char *value_ptr);
+static bool parse_enum(const bs_arg_enum_t *arg_enum_ptr,
+                       const char *value_ptr);
+static bool parse_string(const bs_arg_string_t *arg_string_ptr,
+                         const char *value_ptr);
+static bool parse_uint32(const bs_arg_uint32_t *arg_uint32_ptr,
+                         const char *value_ptr);
+
+static void set_all_defaults(const bs_arg_t *arg_ptr);
+static bool check_arg(const bs_arg_t *arg_ptr);
+
+/**  Store an arg name in a tree, to identify duplicates. */
+typedef struct {
+    /** Tree node. */
+    bs_avltree_node_t         node;
+    /** Argument name. */
+    char                      *name_ptr;
+} arg_name_t;
+
+static int node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr);
+static arg_name_t *node_create(const char *prefix_ptr, const char *name_ptr);
+static void node_destroy(bs_avltree_node_t *node_ptr);
+static bool is_name_valid(const char *name_ptr);
+
+static bool lookup_enum(const bs_arg_enum_table_t *lookup_table,
+                        const char *name_ptr,
+                        uint32_t *value_ptr);
+
+/* == Data ================================================================= */
+
+static const char             *bs_arg_bool_value_true = "true";
+static const char             *bs_arg_bool_value_false = "false";
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bool bs_arg_parse(const bs_arg_t *arg_ptr, const bs_arg_mode_t mode,
+                  int *argc_ptr, const char **argv_ptr)
+{
+    if (!check_arg(arg_ptr)) {
+        return false;
+    }
+
+    int                       not_consumed = 1;
+    const bs_arg_t            *matching_arg_ptr;
+    const char                *next_argv_ptr, *arg_value_ptr;
+
+    set_all_defaults(arg_ptr);
+
+    // Start at 1 -- argv_ptr[0] is the program's name.
+    for (int i = 1; i < *argc_ptr; ++i) {
+        next_argv_ptr = i + 1 >= *argc_ptr ? NULL : argv_ptr[i+1];
+
+        if (!find_matching_arg(arg_ptr, argv_ptr[i], next_argv_ptr,
+                               &matching_arg_ptr, &arg_value_ptr)) {
+            bs_arg_cleanup(arg_ptr);
+            return false;
+        }
+        if (NULL == matching_arg_ptr) {
+            argv_ptr[not_consumed++] = argv_ptr[i];
+            continue;
+        }
+
+        if (!parse_arg(matching_arg_ptr, arg_value_ptr)) {
+            bs_arg_cleanup(arg_ptr);
+            return false;
+        }
+
+        // Move by two args, if the next arg was actually used.
+        if (arg_value_ptr == next_argv_ptr) ++i;
+    }
+
+    // Cleanup rest of argv.
+    for (int i = not_consumed; i < *argc_ptr; ++i) {
+        argv_ptr[i] = NULL;
+    }
+    *argc_ptr = not_consumed;
+
+    bool retval = true;
+    switch (mode) {
+    case BS_ARG_MODE_NO_EXTRA:
+        /* No extra args expected. */
+        for (int i = 1; i < not_consumed; ++i) {
+            bs_log(BS_WARNING, "Unexpected extra argv: %s", argv_ptr[i]);
+            retval = false;
+        }
+        break;
+
+    case BS_ARG_MODE_EXTRA_VALUES:
+        /* Values are permitted, but report any extra "--" prefix. */
+        for (int i = 1; i < not_consumed; ++i) {
+            if (0 == strncmp(argv_ptr[i], "--", 2)) {
+                bs_log(BS_WARNING, "Unexpected extra arg: %s", argv_ptr[i]);
+                retval = false;
+            }
+        }
+        break;
+
+    case BS_ARG_MODE_EXTRA_ARGS:
+        /* We're good. Anything is allowed. */
+        break;
+
+    default:
+        bs_log(BS_FATAL, "Unexpected mode %d.", mode);
+        retval = false;
+        BS_ABORT();
+    }
+
+    if (!retval) bs_arg_cleanup(arg_ptr);
+    return retval;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_cleanup(const bs_arg_t *arg_ptr)
+{
+    for (; NULL != arg_ptr->name_ptr; ++arg_ptr) {
+        switch (arg_ptr->type) {
+        case BS_ARG_TYPE_STRING:
+            if (NULL != *arg_ptr->v.v_string.value_ptr) {
+                free(*arg_ptr->v.v_string.value_ptr);
+                *arg_ptr->v.v_string.value_ptr = NULL;
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr)
+{
+    const bs_arg_enum_table_t *lookup_table;
+    int written_bytes = 0;
+    for (; NULL != arg_ptr->name_ptr; ++arg_ptr) {
+        written_bytes += fprintf(stream_ptr, "--%s : %s\n",
+                                 arg_ptr->name_ptr,
+                                 arg_ptr->description_ptr);
+
+        switch (arg_ptr->type) {
+        case BS_ARG_TYPE_ENUM:
+            lookup_table = arg_ptr->v.v_enum.lookup_table;
+            written_bytes += fprintf(stderr, "    Enum values:\n");
+            for (; NULL != lookup_table->name_ptr; ++lookup_table) {
+                written_bytes += fprintf(
+                    stream_ptr, "      %s (%"PRIu32")\n",
+                    lookup_table->name_ptr, lookup_table->value);
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+    return written_bytes;
+}
+
+/* == Local methods ======================================================== */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Returns the match type (if any) for |*arg_ptr| on |argv|. Updates
+ * |*arg_value_ptr| to point to the value -- either NULL (no value expected),
+ * |next_argv| (on _TWO_ARGS match) or to the part after '=' of |argv|.
+ *
+ * @param arg_ptr
+ * @param argv
+ * @param next_argv
+ * @param arg_value_ptr
+ *
+ * @returns Match type.
+ */
+bs_arg_match_t get_match_type(const bs_arg_t *arg_ptr,
+                              const char *argv, const char *next_argv,
+                              const char **arg_value_ptr)
+{
+    size_t name_len;
+
+    *arg_value_ptr = NULL;
+    if (0 != strncmp(argv, "--", 2)) {
+        return _BS_ARG_NO_MATCH;
+    }
+    argv +=2;
+
+    // BOOL type: A full match, a matching with a "no" prefix. No value!
+    if (BS_ARG_TYPE_BOOL == arg_ptr->type) {
+        if (0 == strcmp(argv, arg_ptr->name_ptr)) {
+            return _BS_ARG_MATCH_BOOL;
+        }
+        if (0 == strncmp(argv, "no", 2) &&
+            0 == strcmp(argv + 2, arg_ptr->name_ptr)) {
+            return _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO;
+        }
+        return _BS_ARG_NO_MATCH;
+    }
+
+    // Other types: Matches must start with the arg name...
+    name_len = strlen(arg_ptr->name_ptr);
+    if (0 == strncmp(argv, arg_ptr->name_ptr, name_len)) {
+        // ... and continue with a '=' and value, or space.
+        if ('=' == argv[name_len]) {
+            *arg_value_ptr = argv + name_len + 1;
+            return _BS_ARG_MATCH_WITH_EQUAL_SIGN;
+        }
+        if ('\0' == argv[name_len]) {
+            *arg_value_ptr = next_argv;
+            return _BS_ARG_MATCH_WITH_TWO_ARGS;
+        }
+    }
+
+    // Fall-through: It's not a match.
+    return _BS_ARG_NO_MATCH;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Finds the arg matching |argv|. Updates arg_value_ptr to the value. */
+bool find_matching_arg(const bs_arg_t *arg_ptr,
+                       const char *argv, const char *next_argv,
+                       const bs_arg_t **matching_arg_ptr,
+                       const char **arg_value_ptr)
+{
+    bs_arg_match_t            match_type;
+
+    *matching_arg_ptr = NULL;
+    *arg_value_ptr = NULL;
+    for (; BS_ARG_TYPE_UNDEFINED != arg_ptr->type; ++arg_ptr) {
+
+        match_type = get_match_type(arg_ptr, argv, next_argv, arg_value_ptr);
+        switch (match_type) {
+        case _BS_ARG_MATCH_WITH_EQUAL_SIGN:
+            BS_ASSERT(NULL != *arg_value_ptr);
+            *matching_arg_ptr = arg_ptr;
+            return true;
+
+        case _BS_ARG_MATCH_WITH_TWO_ARGS:
+            if (NULL == *arg_value_ptr) {
+                bs_log(BS_WARNING, "Missing value for arg '%s'",
+                       arg_ptr->name_ptr);
+                return false;
+            }
+            *matching_arg_ptr = arg_ptr;
+            return true;
+
+        case _BS_ARG_MATCH_BOOL:
+            *arg_value_ptr = bs_arg_bool_value_true;
+            *matching_arg_ptr = arg_ptr;
+            return true;
+
+        case _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO:
+            *arg_value_ptr = bs_arg_bool_value_false;
+            *matching_arg_ptr = arg_ptr;
+            return true;
+
+        case _BS_ARG_NO_MATCH:
+        default:
+            break;
+        }
+
+    }
+    /* no match found, but not an error */
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses |value_ptr| for |arg_ptr|. */
+bool parse_arg(const bs_arg_t *arg_ptr, const char *value_ptr)
+{
+    bool                      retval = false;
+
+    switch(arg_ptr->type) {
+    case BS_ARG_TYPE_BOOL:
+        retval = parse_bool(&arg_ptr->v.v_bool, value_ptr);
+        break;
+
+    case BS_ARG_TYPE_ENUM:
+        retval = parse_enum(&arg_ptr->v.v_enum, value_ptr);
+        break;
+
+    case BS_ARG_TYPE_STRING:
+        retval = parse_string(&arg_ptr->v.v_string, value_ptr);
+        break;
+
+    case BS_ARG_TYPE_UINT32:
+        retval = parse_uint32(&arg_ptr->v.v_uint32, value_ptr);
+        break;
+
+    case BS_ARG_TYPE_UNDEFINED:
+    default:
+        bs_log(BS_FATAL, "Unhandled arg type %d for %s", arg_ptr->type,
+               arg_ptr->name_ptr);
+        BS_ABORT();
+    }
+
+    if (!retval) {
+        bs_log(BS_ERROR, "Failed to parse --%s for \"%s\"",
+               arg_ptr->name_ptr, value_ptr);
+    }
+    return retval;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses a boolean value at |value_ptr|. */
+bool parse_bool(const bs_arg_bool_t *arg_bool_ptr,
+                const char *value_ptr)
+{
+    if (0 == strcmp(bs_arg_bool_value_true, value_ptr)) {
+        *(arg_bool_ptr->value_ptr) = true;
+        return true;
+    }
+
+    if (0 == strcmp(bs_arg_bool_value_false, value_ptr)) {
+        *(arg_bool_ptr->value_ptr) = false;
+        return true;
+    }
+
+    bs_log(BS_ERROR, "Unrecognized bool value \"%s\"", value_ptr);
+    return false;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses an enum from |value_ptr|. */
+bool parse_enum(const bs_arg_enum_t *arg_enum_ptr,
+                const char *value_ptr)
+{
+    if (!lookup_enum(arg_enum_ptr->lookup_table,
+                     value_ptr,
+                     arg_enum_ptr->value_ptr)) {
+        bs_log(BS_ERROR, "Unknown value \"%s\" for enum.", value_ptr);
+        return false;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses a string value at |value_ptr|. */
+bool parse_string(const bs_arg_string_t *arg_string_ptr,
+                  const char *value_ptr)
+{
+    if (NULL != *arg_string_ptr->value_ptr) {
+        free(*arg_string_ptr->value_ptr);
+    }
+    *arg_string_ptr->value_ptr = strdup(value_ptr);
+    if (NULL == *arg_string_ptr->value_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed strdup(%s)", value_ptr);
+        return false;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses a 32-bit unsigned integer value at |value_ptr|. */
+bool parse_uint32(const bs_arg_uint32_t *arg_uint32_ptr,
+                  const char *value_ptr)
+{
+    uint64_t                  value;
+
+    if (!bs_strconvert_uint64(value_ptr, &value, 10)) {
+        return false;
+    }
+
+    if (value < arg_uint32_ptr->min_value) {
+        bs_log(BS_ERROR, "Out of range: \"%s\" (%"PRIu64" < %"PRIu32")",
+               value_ptr, value, arg_uint32_ptr->min_value);
+        return false;
+    }
+
+    if (value > arg_uint32_ptr->max_value) {
+        bs_log(BS_ERROR, "Out of range: \"%s\" (%"PRIu64" > %"PRIu32")",
+               value_ptr, value, arg_uint32_ptr->max_value);
+        return false;
+    }
+
+    BS_ASSERT(value <= UINT32_MAX);
+    *arg_uint32_ptr->value_ptr = (uint32_t)value;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void set_all_defaults(const bs_arg_t *arg_ptr)
+{
+    for (;
+         NULL != arg_ptr->name_ptr && BS_ARG_TYPE_UNDEFINED != arg_ptr->type;
+         ++arg_ptr) {
+
+        switch(arg_ptr->type) {
+        case BS_ARG_TYPE_BOOL:
+            *arg_ptr->v.v_bool.value_ptr = arg_ptr->v.v_bool.default_value;
+            break;
+
+        case BS_ARG_TYPE_ENUM:
+            if (!lookup_enum(arg_ptr->v.v_enum.lookup_table,
+                             arg_ptr->v.v_enum.default_name_ptr,
+                             arg_ptr->v.v_enum.value_ptr)) {
+                bs_log(BS_FATAL, "Failed to lookup default \"%s\" for enum %s",
+                       arg_ptr->v.v_enum.default_name_ptr, arg_ptr->name_ptr);
+                BS_ABORT();
+            }
+            break;
+
+        case BS_ARG_TYPE_STRING:
+            if (NULL == arg_ptr->v.v_string.default_value) {
+                *arg_ptr->v.v_string.value_ptr = NULL;
+            } else {
+                *arg_ptr->v.v_string.value_ptr = strdup(
+                    arg_ptr->v.v_string.default_value);
+                if (NULL == *arg_ptr->v.v_string.value_ptr) {
+                    bs_log(BS_FATAL | BS_ERRNO, "Failed strdup(%s)",
+                           arg_ptr->v.v_string.default_value);
+                    BS_ABORT();
+                }
+            }
+            break;
+
+        case BS_ARG_TYPE_UINT32:
+            *arg_ptr->v.v_uint32.value_ptr = arg_ptr->v.v_uint32.default_value;
+            break;
+
+        case BS_ARG_TYPE_UNDEFINED:
+        default:
+            bs_log(BS_FATAL, "Unhandled arg type %d for %s", arg_ptr->type,
+                   arg_ptr->name_ptr);
+            BS_ABORT();
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+bool check_arg(const bs_arg_t *arg_ptr)
+{
+    bool                      retval = true;
+    bs_avltree_t              *tree_ptr;
+    arg_name_t                *arg_name_ptr;
+
+    tree_ptr = bs_avltree_create(node_cmp, node_destroy);
+
+    for (; BS_ARG_TYPE_UNDEFINED != arg_ptr->type; ++arg_ptr) {
+
+        if (NULL == arg_ptr->name_ptr) {
+            bs_log(BS_ERROR, "Name not given for arg_ptr at %p", arg_ptr);
+            retval = false;
+            continue;
+        }
+        if (!is_name_valid(arg_ptr->name_ptr)) {
+            // Already logged.
+            retval = false;
+            continue;
+        }
+
+        arg_name_ptr = node_create("", arg_ptr->name_ptr);
+        if (!bs_avltree_insert(tree_ptr, arg_name_ptr->name_ptr,
+                               &arg_name_ptr->node, false)) {
+            bs_log(BS_ERROR, "Duplicate argument name \"%s\"",
+                   arg_name_ptr->name_ptr);
+            node_destroy(&arg_name_ptr->node);
+            retval = false;
+            continue;
+        }
+
+
+        switch(arg_ptr->type) {
+        case BS_ARG_TYPE_BOOL:
+            if (NULL == arg_ptr->v.v_bool.value_ptr) {
+                bs_log(BS_ERROR, "Pointer to value not provided for --%s",
+                       arg_ptr->name_ptr);
+                retval = false;
+            }
+
+            arg_name_ptr = node_create("no", arg_ptr->name_ptr);
+            if (!bs_avltree_insert(tree_ptr, arg_name_ptr->name_ptr,
+                                   &arg_name_ptr->node, false)) {
+                bs_log(BS_ERROR, "Duplicate argument name \"%s\"",
+                       arg_name_ptr->name_ptr);
+                node_destroy(&arg_name_ptr->node);
+                retval = false;
+                continue;
+            }
+
+            break;
+
+        case BS_ARG_TYPE_ENUM:
+            if (NULL == arg_ptr->v.v_enum.value_ptr) {
+                bs_log(BS_ERROR, "Pointer to value not provided for --%s",
+                       arg_ptr->name_ptr);
+                retval = false;
+            }
+
+            if (NULL == arg_ptr->v.v_enum.lookup_table ||
+                NULL == arg_ptr->v.v_enum.lookup_table->name_ptr) {
+                bs_log(BS_ERROR, "Lookup table missing or empty for enum --%s",
+                       arg_ptr->name_ptr);
+                retval = false;
+            }
+            break;
+
+        case BS_ARG_TYPE_STRING:
+            if (NULL == arg_ptr->v.v_string.value_ptr) {
+                bs_log(BS_ERROR, "Pointer to value not provided for --%s",
+                       arg_ptr->name_ptr);
+                retval = false;
+            }
+            break;
+
+        case BS_ARG_TYPE_UINT32:
+            if (NULL == arg_ptr->v.v_uint32.value_ptr) {
+                bs_log(BS_ERROR, "Pointer to value not provided for --%s",
+                       arg_ptr->name_ptr);
+                retval = false;
+            }
+            break;
+
+        default:
+            bs_log(BS_ERROR, "Unhandled type for --%s", arg_ptr->name_ptr);
+            retval = false;
+            break;
+        }
+
+    }
+
+    bs_avltree_destroy(tree_ptr);
+    return retval;
+}
+
+/* == Helpers for storing the names in a tree ============================== */
+
+/* ------------------------------------------------------------------------- */
+int node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr)
+{
+    arg_name_t *arg_name_ptr = BS_CONTAINER_OF(node_ptr, arg_name_t, node);
+    return strcmp(arg_name_ptr->name_ptr, (const char*)key_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+arg_name_t *node_create(const char *prefix_ptr, const char *name_ptr)
+{
+    size_t                    len;
+    arg_name_t                *arg_name_ptr;
+
+    arg_name_ptr = (arg_name_t*)calloc(1, sizeof(arg_name_t));
+    if (NULL == arg_name_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %zu)",
+               sizeof(arg_name_t));
+        return NULL;
+    }
+
+    len = strlen(prefix_ptr) + strlen(name_ptr) + 1;
+    arg_name_ptr->name_ptr = malloc(len);
+    if (NULL == arg_name_ptr->name_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", len);
+        node_destroy(&arg_name_ptr->node);
+    }
+    strcpy(arg_name_ptr->name_ptr, prefix_ptr);
+    strcpy(arg_name_ptr->name_ptr + strlen(prefix_ptr), name_ptr);
+    return arg_name_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void node_destroy(bs_avltree_node_t *node_ptr)
+{
+    arg_name_t *arg_name_ptr = BS_CONTAINER_OF(node_ptr, arg_name_t, node);
+    if (NULL != arg_name_ptr->name_ptr) {
+        free(arg_name_ptr->name_ptr);
+        arg_name_ptr->name_ptr = NULL;
+    }
+
+    free(arg_name_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bool is_name_valid(const char *name_ptr)
+{
+    if (!isalpha(*name_ptr) && !(*name_ptr & 0x80)) {
+        bs_log(BS_ERROR, "Argument name must start with [a-zA-Z]: %s",
+               name_ptr);
+        return false;
+    }
+
+    while (*++name_ptr) {
+        if (!(*name_ptr & 0x80) &&  // same as: isascii()
+            (isalnum(*name_ptr) || '-' == *name_ptr || '_' == *name_ptr)) {
+            continue;
+        }
+        bs_log(BS_ERROR, "Argument name must only contain [-_a-zA-Z0-9]: %s",
+               name_ptr);
+        return false;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+bool lookup_enum(const bs_arg_enum_table_t *lookup_table,
+                 const char *name_ptr,
+                 uint32_t *value_ptr)
+{
+    for (;
+         NULL != lookup_table->name_ptr;
+         ++lookup_table) {
+        if (0 == strcmp(lookup_table->name_ptr, name_ptr)) {
+            *value_ptr = lookup_table->value;
+            return true;
+        }
+    }
+    return false;
+}
+
+/** == Unit tests ========================================================= */
+
+static void bs_arg_test_get_match_type_bool(bs_test_t *test_ptr);
+static void bs_arg_test_get_match_type_nonbool(bs_test_t *test_ptr);
+static void bs_arg_test_find_matching_arg(bs_test_t *test_ptr);
+static void bs_arg_test_parse_arg_for_bool(bs_test_t *test_ptr);
+static void bs_arg_test_parse_arg_for_uint32(bs_test_t *test_ptr);
+static void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr);
+static void bs_arg_test_set_defaults(bs_test_t *test_ptr);
+static void bs_arg_test_parse(bs_test_t *test_ptr);
+static void bs_arg_test_check_arg(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_arg_test_cases[] = {
+    { 1, "get_match_type for bool values", bs_arg_test_get_match_type_bool },
+    { 1, "get_match_type for non-bool values", bs_arg_test_get_match_type_nonbool },
+    { 1, "find_matching_args", bs_arg_test_find_matching_arg },
+    { 1, "parse_arg_for_bool", bs_arg_test_parse_arg_for_bool },
+    { 1, "parse_arg_for_uint32", bs_arg_test_parse_arg_for_uint32 },
+    { 1, "parse_arg_for_enum", bs_arg_test_parse_arg_for_enum },
+    { 1, "set_defaults", bs_arg_test_set_defaults },
+    { 1, "parse", bs_arg_test_parse },
+    { 1, "check_arg", bs_arg_test_check_arg },
+    { 0, NULL, NULL }
+};
+
+static const bs_arg_enum_table_t enum_test_table[] = {
+    { "alpha", 1 }, { "bravo", 42 }, { "charlie", 7 }, { NULL, 0 } };
+
+/* -- Verifies get_match_type() with bool args ----------------------------- */
+void bs_arg_test_get_match_type_bool(bs_test_t *test_ptr) {
+    static const bs_arg_t     bool_arg = BS_ARG_BOOL(
+        "novalue", "description", true, NULL );
+    const char                *next_arg_ptr = "something";
+    const char                *arg_value;
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_NO_MATCH,
+        get_match_type(&bool_arg, "--value", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_MATCH_BOOL,
+        get_match_type(&bool_arg, "--novalue", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO,
+        get_match_type(&bool_arg, "--nonovalue", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_NO_MATCH,
+        get_match_type(&bool_arg, "--nononovalue", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+}
+
+/* -- Verifies get_match_type() with non-bool args ------------------------- */
+void bs_arg_test_get_match_type_nonbool(bs_test_t *test_ptr) {
+    static const bs_arg_t     uint32_arg = BS_ARG_UINT32(
+        "value", "description", 42, 0, UINT32_MAX, NULL );
+    const char                *next_arg_ptr = "1234";
+    const char                *arg_value;
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_NO_MATCH,
+        get_match_type(&uint32_arg, "--other", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_MATCH_WITH_EQUAL_SIGN,
+        get_match_type(&uint32_arg, "--value=4321", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, 0, strcmp(arg_value, "4321"));
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_MATCH_WITH_EQUAL_SIGN,
+        get_match_type(&uint32_arg, "--value=", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, '\0', *arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_MATCH_WITH_TWO_ARGS,
+        get_match_type(&uint32_arg, "--value", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, next_arg_ptr, arg_value);
+
+    BS_TEST_VERIFY_EQ(
+        test_ptr,
+        _BS_ARG_NO_MATCH,
+        get_match_type(&uint32_arg, "--novalue", next_arg_ptr, &arg_value));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value);
+
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_find_matching_arg(bs_test_t *test_ptr) {
+    static const bs_arg_t     args[] = {
+        BS_ARG_BOOL("b", "d", true, NULL),
+        BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, NULL),
+        BS_ARG_SENTINEL(),
+    };
+    const bs_arg_t            *matching_arg;
+    const char                *av_ptr;
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        find_matching_arg(args, "--b", NULL, &matching_arg, &av_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, &args[0], matching_arg);
+    BS_TEST_VERIFY_STREQ(test_ptr, "true", av_ptr);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        find_matching_arg(args, "--nob", NULL, &matching_arg, &av_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, &args[0], matching_arg);
+    BS_TEST_VERIFY_STREQ(test_ptr, "false", av_ptr);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        find_matching_arg(args, "--u32=123", NULL, &matching_arg, &av_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, &args[1], matching_arg);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        find_matching_arg(args, "--u32", "456", &matching_arg, &av_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, &args[1], matching_arg);
+    BS_TEST_VERIFY_STREQ(test_ptr, "456", av_ptr);
+
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        find_matching_arg(args, "--u32", NULL, &matching_arg, &av_ptr));
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        find_matching_arg(args, "--unknown", NULL, &matching_arg, &av_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, matching_arg);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, av_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_parse_arg_for_bool(bs_test_t *test_ptr)
+{
+    static bool               value;
+    static const bs_arg_t     arg = BS_ARG_BOOL("b", "d", true, &value );
+
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "true"));
+    BS_TEST_VERIFY_EQ(test_ptr, true, value);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "false"));
+    BS_TEST_VERIFY_EQ(test_ptr, false, value);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "meh"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "truea"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "falsea"));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_parse_arg_for_uint32(bs_test_t *test_ptr)
+{
+    static uint32_t           value;
+    static const bs_arg_t     arg = BS_ARG_UINT32(
+        "u32", "d", 42, 0, UINT32_MAX, &value);
+    static const bs_arg_t     arg_limited = BS_ARG_UINT32(
+        "u32", "d", 42, 10, 100, &value);
+
+
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "0"));
+    BS_TEST_VERIFY_EQ(test_ptr, 0, value);
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "4294967295"));
+    BS_TEST_VERIFY_EQ(test_ptr, UINT32_MAX, value);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "999999999999999999999"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "4294967296"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "12a"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "a"));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg_limited, "10"));
+    BS_TEST_VERIFY_EQ(test_ptr, 10, value);
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg_limited, "100"));
+    BS_TEST_VERIFY_EQ(test_ptr, 100, value);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg_limited, "9"));
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg_limited, "101"));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr)
+{
+    static uint32_t           value;
+    static const bs_arg_t     arg = BS_ARG_ENUM(
+        "e", "d", "alpha", enum_test_table, &value);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "alpha"));
+    BS_TEST_VERIFY_EQ(test_ptr, 1, value);
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "bravo"));
+    BS_TEST_VERIFY_EQ(test_ptr, 42, value);
+    BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "charlie"));
+    BS_TEST_VERIFY_EQ(test_ptr, 7, value);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "delta"));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_set_defaults(bs_test_t *test_ptr)
+{
+    static bool               value_bool;
+    static uint32_t           value_uint32;
+    static uint32_t           value_enum;
+    static const bs_arg_t     args[] = {
+        BS_ARG_BOOL("b", "d", true, &value_bool),
+        BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32),
+        BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum),
+        BS_ARG_SENTINEL(),
+    };
+
+    value_bool = false;
+    value_uint32 = 0;
+
+    set_all_defaults(args);
+
+    BS_TEST_VERIFY_EQ(test_ptr, true, value_bool);
+    BS_TEST_VERIFY_EQ(test_ptr, 42, value_uint32);
+    BS_TEST_VERIFY_EQ(test_ptr, 1, value_enum);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_arg_test_parse(bs_test_t *test_ptr)
+{
+    static bool               value_bool;
+    static uint32_t           value_uint32;
+    static uint32_t           value_enum;
+    static char               *value_string = NULL;
+    static const bs_arg_t     args[] = {
+        BS_ARG_BOOL("b", "d", true, &value_bool),
+        BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum),
+        BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32),
+        BS_ARG_STRING("str", "d", "bravo", &value_string),
+        BS_ARG_SENTINEL(),
+    };
+    const char *argv[] = { "program" };
+    int argc = 1;
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv));
+    BS_TEST_VERIFY_EQ(test_ptr, true, value_bool);
+    BS_TEST_VERIFY_EQ(test_ptr, 42, value_uint32);
+    BS_TEST_VERIFY_EQ(test_ptr, 1, argc);
+    BS_TEST_VERIFY_STREQ(test_ptr, "bravo", value_string);
+    bs_arg_cleanup(args);
+
+    const char *argv1[] = {
+        "program", "x", "--nob", "--u32", "1234", "y", "--e", "bravo",
+        "--str", "charlie" };
+    argc = sizeof(argv1) / sizeof(const char*);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv1));
+    BS_TEST_VERIFY_EQ(test_ptr, false, value_bool);
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, value_uint32);
+    BS_TEST_VERIFY_EQ(test_ptr, 3, argc);
+    BS_TEST_VERIFY_STREQ(test_ptr, "x", argv1[1]);
+    BS_TEST_VERIFY_STREQ(test_ptr, "y", argv1[2]);
+    BS_TEST_VERIFY_EQ(test_ptr, 42, value_enum);
+    BS_TEST_VERIFY_STREQ(test_ptr, "charlie", value_string);
+    bs_arg_cleanup(args);
+
+    const char *argv2[] = { "program", "--u32=4321", "1234" };
+    argc = sizeof(argv2) / sizeof(const char*);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv2));
+    BS_TEST_VERIFY_EQ(test_ptr, 4321, value_uint32);
+    BS_TEST_VERIFY_EQ(test_ptr, 2, argc);
+    BS_TEST_VERIFY_STREQ(test_ptr, "1234", argv2[1]);
+    bs_arg_cleanup(args);
+
+    const char *argv3[] = { "program", "--u32" };
+    argc = sizeof(argv3) / sizeof(const char*);
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv3));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, value_string);
+
+    const char *argv4[] = { "program", "--unknown=123" };
+    argc = sizeof(argv4) / sizeof(const char*);
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv4));
+    BS_TEST_VERIFY_EQ(test_ptr, 2, argc);
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv4));
+    BS_TEST_VERIFY_EQ(test_ptr, 2, argc);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv4));
+    BS_TEST_VERIFY_EQ(test_ptr, 2, argc);
+    bs_arg_cleanup(args);
+}
+
+/* ------------------------------------------------------------------------- */
+static void bs_arg_test_check_arg(bs_test_t *test_ptr)
+{
+    static bool               value_bool;
+    static uint32_t           value_uint32;
+    static uint32_t           value_enum;
+    static char               *value_string;
+
+    // A NULL name.
+    static const bs_arg_t     args1[] = {
+        BS_ARG_BOOL(NULL, "d", true, &value_bool),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args1));
+
+    // Not starting with a-zA-Z.
+    static const bs_arg_t     args2[] = {
+        BS_ARG_BOOL(NULL, "9", true, &value_bool),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args2));
+
+    // Invalid characters.
+    static const bs_arg_t     args3[] = {
+        BS_ARG_BOOL(NULL, "a-b.", true, &value_bool),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args3));
+
+    // Duplicate names.
+    static const bs_arg_t     args4[] = {
+        BS_ARG_BOOL("b", "d", true, &value_bool),
+        BS_ARG_BOOL("b", "e", false, &value_bool),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args4));
+
+    // Duplicate names, with the boolean extension.
+    static const bs_arg_t     args5[] = {
+        BS_ARG_BOOL("b", "d", true, &value_bool),
+        BS_ARG_BOOL("nob", "e", false, &value_bool),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args5));
+
+    // All valid.
+    value_string = NULL;
+    static const bs_arg_t     valid_args[] = {
+        BS_ARG_BOOL("b", "d", true, &value_bool),
+        BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum),
+        BS_ARG_STRING("s", "d", "default", &value_string),
+        BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32),
+        BS_ARG_SENTINEL(),
+    };
+    BS_TEST_VERIFY_TRUE(test_ptr, check_arg(valid_args));
+}
+
+/* == End of arg.c ========================================================= */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/arg.h
@@ -0,0 +1,216 @@
+/* ========================================================================= */
+/**
+ * @file arg.h
+ * Permits to declare commandline flags, define defaults and a constraints,
+ * and provides parsing functions.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_ARG_H__
+#define __LIBBASE_ARG_H__
+
+#include "test.h"
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Parsing mode, whether to permit extra args or not. */
+typedef enum {
+    /** Expects that the defined args consume all of argc/argv. */
+    BS_ARG_MODE_NO_EXTRA,
+    /** Permits extra values (but nothing with a "--" prefix). */
+    BS_ARG_MODE_EXTRA_VALUES,
+    /** Permits any leftovers. */
+    BS_ARG_MODE_EXTRA_ARGS,
+} bs_arg_mode_t;
+
+/** Type to consider for the arg. */
+typedef enum {
+    /** Used for sentinel. */
+    BS_ARG_TYPE_UNDEFINED = 0,
+    /** A boolean. */
+    BS_ARG_TYPE_BOOL,
+    /** An enum, from a set of strings. */
+    BS_ARG_TYPE_ENUM,
+    /** A string value. */
+    BS_ARG_TYPE_STRING,
+    /** An unsigned 32-bit value. */
+    BS_ARG_TYPE_UINT32,
+} bs_arg_type_t;
+
+/** Holds the specification for a boolean argument. */
+typedef struct {
+    /** Default value, if not specified on the commandline. */
+    const bool                default_value;
+    /** Points to the boolean that will hold the value. */
+    bool                      *value_ptr;
+} bs_arg_bool_t;
+
+/** Lookup table for enum. */
+typedef struct {
+    /** The human-readable string of the enum. */
+    const char                *name_ptr;
+    /** Correspnding numeric value. */
+    uint32_t                  value;
+} bs_arg_enum_table_t;
+
+/** Holds the specification for a string argument. */
+typedef struct {
+    /** Default value, if not specified on the commandline. */
+    const char                *default_value;
+    /** Points to the string that will hold the value. */
+    char                      **value_ptr;
+} bs_arg_string_t;
+
+/** Holds the specification for an unsigned 32-bit argument. */
+typedef struct {
+    /** Default value, if not specified on the commandline. */
+    const uint32_t            default_value;
+    /** Minimum permitted value. Use 0 to permit all possible values. */
+    const uint32_t            min_value;
+    /** Maximum permitted value. Use UINT32_MAX to permit everything. */
+    const uint32_t            max_value;
+    /** Points to the uint32_t that will hold the value. */
+    uint32_t                  *value_ptr;
+} bs_arg_uint32_t;
+
+/** Holds the specification for an enum. */
+typedef struct {
+    /** Default value, if not specified on the commandline. */
+    const char                *default_name_ptr;
+    /** Lookup table. */
+    const bs_arg_enum_table_t *lookup_table;
+    /** Points to the uint32_t that will hold the value. */
+    uint32_t                  *value_ptr;
+} bs_arg_enum_t;
+
+/** Holds specification for one argument. */
+typedef struct {
+    /** Type of the argument. */
+    bs_arg_type_t             type;
+    /** Name of the argument. */
+    const char                *name_ptr;
+    /** Description, may be NULL. */
+    const char                *description_ptr;
+
+    /** Specification for the type. */
+    union {
+        void                  *v_sentinel;
+        bs_arg_bool_t         v_bool;
+        bs_arg_enum_t         v_enum;
+        bs_arg_string_t       v_string;
+        bs_arg_uint32_t       v_uint32;
+    } v;
+} bs_arg_t ;
+
+/** Defines a boolean argument. */
+#define BS_ARG_BOOL(_name, _desc, _default, _value_ptr) {               \
+    .type = BS_ARG_TYPE_BOOL,                                           \
+    .name_ptr = _name,                                                  \
+    .description_ptr = _desc,                                           \
+    .v = { .v_bool = {                                                  \
+        .default_value = _default,                                      \
+        .value_ptr = _value_ptr                                         \
+    } }                                                                 \
+}
+
+/** Defines an enum argument. */
+#define BS_ARG_ENUM(_name, _desc, _default, _lookup_table, _value_ptr) {\
+    .type = BS_ARG_TYPE_ENUM,                                           \
+    .name_ptr = _name,                                                  \
+    .description_ptr = _desc,                                           \
+    .v = { .v_enum = {                                                  \
+        .default_name_ptr = _default,                                   \
+        .lookup_table = _lookup_table,                                  \
+        .value_ptr = _value_ptr                                         \
+    } }                                                                 \
+}
+
+/** Defines a string argument. */
+#define BS_ARG_STRING(_name, _desc, _default, _value_ptr) {             \
+     .type = BS_ARG_TYPE_STRING,                                         \
+    .name_ptr = _name,                                                  \
+    .description_ptr = _desc,                                           \
+    .v = { .v_string = {                                                \
+        .default_value = _default,                                      \
+        .value_ptr = _value_ptr                                         \
+    } }                                                                 \
+}
+
+/** Defines an unsigned 32-bit integer argument. */
+#define BS_ARG_UINT32(_name, _desc, _default, _min, _max, _value_ptr) { \
+    .type = BS_ARG_TYPE_UINT32,                                         \
+    .name_ptr = _name,                                                  \
+    .description_ptr = _desc,                                           \
+    .v = { .v_uint32 = {                                                \
+        .default_value = _default,                                      \
+        .min_value = _min,                                              \
+        .max_value = _max,                                              \
+        .value_ptr = _value_ptr                                         \
+    } }                                                                 \
+}
+
+/** Defines a sentinel for the argument list. */
+#define BS_ARG_SENTINEL() {                     \
+    .type = BS_ARG_TYPE_UNDEFINED,              \
+    .name_ptr = NULL,                           \
+    .description_ptr = NULL,                    \
+    .v = { .v_sentinel = NULL }                 \
+}
+
+/**
+ * Parses the commandline.
+ *
+ * @param arg_ptr Specifies the array of arguments, ending in a sentinel.
+ * @param mode How to treat extra arguments.
+ * @param argc_ptr Pointer to the number of arguments.
+ * @param argv_ptr Pointer to the list of argument values. It is assumed that
+ *     argv_ptr[0] holds the program's name. It will be skipped for parsing.
+ *
+ * @return true if the parsing succeeded.
+ */
+bool bs_arg_parse(const bs_arg_t *arg_ptr, const bs_arg_mode_t mode,
+                  int *argc_ptr, const char **argv_ptr);
+
+/**
+ * Cleanup any allocated resources during parsing by |bs_arg_parse|.
+ *
+ * @param arg_ptr Specifies the array of arguments, ending in a sentinel.
+ */
+void bs_arg_cleanup(const bs_arg_t *arg_ptr);
+
+/**
+ * Prints the arguments.
+ *
+ * @param stream_ptr
+ * @param arg_ptr
+ */
+int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_arg_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_ARG_H__ */
+/* == End of arg.h ========================================================= */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/assert.h
@@ -0,0 +1,58 @@
+/* ========================================================================= */
+/**
+ * @file assert.h
+ * Assertions.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_ASSERT_H__
+#define __LIBBASE_ASSERT_H__
+
+#include "log.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(__GNUC__) && (defined(i386) || defined(__x86_64__))
+#define BS_ABORT() { __asm __volatile ( "int $3;" ); }
+#else
+#include <signal.h>
+/** Triggers an abort, ie. fatal error. */
+#define BS_ABORT() { raise(SIGTRAP); }
+#endif
+
+#if defined(BS_ASSERT)
+#undef BS_ASSERT
+#endif
+
+/** An assertion, triggers a fatal error if `_expr` is false. */
+#define BS_ASSERT(_expr) do {                                           \
+        if (!(_expr)) {                                                 \
+            bs_log(BS_FATAL, "ASSERT failed: %s", #_expr);              \
+            BS_ABORT();                                                 \
+        }                                                               \
+    } while (0)
+
+/** Asserts that _expr is not NULL, and returns the value of _expr. */
+#define BS_ASSERT_NOTNULL(_expr)                                        \
+    ({                                                                  \
+        __typeof__(_expr) __expr = (_expr);                             \
+        BS_ASSERT(NULL != __expr);                                      \
+        __expr;                                                         \
+    })
+
+#endif /* __LIBBASE_ASSERT_H__ */
+/* == End of assert.h ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/atomic.c
@@ -0,0 +1,100 @@
+/* ========================================================================= */
+/**
+ * @file atomic.c
+ *
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "atomic.h"
+#include "test.h"
+
+#if defined(__cplusplus) || defined(__clang__)
+
+const bs_test_case_t          bs_atomic_test_cases[] = {
+    { 0, NULL, NULL }
+};
+
+#else  // defined(__cplusplus) || !defined(__clang__)
+
+/* == Atomic tests ========================================================= */
+
+static void bs_atomic_test_int32(bs_test_t *test_ptr);
+static void bs_atomic_test_int64(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_atomic_test_cases[] = {
+    { 1, "int32 unit tests", bs_atomic_test_int32 },
+    { 1, "int64 unit tests", bs_atomic_test_int64 },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+void bs_atomic_test_int32(bs_test_t *test_ptr)
+{
+    bs_atomic_int32_t         a = BS_ATOMIC_INT32_INIT(42);
+    int32_t                   b;
+
+    BS_TEST_VERIFY_EQ(test_ptr, 42, bs_atomic_int32_get(&a));
+    bs_atomic_int32_set(&a, 27972);
+    BS_TEST_VERIFY_EQ(test_ptr, 27972, bs_atomic_int32_get(&a));
+    BS_TEST_VERIFY_EQ(test_ptr, 27900, bs_atomic_int32_add(&a, -72));
+    BS_TEST_VERIFY_EQ(test_ptr, 27900, bs_atomic_int32_get(&a));
+    b = 1234;
+    bs_atomic_int32_xchg(&a, &b);
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_get(&a));
+    BS_TEST_VERIFY_EQ(test_ptr, 27900, b);
+
+    // CAS, but with non-matching old_val. Must not swap.
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_cas(&a, 4321, 2222));
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_get(&a));
+
+    // CAS, with matching old_val. Must swap.
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_cas(&a, 4321, 1234));
+    BS_TEST_VERIFY_EQ(test_ptr, 4321, bs_atomic_int32_get(&a));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_atomic_test_int64(bs_test_t *test_ptr)
+{
+    bs_atomic_int64_t         a = BS_ATOMIC_INT64_INIT(0x0102030405060708);
+    int64_t                   b;
+
+    BS_TEST_VERIFY_EQ(test_ptr, 0x0102030405060708, bs_atomic_int64_get(&a));
+    bs_atomic_int64_set(&a, 0x0807060504030201);
+    BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_get(&a));
+    BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281,
+                      bs_atomic_int64_add(&a, 0x1020304050607080));
+    BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281, bs_atomic_int64_get(&a));
+    b = 1234;
+    bs_atomic_int64_xchg(&a, &b);
+    BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int64_get(&a));
+    BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281, b);
+
+    // CAS, but with non-matching old_val. Must not swap.
+    bs_atomic_int64_set(&a, 0x0807060504030201);
+    BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201,
+                      bs_atomic_int64_cas(&a, 0x1122334455667788, 2222));
+    BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_get(&a));
+
+    // CAS, with matching old_val. Must swap.
+    BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201,
+                      bs_atomic_int64_cas(&a, 0x1122334455667788,
+                                          0x0807060504030201));
+    BS_TEST_VERIFY_EQ(test_ptr, 0x1122334455667788, bs_atomic_int64_get(&a));
+}
+#endif
+
+/* == End of atomic.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/atomic.h
@@ -0,0 +1,469 @@
+/* ========================================================================= */
+/**
+ * @file atomic.h
+ * Methods for atomic access to a few basic types.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_ATOMIC_H__
+#define __LIBBASE_ATOMIC_H__
+
+#include "test.h"
+
+#include <stdint.h>
+
+#if defined(__cplusplus)
+
+// C++ (gcc) is not compatible with <stdatomic.h>. When using C++, please
+// resort to using C++ STL <atomic> definitions instead.
+//
+// Therefore, the __cplusplus #if is just an empty block.
+
+#else  // defined(__cplusplus)
+
+/* == Types and Definitions ================================================ */
+
+#if (defined(__GNUC__) || defined(__clang__)) && defined(i386)
+#define __BS_ATOMIC_GCC_ASM_i386
+#elif (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__)
+#define __BS_ATOMIC_GCC_ASM_x86_64
+
+#else
+
+/** C11 supports atomics. We expect support for at least 32-bit atomics. */
+#define __BS_ATOMIC_C11_STDATOMIC
+#include <stdatomic.h>
+
+#if defined(ATOMIC_LLONG_LOCK_FREE)
+
+/** We can use C11 definitions for 64-bit atomics. */
+#define __BS_ATOMIC_C11_STDATOMIC_INT64
+#else  // defined(ATOMIC_LLONG_LOCK_FREE)
+/** fall back to use a mutex for 64-bit atomics. */
+#define __BS_ATOMIC_INT64_MUTEX
+#endif  // defined(ATOMIC_LLONG_LOCK_FREE)
+
+#endif  //  (defined(__GNUC__) || defined(__clang__)) && (i386 || __x86_64__).
+
+#if defined(__BS_ATOMIC_INT64_MUTEX)
+#include <pthread.h>
+static pthread_mutex_t        _bs_atomic_mutex = PTHREAD_MUTEX_INITIALIZER;
+#endif  // defined(__BS_ATOMIC_INT64_MUTEX)
+
+/**
+ * Initializes a bs_atomic_int32_t to value v.
+ */
+#define BS_ATOMIC_INT32_INIT(v)  { (v) }
+
+/**
+ * Initializes a bs_atomic_int64_t to value v.
+ */
+#define BS_ATOMIC_INT64_INIT(v)  { (v) }
+
+/** An atomically accessible 32-bit integer. */
+typedef struct {
+#if defined(__BS_ATOMIC_C11_STDATOMIC)
+    /** The actual value. */
+    atomic_int_least32_t      v;
+#else  // defined (__BS_ATOMIC_C11_STDATOMIC)
+    /** The actual value. */
+    int32_t                   v;
+#endif  // defined (__BS_ATOMIC_C11_STDATOMIC)
+} bs_atomic_int32_t;
+
+/** An atomically accessible 64-bit integer. */
+typedef struct {
+#if defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+    /** The actual value . */
+    atomic_int_least64_t      v;
+#else  // defined (__BS_ATOMIC_C11_STDATOMIC_INT64)
+    /** The actual value . */
+    int64_t                   v;
+#endif  // defined (__BS_ATOMIC_C11_STDATOMIC_INT64)
+} bs_atomic_int64_t;
+
+/* == 32-bit integer ======================================================= */
+
+/* ------------------------------------------------------------------------- */
+/** Set atomic value to v. */
+static inline void bs_atomic_int32_set(bs_atomic_int32_t *a_ptr, int32_t v)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64)
+
+    // A 32-bit assignment is atomic on i386 */
+    a_ptr->v = v;
+
+#elif defined(__BS_ATOMIC_C11_STDATOMIC)
+
+    atomic_store(&a_ptr->v, v);
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/** Get value of atomic. */
+static inline int32_t bs_atomic_int32_get(bs_atomic_int32_t *a_ptr)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64)
+
+    /* A 32-bit read is atomic on i386 */
+    return a_ptr->v;
+
+#elif defined(__BS_ATOMIC_C11_STDATOMIC)
+
+    return atomic_load(&a_ptr->v);
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+
+}
+
+/* ------------------------------------------------------------------------- */
+/** Add value to atomic. */
+static inline int32_t bs_atomic_int32_add(bs_atomic_int32_t *a_ptr, int32_t v)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    int32_t old_v = v;
+    __asm__ volatile (
+        "lock; \n"
+        "xaddl %0, %1; \n"
+        : "=r" (v)
+        : "m" (a_ptr->v), "0" (v)
+        : "memory"
+        );
+    return v + old_v;
+#elif defined(__BS_ATOMIC_C11_STDATOMIC)
+
+    return atomic_fetch_add(&a_ptr->v, v) + v;
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Compare-And-Swap value with atomic.
+ *
+ * If the returned value is not equal to old_val, no exchange was done.
+ *
+ * @param a_ptr
+ * @param new_val             Value to assign to atomic.
+ * @param old_val             Value to compare for.
+ *
+ * @return value of atomic.
+ */
+static inline int32_t bs_atomic_int32_cas(bs_atomic_int32_t *a_ptr,
+                                          int32_t new_val, int32_t old_val)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    int32_t curr_val;
+    __asm__ volatile (
+        "lock; \n"
+        "cmpxchgl %2, %1; \n"
+        : "=a" (curr_val), "=m" (a_ptr->v)
+        : "r" (new_val), "m" (a_ptr->v), "0" (old_val)
+        );
+    return curr_val;
+#elif defined(__BS_ATOMIC_C11_STDATOMIC)
+
+    if (atomic_compare_exchange_strong(&a_ptr->v, &old_val, new_val)) {
+        return old_val;
+    } else {
+        return atomic_load(&a_ptr->v);
+    }
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/** Exchange value with atomic. */
+static inline void bs_atomic_int32_xchg(bs_atomic_int32_t *a_ptr,
+                                        int32_t *v_ptr)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    __asm__ volatile (
+        "lock; \n"
+        "xchgl %0, %%eax; \n"
+        : "+m" (a_ptr->v), "=a" (*v_ptr)
+        : "m" (a_ptr->v), "a" (*v_ptr)
+        );
+#elif defined(__BS_ATOMIC_C11_STDATOMIC)
+
+    *v_ptr = atomic_exchange(&a_ptr->v, *v_ptr);
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* == 64-bit integer ======================================================= */
+
+/* ------------------------------------------------------------------------- */
+/** Set atomic value to v. */
+static inline void bs_atomic_int64_set(bs_atomic_int64_t *a_ptr, int64_t v)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386)
+    __asm__ volatile (
+        "movl %%ebx, %%esi; \n"         /* backup ebx, because -fPIC */
+        "movl %%eax, %%ebx; \n"         /* v goes into ebx:ecx */
+        "movl %%edx, %%ecx; \n"
+        "1: \n"
+        "movl (%%edi), %%eax; \n"       /* load atom value into edx:eax */
+        "movl 4(%%edi), %%edx; \n"
+        "lock cmpxchg8b (%%edi); \n"
+        "jnz 1b; \n"                    /* spin until write was atomic. */
+        "movl %%esi, %%ebx; \n"
+        :
+        : "D" (&a_ptr->v), "A" (v) /* "A" is edx:eax */
+        : "ecx", "memory", "esi"
+        );
+#elif defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    __asm__ volatile (
+        "movq %1, %0; \n"
+        : "=m" (a_ptr->v)
+        : "r" (v)
+        : "memory"
+        );
+#elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+
+    atomic_store(&a_ptr->v, v);
+
+#elif defined(__BS_ATOMIC_INT64_MUTEX)
+
+    pthread_mutex_lock(&_bs_atomic_mutex);
+    a_ptr->v = v;
+    pthread_mutex_unlock(&_bs_atomic_mutex);
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/** Get value of atomic. */
+static inline int64_t bs_atomic_int64_get(bs_atomic_int64_t *a_ptr)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386)
+    int64_t rv = 0;
+    __asm __volatile (
+        "movl %%ebx, %%esi; \n"         /* backup ebx, becuase -fPIC */
+        "1: \n"
+        "movl (%%edi), %%eax; \n"       /* load atom value into edx:eax */
+        "movl 4(%%edi), %%edx; \n"
+        "movl %%eax, %%ebx; \n"         /* store in ebx:ecx for verfication */
+        "movl %%edx, %%ecx; \n"
+        "lock cmpxchg8b (%%edi); \n"
+        "jnz 1b; \n"                    /* spin until read was atomic */
+        "movl %%esi, %%ebx; \n"
+        : "=A" (rv)  /* "A" is edx:eax */
+        : "D" (&a_ptr->v)
+        : "ecx", "memory", "esi"
+        );
+    return rv;
+#elif defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    int64_t rv = 0;
+    __asm__ volatile (
+        "movq %1, %0; \n"
+        : "=r" (rv)
+        : "m" (a_ptr->v)
+        );
+    return rv;
+#elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+
+    return atomic_load(&a_ptr->v);
+
+#elif defined(__BS_ATOMIC_INT64_MUTEX)
+
+    int64_t rv;
+    pthread_mutex_lock(&_bs_atomic_mutex);
+    rv = a_ptr->v;
+    pthread_mutex_unlock(&_bs_atomic_mutex);
+    return rv;
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/** Add value to atomic. */
+static inline int64_t bs_atomic_int64_add(bs_atomic_int64_t *a_ptr, int64_t v)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386)
+    int64_t rv;
+    __asm__ volatile (
+        "pushl %%ebx; \n"               /* must backup ebx, because -fPIC */
+        "1: \n"
+
+        "movl (%%edi), %%eax; \n"       /* current atomic into edx:eax */
+        "movl 4(%%edi), %%edx; \n"
+        "movl %%eax, %%ebx; \n"         /* for verification, copy to ecx:ebx */
+        "movl %%edx, %%ecx; \n"
+        "addl (%%esi), %%ebx; \n"       /* add 'v' */
+        "adcl 4(%%esi), %%ecx; \n"
+
+        "lock cmpxchg8b (%%edi); \n"
+        "jnz 1b; \n"                    /* spin until results op was atomic */
+
+        "movl %%ebx, %%eax; \n"         /* copy result to edx:eax */
+        "movl %%ecx, %%edx; \n"
+        "popl %%ebx; \n"
+        : "=A" (rv)
+        : "D" (&a_ptr->v), "S" (&v)
+        : "ecx", "memory"
+        );
+    return rv;
+#elif defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    int64_t old_v = v;
+    __asm__ volatile (
+        "lock; \n"
+        "xaddq %0, %1; \n"
+        : "=r" (v)
+        : "m" (a_ptr->v), "0" (v)
+        : "memory"
+        );
+    return v + old_v;
+#elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+
+    return atomic_fetch_add(&a_ptr->v, v) + v;
+
+#elif defined(__BS_ATOMIC_INT64_MUTEX)
+
+    int64_t rv;
+    pthread_mutex_lock(&_bs_atomic_mutex);
+    a_ptr->v +=v;
+    rv = a_ptr->v;
+    pthread_mutex_unlock(&_bs_atomic_mutex);
+    return rv;
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Compare-And-Swap value with atomic.
+ *
+ * If the returned value is not equal to old_val, no exchange was done.
+ *
+ * @param a_ptr
+ * @param new_val             Value to assign to atomic.
+ * @param old_val             Value to compare for.
+ *
+ * @return value of atomic.
+ * */
+static inline int64_t bs_atomic_int64_cas(bs_atomic_int64_t *a_ptr,
+                                          int64_t new_val, int64_t old_val)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386)
+    int64_t rv;
+    __asm __volatile (
+        "movl 4(%%esi), %%ecx; \n"
+        "movl (%%esi), %%esi; \n"
+        "xchgl %%esi, %%ebx; \n"
+        "lock cmpxchg8b (%%edi); \n"
+        "xchgl %%esi, %%ebx; \n"
+        : "=A" (rv)
+        : "D" (&a_ptr->v), "A" (old_val), "S" (&new_val)
+        : "ecx", "memory"
+        );
+    return rv;
+#elif defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    int64_t curr_val;
+    __asm__ volatile (
+        "lock;  \n"
+        "cmpxchgq %2, %1; \n"
+        : "=a" (curr_val), "=m" (a_ptr->v)
+        : "r" (new_val), "m" (a_ptr->v), "0" (old_val)
+        : "memory"
+        );
+    return curr_val;
+#elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+
+    if (atomic_compare_exchange_strong(&a_ptr->v, &old_val, new_val)) {
+        return old_val;
+    } else {
+        return atomic_load(&a_ptr->v);
+    }
+
+#elif defined(__BS_ATOMIC_INT64_MUTEX)
+
+    int64_t rv;
+    pthread_mutex_lock(&_bs_atomic_mutex);
+    if (a_ptr->v == old_val) {
+        a_ptr->v = new_val;
+        rv = old_val;
+    } else {
+        rv = a_ptr->v;
+    }
+    pthread_mutex_unlock(&_bs_atomic_mutex);
+    return rv;
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+/* ------------------------------------------------------------------------- */
+/** Exchange value with atomic. */
+static inline void bs_atomic_int64_xchg(bs_atomic_int64_t *a_ptr,
+                                        int64_t *v_ptr)
+{
+#if defined(__BS_ATOMIC_GCC_ASM_i386)
+    // Exchange using CAS.
+    int64_t curr_value;
+    do {
+        curr_value = bs_atomic_int64_get(a_ptr);
+    } while (curr_value != bs_atomic_int64_cas(a_ptr, *v_ptr, curr_value));
+    *v_ptr = curr_value;
+#elif defined(__BS_ATOMIC_GCC_ASM_x86_64)
+    __asm__ volatile (
+        "lock; \n"
+        "xchgq %0, %%rax; \n"
+        : "+m" (a_ptr->v), "=a" (*v_ptr)
+        : "m" (a_ptr->v), "a" (*v_ptr)
+        );
+#elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64)
+
+    *v_ptr = atomic_exchange(&a_ptr->v, *v_ptr);
+
+#elif defined(__BS_ATOMIC_INT64_MUTEX)
+
+    pthread_mutex_lock(&_bs_atomic_mutex);
+    int64_t tmp = a_ptr->v;
+    a_ptr->v = *v_ptr;
+    *v_ptr = tmp;
+    pthread_mutex_unlock(&_bs_atomic_mutex);
+
+#else
+#error "Unsupported compiler or architecture."
+#endif
+}
+
+#endif  // defined(__cplusplus)
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_atomic_test_cases[];
+
+#endif /* __LIBBASE_ATOMIC_H__ */
+/* == End of atomic.h ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/avltree.c
@@ -0,0 +1,1033 @@
+/* ========================================================================= */
+/**
+ * @file avltree.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "avltree.h"
+#include "assert.h"
+
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+
+/* == Declarations ========================================================= */
+
+/** @private State of the AVL tree. */
+struct _bs_avltree_t {
+    /** Pointer to the root node. NULL indicates the tree is empty. */
+    bs_avltree_node_t         *root_ptr;
+    /** Number of nodes stored in this tree. */
+    size_t                    nodes;
+    /** Points to the method for comparing nodes. */
+    bs_avltree_node_cmp_t     cmp;
+    /** Points to the method for destroying a node. May be NULL. */
+    bs_avltree_node_destroy_t destroy;
+};
+
+static void bs_avltree_flush(bs_avltree_t *tree_ptr);
+static void bs_avltree_node_replace(bs_avltree_t *tree_ptr,
+                                    bs_avltree_node_t *old_node_ptr,
+                                    bs_avltree_node_t *new_node_ptr);
+static size_t bs_avltree_node_size(bs_avltree_t *tree_ptr,
+                                   bs_avltree_node_t *node_ptr);
+static size_t bs_avltree_node_height(bs_avltree_t *tree_ptr,
+                                     bs_avltree_node_t *node_ptr);
+static void bs_avltree_node_exchange(bs_avltree_t *tree_ptr,
+                                     bs_avltree_node_t *old_node_ptr,
+                                     bs_avltree_node_t *new_node_ptr);
+static void bs_avltree_node_delete(bs_avltree_t *tree_ptr,
+                                   bs_avltree_node_t *node_ptr);
+
+static bool bs_avltree_rot_right(bs_avltree_t *tree_ptr,
+                                 bs_avltree_node_t *node_ptr);
+static bool bs_avltree_rot_left(bs_avltree_t *tree_ptr,
+                                bs_avltree_node_t *node_ptr);
+static void bs_avltree_verify_node(
+    bs_avltree_t *tree_ptr,
+    const void *(*key_from_node)(const bs_avltree_node_t *node_ptr),
+    bs_avltree_node_t *node_ptr,
+    const void *min_key_ptr,
+    const void *max_key_ptr);
+
+static bs_avltree_node_t *bs_avltree_node_min(bs_avltree_node_t *node_ptr);
+static bs_avltree_node_t *bs_avltree_node_max(bs_avltree_node_t *node_ptr);
+
+/* == Exported Functions =================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_t *bs_avltree_create(bs_avltree_node_cmp_t cmp,
+                                bs_avltree_node_destroy_t destroy)
+{
+    bs_avltree_t              *tree_ptr;
+
+    tree_ptr = calloc(1, sizeof(bs_avltree_t));
+    if (NULL == tree_ptr) return NULL;
+
+    tree_ptr->cmp = cmp;
+    tree_ptr->destroy = destroy;
+    return tree_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_avltree_destroy(bs_avltree_t *tree_ptr)
+{
+    bs_avltree_flush(tree_ptr);
+
+    free(tree_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_lookup(bs_avltree_t *tree_ptr,
+                                     const void *key_ptr)
+{
+    bs_avltree_node_t         *node_ptr;
+    int                       cmp_rv;
+
+    node_ptr = tree_ptr->root_ptr;
+    while (NULL != node_ptr) {
+        cmp_rv = tree_ptr->cmp(node_ptr, key_ptr);
+        if (0 == cmp_rv) {
+            return node_ptr;
+        } else if (0 < cmp_rv) {
+            /* node larger than key. proceed on the left. */
+            node_ptr = node_ptr->left_ptr;
+        } else {
+            /* node less than key. proceed on the right. */
+            node_ptr = node_ptr->right_ptr;
+        }
+    }
+
+    return NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_avltree_insert(bs_avltree_t *tree_ptr,
+                       const void *new_key_ptr,
+                       bs_avltree_node_t *new_node_ptr,
+                       bool do_overwrite)
+{
+    bs_avltree_node_t         *node_ptr;
+    bs_avltree_node_t         *parent_ptr;
+    bs_avltree_node_t         *non_balanced_ptr;
+    int                       cmp_rv;
+
+    parent_ptr = NULL;
+    non_balanced_ptr = NULL;
+    node_ptr = tree_ptr->root_ptr;
+
+    /* walk tree to appropriate bottom node. */
+    parent_ptr = NULL;
+    while (NULL != node_ptr) {
+
+        /* keep track of current node and of last non-balanced node */
+        parent_ptr = node_ptr;
+        if (0 != node_ptr->balance) non_balanced_ptr = node_ptr;
+
+        cmp_rv = tree_ptr->cmp(node_ptr, new_key_ptr);
+        if (0 == cmp_rv) {
+            /* node equals new node. replace, but only if requested. */
+            if (!do_overwrite) return false;
+            bs_avltree_node_replace(tree_ptr, node_ptr, new_node_ptr);
+            return true;
+        } else if (0 < cmp_rv) {
+            /* node greater than new node. proceed on the left. */
+            node_ptr = node_ptr->left_ptr;
+        } else if (0 > cmp_rv) {
+            /* node less than new node. proceed on the right. */
+            node_ptr = node_ptr->right_ptr;
+        }
+    }
+
+    /* attach new node to bottom node */
+    tree_ptr->nodes++;
+    new_node_ptr->parent_ptr = parent_ptr;
+    if (NULL == new_node_ptr->parent_ptr) {
+        /* was an empty tree, all fine. */
+        tree_ptr->root_ptr = new_node_ptr;
+        return true;
+    }
+
+    /* attach to correct side of parent node */
+    if (0 < cmp_rv) {
+        parent_ptr->left_ptr = new_node_ptr;
+    } else {
+        parent_ptr->right_ptr = new_node_ptr;
+    }
+
+    /* walk up along all balanced parents. adjust balance accordingly. */
+    node_ptr = new_node_ptr;
+    while (parent_ptr != non_balanced_ptr) {
+        if (parent_ptr->left_ptr == node_ptr) {
+            parent_ptr->balance--;
+        } else {
+            parent_ptr->balance++;
+        }
+
+        node_ptr = parent_ptr;
+        parent_ptr = node_ptr->parent_ptr;
+    }
+
+    /* return, if entire path was in full balance */
+    if (NULL == non_balanced_ptr) return true;
+
+    /* tree was not balanced. adjust non-balanced node. */
+    if (non_balanced_ptr->left_ptr == node_ptr) {
+        non_balanced_ptr->balance--;
+        if (-2 >= non_balanced_ptr->balance) {
+            if (0 < node_ptr->balance) {
+                bs_avltree_rot_left(tree_ptr, node_ptr);
+            }
+            bs_avltree_rot_right(tree_ptr, non_balanced_ptr);
+        }
+    } else {
+        non_balanced_ptr->balance++;
+        if (2 <= non_balanced_ptr->balance) {
+            if (0 > node_ptr->balance) {
+                bs_avltree_rot_right(tree_ptr, node_ptr);
+            }
+            bs_avltree_rot_left(tree_ptr, non_balanced_ptr);
+        }
+    }
+
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_delete(bs_avltree_t *tree_ptr,
+                                     const void *key_ptr) {
+    bs_avltree_node_t *node_ptr;
+
+    node_ptr = bs_avltree_lookup(tree_ptr, key_ptr);
+    if (NULL == node_ptr) {
+        return NULL;
+    }
+
+    bs_avltree_node_delete(tree_ptr, node_ptr);
+    return node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+size_t bs_avltree_size(const bs_avltree_t *tree_ptr)
+{
+    return tree_ptr->nodes;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_min(bs_avltree_t *tree_ptr)
+{
+    bs_avltree_node_t         *node_ptr;
+
+    node_ptr = tree_ptr->root_ptr;
+    if (NULL == node_ptr) {
+        return NULL;
+    }
+    return bs_avltree_node_min(node_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_max(bs_avltree_t *tree_ptr)
+{
+    bs_avltree_node_t         *node_ptr;
+
+    node_ptr = tree_ptr->root_ptr;
+    if (NULL == node_ptr) {
+        return NULL;
+    }
+    return bs_avltree_node_max(node_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_node_next(__UNUSED__ bs_avltree_t *tree_ptr,
+                                        bs_avltree_node_t *node_ptr)
+{
+    if (NULL != node_ptr->right_ptr) {
+        node_ptr = node_ptr->right_ptr;
+
+        while (NULL != node_ptr->left_ptr) {
+            node_ptr = node_ptr->left_ptr;
+        }
+
+        return node_ptr;
+    }
+
+    while ((NULL != node_ptr->parent_ptr) &&
+           (node_ptr == node_ptr->parent_ptr->right_ptr)) {
+        node_ptr = node_ptr->parent_ptr;
+    }
+    return node_ptr->parent_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_node_t *bs_avltree_node_prev(__UNUSED__ bs_avltree_t *tree_ptr,
+                                        bs_avltree_node_t *node_ptr)
+{
+    if (NULL != node_ptr->left_ptr) {
+        node_ptr = node_ptr->left_ptr;
+
+        while (NULL != node_ptr->right_ptr) {
+            node_ptr = node_ptr->right_ptr;
+        }
+
+        return node_ptr;
+    }
+
+    while ((NULL != node_ptr->parent_ptr) &&
+           (node_ptr == node_ptr->parent_ptr->left_ptr)) {
+        node_ptr = node_ptr->parent_ptr;
+    }
+    return node_ptr->parent_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+int bs_avltree_cmp_ptr(const void *node_key_ptr,
+                       const void *key_ptr)
+{
+    if (node_key_ptr < key_ptr) {
+        return -1;
+    } else if (node_key_ptr > key_ptr) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+/* == Local methods ======================================================== */
+
+/* ------------------------------------------------------------------------- */
+/** Will destroy (if given) each of the nodes. */
+void bs_avltree_flush(bs_avltree_t *tree_ptr)
+{
+    bs_avltree_node_t         *node_ptr;
+    bs_avltree_node_t         *parent_ptr;
+
+    node_ptr = tree_ptr->root_ptr;
+    while (NULL != node_ptr) {
+
+        /* walk tree downwards as far as possible */
+        if (NULL != node_ptr->left_ptr) {
+            node_ptr = node_ptr->left_ptr;
+            continue;
+        }
+        if (NULL != node_ptr->right_ptr) {
+            node_ptr = node_ptr->right_ptr;
+            continue;
+        }
+
+        /* store parent node, then clear bottom-most node */
+        parent_ptr = node_ptr->parent_ptr;
+        memset(node_ptr, 0, sizeof(bs_avltree_node_t));
+        if (NULL != tree_ptr->destroy) {
+            tree_ptr->destroy(node_ptr);
+        }
+        tree_ptr->nodes--;
+
+        /* if the parent is NULL, we just flushed the root node. all done. */
+        if (NULL == parent_ptr) break;
+
+        /* in parent: mark the just-deleted node as gone */
+        if (parent_ptr->left_ptr == node_ptr) {
+            parent_ptr->left_ptr = NULL;
+        } else {
+            parent_ptr->right_ptr = NULL;
+        }
+
+        node_ptr = parent_ptr;
+    }
+
+    tree_ptr->root_ptr = NULL;
+    BS_ASSERT(0 == tree_ptr->nodes);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Replaces the node at |old_node_ptr| with |new_node_ptr| in the |tree_ptr|.
+ */
+void bs_avltree_node_replace(bs_avltree_t *tree_ptr,
+                             bs_avltree_node_t *old_node_ptr,
+                             bs_avltree_node_t *new_node_ptr)
+{
+    memcpy(new_node_ptr, old_node_ptr, sizeof(bs_avltree_node_t));
+    memset(old_node_ptr, 0, sizeof(bs_avltree_node_t));
+    if (NULL != tree_ptr->destroy) {
+        tree_ptr->destroy(old_node_ptr);
+    }
+
+    /* update parent's (respectively root's) child node */
+    if (NULL == new_node_ptr->parent_ptr) {
+        tree_ptr->root_ptr = new_node_ptr;
+    } else {
+        if (new_node_ptr->parent_ptr->left_ptr == old_node_ptr) {
+            new_node_ptr->parent_ptr->left_ptr = new_node_ptr;
+        } else {
+            new_node_ptr->parent_ptr->right_ptr = new_node_ptr;
+        }
+    }
+
+    /* update children's parent node */
+    if (NULL != new_node_ptr->left_ptr) {
+        new_node_ptr->left_ptr->parent_ptr = new_node_ptr;
+    }
+    if (NULL != new_node_ptr->right_ptr) {
+        new_node_ptr->right_ptr->parent_ptr = new_node_ptr;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+/** Returns the size of the subtree rooted at |node_ptr|. */
+size_t bs_avltree_node_size(__UNUSED__ bs_avltree_t *tree_ptr,
+                            bs_avltree_node_t *node_ptr) {
+    if (NULL == node_ptr) return 0;
+
+    return 1 +
+        bs_avltree_node_size(tree_ptr, node_ptr->left_ptr) +
+        bs_avltree_node_size(tree_ptr, node_ptr->right_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Returns the height of the subtree rooted at |node_ptr|. */
+size_t bs_avltree_node_height(__UNUSED__ bs_avltree_t *tree_ptr,
+                              bs_avltree_node_t *node_ptr) {
+    size_t size_left = 0, size_right = 0;
+    if (NULL != node_ptr->left_ptr) {
+        size_left = bs_avltree_node_height(tree_ptr, node_ptr->left_ptr);
+    }
+    if (NULL != node_ptr->right_ptr) {
+        size_right = bs_avltree_node_height(tree_ptr, node_ptr->right_ptr);
+    }
+    return size_left > size_right ? size_left + 1 : size_right + 1;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Exchanges the position of |node1_ptr| and |node2_ptr| in the tree.
+ *
+ * @param tree_ptr
+ * @param node1_ptr
+ * @param node2_ptr
+ *
+ * @note This will not maintain the AVL tree integrity.
+ */
+void bs_avltree_node_exchange(bs_avltree_t *tree_ptr,
+                              bs_avltree_node_t *node1_ptr,
+                              bs_avltree_node_t *node2_ptr)
+{
+    bs_avltree_node_t         tmp_node;
+    int                       pos1, pos2;
+
+    /* Store relative position of the parents. We want to rebuild that. */
+    if (NULL == node1_ptr->parent_ptr) {
+        pos1 = 0;
+    } else if (node1_ptr->parent_ptr->left_ptr == node1_ptr) {
+        pos1 = -1;
+    } else {
+        BS_ASSERT(node1_ptr->parent_ptr->right_ptr == node1_ptr);
+        pos1 = 1;
+    }
+
+    if (NULL == node2_ptr->parent_ptr) {
+        pos2 = 0;
+    } else if (node2_ptr->parent_ptr->left_ptr == node2_ptr) {
+        pos2 = -1;
+    } else {
+        BS_ASSERT(node2_ptr->parent_ptr->right_ptr == node2_ptr);
+        pos2 = 1;
+    }
+
+    memcpy(&tmp_node, node1_ptr, sizeof(bs_avltree_node_t));
+    memcpy(node1_ptr, node2_ptr, sizeof(bs_avltree_node_t));
+    memcpy(node2_ptr, &tmp_node, sizeof(bs_avltree_node_t));
+
+    /* Catch the case where we just exchanged direct neighbors */
+    if (node1_ptr->parent_ptr == node1_ptr) {
+        node1_ptr->parent_ptr = node2_ptr;
+    }
+    if (node1_ptr->left_ptr == node1_ptr) {
+        node1_ptr->left_ptr = node2_ptr;
+    }
+    if (node1_ptr->right_ptr == node1_ptr) {
+        node1_ptr->right_ptr = node2_ptr;
+    }
+    if (node2_ptr->parent_ptr == node2_ptr) {
+        node2_ptr->parent_ptr = node1_ptr;
+    }
+    if (node2_ptr->left_ptr == node2_ptr) {
+        node2_ptr->left_ptr = node1_ptr;
+    }
+    if (node2_ptr->right_ptr == node2_ptr) {
+        node2_ptr->right_ptr = node1_ptr;
+    }
+
+    /* update children's parent node(s) */
+    if (NULL != node1_ptr->left_ptr) {
+        node1_ptr->left_ptr->parent_ptr = node1_ptr;
+    }
+    if (NULL != node1_ptr->right_ptr) {
+        node1_ptr->right_ptr->parent_ptr = node1_ptr;
+    }
+
+    if (NULL != node2_ptr->left_ptr) {
+        node2_ptr->left_ptr->parent_ptr = node2_ptr;
+    }
+    if (NULL != node2_ptr->right_ptr) {
+        node2_ptr->right_ptr->parent_ptr = node2_ptr;
+    }
+
+    /* update parent's child nodes */
+    if (0 == pos2) {
+        tree_ptr->root_ptr = node1_ptr;
+    } else if (0 > pos2) {
+        node1_ptr->parent_ptr->left_ptr = node1_ptr;
+    } else {
+        node1_ptr->parent_ptr->right_ptr = node1_ptr;
+    }
+
+    if (0 == pos1) {
+        tree_ptr->root_ptr = node2_ptr;
+    } else if (0 > pos1) {
+        node2_ptr->parent_ptr->left_ptr = node2_ptr;
+    } else {
+        node2_ptr->parent_ptr->right_ptr = node2_ptr;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_avltree_node_delete(bs_avltree_t *tree_ptr,
+                            bs_avltree_node_t *node_ptr)
+{
+    bs_avltree_node_t *next_larger_node_ptr = NULL;
+
+    if ((NULL != node_ptr->left_ptr) && (NULL != node_ptr->right_ptr)) {
+        // Node with two children. Replace with the min of the right-hand-side
+        // subtree.
+        next_larger_node_ptr = bs_avltree_node_min(node_ptr->right_ptr);
+        bs_avltree_node_exchange(tree_ptr, node_ptr, next_larger_node_ptr);
+    }
+
+    // Being here: |node_ptr| has either no or one child(ren). If there is any
+    // child, swap it with that one. Then, |node_ptr| is a leaf and easily
+    // detached.
+    bs_avltree_node_t *leaf_node_ptr =
+        (node_ptr->left_ptr != NULL) ? node_ptr->left_ptr : node_ptr->right_ptr;
+    if (NULL != leaf_node_ptr) {
+        // swap it with the one to the right.
+        bs_avltree_node_exchange(tree_ptr, node_ptr, leaf_node_ptr);
+    }
+
+    if (NULL == node_ptr->parent_ptr) {
+        tree_ptr->root_ptr = NULL;
+        tree_ptr->nodes--;
+        memset(node_ptr, 0, sizeof(bs_avltree_node_t));
+        return;
+    }
+
+    // Now we need to start backtracing...
+    bs_avltree_node_t *parent_ptr = node_ptr->parent_ptr;
+    bool removed_left = parent_ptr->left_ptr == node_ptr;
+
+    // Now: Actually remove the node.
+    if (removed_left) {
+        parent_ptr->left_ptr = NULL;
+    } else {
+        parent_ptr->right_ptr = NULL;
+    }
+    memset(node_ptr, 0, sizeof(bs_avltree_node_t));
+
+    do {
+        // Adjust balance according to which side the removal happened.
+        if (removed_left) {
+            parent_ptr->balance++;
+        } else {
+            parent_ptr->balance--;
+        }
+
+        if (parent_ptr->balance == 0) {
+            node_ptr = parent_ptr;
+            parent_ptr = node_ptr->parent_ptr;
+            if (NULL != parent_ptr) {
+                removed_left = parent_ptr->left_ptr == node_ptr;
+            }
+            continue;
+        }
+
+        if (parent_ptr->balance == -2) {  // Leaning left.
+            BS_ASSERT(parent_ptr->left_ptr != NULL);
+            if (parent_ptr->left_ptr->balance > 0) {
+                bs_avltree_rot_left(tree_ptr, parent_ptr->left_ptr);
+                bs_avltree_rot_right(tree_ptr, parent_ptr);
+            } else {
+                BS_ASSERT(parent_ptr->left_ptr->left_ptr != NULL);
+                if (bs_avltree_rot_right(tree_ptr, parent_ptr)) {
+                    break;
+                }
+            }
+        } else if (parent_ptr->balance == 2) {  // Leaning right.
+            BS_ASSERT(parent_ptr->right_ptr != NULL);
+            if (parent_ptr->right_ptr->balance < 0) {
+                bs_avltree_rot_right(tree_ptr, parent_ptr->right_ptr);
+                bs_avltree_rot_left(tree_ptr, parent_ptr);
+            } else {
+                BS_ASSERT(parent_ptr->right_ptr->right_ptr != NULL);
+                if (bs_avltree_rot_left(tree_ptr, parent_ptr)) {
+                    break;
+                }
+            }
+        } else {
+            // Balance is only one off, no furter re-tracing needed.
+            break;
+        }
+
+        node_ptr = parent_ptr->parent_ptr;
+        parent_ptr = node_ptr->parent_ptr;
+        if (NULL != parent_ptr) {
+            removed_left = parent_ptr->left_ptr == node_ptr;
+        }
+    } while (NULL != parent_ptr);
+
+    tree_ptr->nodes--;
+}
+
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Rotate tree right around node_ptr (D):
+ *
+ *     \?/         \?/
+ *      D           B
+ *     / \         / \  *
+ *    B   E  ==>  A   D
+ *   / \             / \  *
+ *  A   C           C   E
+ *
+ * @return Whether this changed the subtree's height.
+ */
+bool bs_avltree_rot_right(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr)
+{
+    bs_avltree_node_t         *left_ptr;
+
+    left_ptr = node_ptr->left_ptr;
+    bool changed_height = 0 == left_ptr->balance;
+
+    node_ptr->balance++;
+    if (0 > left_ptr->balance) {
+        node_ptr->balance -= left_ptr->balance;
+    }
+
+    left_ptr->balance++;
+    if (0 < node_ptr->balance) {
+        left_ptr->balance += node_ptr->balance;
+    }
+
+    /* establish D <-> C */
+    node_ptr->left_ptr = left_ptr->right_ptr;
+    if (NULL != node_ptr->left_ptr) {
+        node_ptr->left_ptr->parent_ptr = node_ptr;
+    }
+
+    /* establish B to D's parent */
+    left_ptr->parent_ptr = node_ptr->parent_ptr;
+    if (NULL != left_ptr->parent_ptr) {
+        if (left_ptr->parent_ptr->left_ptr == node_ptr) {
+            left_ptr->parent_ptr->left_ptr = left_ptr;
+        } else {
+            left_ptr->parent_ptr->right_ptr = left_ptr;
+        }
+    } else {
+        tree_ptr->root_ptr = left_ptr;
+    }
+
+    /* B -> D */
+    left_ptr->right_ptr = node_ptr;
+    node_ptr->parent_ptr = left_ptr;
+    return changed_height;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Rotate tree left around node_ptr (B):
+ *
+ *    \?/           \?/
+ *     B             D
+ *    / \           / \
+ *   A   D   ==>   B   E
+ *      / \       / \
+ *     C   E     A   C
+ *
+ * @return Whether this changed the subtree's height.
+ */
+bool bs_avltree_rot_left(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr)
+{
+    bs_avltree_node_t         *right_ptr;
+
+    right_ptr = node_ptr->right_ptr;
+    bool changed_height = 0 == right_ptr->balance;
+
+    node_ptr->balance--;
+    if (0 < right_ptr->balance) {
+        node_ptr->balance -= right_ptr->balance;
+    }
+
+    right_ptr->balance--;
+    if (0 > node_ptr->balance) {
+        right_ptr->balance += node_ptr->balance;
+    }
+
+    /* establish B <-> C */
+    node_ptr->right_ptr = right_ptr->left_ptr;
+    if (NULL != right_ptr->left_ptr) {
+        right_ptr->left_ptr->parent_ptr = node_ptr;
+    }
+
+    /* establish D to B's parent */
+    right_ptr->parent_ptr = node_ptr->parent_ptr;
+    if (NULL != right_ptr->parent_ptr) {
+        if (right_ptr->parent_ptr->left_ptr == node_ptr) {
+            right_ptr->parent_ptr->left_ptr = right_ptr;
+        } else {
+            right_ptr->parent_ptr->right_ptr = right_ptr;
+        }
+    } else {
+        tree_ptr->root_ptr = right_ptr;
+    }
+
+    /* D -> B */
+    right_ptr->left_ptr = node_ptr;
+    node_ptr->parent_ptr = right_ptr;
+    return changed_height;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Verifies the tree's consistency.
+ */
+void bs_avltree_verify_node(
+    bs_avltree_t *tree_ptr,
+    const void *(*key_from_node)(const bs_avltree_node_t *node_ptr),
+    bs_avltree_node_t *node_ptr,
+    const void *min_key_ptr,
+    const void *max_key_ptr)
+{
+    const void* key_ptr;
+
+    if (node_ptr == NULL) return;
+
+    if (node_ptr->parent_ptr == NULL) {
+        BS_ASSERT(tree_ptr->root_ptr == node_ptr);
+    } else {
+        BS_ASSERT((node_ptr == node_ptr->parent_ptr->left_ptr) ||
+                  (node_ptr == node_ptr->parent_ptr->right_ptr));
+    }
+
+    if (NULL != min_key_ptr) {
+        BS_ASSERT(tree_ptr->cmp(node_ptr, min_key_ptr) > 0);
+    }
+    if (NULL != max_key_ptr) {
+        BS_ASSERT(tree_ptr->cmp(node_ptr, max_key_ptr) < 0);
+    }
+
+    if (NULL != key_from_node) {
+        key_ptr = key_from_node(node_ptr);
+    } else {
+        key_ptr = NULL;
+    }
+
+    int height_left = 0, height_right = 0;
+
+    if (NULL != node_ptr->left_ptr) {
+        BS_ASSERT(node_ptr->left_ptr->parent_ptr == node_ptr);
+        bs_avltree_verify_node(tree_ptr,
+                               key_from_node,
+                               node_ptr->left_ptr,
+                               min_key_ptr,
+                               key_ptr);
+        height_left = bs_avltree_node_height(tree_ptr, node_ptr->left_ptr);
+    }
+    if (NULL != node_ptr->right_ptr) {
+        BS_ASSERT(node_ptr->right_ptr->parent_ptr == node_ptr);
+        bs_avltree_verify_node(tree_ptr,
+                               key_from_node,
+                               node_ptr->right_ptr,
+                               key_ptr,
+                               max_key_ptr);
+        height_right = bs_avltree_node_height(tree_ptr, node_ptr->right_ptr);
+    }
+
+    BS_ASSERT(node_ptr->balance == height_right - height_left);
+
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * @param node_ptr
+ *
+ * @return The minimum node of the given subtree.
+ */
+bs_avltree_node_t *bs_avltree_node_min(bs_avltree_node_t *node_ptr)
+{
+    if (NULL == node_ptr->left_ptr) {
+        return node_ptr;
+    }
+    return bs_avltree_node_min(node_ptr->left_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * @param node_ptr
+ *
+ * @return The minimum node of the given subtree.
+ */
+bs_avltree_node_t *bs_avltree_node_max(bs_avltree_node_t *node_ptr)
+{
+    if (NULL == node_ptr->right_ptr) {
+        return node_ptr;
+    }
+    return bs_avltree_node_max(node_ptr->right_ptr);
+}
+
+/* == Test Functions ======================================================= */
+/** @cond TEST */
+
+#define BS_AVLTREE_TEST_VALUES          4096
+#define BS_AVLTREE_TEST_VALUE_MAX       3500
+
+/** @internal */
+typedef struct {
+    bs_avltree_node_t         node;
+    int                       value;
+} bs_avltree_test_node_t;
+
+
+static bs_avltree_test_node_t *bs_avltree_test_node_create(int value);
+static void bs_avltree_test_node_destroy(bs_avltree_node_t *node_ptr);
+static int bs_avltree_test_node_cmp(const bs_avltree_node_t *node_ptr,
+                                    const void *key_ptr);
+static const void *bs_avltree_test_node_key(const bs_avltree_node_t *node_ptr);
+
+static void bs_avltree_test_random(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_avltree_test_cases[] = {
+    { 1, "random", bs_avltree_test_random },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+void bs_avltree_test_random(bs_test_t *test_ptr)
+{
+    char                      test_flag[BS_AVLTREE_TEST_VALUE_MAX];
+    int                       random_values[BS_AVLTREE_TEST_VALUES];
+    bs_avltree_test_node_t    *node_ptr;
+    bs_avltree_t              *tree_ptr;
+    int                       value_idx, value;
+    int                       min_value, max_value;
+    int                       outcome, expected_outcome;
+    bool                      do_overwrite;
+    size_t                    nodes;
+
+    /* setup BS_AVLTREE_TEST_NUM random values. Multiple occurrences OK */
+    srand(12345);
+    memset(&random_values, 0, sizeof(random_values));
+    max_value = 0;
+    min_value = BS_AVLTREE_TEST_VALUE_MAX;
+    for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) {
+        value = rand() % BS_AVLTREE_TEST_VALUE_MAX;
+        random_values[value_idx] = value;
+        if (min_value > value) min_value = value;
+        if (max_value < value) max_value = value;
+    }
+
+    /* build tree. should better work */
+    tree_ptr = bs_avltree_create(bs_avltree_test_node_cmp,
+                                 bs_avltree_test_node_destroy);
+    BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr, NULL);
+
+    /* run through all random values and add each one to the tree */
+    nodes = 0;
+    memset(&test_flag, 0, sizeof(test_flag));
+    for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) {
+        value = random_values[value_idx];
+
+        /* setup node */
+        node_ptr = bs_avltree_test_node_create(value);
+        node_ptr->value = value;
+        BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr, NULL);
+        if (NULL == node_ptr) break;
+
+        /* specific handling for multiple occurrences */
+        switch (test_flag[value]) {
+        case 0:
+            /* first attempt: must insert */
+            expected_outcome = true;
+            nodes++;
+            do_overwrite = false;
+            break;
+
+        case 1:
+            /* second atttempt: overwrite = false, must reject */
+            expected_outcome = false;
+            do_overwrite = false;
+            break;
+
+        default:
+            /* third+ attempt(s): overwrite = true, must insert */
+            expected_outcome = true;
+            do_overwrite = true;
+            break;
+        }
+        test_flag[value]++;
+
+        /* insert and verify outcome */
+        outcome = bs_avltree_insert(
+            tree_ptr, &node_ptr->value, &node_ptr->node, do_overwrite);
+
+        BS_TEST_VERIFY_EQ(test_ptr, outcome, expected_outcome);
+        BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr->root_ptr, NULL);
+        BS_TEST_VERIFY_EQ(test_ptr, tree_ptr->nodes, nodes);
+
+        BS_ASSERT(tree_ptr->nodes == bs_avltree_node_size(
+                      tree_ptr, tree_ptr->root_ptr));
+        bs_avltree_verify_node(tree_ptr,
+                               bs_avltree_test_node_key,
+                               tree_ptr->root_ptr,
+                               NULL,
+                               NULL);
+
+        /* clean up if node was rejected */
+        if (!outcome) {
+            bs_avltree_test_node_destroy(&node_ptr->node);
+        }
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, nodes, bs_avltree_size(tree_ptr));
+
+    /* some lookup operations */
+    for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) {
+        value = random_values[value_idx];
+
+        node_ptr = (bs_avltree_test_node_t*)
+            bs_avltree_lookup(tree_ptr, &value);
+
+        BS_TEST_VERIFY_NEQ(test_ptr, node_ptr, NULL);
+        if (NULL != node_ptr) {
+            BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, value);
+        }
+    }
+
+    /* Verify min & max operations */
+    node_ptr = (bs_avltree_test_node_t*)bs_avltree_min(tree_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, min_value);
+    node_ptr = (bs_avltree_test_node_t*)bs_avltree_max(tree_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, max_value);
+
+    /* Step through the tree, verify it is strictly monotously increasing */
+    value = 0;
+    node_ptr = (bs_avltree_test_node_t*)bs_avltree_min(tree_ptr);
+    do {
+        while ((value < BS_AVLTREE_TEST_VALUE_MAX) &&
+               (0 == test_flag[value])) {
+            value++;
+        }
+        BS_TEST_VERIFY_EQ(test_ptr, value, node_ptr->value);
+
+        node_ptr = (bs_avltree_test_node_t*)
+            bs_avltree_node_next(tree_ptr, &node_ptr->node);
+        value++;
+    } while (NULL != node_ptr);
+
+    while ((value < BS_AVLTREE_TEST_VALUE_MAX) &&
+           (0 == test_flag[value])) {
+        value++;
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, value, BS_AVLTREE_TEST_VALUE_MAX);
+
+    /* deletion operation. Remove half the nodes, the others to flush */
+    for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) {
+        value = random_values[value_idx];
+
+        node_ptr = (bs_avltree_test_node_t*)
+            bs_avltree_delete(tree_ptr, &value);
+        if (node_ptr != NULL) {
+            BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, value);
+            bs_avltree_test_node_destroy(&node_ptr->node);
+        }
+
+        BS_ASSERT(tree_ptr->nodes == bs_avltree_node_size(tree_ptr, tree_ptr->root_ptr));
+        bs_avltree_verify_node(tree_ptr,
+                               bs_avltree_test_node_key,
+                               tree_ptr->root_ptr,
+                               NULL,
+                               NULL);
+    }
+
+    /* Attempt to delete a non-existing node. */
+    value = BS_AVLTREE_TEST_VALUE_MAX;
+    BS_ASSERT(NULL == bs_avltree_delete(tree_ptr, &value));
+
+    bs_avltree_flush(tree_ptr);
+
+    BS_TEST_VERIFY_EQ(test_ptr, tree_ptr->root_ptr, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, bs_avltree_size(tree_ptr));
+
+    bs_avltree_destroy(tree_ptr);
+
+    bs_test_succeed(test_ptr, "Operations with %d randomized nodes",
+                    BS_AVLTREE_TEST_VALUES);
+}
+
+
+/* ------------------------------------------------------------------------- */
+bs_avltree_test_node_t *bs_avltree_test_node_create(int value)
+{
+    bs_avltree_test_node_t    *node_ptr;
+
+    node_ptr = calloc(1, sizeof(bs_avltree_test_node_t));
+    if (NULL == node_ptr) return NULL;
+
+    node_ptr->value = value;
+    return node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_avltree_test_node_destroy(bs_avltree_node_t *node_ptr)
+{
+    free(node_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+int bs_avltree_test_node_cmp(const bs_avltree_node_t *node_ptr,
+                             const void *key_ptr)
+{
+    const bs_avltree_test_node_t        *bs_node_ptr;
+    const int                           *int_key_ptr;
+
+    bs_node_ptr = (const bs_avltree_test_node_t*)node_ptr;
+    int_key_ptr = (const int*)key_ptr;
+
+    if (bs_node_ptr->value < *int_key_ptr) return -1;
+    if (bs_node_ptr->value > *int_key_ptr) return 1;
+    return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+const void *bs_avltree_test_node_key(const bs_avltree_node_t *node_ptr)
+{
+    const bs_avltree_test_node_t        *bs_node_ptr;
+
+    bs_node_ptr = (const bs_avltree_test_node_t*)node_ptr;
+    return &bs_node_ptr->value;
+}
+
+/** @endcond */
+/* == End of avltree.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/avltree.h
@@ -0,0 +1,163 @@
+/* ========================================================================= */
+/**
+ * @file avltree.h
+ * Implements an AVL tree, with nodes provided such they can be embedded in
+ * the element's struct.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_AVLTREE_H__
+#define __LIBBASE_AVLTREE_H__
+
+#include "test.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** The tree. */
+typedef struct _bs_avltree_t bs_avltree_t;
+/** A tree node. */
+typedef struct _bs_avltree_node_t bs_avltree_node_t;
+
+/** Actual struct of the tree node. */
+struct _bs_avltree_node_t {
+    /** Back-link to the parent node. NULL for the root node. */
+    bs_avltree_node_t         *parent_ptr;
+    /** Links to the left (smaller) node. NULL if this is the smallest node. */
+    bs_avltree_node_t         *left_ptr;
+    /** Links to the left (greater) node. NULL if this is the greatest node. */
+    bs_avltree_node_t         *right_ptr;
+    /** Node balance. */
+    int8_t                    balance;
+};
+
+/**
+ * Functor type to compare two avltree nodes.
+ *
+ * @param node_ptr
+ * @param key_ptr
+ *
+ * @return
+ * - a negative value if node_ptr is less than key_ptr
+ * - 0 if node equals key
+ * - a positive value if node_ptr is greater than key_ptr
+ */
+typedef int (*bs_avltree_node_cmp_t)(const bs_avltree_node_t *node_ptr,
+                                     const void *key_ptr);
+
+/**
+ * Functor type to destroy an avltree node.
+ *
+ * @param node_ptr
+ */
+typedef void (*bs_avltree_node_destroy_t)(bs_avltree_node_t *node_ptr);
+
+/**
+ * Creates a tree.
+ *
+ * @param cmp
+ * @param destroy Optionally, a functor to destroy nodes. If destroy is NULL,
+ *     the tree will not destroy any nodes when overwriting or flushing.
+ *
+ * @return A pointer to the tree, or NULL on error.
+ */
+bs_avltree_t *bs_avltree_create(bs_avltree_node_cmp_t cmp,
+                                bs_avltree_node_destroy_t destroy);
+
+/**
+ * Destroys the tree. Also destroys all remaining elements.
+ *
+ * @param tree_ptr
+ */
+void bs_avltree_destroy(bs_avltree_t *tree_ptr);
+
+/** Returns the node matching |key_ptr| in the tree. */
+bs_avltree_node_t *bs_avltree_lookup(bs_avltree_t *tree_ptr,
+                                     const void *key_ptr);
+
+/**
+ * Inserts a node into the tree.
+ *
+ * @param tree_ptr
+ * @param key_ptr The key for this node, required for insert.
+ * @param node_ptr The node.
+ * @param do_overwrite Whether to overwrite any already-existing node at the
+ *     specified |key_ptr|. The overwritten node will be destroyed, if a
+ *     destroy method was specified.
+ *
+ * @return Whether the insert succeeded. It can fail if |do_overwrite| is false
+ *     and a node at |key_ptr| already exists.
+ */
+bool bs_avltree_insert(bs_avltree_t *tree_ptr,
+                       const void *key_ptr,
+                       bs_avltree_node_t *node_ptr,
+                       bool do_overwrite);
+
+/**
+ * Deletes a node from the tree.
+ *
+ * @param tree_ptr
+ * @param key_ptr The key for the node that is to be deleted from the tree.
+ *
+ * @return A pointer to the deleted node, or NULL if it was not found.
+ *     Note: The node will NOT be destroyed.
+ */
+bs_avltree_node_t *bs_avltree_delete(bs_avltree_t *tree_ptr,
+                                     const void *key_ptr);
+
+/** Returns the minimum node of the tree. */
+bs_avltree_node_t *bs_avltree_min(bs_avltree_t *tree_ptr);
+
+/** Returns the maximum node of the tree. */
+bs_avltree_node_t *bs_avltree_max(bs_avltree_t *tree_ptr);
+
+/** Returns the size of the tree. */
+size_t bs_avltree_size(const bs_avltree_t *tree_ptr);
+
+/** Returns the next-larger node from the tree, seen from |node_ptr|. */
+bs_avltree_node_t *bs_avltree_node_next(bs_avltree_t *tree_ptr,
+                                        bs_avltree_node_t *node_ptr);
+/** Returns the next-smaller node from the tree, seen from |node_ptr|. */
+bs_avltree_node_t *bs_avltree_node_prev(bs_avltree_t *tree_ptr,
+                                        bs_avltree_node_t *node_ptr);
+
+/**
+ * Helper: Comparator to compare two pointers.
+ *
+ * To be used for @ref bs_avltree_node_cmp_t, after the comparison function
+ * looks up the key from the `node_ptr` argument.
+ *
+ * @param node_key_ptr        The key obtained for the node under comparison.
+ * @param key_ptr             As passed from @ref bs_avltree_node_cmp_t.
+ *
+ * @return See @ref bs_avltree_node_cmp_t.
+ */
+int bs_avltree_cmp_ptr(const void *node_key_ptr,
+                       const void *key_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_avltree_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_AVLTREE_H__ */
+/* == End of avltree.h ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/c2x_compat.c
@@ -0,0 +1,38 @@
+/* ========================================================================= */
+/**
+ * @file c2x_compat.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "c2x_compat.h"
+
+#include "log_wrappers.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+/* ------------------------------------------------------------------------- */
+char *strdup(const char *str) {
+    size_t len = strlen(str);
+    char *new_ptr = logged_malloc(len + 1);
+    if (NULL == new_ptr) return NULL;
+    new_ptr[len] = '\0';
+    memcpy(new_ptr, str, len);
+    return new_ptr;
+}
+
+/* == End of c2x_compat.c ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/c2x_compat.h
@@ -0,0 +1,42 @@
+/* ========================================================================= */
+/**
+ * @file c2x_compat.h
+ * Compatibility layer for upcoming convenient C2x methods.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_C2X_COMPAT_H__
+#define __LIBBASE_C2X_COMPAT_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Returns a pointer to a new string which is a duplicate of `s`.
+ *
+ * @param s
+ *
+ * @return A pointer to the duplicated string, to be free'd with free(3).
+ */
+char *strdup(const char*s);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_C2X_COMPAT_H__ */
+/* == End of c2x_compat.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/def.h
@@ -0,0 +1,68 @@
+/* ========================================================================= */
+/**
+ * @file def.h
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_DEF_H__
+#define __LIBBASE_DEF_H__
+
+// For offsetof.
+#include <stddef.h>
+
+/* == Definitions for arguments et.al. ===================================== */
+
+#if !defined(__UNUSED__)
+/** Compiler hint, indicating unused elements. */
+#define __UNUSED__ __attribute__ ((unused))
+#endif
+
+#if !defined(__ARG_PRINTF__)
+/** Compiler hint, indicating these are printf-style format args. */
+#define __ARG_PRINTF__(_fmtidx, _paridx) \
+    __attribute__ ((format(printf, _fmtidx, _paridx)))
+#endif
+
+/* == Function-like definitions ============================================ */
+
+/** Returns the lesser of (a, b) */
+#if !defined(BS_MIN)
+#define BS_MIN(a, b)                                \
+    ({                                              \
+        __typeof__(a) __a = (a);                    \
+        __typeof__(b) __b = (b);                    \
+        __a < __b ? __a : __b;                      \
+    })
+#endif  // !defined(BS_MIN)
+
+/** Returns the greater of (a, b) */
+#if !defined(BS_MAX)
+#define BS_MAX(a, b)                                \
+    ({                                              \
+        __typeof__(a) __a = (a);                    \
+        __typeof__(b) __b = (b);                    \
+        __a > __b ? __a : __b;                      \
+    })
+#endif  // !defined(BS_MAX)
+
+/** Helper to retrieve the base container, given a pointer to an element. */
+#define BS_CONTAINER_OF(elem_ptr, container_type, elem_field)   \
+    (container_type*)(                                          \
+        (uint8_t*)(elem_ptr) -                                  \
+        offsetof(container_type, elem_field))
+
+#endif /* __LIBBASE_DEF_H__ */
+/* == End of def.h ========================================================= */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/dequeue.c
@@ -0,0 +1,91 @@
+/* ========================================================================= */
+/**
+ * @file dequeue.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "dequeue.h"
+
+/* == Tests =============================================================== */
+
+static void bs_dequeue_front_test(bs_test_t *test_ptr);
+static void bs_dequeue_back_test(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_dequeue_test_cases[] = {
+    { 1, "push front & pop", bs_dequeue_front_test },
+    { 1, "push back & pop", bs_dequeue_back_test },
+    { 0, NULL, NULL }  // sentinel.
+};
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Tests that push front & pop works.
+ */
+void bs_dequeue_front_test(bs_test_t *test_ptr)
+{
+    bs_dequeue_t              queue;
+    bs_dequeue_node_t         node1, node2;
+
+    memset(&queue, 0, sizeof(bs_dequeue_t));
+
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL);
+
+    bs_dequeue_push_front(&queue, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL);
+
+    bs_dequeue_push_front(&queue, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL);
+
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL);
+
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Tests that push back & pop works.
+ */
+void bs_dequeue_back_test(bs_test_t *test_ptr)
+{
+    bs_dequeue_t              queue;
+    bs_dequeue_node_t         node1, node2, node3;
+
+    memset(&queue, 0, sizeof(bs_dequeue_t));
+
+    bs_dequeue_push_back(&queue, &node1);
+    bs_dequeue_push_back(&queue, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL);
+
+    // Build a node2 -> node1 -> node3 queue.
+    bs_dequeue_push_back(&queue, &node1);
+    bs_dequeue_push_front(&queue, &node2);
+    bs_dequeue_push_back(&queue, &node3);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node3);
+    BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL);
+}
+
+/* == End of dequeue.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/dequeue.h
@@ -0,0 +1,103 @@
+/* ========================================================================= */
+/**
+ * @file dequeue.h
+ * Provides a double-ended queue.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_DEQUEUE_H__
+#define __LIBBASE_DEQUEUE_H__
+
+#include "assert.h"
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** A double-ended queue. */
+typedef struct _bs_dequeue_t bs_dequeue_t;
+/** A node in a double-ended queue. */
+typedef struct _bs_dequeue_node_t bs_dequeue_node_t;
+
+/** Details of the queue. */
+struct _bs_dequeue_t {
+    /** Head of the queue. */
+    bs_dequeue_node_t          *head_ptr;
+    /** Tail of the queue. */
+    bs_dequeue_node_t          *tail_ptr;
+};
+
+/** Details of the node. */
+struct _bs_dequeue_node_t {
+    /** Next node. */
+    bs_dequeue_node_t          *next_ptr;
+};
+
+/** Pushes |node_ptr| to the front of the double-ended |queue_ptr|. */
+static inline void bs_dequeue_push_front(
+    bs_dequeue_t *queue_ptr, bs_dequeue_node_t *node_ptr)
+{
+    node_ptr->next_ptr = queue_ptr->head_ptr;
+    queue_ptr->head_ptr = node_ptr;
+
+    if (NULL == queue_ptr->tail_ptr) {
+        BS_ASSERT(NULL == node_ptr->next_ptr);
+        queue_ptr->tail_ptr = node_ptr;
+    }
+}
+
+/** Pushes |node_ptr| to the back the double-ended |queue_ptr|. */
+static inline void bs_dequeue_push_back(
+    bs_dequeue_t *queue_ptr, bs_dequeue_node_t *node_ptr)
+{
+    node_ptr->next_ptr = NULL;
+    if (NULL != queue_ptr->tail_ptr) {
+        BS_ASSERT(NULL == queue_ptr->tail_ptr->next_ptr);
+        queue_ptr->tail_ptr->next_ptr = node_ptr;
+    } else {
+        BS_ASSERT(NULL == queue_ptr->head_ptr);
+        queue_ptr->head_ptr = node_ptr;
+    }
+    queue_ptr->tail_ptr = node_ptr;
+}
+
+/** Pops the (head) node of the double-ended queue at |queue_ptr|. */
+static inline bs_dequeue_node_t *bs_dequeue_pop(bs_dequeue_t *queue_ptr)
+{
+    bs_dequeue_node_t *node_ptr = queue_ptr->head_ptr;
+    if (NULL == queue_ptr->head_ptr) {
+        BS_ASSERT(NULL == queue_ptr->tail_ptr);
+    } else {
+        queue_ptr->head_ptr = queue_ptr->head_ptr->next_ptr;
+        if (queue_ptr->tail_ptr == node_ptr) {
+            BS_ASSERT(NULL == queue_ptr->head_ptr);
+            queue_ptr->tail_ptr = NULL;
+        }
+        node_ptr->next_ptr = NULL;
+    }
+    return node_ptr;
+}
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_dequeue_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_DEQUEUE_H__ */
+/* == End of dequeue.h ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/dllist.c
@@ -0,0 +1,560 @@
+/* ========================================================================= */
+/**
+ * @file dllist.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <string.h>
+
+#include "assert.h"
+
+#include "dllist.h"
+#include "test.h"
+
+static bool find_is(bs_dllist_node_t *dlnode_ptr, void *ud_ptr);
+static void assert_consistency(const bs_dllist_t *list_ptr);
+static bool node_orphaned(const bs_dllist_node_t *dlnode_ptr);
+
+/* == Exported Functions =================================================== */
+
+/* ------------------------------------------------------------------------- */
+size_t bs_dllist_size(const bs_dllist_t *list_ptr)
+{
+    size_t                    count;
+    bs_dllist_node_t          *node_ptr;
+
+    node_ptr = list_ptr->head_ptr;
+    count = 0;
+    while (NULL != node_ptr) {
+        node_ptr = node_ptr->next_ptr;
+        count++;
+    }
+    return count;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_dllist_empty(const bs_dllist_t *list_ptr)
+{
+    return (NULL == list_ptr->head_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_push_back(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr)
+{
+    BS_ASSERT(NULL == node_ptr->prev_ptr);
+    BS_ASSERT(NULL == node_ptr->next_ptr);
+
+    if (NULL != list_ptr->tail_ptr) {
+        BS_ASSERT(NULL != list_ptr->head_ptr);
+        BS_ASSERT(NULL == list_ptr->tail_ptr->next_ptr);
+
+        node_ptr->prev_ptr = list_ptr->tail_ptr;
+        list_ptr->tail_ptr->next_ptr = node_ptr;
+
+        list_ptr->tail_ptr = node_ptr;
+
+    } else {
+        BS_ASSERT(NULL == list_ptr->head_ptr);
+
+        list_ptr->head_ptr = node_ptr;
+        list_ptr->tail_ptr = node_ptr;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_push_front(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr)
+{
+    BS_ASSERT(NULL == node_ptr->prev_ptr);
+    BS_ASSERT(NULL == node_ptr->next_ptr);
+
+    if (NULL != list_ptr->head_ptr) {
+        BS_ASSERT(NULL != list_ptr->tail_ptr);
+        BS_ASSERT(NULL == list_ptr->head_ptr->prev_ptr);
+
+        node_ptr->next_ptr = list_ptr->head_ptr;
+        list_ptr->head_ptr->prev_ptr = node_ptr;
+
+        list_ptr->head_ptr = node_ptr;
+
+    } else {
+        BS_ASSERT(NULL == list_ptr->head_ptr);
+
+        list_ptr->head_ptr = node_ptr;
+        list_ptr->tail_ptr = node_ptr;
+    }
+}
+
+
+/* ------------------------------------------------------------------------- */
+bs_dllist_node_t *bs_dllist_pop_back(bs_dllist_t *list_ptr)
+{
+    bs_dllist_node_t          *node_ptr;
+
+    if (NULL == list_ptr->tail_ptr) {
+        BS_ASSERT(NULL == list_ptr->head_ptr);
+        return NULL;
+    }
+    BS_ASSERT(NULL != list_ptr->head_ptr);
+
+    node_ptr = list_ptr->tail_ptr;
+    list_ptr->tail_ptr = node_ptr->prev_ptr;
+    if (NULL != list_ptr->tail_ptr) {
+        list_ptr->tail_ptr->next_ptr = NULL;
+    } else {
+        BS_ASSERT(list_ptr->head_ptr == node_ptr);
+        list_ptr->head_ptr = NULL;
+    }
+    node_ptr->prev_ptr = NULL;
+    return node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_dllist_node_t * bs_dllist_pop_front(bs_dllist_t *list_ptr)
+{
+    bs_dllist_node_t          *node_ptr;
+
+    if (NULL == list_ptr->head_ptr) {
+        BS_ASSERT(NULL == list_ptr->tail_ptr);
+        return NULL;
+    }
+    BS_ASSERT(NULL != list_ptr->tail_ptr);
+
+    node_ptr = list_ptr->head_ptr;
+    list_ptr->head_ptr = node_ptr->next_ptr;
+    if (NULL != list_ptr->head_ptr) {
+        list_ptr->head_ptr->prev_ptr = NULL;
+    } else {
+        BS_ASSERT(list_ptr->tail_ptr == node_ptr);
+        list_ptr->tail_ptr = NULL;
+    }
+
+    node_ptr->next_ptr =  NULL;
+    return node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_remove(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr)
+{
+    BS_ASSERT(bs_dllist_contains(list_ptr, node_ptr));
+    if (NULL == node_ptr->prev_ptr) {
+        BS_ASSERT(list_ptr->head_ptr == node_ptr);
+        list_ptr->head_ptr = node_ptr->next_ptr;
+    } else {
+        node_ptr->prev_ptr->next_ptr = node_ptr->next_ptr;
+    }
+
+    if (NULL == node_ptr->next_ptr) {
+        BS_ASSERT(list_ptr->tail_ptr == node_ptr);
+        list_ptr->tail_ptr = node_ptr->prev_ptr;
+    } else {
+        node_ptr->next_ptr->prev_ptr = node_ptr->prev_ptr;
+    }
+
+    node_ptr->prev_ptr = NULL;
+    node_ptr->next_ptr = NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_insert_node_before(
+    bs_dllist_t *list_ptr,
+    bs_dllist_node_t *reference_node_ptr,
+    bs_dllist_node_t *new_node_ptr)
+{
+    BS_ASSERT(bs_dllist_contains(list_ptr, reference_node_ptr));
+    BS_ASSERT(node_orphaned(new_node_ptr));
+
+    if (NULL == reference_node_ptr->prev_ptr) {
+        bs_dllist_push_front(list_ptr, new_node_ptr);
+        return;
+    }
+
+    reference_node_ptr->prev_ptr->next_ptr = new_node_ptr;
+    new_node_ptr->prev_ptr = reference_node_ptr->prev_ptr;
+
+    reference_node_ptr->prev_ptr = new_node_ptr;
+    new_node_ptr->next_ptr = reference_node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_dllist_contains(
+    const bs_dllist_t *list_ptr,
+    bs_dllist_node_t *dlnode_ptr)
+{
+    return bs_dllist_find(list_ptr, find_is, dlnode_ptr) == dlnode_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_dllist_node_t *bs_dllist_find(
+    const bs_dllist_t *list_ptr,
+    bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr),
+    void *ud_ptr)
+{
+    for (bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr;
+         dlnode_ptr != NULL;
+         dlnode_ptr = dlnode_ptr->next_ptr) {
+        if (func(dlnode_ptr, ud_ptr)) return dlnode_ptr;
+    }
+    return NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_for_each(
+    const bs_dllist_t *list_ptr,
+    void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr),
+    void *ud_ptr)
+{
+    bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr;
+    while (NULL != dlnode_ptr) {
+        bs_dllist_node_t *current_dlnode_ptr = dlnode_ptr;
+        dlnode_ptr = dlnode_ptr->next_ptr;
+        func(current_dlnode_ptr, ud_ptr);
+    }
+}
+
+/* == Local methods ======================================================== */
+
+/* ------------------------------------------------------------------------- */
+void assert_consistency(const bs_dllist_t *list_ptr)
+{
+    const bs_dllist_node_t *node_ptr;
+
+    // An empty list is consistent if both head/tail are NULL.
+    if (NULL == list_ptr->head_ptr || NULL == list_ptr->tail_ptr) {
+        BS_ASSERT(NULL == list_ptr->head_ptr);
+        BS_ASSERT(NULL == list_ptr->tail_ptr);
+        return;
+    }
+
+    // Head and tail don't continue further.
+    BS_ASSERT(NULL == list_ptr->head_ptr->prev_ptr);
+    BS_ASSERT(NULL == list_ptr->tail_ptr->next_ptr);
+
+    for (node_ptr = list_ptr->head_ptr;
+         node_ptr != NULL;
+         node_ptr = node_ptr->next_ptr) {
+        if (NULL != node_ptr->next_ptr) {
+            BS_ASSERT(node_ptr == node_ptr->next_ptr->prev_ptr);
+        } else {
+            BS_ASSERT(node_ptr == list_ptr->tail_ptr);
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+/** Returns whether dlnode_ptr == ud_ptr. */
+bool find_is(bs_dllist_node_t *dlnode_ptr, void *ud_ptr)
+{
+    return dlnode_ptr == ud_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Returns whether |dlnode_ptr| is orphaned, ie. not in any list.
+ *
+ * Note: This does not guarantee that dlnode_ptr is not member of a list,
+ * merely that it refers no neighbours. The case of it being the single list
+ * node cannot be distinguished.
+ *
+ * @param dlnode_ptr
+ *
+ * @reutrn Whether the list holds no references.
+ */
+bool node_orphaned(const bs_dllist_node_t *dlnode_ptr)
+{
+    return (NULL == dlnode_ptr->prev_ptr) && (NULL == dlnode_ptr->next_ptr);
+}
+
+/* == Tests =============================================================== */
+
+static void bs_dllist_test_back(bs_test_t *test_ptr);
+static void bs_dllist_test_front(bs_test_t *test_ptr);
+static void bs_dllist_test_remove(bs_test_t *test_ptr);
+static void bs_dllist_test_insert(bs_test_t *test_ptr);
+static void bs_dllist_test_find(bs_test_t *test_ptr);
+static void bs_dllist_test_for_each(bs_test_t *test_ptr);
+static void bs_dllist_test_for_each_dtor(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_dllist_test_cases[] = {
+    { 1, "push/pop back", bs_dllist_test_back },
+    { 1, "push/pop front", bs_dllist_test_front },
+    { 1, "remove", bs_dllist_test_remove },
+    { 1, "insert", bs_dllist_test_insert },
+    { 1, "find", bs_dllist_test_find },
+    { 1, "for_each", bs_dllist_test_for_each },
+    { 1, "for_each_dtor", bs_dllist_test_for_each_dtor },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Tests that push_back and pop_back works.
+ */
+void bs_dllist_test_back(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list1;
+    bs_dllist_node_t          node1, node2, node3;
+
+    memset(&list1, 0, sizeof(bs_dllist_t));
+    memset(&node1, 0, sizeof(bs_dllist_node_t));
+    memset(&node2, 0, sizeof(bs_dllist_node_t));
+    memset(&node3, 0, sizeof(bs_dllist_node_t));
+
+    bs_dllist_push_back(&list1, &node1);
+    bs_dllist_push_back(&list1, &node2);
+    bs_dllist_push_back(&list1, &node3);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node1.prev_ptr, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, &node2);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node2.prev_ptr, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node3.prev_ptr, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, NULL);
+
+    BS_TEST_VERIFY_EQ(test_ptr, &node3, bs_dllist_pop_back(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, &node2, bs_dllist_pop_back(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, &node1, bs_dllist_pop_back(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr);
+
+    BS_TEST_VERIFY_EQ(test_ptr, list1.head_ptr, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, list1.tail_ptr, NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Tests that push_front and pop_front works.
+ */
+void bs_dllist_test_front(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list1;
+    bs_dllist_node_t          node1, node2, node3;
+
+    memset(&list1, 0, sizeof(bs_dllist_t));
+    memset(&node1, 0, sizeof(bs_dllist_node_t));
+    memset(&node2, 0, sizeof(bs_dllist_node_t));
+    memset(&node3, 0, sizeof(bs_dllist_node_t));
+
+    bs_dllist_push_front(&list1, &node3);
+    bs_dllist_push_front(&list1, &node2);
+    bs_dllist_push_front(&list1, &node1);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node1.prev_ptr, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, &node2);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node2.prev_ptr, &node1);
+    BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3);
+
+    BS_TEST_VERIFY_EQ(test_ptr, node3.prev_ptr, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, NULL);
+
+    BS_TEST_VERIFY_EQ(test_ptr, &node1, bs_dllist_pop_front(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, &node2, bs_dllist_pop_front(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, &node3, bs_dllist_pop_front(&list1));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr);
+
+    BS_TEST_VERIFY_EQ(test_ptr, list1.head_ptr, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, list1.tail_ptr, NULL);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_test_remove(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list;
+    bs_dllist_node_t          node1, node2, node3, node4;
+
+    memset(&list, 0, sizeof(bs_dllist_t));
+    memset(&node1, 0, sizeof(bs_dllist_node_t));
+    memset(&node2, 0, sizeof(bs_dllist_node_t));
+    memset(&node3, 0, sizeof(bs_dllist_node_t));
+    memset(&node4, 0, sizeof(bs_dllist_node_t));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1));
+
+    // Sequence: 1, 2, 3, 4.
+    bs_dllist_push_back(&list, &node1);
+    assert_consistency(&list);
+    bs_dllist_push_back(&list, &node2);
+    assert_consistency(&list);
+    bs_dllist_push_back(&list, &node3);
+    assert_consistency(&list);
+    bs_dllist_push_back(&list, &node4);
+    assert_consistency(&list);
+
+    BS_TEST_VERIFY_EQ(test_ptr, 4, bs_dllist_size(&list));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_empty(&list));
+    BS_TEST_VERIFY_FALSE(test_ptr, node_orphaned(&node1));
+
+    bs_dllist_remove(&list, &node3);
+    // Now: 1, 2, 4.
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr);
+
+    bs_dllist_remove(&list, &node1);
+    // Now: 2, 4.
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1));
+
+    bs_dllist_remove(&list, &node4);
+    // Now: 2.
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node4.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node4.next_ptr);
+
+    bs_dllist_remove(&list, &node2);
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr);
+
+    BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&list));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_empty(&list));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_test_insert(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list;
+    bs_dllist_node_t          node1, node2, node3, node4;
+
+    memset(&list, 0, sizeof(bs_dllist_t));
+    memset(&node1, 0, sizeof(bs_dllist_node_t));
+    memset(&node2, 0, sizeof(bs_dllist_node_t));
+    memset(&node3, 0, sizeof(bs_dllist_node_t));
+    memset(&node4, 0, sizeof(bs_dllist_node_t));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1));
+
+    bs_dllist_push_back(&list, &node1);
+    bs_dllist_insert_node_before(&list, &node1, &node2);
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, list.head_ptr, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node1);
+
+    bs_dllist_insert_node_before(&list, &node1, &node3);
+    assert_consistency(&list);
+    BS_TEST_VERIFY_EQ(test_ptr, list.head_ptr, &node2);
+    BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3);
+    BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, &node1);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Function to use in the test for @ref bs_dllist_any. */
+static bool test_find(bs_dllist_node_t *dlnode_ptr, void *ud_ptr)
+{
+    return dlnode_ptr == ud_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Function to use in the test for @ref bs_dllist_for_each. */
+static void test_for_each(
+    __UNUSED__ bs_dllist_node_t *dlnode_ptr,
+    void *ud_ptr)
+{
+    *((int*)ud_ptr) += 1;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_test_find(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list;
+    bs_dllist_node_t          n1, n2;
+
+    memset(&list, 0, sizeof(bs_dllist_t));
+    memset(&n1, 0, sizeof(bs_dllist_node_t));
+    memset(&n2, 0, sizeof(bs_dllist_node_t));
+
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_dllist_find(&list, test_find, &n1));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n1));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n2));
+    bs_dllist_push_back(&list, &n1);
+    BS_TEST_VERIFY_EQ(test_ptr, &n1, bs_dllist_find(&list, test_find, &n1));
+    BS_TEST_VERIFY_NEQ(test_ptr, &n2, bs_dllist_find(&list, test_find, &n2));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n1));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n2));
+    bs_dllist_push_back(&list, &n2);
+    BS_TEST_VERIFY_EQ(test_ptr, &n1, bs_dllist_find(&list, test_find, &n1));
+    BS_TEST_VERIFY_EQ(test_ptr, &n2, bs_dllist_find(&list, test_find, &n2));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n1));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n2));
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_dllist_test_for_each(bs_test_t *test_ptr)
+{
+    bs_dllist_t               list;
+    bs_dllist_node_t          n1, n2;
+    int                       outcome;
+
+    memset(&list, 0, sizeof(bs_dllist_t));
+    memset(&n1, 0, sizeof(bs_dllist_node_t));
+    memset(&n2, 0, sizeof(bs_dllist_node_t));
+
+    outcome = 0;
+    bs_dllist_for_each(&list, test_for_each, &outcome);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, outcome);
+
+    bs_dllist_push_back(&list, &n1);
+    outcome = 0;
+    bs_dllist_for_each(&list, test_for_each, &outcome);
+    BS_TEST_VERIFY_EQ(test_ptr, 1, outcome);
+
+    bs_dllist_push_back(&list, &n2);
+    outcome = 0;
+    bs_dllist_for_each(&list, test_for_each, &outcome);
+    BS_TEST_VERIFY_EQ(test_ptr, 2, outcome);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Helper: dtor for the allocated @ref bs_dllist_node_t. */
+static void test_for_each_dtor(bs_dllist_node_t *dlnode_ptr,
+                               __UNUSED__ void *ud_ptr)
+{
+    bs_dllist_remove(ud_ptr, dlnode_ptr);
+    free(dlnode_ptr);
+}
+
+/** Runs bs_dllist_for_each with a dtor, ensuring no invalid access. */
+void bs_dllist_test_for_each_dtor(bs_test_t *test_ptr)
+{
+    bs_dllist_t list = {};
+
+    for (int i = 0; i < 5; ++i) {
+        bs_dllist_node_t *node_ptr = BS_ASSERT_NOTNULL(
+            calloc(1, sizeof(bs_dllist_node_t)));
+        bs_dllist_push_back(&list, node_ptr);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, 5, bs_dllist_size(&list));
+
+    bs_dllist_for_each(&list, test_for_each_dtor, &list);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_empty(&list));
+}
+
+
+/* == End of dllist.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/dllist.h
@@ -0,0 +1,117 @@
+/* ========================================================================= */
+/**
+ * @file dllist.h
+ * Provides a double-linked list.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_DLLIST_H__
+#define __LIBBASE_DLLIST_H__
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** A doubly-linked list. */
+typedef struct _bs_dllist_t bs_dllist_t;
+/** A node in a doubly-linked list. */
+typedef struct _bs_dllist_node_t bs_dllist_node_t;
+
+/** Details of the list. */
+struct _bs_dllist_t {
+    /** Head of the double-linked list. NULL if empty. */
+    bs_dllist_node_t          *head_ptr;
+    /** Tail of the double-linked list. NULL if empty. */
+    bs_dllist_node_t          *tail_ptr;
+};
+
+/** Details of said node. */
+struct _bs_dllist_node_t {
+    /** The previous node, or NULL if this is the only node. */
+    bs_dllist_node_t          *prev_ptr;
+    /** The next node, or NULL if this is the only node. */
+    bs_dllist_node_t          *next_ptr;
+};
+
+/** Returns the number of elements in |list_ptr|. */
+size_t bs_dllist_size(const bs_dllist_t *list_ptr);
+/** Returns whether the list is empty. */
+bool bs_dllist_empty(const bs_dllist_t *list_ptr);
+
+/** Adds |node_ptr| at the back of |list_ptr|. */
+void bs_dllist_push_back(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr);
+/** Adds |node_ptr| at the front of |list_ptr|. */
+void bs_dllist_push_front(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr);
+
+/** Retrieves the node from the back of |list_ptr|, or NULL if empty. */
+bs_dllist_node_t *bs_dllist_pop_back(bs_dllist_t *list_ptr);
+/** Retrieves the node from the front of |list_ptr|, or NULL if empty. */
+bs_dllist_node_t * bs_dllist_pop_front(bs_dllist_t *list_ptr);
+
+/** Removes the node from the list. */
+void bs_dllist_remove(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr);
+
+/** Inserts `new_node_ptr` into the list, before `reference_node_ptr`. */
+void bs_dllist_insert_node_before(
+    bs_dllist_t *list_ptr,
+    bs_dllist_node_t *reference_node_ptr,
+    bs_dllist_node_t *new_node_ptr);
+
+/** Returns whether |list_ptr| contains |dlnode_ptr|. */
+bool bs_dllist_contains(
+    const bs_dllist_t *list_ptr,
+    bs_dllist_node_t *dlnode_ptr);
+
+/**
+ * Returns the node for which |func()| is true.
+ *
+ * @param list_ptr
+ * @param func
+ * @param ud_ptr
+ *
+ * @return A pointer to the corresponding @ref bs_dllist_node_t or NULL.
+ */
+bs_dllist_node_t *bs_dllist_find(
+    const bs_dllist_t *list_ptr,
+    bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr),
+    void *ud_ptr);
+
+/**
+ * Runs |func()| for each of the nodes in |list_ptr|.
+ *
+ * The list iterator is kept safe, so it is permitted to remove the called-back
+ * node from the list: Useful for eg. destroying all list elements.
+ *
+ * @param list_ptr
+ * @param func
+ * @param ud_ptr
+ */
+void bs_dllist_for_each(
+    const bs_dllist_t *list_ptr,
+    void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr),
+    void *ud_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_dllist_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_DLLIST_H__ */
+/* == End of dllist.h ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/file.c
@@ -0,0 +1,257 @@
+/* ========================================================================= */
+/**
+ * @file file.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "file.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "log.h"
+#include "strutil.h"
+
+/* == Declarations ========================================================= */
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+ssize_t bs_file_read_buffer(
+    const char *fname_ptr,
+    char *buf_ptr,
+    size_t buf_len)
+{
+    ssize_t                   read_bytes;
+    int                       fd;
+
+    fd = open(fname_ptr, O_RDONLY);
+    if (0 > fd) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed open(%s, O_RDONLY)",
+               fname_ptr);
+        return -1;
+    }
+
+    read_bytes = read(fd, buf_ptr, buf_len);
+    if (0 > read_bytes) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed read(%d, %p, %zu) from %s",
+               fd, buf_ptr, buf_len - 1, fname_ptr);
+    } if ((size_t)read_bytes >= buf_len) {
+        bs_log(BS_WARNING | BS_ERRNO,
+               "Read %zd >= %zu bytes. Too much data in %s",
+               read_bytes, buf_len, fname_ptr);
+        read_bytes = -1;
+    } else {
+        buf_ptr[read_bytes] = '\0';
+    }
+
+    if (0 != close(fd)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d) for %s", fd,
+               fname_ptr);
+        return -1;
+    }
+
+    return read_bytes;
+}
+
+/* ------------------------------------------------------------------------- */
+ssize_t bs_file_write_buffer(
+    const char *fname_ptr,
+    const char *buf_ptr,
+    size_t buf_len)
+{
+    ssize_t                   written_bytes;
+    int                       fd;
+
+    fd = open(fname_ptr, O_WRONLY);
+    if (0 > fd) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed open(%s, O_WRONLY)", fname_ptr);
+        return -1;
+    }
+
+    written_bytes = write(fd, buf_ptr, buf_len);
+    if (0 > written_bytes) {
+        bs_log(BS_ERROR | BS_ERRNO, "Falied write(%d, %p, %zu) to %s",
+               fd, buf_ptr, buf_len, fname_ptr);
+    } else if ((size_t)written_bytes != buf_len) {
+        bs_log(BS_ERROR, "Incomplete write(%d, %p, %zu): %zd bytes to %s",
+               fd, buf_ptr, buf_len, written_bytes, fname_ptr);
+        written_bytes = -1;
+    }
+
+    if (0 != close(fd)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d) for %s", fd,
+               fname_ptr);
+        return -1;
+    }
+
+    return written_bytes;
+}
+
+/* ------------------------------------------------------------------------- */
+char *bs_file_resolve_path(
+    const char *path_ptr,
+    char *resolved_path_buf_ptr)
+{
+    char expanded_path[PATH_MAX];
+    if (bs_str_startswith(path_ptr, "~/")) {
+        const char *home_dir_ptr = getenv("HOME");
+        if (NULL == home_dir_ptr) {
+            bs_log(BS_WARNING, "Failed getenv(\"HOME\") for path %s",
+                   path_ptr);
+        } else {
+            size_t i = bs_strappend(expanded_path, PATH_MAX, 0, home_dir_ptr);
+            i = bs_strappend(expanded_path, PATH_MAX, i, "/");
+            i = bs_strappend(expanded_path, PATH_MAX, i, path_ptr + 2);
+            if (i >= PATH_MAX) {
+                errno = ENAMETOOLONG;
+                return NULL;
+            }
+            path_ptr = &expanded_path[0];
+        }
+    }
+
+    return realpath(path_ptr, resolved_path_buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+char *bs_file_join_resolve_path(
+    const char *path_ptr,
+    const char *fname_ptr,
+    char *resolved_path_buf_ptr)
+{
+    char path[PATH_MAX];
+    size_t pos = bs_strappend(path, PATH_MAX, 0, path_ptr);
+    pos = bs_strappend(path, PATH_MAX, pos, "/");
+    pos = bs_strappend(path, PATH_MAX, pos, fname_ptr);
+    if (pos > PATH_MAX) {
+        errno = ENAMETOOLONG;
+        return NULL;
+    }
+
+    return bs_file_resolve_path(path, resolved_path_buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+char *bs_file_resolve_and_lookup_from_paths(
+    const char *fname_ptr,
+    const char **paths_ptr_ptr,
+    int mode,
+    char *resolved_path_buf_ptr)
+{
+    for (; NULL != *paths_ptr_ptr; ++paths_ptr_ptr) {
+        char *resolved_path_ptr = bs_file_join_resolve_path(
+            *paths_ptr_ptr, fname_ptr, resolved_path_buf_ptr);
+        if (NULL == resolved_path_ptr) continue;
+
+        // Found something and not needed to check type? We have it.
+        if (0 == mode) return resolved_path_ptr;
+
+        // Otherwise, verify type.
+        struct stat stat_buf;
+        if (0 == stat(resolved_path_ptr, &stat_buf)) {
+            if ((stat_buf.st_mode & S_IFMT) == (mode & S_IFMT)) {
+                return resolved_path_ptr;
+            }
+        }
+        if (NULL == resolved_path_buf_ptr) free(resolved_path_ptr);
+    }
+    return NULL;
+}
+
+/* == Static (local) functions ============================================= */
+
+/* == Test Functions ======================================================= */
+
+static void test_resolve_path(bs_test_t *test_ptr);
+static void test_join_resolve_path(bs_test_t *test_ptr);
+static void test_lookup(bs_test_t *test_ptr);
+
+const bs_test_case_t bs_file_test_cases[] = {
+    { 1, "resolve_path", test_resolve_path },
+    { 1, "join_resolve_path", test_join_resolve_path },
+    { 1, "lookup", test_lookup },
+    { 0, NULL, NULL }  // sentinel.
+};
+
+/* ------------------------------------------------------------------------- */
+void test_resolve_path(bs_test_t *test_ptr)
+{
+    char *p;
+
+    p = bs_file_resolve_path("/proc/self/cwd/libbase_test", NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+    free(p);
+
+    char path[PATH_MAX];
+    p = bs_file_resolve_path("/proc/self/cwd/libbase_test", path);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+
+    p = bs_file_resolve_path("~/", path);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_join_resolve_path(bs_test_t *test_ptr)
+{
+    char *p;
+    p = bs_file_join_resolve_path("/proc/self/cwd", "libbase_test", NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+    free(p);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_lookup(bs_test_t *test_ptr)
+{
+    const char *paths[] = { "/anywhere", "/proc/self/cwd", NULL };
+    char *p;
+
+    p = bs_file_resolve_and_lookup_from_paths(
+        "libbase_test", paths, 0, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+    free(p);
+
+    p = bs_file_resolve_and_lookup_from_paths(
+        "libbase_test", paths, S_IFBLK, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, p);
+
+    p = bs_file_resolve_and_lookup_from_paths(
+        "does_not_exist", paths, 0, NULL);
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, p);
+
+    char path[PATH_MAX];
+    p = bs_file_resolve_and_lookup_from_paths(
+        "libbase_test", paths, 0, path);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+
+    p = bs_file_resolve_and_lookup_from_paths(
+        "libbase_test", paths, S_IFREG, path);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+
+    paths[0] = "~/";
+    paths[1] = NULL;
+    p = bs_file_resolve_and_lookup_from_paths(
+        "", paths, S_IFDIR, path);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, p);
+}
+
+/* == End of file.c ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/file.h
@@ -0,0 +1,136 @@
+/* ========================================================================= */
+/**
+ * @file file.h
+ * Convenience methods for reading a buffer from a file, respectively writing
+ * a buffer to a file.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_FILE_H__
+#define __LIBBASE_FILE_H__
+
+#include <stddef.h>
+
+#include <sys/types.h>
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Reads the contents from |fname_ptr| into |buf_ptr|, up to |buf_len|-1 bytes.
+ * It will add a trailing NUL character to buf_ptr, for convenience.
+ *
+ * @param fname_ptr
+ * @param buf_ptr
+ * @param buf_len
+ *
+ * @return A negative value on error, or the number of bytes read on success.
+ *     Any error will be logged.
+ */
+ssize_t bs_file_read_buffer(
+    const char *fname_ptr,
+    char *buf_ptr,
+    size_t buf_len);
+
+/**
+ * Writes |buf_len| bytes from |buf_ptr| into the file |fname_ptr|.
+ *
+ * @param fname_ptr
+ * @param buf_ptr
+ * @param buf_len
+ *
+ * @return A negative value on error, or the number of bytes written.
+ *     Any error will be logged.
+ */
+ssize_t bs_file_write_buffer(
+    const char *fname_ptr,
+    const char *buf_ptr,
+    size_t buf_len);
+
+/**
+ * Resolves the real path to `path_ptr`, with home directory expansion.
+ *
+ * @param path_ptr            Path to join from. Paths that start with "~/"
+ *                            will be expanded with getenv("HOME").
+ * @param resolved_path_buf_ptr If specified as NULL, this method will allocate
+ *                            a buffer of suitable length, and return the
+ *                            pointer to it. The caller then needs to release
+ *                            that buffer by calling free(3).
+ *                            Otherwise, the resolved path is stored here.
+ *
+ * @return Upon success, a pointer to the path is returned. If
+ *     resolved_realpath_ptr was NULL, it must be released by calling free(3).
+ *     NULL is returned if the the path resolution (realpath) failed, or if
+ *     the expanded path was too long. errno will be set appropriately in both
+ *     cases (See realpath(3) for the former; ENAMETOOLONG for the latter).
+ */
+char *bs_file_resolve_path(
+    const char *path_ptr,
+    char *resolved_path_buf_ptr);
+
+/**
+ * Joins and resolves `path_ptr` and `fname_ptr`, with home dir expansion.
+ *
+ * @param path_ptr            Path to use for joining. Paths that start with
+ *                            "~/" will be expanded with getenv("HOME").
+ * @param fname_ptr           Filename to join the path on.
+ * @param resolved_path_buf_ptr See the `resolved_path_buf_ptr` argument to
+ *                            @ref bs_file_resolve_path.
+ *
+ * @return Upon success, a pointer to the joined, expanded and resolved path,
+ *     of NULL on error. See @ref bs_file_resolve_path for details.
+ */
+char *bs_file_join_resolve_path(
+    const char *path_ptr,
+    const char *fname_ptr,
+    char *resolved_path_buf_ptr);
+
+/**
+ * Looks up a file from the set of provided paths, with path resolutio and
+ * home directory expansion..
+ *
+ * @param fname_ptr           Name of the file that shall be looked up.
+ * @param paths_ptr_ptr       A NULL-terminated array of paths to search. Paths
+ *                            starting with "~/" will be expanded as described
+ *                            by @ref bs_file_join_resolve_path.
+ * @param mode                Optional, indicates to only consider these types
+ *                            of the file. Matches the `st_mode` field of stat.
+ * @param resolved_path_buf_ptr See the `resolved_path_buf_ptr` argument to
+ *                            @ref bs_file_resolve_path.
+ *
+ * @return Upon success, a pointer to the joined, expanded and resolved path of
+ *     the first file found from a path & file name combination.
+ *     Otherwise, NULL on error, with errno set appropriately and for the last
+ *     of the attempted paths. See @ref bs_file_resolve_path for details.
+ */
+char *bs_file_resolve_and_lookup_from_paths(
+    const char *fname_ptr,
+    const char **paths_ptr_ptr,
+    int mode,
+    char *resolved_path_buf_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_file_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_FILE_H__ */
+/* == End of file.h ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/gfxbuf.c
@@ -0,0 +1,525 @@
+/* ========================================================================= */
+/**
+ * @file gfxbuf.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "gfxbuf.h"
+
+#include <inttypes.h>
+#include <libgen.h>
+#include <limits.h>
+#include <math.h>
+
+#include "log_wrappers.h"
+#include "test.h"
+#include "time.h"
+
+/* == Declarations ========================================================= */
+
+/** Internal handle of a graphics buffer. */
+typedef struct {
+    /** Publicly accessible elements. */
+    bs_gfxbuf_t               public;
+    /** Whether `data_ptr` is owned by @ref bs_gfxbuf_t `public`. */
+    bool                      managed;
+} bs_gfxbuf_internal_t;
+
+/** Returns the @ref bs_gfxbuf_internal_t for a @ref bs_gfxbuf_t. */
+static inline bs_gfxbuf_internal_t *internal_from_gfxbuf(
+    bs_gfxbuf_t *gfxbuf_ptr)
+{
+    return BS_CONTAINER_OF(gfxbuf_ptr, bs_gfxbuf_internal_t, public);
+}
+
+#ifdef HAVE_CAIRO
+/** Image format used for @ref bs_gfxbuf_t, translated to Cairo terms. */
+static const cairo_format_t   bs_gfx_cairo_image_format = CAIRO_FORMAT_ARGB32;
+#endif  // HAVE_CAIRO
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_gfxbuf_t *bs_gfxbuf_create(unsigned width, unsigned height)
+{
+    uint32_t *data_ptr = (uint32_t*)logged_calloc(1, 4 * width * height);
+    if (NULL == data_ptr) return NULL;
+
+    bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create_unmanaged(
+        width, height, width, data_ptr);
+    if (NULL == gfxbuf_ptr) {
+        free(data_ptr);
+    } else {
+        bs_gfxbuf_internal_t *gfxbuf_internal_ptr =
+            internal_from_gfxbuf(gfxbuf_ptr);
+        gfxbuf_internal_ptr->managed = true;
+    }
+    return gfxbuf_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_gfxbuf_t *bs_gfxbuf_create_unmanaged(unsigned width, unsigned height,
+                                        unsigned pixels_per_line,
+                                        uint32_t* data_ptr)
+{
+    bs_gfxbuf_internal_t *buf_internal_ptr = logged_calloc(
+        1, sizeof(bs_gfxbuf_internal_t));
+    if (NULL == buf_internal_ptr) return NULL;
+
+    buf_internal_ptr->public.width = width;
+    buf_internal_ptr->public.height = height;
+    buf_internal_ptr->public.pixels_per_line = pixels_per_line;
+    buf_internal_ptr->public.data_ptr = data_ptr;
+    buf_internal_ptr->managed = false;
+    return &buf_internal_ptr->public;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_gfxbuf_destroy(bs_gfxbuf_t *gfxbuf_ptr)
+{
+    bs_gfxbuf_internal_t *gfxbuf_internal_ptr =
+        internal_from_gfxbuf(gfxbuf_ptr);
+    if (gfxbuf_internal_ptr->managed &&
+        NULL != gfxbuf_internal_ptr->public.data_ptr) {
+        free(gfxbuf_internal_ptr->public.data_ptr);
+        gfxbuf_ptr->data_ptr = NULL;
+    }
+    free(gfxbuf_internal_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_gfxbuf_clear(bs_gfxbuf_t *gfxbuf_ptr, const uint32_t color)
+{
+    uint32_t *pixel_ptr;
+
+    if (color == 0) {
+        for (unsigned y = 0; y < gfxbuf_ptr->height; ++y) {
+            pixel_ptr = &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line];
+            // On amd64, using memset is ca 3x faster than filling self.
+            memset(pixel_ptr, 0, sizeof(uint32_t) * gfxbuf_ptr->width);
+        }
+    } else {
+        for (unsigned y = 0; y < gfxbuf_ptr->height; ++y) {
+            pixel_ptr = &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line];
+            unsigned width = gfxbuf_ptr->width;
+            while (width--) *pixel_ptr++ = color;
+        }
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_gfxbuf_copy(bs_gfxbuf_t *dest_gfxbuf_ptr,
+                    const bs_gfxbuf_t *src_gfxbuf_ptr)
+{
+    BS_ASSERT(src_gfxbuf_ptr->width == dest_gfxbuf_ptr->width);
+    BS_ASSERT(src_gfxbuf_ptr->height == dest_gfxbuf_ptr->height);
+
+    uint32_t *src_pixel_ptr, *dest_pixel_ptr;
+    for (unsigned y = 0; y < src_gfxbuf_ptr->height; ++y) {
+        src_pixel_ptr = &src_gfxbuf_ptr->data_ptr[
+            y * src_gfxbuf_ptr->pixels_per_line];
+        dest_pixel_ptr = &dest_gfxbuf_ptr->data_ptr[
+            y * dest_gfxbuf_ptr->pixels_per_line];
+
+#if 1
+        // On amd64, using memcpy is up to 3x faster.
+        memcpy(dest_pixel_ptr, src_pixel_ptr,
+               sizeof(uint32_t) * src_gfxbuf_ptr->width);
+#else
+        unsigned width = src_gfxbuf_ptr->width;
+        while (width--) *dest_pixel_ptr++ = *src_pixel_ptr++;
+#endif  // 1
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_gfxbuf_copy_area(
+    bs_gfxbuf_t *dest_gfxbuf_ptr,
+    unsigned dest_x,
+    unsigned dest_y,
+    const bs_gfxbuf_t *src_gfxbuf_ptr,
+    unsigned src_x,
+    unsigned src_y,
+    unsigned width,
+    unsigned height)
+{
+    // Sanity check, don't copy from/in outside the valid buffers.
+    if (src_gfxbuf_ptr->width <= src_x ||
+        src_gfxbuf_ptr->height <= src_y ||
+        dest_gfxbuf_ptr->width <= dest_x ||
+        dest_gfxbuf_ptr->height <= dest_y) {
+        return;
+    }
+
+    // Restrict area to buffer dimensions.
+    width = BS_MIN(dest_gfxbuf_ptr->width - dest_x,
+                   BS_MIN(src_gfxbuf_ptr->width - src_x, width));
+    height = BS_MIN(dest_gfxbuf_ptr->height - dest_y,
+                    BS_MIN(src_gfxbuf_ptr->height - src_y, height));
+
+    uint32_t *src_data_ptr, *dest_data_ptr;
+    for (unsigned y = 0; y < height; ++y) {
+        dest_data_ptr = &dest_gfxbuf_ptr->data_ptr[
+            (dest_y + y) * dest_gfxbuf_ptr->pixels_per_line + dest_x];
+        src_data_ptr = &src_gfxbuf_ptr->data_ptr[
+            (src_y + y) * src_gfxbuf_ptr->pixels_per_line + src_x];
+        memcpy(dest_data_ptr, src_data_ptr, sizeof(uint32_t) * width);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_gfxbuf_argb8888_to_floats(
+    const uint32_t argb8888,
+    float *red_ptr,
+    float *green_ptr,
+    float *blue_ptr,
+    float *alpha_ptr)
+{
+    *red_ptr = BS_MIN(1.0, ((argb8888 & 0xff0000) >> 0x10) / 255.0);
+    *green_ptr = BS_MIN(1.0, ((argb8888 & 0x00ff00) >> 0x8) / 255.0);
+    *blue_ptr = BS_MIN(1.0, ((argb8888 & 0x0000ff)) / 255.0);
+    if (NULL != alpha_ptr) {
+        *alpha_ptr = BS_MIN(1.0, ((argb8888 & 0xff000000) >> 0x18) / 255.0);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+#ifdef HAVE_CAIRO
+cairo_t *cairo_create_from_bs_gfxbuf(const bs_gfxbuf_t *gfxbuf_ptr)
+{
+    cairo_surface_t *cairo_surface_ptr = cairo_image_surface_create_for_data(
+        (unsigned char*)gfxbuf_ptr->data_ptr, bs_gfx_cairo_image_format,
+        gfxbuf_ptr->width, gfxbuf_ptr->height,
+        gfxbuf_ptr->pixels_per_line * sizeof(uint32_t));
+    if (NULL == cairo_surface_ptr) {
+        bs_log(
+            BS_ERROR,
+            "Failed cairo_image_surface_create_for_data(%p, %d, %u, %u, %zu)",
+            gfxbuf_ptr->data_ptr, bs_gfx_cairo_image_format,
+            gfxbuf_ptr->width, gfxbuf_ptr->height,
+            gfxbuf_ptr->pixels_per_line * sizeof(uint32_t));
+        return NULL;
+    }
+    cairo_t *cairo_ptr = cairo_create(cairo_surface_ptr);
+    cairo_surface_destroy(cairo_surface_ptr);
+    if (NULL == cairo_ptr) {
+        bs_log(BS_ERROR, "Failed cairo_create(%p)", cairo_surface_ptr);
+    }
+    return cairo_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void cairo_set_source_argb8888(
+    cairo_t *cairo_ptr,
+    uint32_t argb8888)
+{
+    float r, g, b, alpha;
+    bs_gfxbuf_argb8888_to_floats(argb8888, &r, &g, &b, &alpha);
+    cairo_set_source_rgba(cairo_ptr, r, g, b, alpha);
+
+}
+
+/* ------------------------------------------------------------------------- */
+/** TODO(kaeser@gubbe.ch): Change this to use libpng, and clean the code. */
+void bs_test_gfxbuf_equals_png_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    int line,
+    const bs_gfxbuf_t *gfxbuf_ptr,
+    const char *png_fname_ptr)
+{
+    if (NULL == png_fname_ptr) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line, "PNG file name is NULL");
+        return;
+    }
+
+    cairo_surface_t *png_surface_ptr =
+        cairo_image_surface_create_from_png(png_fname_ptr);
+    if (NULL == png_surface_ptr) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Failed cairo_image_surface_create_from_png(\"%s\")",
+            png_fname_ptr);
+        return;
+    }
+
+    if (CAIRO_STATUS_SUCCESS != cairo_surface_status(png_surface_ptr)) {
+        bs_test_fail_at(test_ptr, fname_ptr, line,
+                        "Failed to load PNG surface from \"%s\"",
+                        png_fname_ptr);
+    }
+
+    if ((unsigned)cairo_image_surface_get_width(png_surface_ptr) !=
+        gfxbuf_ptr->width) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "gfxbuf width %u != expected width %d. "
+            "Expected output: \"%s\", gfxbuf under test: \"%s\"",
+            gfxbuf_ptr->width,
+            cairo_image_surface_get_width(png_surface_ptr),
+            png_fname_ptr, "/tmp/out.png");
+    }
+    if ((unsigned)cairo_image_surface_get_height(png_surface_ptr) !=
+        gfxbuf_ptr->height) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "gfxbuf height %u != expected height %d. "
+            "Expected output: \"%s\", gfxbuf under test: \"%s\"",
+            gfxbuf_ptr->width,
+            cairo_image_surface_get_height(png_surface_ptr),
+            png_fname_ptr, "/tmp/out.png");
+    }
+    if ((unsigned)cairo_image_surface_get_stride(png_surface_ptr) <
+        gfxbuf_ptr->width * sizeof(uint32_t)) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "gfxbuf bytes per line (%u * %zu) lower than PNG stride %d. "
+            "Expected output: \"%s\", gfxbuf under test: \"%s\"",
+            gfxbuf_ptr->width, sizeof(uint32_t),
+            cairo_image_surface_get_stride(png_surface_ptr),
+            png_fname_ptr, "/tmp/out.png");
+    }
+
+    if (!bs_test_failed(test_ptr)) {
+        for (unsigned l = 0; l < gfxbuf_ptr->height; ++l) {
+            if (0 != memcmp(
+                    bs_gfxbuf_pixel_at(gfxbuf_ptr, 0, l),
+                    cairo_image_surface_get_data(png_surface_ptr) + (
+                        l * cairo_image_surface_get_stride(png_surface_ptr)),
+                    gfxbuf_ptr->width * sizeof(uint32_t))) {
+                bs_test_fail_at(
+                    test_ptr, fname_ptr, line,
+                    "gfxbuf content at line %u differs from expected PNG. "
+                    "Expected output: \"%s\", gfxbuf under test: \"%s\"",
+                    l, png_fname_ptr, "/tmp/out.png");
+            }
+        }
+    }
+
+    cairo_surface_destroy(png_surface_ptr);
+
+    if (!bs_test_failed(test_ptr)) return;
+
+    cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr);
+    if (NULL == cairo_ptr) {
+        bs_log(BS_ERROR, "Failed cairo_create_from_bs_gfxbuf");
+        return;
+    }
+    cairo_surface_t *surface_ptr = cairo_get_target(cairo_ptr);
+    if (NULL == cairo_ptr) {
+        bs_log(BS_ERROR, "Failed cairo_get_target");
+        cairo_destroy(cairo_ptr);
+        return;
+    }
+
+    cairo_status_t status = cairo_surface_write_to_png(surface_ptr, "/tmp/out.png");
+    if (CAIRO_STATUS_SUCCESS != status) {
+        bs_log(BS_ERROR, "Failed cairo_surface_write_to_png(%p, \"/tmp/out.png\")",
+               surface_ptr);
+    }
+
+    cairo_destroy(cairo_ptr);
+}
+
+#endif  // HAVE_CAIRO
+
+/* == Tests ================================================================ */
+
+static void test_copy_area(bs_test_t *test_ptr);
+static void test_argb8888_to_floats(bs_test_t *test_ptr);
+#ifdef HAVE_CAIRO
+static void test_cairo(bs_test_t *test_ptr);
+static void test_equals_png(bs_test_t *test_ptr);
+#endif  // HAVE_CAIRO
+
+const bs_test_case_t          bs_gfxbuf_test_cases[] = {
+    { 1, "copy_area", test_copy_area },
+    { 1, "argb8888_fo_floats", test_argb8888_to_floats },
+#ifdef HAVE_CAIRO
+    { 1, "cairo", test_cairo },
+    { 1, "equals_png", test_equals_png },
+#endif  // HAVE_CAIRO
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+void test_copy_area(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf1 = bs_gfxbuf_create(3, 3);
+    bs_gfxbuf_clear(buf1, 0x10203040);
+    bs_gfxbuf_t *buf2 = bs_gfxbuf_create(4, 4);
+
+    BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 1, 1));
+
+    bs_gfxbuf_copy_area(buf2, 1, 1, buf1, 1, 1, 3, 3);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 0, 0));
+    BS_TEST_VERIFY_EQ(test_ptr, 0X10203040, *bs_gfxbuf_pixel_at(buf2, 1, 1));
+    BS_TEST_VERIFY_EQ(test_ptr, 0X10203040, *bs_gfxbuf_pixel_at(buf2, 2, 2));
+    BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 3, 3));
+
+    bs_gfxbuf_destroy(buf2);
+    bs_gfxbuf_destroy(buf1);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Verifies that cairo_util_argb8888_to_floats behaves properly */
+void test_argb8888_to_floats(bs_test_t *test_ptr) {
+    float r, g, b, alpha;
+    bs_gfxbuf_argb8888_to_floats(0, &r, &g, &b, &alpha);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, r);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, g);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, b);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, alpha);
+
+    bs_gfxbuf_argb8888_to_floats(0xffffffff, &r, &g, &b, &alpha);
+    BS_TEST_VERIFY_EQ(test_ptr, 1.0, r);
+    BS_TEST_VERIFY_EQ(test_ptr, 1.0, g);
+    BS_TEST_VERIFY_EQ(test_ptr, 1.0, b);
+    BS_TEST_VERIFY_EQ(test_ptr, 1.0, alpha);
+
+    // Floating point - we're fine with a near-enough value.
+    bs_gfxbuf_argb8888_to_floats(0xffc08040, &r, &g, &b, NULL);
+    BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(r - 0.7529));
+    BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(g - 0.5020));
+    BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(b - 0.2510));
+}
+
+#ifdef HAVE_CAIRO
+/* ------------------------------------------------------------------------- */
+void test_cairo(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf = bs_gfxbuf_create(1, 1);
+    cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(buf);
+
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr);
+    // Color is 0 after initialization.
+    BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf, 0, 0));
+
+    cairo_set_source_rgba(cairo_ptr, 1.0, 1.0, 1.0, 1.0);
+    cairo_rectangle(cairo_ptr, 0, 0, 1, 1);
+    cairo_fill(cairo_ptr);
+
+    // Cairo should have filled it with white.
+    BS_TEST_VERIFY_EQ(test_ptr, 0xffffffff, *bs_gfxbuf_pixel_at(buf, 0, 0));
+
+    cairo_destroy(cairo_ptr);
+    bs_gfxbuf_destroy(buf);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_equals_png(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf = bs_gfxbuf_create(1, 1);
+    bs_gfxbuf_clear(buf, 0xff804020);
+    bs_test_gfxbuf_equals_png_at(
+        test_ptr, __FILE__, __LINE__, buf,
+        bs_test_resolve_path("testdata/gfxbuf_equals.png"));
+    bs_gfxbuf_destroy(buf);
+}
+#endif  // HAVE_CAIRO
+
+/* == Benchmarks =========================================================== */
+
+static void benchmark_clear(bs_test_t *test_ptr);
+static void benchmark_clear_nonblack(bs_test_t *test_ptr);
+static void benchmark_copy(bs_test_t *test_ptr);
+
+/* set benchmarks to last for 2.5s each */
+static const uint64_t benchmark_duration = 2500000;
+
+const bs_test_case_t          bs_gfxbuf_benchmarks[] = {
+    { 1, "benchmark-gfxbuf_clear-black", benchmark_clear },
+    { 1, "benchmark-gfxbuf_clear-nonblack", benchmark_clear_nonblack },
+    { 1, "benchmark-gfxbuf_copy", benchmark_copy },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+static void benchmark_clear(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(1024, 768);
+    if (NULL == buf_ptr) {
+        BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)");
+        return;
+    }
+
+    uint64_t usec = bs_usec();
+    unsigned iterations = 0;
+    while (usec + benchmark_duration >= bs_usec()) {
+        bs_gfxbuf_clear(buf_ptr, 0);
+        iterations++;
+    }
+    usec = bs_usec() - usec;
+
+    bs_test_succeed(test_ptr, "bs_gfxbuf_clear: %.3e pix/sec - %"PRIu64"us",
+                    (double)iterations * 1024 * 768 / (usec * 1e-6), usec);
+    bs_gfxbuf_destroy(buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+static void benchmark_clear_nonblack(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(1024, 768);
+    if (NULL == buf_ptr) {
+        BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)");
+        return;
+    }
+
+    uint64_t usec = bs_usec();
+    unsigned iterations = 0;
+    while (usec + benchmark_duration >= bs_usec()) {
+        bs_gfxbuf_clear(buf_ptr, 0x204080ff);
+        iterations++;
+    }
+    usec = bs_usec() - usec;
+
+    bs_test_succeed(test_ptr, "bs_gfxbuf_clear: %.3e pix/sec - %"PRIu64"us",
+                    (double)iterations * 1024 * 768 / (usec * 1e-6), usec);
+    bs_gfxbuf_destroy(buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+static void benchmark_copy(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf_1_ptr = bs_gfxbuf_create(1024, 768);
+    if (NULL == buf_1_ptr) {
+        BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)");
+        return;
+    }
+    bs_gfxbuf_t *buf_2_ptr = bs_gfxbuf_create(1024, 768);
+    if (NULL == buf_2_ptr) {
+        BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)");
+        bs_gfxbuf_destroy(buf_1_ptr);
+        return;
+    }
+
+    uint64_t usec = bs_usec();
+    unsigned iterations = 0;
+    while (usec + benchmark_duration >= bs_usec()) {
+        bs_gfxbuf_copy(buf_2_ptr, buf_1_ptr);
+        iterations++;
+    }
+    usec = bs_usec() - usec;
+
+    bs_test_succeed(test_ptr, "bs_gfxbuf_copy: %.3e pix/sec",
+                    (double)iterations * 1024 * 768 / (usec * 1e-6));
+    bs_gfxbuf_destroy(buf_1_ptr);
+    bs_gfxbuf_destroy(buf_2_ptr);
+}
+
+/* == End of gfxbuf.c ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/gfxbuf.h
@@ -0,0 +1,247 @@
+/* ========================================================================= */
+/**
+ * @file gfxbuf.h
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_GFXBUF_H__
+#define __LIBBASE_GFXBUF_H__
+
+#include <stdint.h>
+
+#include "assert.h"
+
+#ifdef HAVE_CAIRO
+#include <cairo.h>
+#endif  // HAVE_CAIRO
+
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** A graphics buffer. */
+typedef struct {
+    /** Width, in pixels. */
+    unsigned                  width;
+    /** Height, in pixels. */
+    unsigned                  height;
+    /** Pixels per line. */
+    unsigned                  pixels_per_line;
+    /** The pixels buffer. Has `height` * `pixels_per_line` elements. */
+    uint32_t                  *data_ptr;
+} bs_gfxbuf_t;
+
+/**
+ * Creates a graphics buffer @ref bs_gfxbuf_t.
+ *
+ * @param width
+ * @param height
+ *
+ * @return A pointer to @ref bs_gfxbuf_t. Must be free'd by
+ *     @ref bs_gfxbuf_destroy().
+ */
+bs_gfxbuf_t *bs_gfxbuf_create(unsigned width, unsigned height);
+
+/**
+ * Creates a graphics buffer @ref bs_gfxbuf_t.
+ *
+ * Will use the existing pixels buffer at `data_ptr`, and expects it to remain
+ * available throughout the lifetime of the created @ref bs_gfxbuf_t.
+ *
+ * @param width
+ * @param height
+ * @param pixels_per_line
+ * @param data_ptr
+ */
+bs_gfxbuf_t *bs_gfxbuf_create_unmanaged(unsigned width, unsigned height,
+                                        unsigned pixels_per_line,
+                                        uint32_t* data_ptr);
+
+/**
+ * Destroys the graphics buffer.
+ *
+ * @param gfxbuf_ptr
+ */
+void bs_gfxbuf_destroy(bs_gfxbuf_t *gfxbuf_ptr);
+
+/**
+ * Clears the graphics buffer with the specified `color`.
+ *
+ * @param gfxbuf_ptr
+ * @param color               The fill color, as an ARGB 8888.
+ */
+void bs_gfxbuf_clear(bs_gfxbuf_t *gfxbuf_ptr, const uint32_t color);
+
+/**
+ * Copies the contents of `src_gfxbuf_ptr` to `dest_gfxbuf_ptr`.
+ *
+ * Expects width and height of `src_gfxbuf_ptr` and `dest_gfxbuf_ptr` the same.
+ *
+ * @param src_gfxbuf_ptr
+ * @param dest_gfxbuf_ptr
+ */
+void bs_gfxbuf_copy(bs_gfxbuf_t *dest_gfxbuf_ptr,
+                    const bs_gfxbuf_t *src_gfxbuf_ptr);
+
+/**
+ * Copies a rectangular area between graphics buffers.
+ *
+ * The width and height of the rectangular area will be truncated to fit both
+ * source and destination buffers.
+ *
+ * @param dest_gfxbuf_ptr     Destination graphics buffer.
+ * @param dest_x              Destination coordinate.
+ * @param dest_y              Destination coordinate.
+ * @param src_gfxbuf_ptr      Source graphics buffere.
+ * @param src_x               Source coordinate.
+ * @param src_y               Source coordinate.
+ * @param width               Width of the rectangle to copy.
+ * @param height              Height of the rectangle to copy.
+ */
+void bs_gfxbuf_copy_area(
+    bs_gfxbuf_t *dest_gfxbuf_ptr,
+    unsigned dest_x,
+    unsigned dest_y,
+    const bs_gfxbuf_t *src_gfxbuf_ptr,
+    unsigned src_x,
+    unsigned src_y,
+    unsigned width,
+    unsigned height);
+
+/**
+ * Returns a pointer to the pixel at the given coordinates.
+ *
+ * @param gfxbuf_ptr
+ * @param x
+ * @param y
+ *
+ * @return A pointer to the pixel at the specified position.
+ */
+static inline uint32_t *bs_gfxbuf_pixel_at(
+    const bs_gfxbuf_t *gfxbuf_ptr,
+    unsigned x,
+    unsigned y) {
+    BS_ASSERT(x < gfxbuf_ptr->width);
+    BS_ASSERT(y < gfxbuf_ptr->height);
+    return &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line + x];
+}
+
+/**
+ * Colors the pixel at the given coordinates.
+ *
+ * @param gfxbuf_ptr
+ * @param x
+ * @param y
+ * @param color               Color, in ARGB8888 format.
+ */
+static inline void bs_gfxbuf_set_pixel(
+    bs_gfxbuf_t *gfxbuf_ptr,
+    unsigned x,
+    unsigned y,
+    uint32_t color) {
+    *bs_gfxbuf_pixel_at(gfxbuf_ptr, x, y) = color;
+}
+
+/**
+ * Computes the red, green, blue and alpha components as floating points from
+ * the given `argb8888` value.
+ *
+ * @param argb8888            Color, in ARGB 8888 format.
+ * @param red_ptr             Output value, will be clamped to [0, 1].
+ * @param blue_ptr            Output value, will be clamped to [0, 1].
+ * @param green_ptr           Output value, will be clamped to [0, 1].
+ * @param alpha_ptr           Output value, will be clamped to [0, 1].
+ */
+void bs_gfxbuf_argb8888_to_floats(
+    const uint32_t argb8888,
+    float *red_ptr,
+    float *green_ptr,
+    float *blue_ptr,
+    float *alpha_ptr);
+
+#ifdef HAVE_CAIRO
+/**
+ * Creates a Cairo drawing context for the @ref bs_gfxbuf_t.
+ *
+ * This is a convenience function that permits drawing into the graphics buffer
+ * using the Cairo 2D graphics library (https://cairographics.org/).
+ *
+ * @param gfxbuf_ptr          Graphics buffer. Must outlive the returned
+ *                            `cairo_t`.
+ *
+ * @return A cairo drawing context, or NULL on error. The returned context must
+ *     be destroyed by calling cairo_destroy().
+ */
+cairo_t *cairo_create_from_bs_gfxbuf(const bs_gfxbuf_t *gfxbuf_ptr);
+
+/**
+ * Sets the source color for the cairo at `cairo_ptr`.
+ *
+ * @param cairo_ptr
+ * @param argb8888            Color, in ARGB 8888 format.
+ */
+void cairo_set_source_argb8888(
+    cairo_t *cairo_ptr,
+    uint32_t argb8888);
+
+/**
+ * Tests whether the graphics is equal to the contents of the PNG file.
+ *
+ * @param test_ptr
+ * @paran fname_ptr
+ * @paran line
+ * @param gfxbuf_ptr
+ * @param png_fname_ptr       Name of the PNG file. The test will report as
+ *                            failed if `png_fname_ptr` is NULL.
+ */
+void bs_test_gfxbuf_equals_png_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    int line,
+    const bs_gfxbuf_t *gfxbuf_ptr,
+    const char *png_fname_ptr);
+
+/**
+ * Tests whether @ref bs_gfxbuf_t equals the PNG file.
+ *
+ * @param _test               The @ref bs_test_t of the current test case.
+ * @param _gfxbuf_ptr         A @ref bs_gfxbuf_t, the graphics buffer to test.
+ * @param _png_name           Path to the PNG file name. Relative paths will be
+ *                            resolved using @ref bs_test_resolve_path.
+ */
+#define BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(_test, _gfxbuf_ptr, _png_name) { \
+        bs_test_gfxbuf_equals_png_at(                                   \
+            (_test), __FILE__, __LINE__, (_gfxbuf_ptr),                 \
+            bs_test_resolve_path(_png_name));                           \
+    }
+
+#endif  // HAVE_CAIRO
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_gfxbuf_test_cases[];
+
+/** Benchmarks, in the form of an unit test. */
+extern const bs_test_case_t   bs_gfxbuf_benchmarks[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_GFXBUF_H__ */
+/* == End of gfxbuf.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/gfxbuf_xpm.c
@@ -0,0 +1,443 @@
+/* ========================================================================= */
+/**
+ * @file gfxbuf_xpm.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "gfxbuf_xpm.h"
+
+#include <ctype.h>
+
+#include "avltree.h"
+#include "strutil.h"
+
+/* == Declarations ========================================================= */
+
+/** Node for storing & looking up colors from the XPM. */
+typedef struct {
+    /** Tree node. */
+    bs_avltree_node_t         node;
+    /** The characters that define this color. */
+    char                      *pixel_chars_ptr;
+    /** Number of caracters that make up the pixel. */
+    unsigned                  chars_per_pixel;
+    /** Corresponding color, in ARGB8888. */
+    uint32_t                  color;
+} bs_gfxbuf_xpm_color_node_t;
+
+static bool _bs_gfxbuf_xpm_copy_data(
+    bs_gfxbuf_t *gfxbuf_ptr,
+    char **xpm_data_ptr,
+    unsigned dest_x,
+    unsigned dest_y);
+
+static bool _bs_gfxbuf_xpm_parse_header_line(
+    const char *header_line_ptr,
+    unsigned *width_ptr,
+    unsigned *height_ptr,
+    unsigned *colors_ptr,
+    unsigned *chars_per_pixel_ptr);
+static bool _bs_gfxbuf_xpm_parse_color_into_node(
+    bs_gfxbuf_xpm_color_node_t *node_ptr,
+    unsigned chars_per_pixel,
+    const char* color_line_ptr);
+
+static bs_gfxbuf_xpm_color_node_t *_bs_gfxbuf_xpm_color_node_create(
+    unsigned chars_per_pixel);
+static void _bs_gfxbuf_xpm_color_node_destroy(
+    bs_avltree_node_t *node_ptr);
+static int _bs_gfxbuf_xpm_color_node_cmp(
+    const bs_avltree_node_t *node_ptr,
+    const void *key_ptr);
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_gfxbuf_t *bs_gfxbuf_xpm_create_from_data(char **xpm_data_ptr)
+{
+    unsigned width, height, colors, chars_per_pixel;
+    if (!_bs_gfxbuf_xpm_parse_header_line(
+            *xpm_data_ptr, &width, &height, &colors, &chars_per_pixel)) {
+        return NULL;
+    }
+
+    bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(width, height);
+    if (_bs_gfxbuf_xpm_copy_data(gfxbuf_ptr, xpm_data_ptr, 0, 0)) {
+        return gfxbuf_ptr;
+    }
+    bs_gfxbuf_destroy(gfxbuf_ptr);
+    return NULL;
+}
+
+/* == Local (static) methods =============================================== */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Loads an XPM from included data at `xpm_data_ptr` to the position
+ * `dest_x`, `dest_y` into `gfxbuf_ptr`. Will not overwrite pixels where the
+ * XPM is transparent (color: None).
+ *
+ * @param gfxbuf_ptr
+ * @param xpm_data_ptr
+ * @param dest_x
+ * @param dest_y
+ *
+ * @return true on success.
+ */
+bool _bs_gfxbuf_xpm_copy_data(
+    bs_gfxbuf_t *gfxbuf_ptr,
+    char **xpm_data_ptr,
+    unsigned dest_x,
+    unsigned dest_y)
+{
+    unsigned width, height, colors, chars_per_pixel;
+    bs_gfxbuf_xpm_color_node_t *color_node_ptr;
+
+    // Guard clause.
+    if (NULL == gfxbuf_ptr) return false;
+
+    if (!_bs_gfxbuf_xpm_parse_header_line(
+            *xpm_data_ptr, &width, &height, &colors, &chars_per_pixel)) {
+        return false;
+    }
+
+    // Prevent the XPM from spilling beyond the buffer.
+    if (width + dest_x > gfxbuf_ptr->width) {
+        width = gfxbuf_ptr->width - dest_x;
+    }
+    if (height + dest_y > gfxbuf_ptr->height) {
+        height = gfxbuf_ptr->height - dest_y;
+    }
+    xpm_data_ptr++;
+
+    // Parse the colors and store htem in the lookup tree.
+    bs_avltree_t *tree_ptr = bs_avltree_create(
+        _bs_gfxbuf_xpm_color_node_cmp, _bs_gfxbuf_xpm_color_node_destroy);
+    if (NULL == tree_ptr) {
+        bs_log(BS_ERROR, "Failed bs_avltree_create");
+        return false;
+    }
+    for (unsigned color = 0; color < colors; ++color, ++xpm_data_ptr) {
+        color_node_ptr = _bs_gfxbuf_xpm_color_node_create(chars_per_pixel);
+        if (NULL != color_node_ptr) {
+            if (_bs_gfxbuf_xpm_parse_color_into_node(
+                    color_node_ptr, chars_per_pixel, *xpm_data_ptr)) {
+                if (bs_avltree_insert(
+                        tree_ptr,
+                        color_node_ptr->pixel_chars_ptr,
+                        &color_node_ptr->node,
+                        false)) {
+                    continue;
+                }
+                bs_log(BS_ERROR, "Color \"%s\" already exists", *xpm_data_ptr);
+            }
+            _bs_gfxbuf_xpm_color_node_destroy(&color_node_ptr->node);
+        }
+        bs_avltree_destroy(tree_ptr);
+        return false;
+    }
+
+    // Now, parse the XPM pixels.
+    bool outcome = true;
+    for (unsigned y = 0; y < height; ++y, ++xpm_data_ptr) {
+        if (width * chars_per_pixel > strlen(*xpm_data_ptr)) {
+            bs_log(BS_ERROR, "Shorter than %u chars: \"%s\"",
+                   width * chars_per_pixel, *xpm_data_ptr);
+            outcome = false;
+            break;
+        }
+        for (unsigned x = 0; x < width; ++x) {
+            bs_gfxbuf_xpm_color_node_t *color_node_ptr =
+                (bs_gfxbuf_xpm_color_node_t*)bs_avltree_lookup(
+                    tree_ptr,
+                    *xpm_data_ptr + x * chars_per_pixel);
+            // Ignore transparent pixels.
+            if (color_node_ptr->color != 0x00000000) {
+                bs_gfxbuf_set_pixel(
+                    gfxbuf_ptr,
+                    dest_x + x, dest_y + y,
+                    color_node_ptr->color);
+            }
+        }
+    }
+
+    bs_avltree_destroy(tree_ptr);
+    return outcome;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Parses the XPM header line. */
+bool _bs_gfxbuf_xpm_parse_header_line(
+    const char *header_line_ptr,
+    unsigned *width_ptr,
+    unsigned *height_ptr,
+    unsigned *colors_ptr,
+    unsigned *chars_per_pixel_ptr)
+{
+    int                       scanned_values;
+
+    scanned_values = sscanf(
+        header_line_ptr, "%u %u %u %u",
+        width_ptr, height_ptr, colors_ptr, chars_per_pixel_ptr);
+    if (0 > scanned_values) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed sscanf(%s, \"%%u %%u %%u %%u\")",
+               header_line_ptr);
+        return false;
+    }
+    if (4 != scanned_values) {
+        bs_log(BS_ERROR, "Failed to match & assign 4 input values for "
+               "sscanf(%s, \"%%u %%u %%u %%u\")", header_line_ptr);
+        return false;
+    }
+
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Parses an XPM color line into `color_node_ptr`.
+ *
+ * It has the format <characters> (<representation> <color>)+.
+ */
+bool _bs_gfxbuf_xpm_parse_color_into_node(
+    bs_gfxbuf_xpm_color_node_t *color_node_ptr,
+    unsigned chars_per_pixel,
+    const char* color_line_ptr)
+{
+    // Sanity check: String must at least hold the color.
+    if (chars_per_pixel > strlen(color_line_ptr)) {
+        bs_log(BS_ERROR, "shorter than %u chars: \"%s\"",
+               chars_per_pixel, color_line_ptr);
+        return false;
+    }
+
+    // Copy the <character> component.
+    BS_ASSERT(color_node_ptr->chars_per_pixel == chars_per_pixel);
+    memcpy(color_node_ptr->pixel_chars_ptr, color_line_ptr, chars_per_pixel);
+
+    // Then, skip over it and over succeeding whitespace.
+    color_line_ptr += chars_per_pixel;
+    if (!isspace(*color_line_ptr)) {
+        bs_log(BS_ERROR, "Whitespace missing after <characters>");
+        return false;
+    }
+    while (*color_line_ptr && isspace(*color_line_ptr)) color_line_ptr++;
+
+    // Check <representation>. We only support 'c' for "color".
+    if ('c' != *color_line_ptr) {
+        bs_log(BS_ERROR, "Unsupported color representation: \"%s\"",
+               color_line_ptr);
+        return false;
+    }
+
+    // Again, skip over it and over succeeding whitespace.
+    color_line_ptr++;
+    while (*color_line_ptr && isspace(*color_line_ptr)) color_line_ptr++;
+
+    // The <color>. If it's "None", it means the pixel is transparent.
+    if (0 == strncmp(color_line_ptr, "None", 4)) {
+        color_node_ptr->color = 0;
+        return true;
+    }
+
+    // If the <color> start with '#', it is a RGB representation.
+    if (*color_line_ptr == '#') {
+        size_t alnum_count = 0;
+        while (isalnum(color_line_ptr[1 + alnum_count])) ++alnum_count;
+        if (6 != alnum_count) {
+            bs_log(BS_ERROR, "Not a #RRGGBB representation: %s",
+                   color_line_ptr + 1);
+            return false;
+        }
+
+        uint64_t tmp_value;
+        if (!bs_strconvert_uint64(color_line_ptr + 1, &tmp_value, 16)) {
+            return false;
+        }
+        if (tmp_value > 0xffffff) {
+            bs_log(BS_ERROR, "Color value out of range: 0x%"PRIx64,
+                   tmp_value);
+            return false;
+        }
+        color_node_ptr->color = tmp_value | 0xff000000;
+
+        return true;
+    }
+
+    bs_log(BS_ERROR, "Unsupported color encoding: %s", color_line_ptr + 1);
+    return false;
+}
+
+/* ------------------------------------------------------------------------- */
+bs_gfxbuf_xpm_color_node_t *_bs_gfxbuf_xpm_color_node_create(
+    unsigned chars_per_pixel)
+{
+    bs_gfxbuf_xpm_color_node_t *color_node_ptr = calloc(
+        1, sizeof(bs_gfxbuf_xpm_color_node_t));
+    if (NULL == color_node_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %zu)",
+               sizeof(bs_gfxbuf_xpm_color_node_t));
+        return NULL;
+    }
+
+    color_node_ptr->pixel_chars_ptr = calloc(1, chars_per_pixel);
+    if (NULL == color_node_ptr->pixel_chars_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %u)", chars_per_pixel);
+        _bs_gfxbuf_xpm_color_node_destroy(&color_node_ptr->node);
+        return NULL;
+    }
+    color_node_ptr->chars_per_pixel = chars_per_pixel;
+    return color_node_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void _bs_gfxbuf_xpm_color_node_destroy(bs_avltree_node_t *node_ptr)
+{
+    bs_gfxbuf_xpm_color_node_t *color_node_ptr =
+        (bs_gfxbuf_xpm_color_node_t*)node_ptr;
+    if (NULL != color_node_ptr->pixel_chars_ptr) {
+        free(color_node_ptr->pixel_chars_ptr);
+        color_node_ptr->pixel_chars_ptr = NULL;
+    }
+    free(color_node_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+int _bs_gfxbuf_xpm_color_node_cmp(const bs_avltree_node_t *node_ptr,
+                                  const void *key_ptr) {
+    bs_gfxbuf_xpm_color_node_t *color_node_ptr =
+        (bs_gfxbuf_xpm_color_node_t*)node_ptr;
+    return memcmp(color_node_ptr->pixel_chars_ptr,
+                  (const char*)key_ptr,
+                  color_node_ptr->chars_per_pixel);
+}
+
+/* == Unit tests =========================================================== */
+
+static void test_parse_color(bs_test_t *test_ptr);
+static void test_parse_xpm(bs_test_t *test_ptr);
+static void test_create_xpm(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_gfxbuf_xpm_test_cases[] = {
+    { 1, "parse_color", test_parse_color },
+    { 1, "parse_xpm", test_parse_xpm },
+    { 1, "create_xpm", test_create_xpm },
+    { 0, NULL, NULL }
+};
+
+static char *test_xpm_data[] = {
+    "2 2 3 1",
+    "  c None",
+    ". c #0000ff",
+    "+ c #000000",
+    ".+",
+    "+ "};
+
+/* ------------------------------------------------------------------------- */
+void test_parse_color(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_xpm_color_node_t cnode;
+    char pixel_chars[2];
+
+    cnode.pixel_chars_ptr = &pixel_chars[0];
+    cnode.chars_per_pixel = 2;
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "xy c #123456"));
+    BS_TEST_VERIFY_EQ(test_ptr, 0xff123456, cnode.color);
+    BS_TEST_VERIFY_EQ(test_ptr, 'x', cnode.pixel_chars_ptr[0]);
+    BS_TEST_VERIFY_EQ(test_ptr, 'y', cnode.pixel_chars_ptr[1]);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "xy c #ffffff"));
+    BS_TEST_VERIFY_EQ(test_ptr, 0xffffffff, cnode.color);
+    BS_TEST_VERIFY_EQ(test_ptr, 'x', cnode.pixel_chars_ptr[0]);
+    BS_TEST_VERIFY_EQ(test_ptr, 'y', cnode.pixel_chars_ptr[1]);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c None"));
+    BS_TEST_VERIFY_EQ(test_ptr, cnode.color, 0x00000000);
+    BS_TEST_VERIFY_EQ(test_ptr, 'a', cnode.pixel_chars_ptr[0]);
+    BS_TEST_VERIFY_EQ(test_ptr, 'b', cnode.pixel_chars_ptr[1]);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "a  c None"));
+    BS_TEST_VERIFY_EQ(test_ptr, cnode.color, 0x00000000);
+    BS_TEST_VERIFY_EQ(test_ptr, 'a', cnode.pixel_chars_ptr[0]);
+    BS_TEST_VERIFY_EQ(test_ptr, ' ', cnode.pixel_chars_ptr[1]);
+
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "t c None"));
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "abc c None"));
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #12345"));
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #1234567"));
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #12xx56"));
+}
+
+/* ------------------------------------------------------------------------- */
+void test_parse_xpm(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(3, 3);
+    bs_gfxbuf_clear(buf_ptr, 42);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _bs_gfxbuf_xpm_copy_data(buf_ptr, test_xpm_data, 1, 1));
+
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 0));
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 1, 0));
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 2, 0));
+
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 1));
+    BS_TEST_VERIFY_EQ(test_ptr, 0xff0000ff, *bs_gfxbuf_pixel_at(buf_ptr, 1, 1));
+    BS_TEST_VERIFY_EQ(test_ptr, 0xff000000, *bs_gfxbuf_pixel_at(buf_ptr, 2, 1));
+
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 2));
+    BS_TEST_VERIFY_EQ(test_ptr, 0xff000000, *bs_gfxbuf_pixel_at(buf_ptr, 1, 2));
+    BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 2, 2));
+
+    bs_gfxbuf_destroy(buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_create_xpm(bs_test_t *test_ptr)
+{
+    bs_gfxbuf_t *buf_ptr;
+
+    buf_ptr = bs_gfxbuf_xpm_create_from_data(test_xpm_data);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, buf_ptr);
+    if (buf_ptr != NULL) {
+        BS_TEST_VERIFY_EQ(test_ptr, 2, buf_ptr->width);
+        BS_TEST_VERIFY_EQ(test_ptr, 2, buf_ptr->height);
+        bs_gfxbuf_destroy(buf_ptr);
+    }
+}
+
+/* == End of gfxbuf_xpm.c ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/gfxbuf_xpm.h
@@ -0,0 +1,48 @@
+/* ========================================================================= */
+/**
+ * @file gfxbuf_xpm.h
+ * Implements a simple XPM loader for compiled-in XPM images.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __GFXBUF_XPM_H__
+#define __GFXBUF_XPM_H__
+
+#include "gfxbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Creates a @ref bs_gfxbuf_t from the XPM data at `xpm_data_ptr`.
+ *
+ * @param xpm_data_ptr
+ *
+ * @return A new @ref bs_gfxbuf_t, or NULL on error. Must be destroyed by
+ *     calling @ref bs_gfxbuf_destroy.
+ */
+bs_gfxbuf_t *bs_gfxbuf_xpm_create_from_data(char **xpm_data_ptr);
+
+/** Unit test cases. */
+extern const bs_test_case_t bs_gfxbuf_xpm_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __GFXBUF_XPM_H__ */
+/* == End of gfxbuf_xpm.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/libbase.h
@@ -0,0 +1,49 @@
+/* ========================================================================= */
+/**
+ * @file libbase.h
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_H__
+#define __LIBBASE_H__
+
+#include "arg.h"
+#include "assert.h"
+#include "atomic.h"
+#include "avltree.h"
+#include "c2x_compat.h"
+#include "def.h"
+#include "dequeue.h"
+#include "dllist.h"
+#include "file.h"
+#include "gfxbuf.h"
+#include "gfxbuf_xpm.h"
+#include "log.h"
+#include "log_wrappers.h"
+#include "ptr_set.h"
+#include "ptr_stack.h"
+#include "ptr_vector.h"
+#include "subprocess.h"
+#include "sock.h"
+#include "strutil.h"
+#include "thread.h"
+#include "time.h"
+#include "vector.h"
+
+#include "test.h"
+
+#endif /* __LIBBASE_H__ */
+/* == End of libbase.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/libbase.pc.in
@@ -0,0 +1,24 @@
+# Copyright 2023 Google LLC
+#
+# 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
+#
+# https://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.
+
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+Name: @PROJECT_NAME@
+Description: @PROJECT_DESCRIPTION@
+Version: @PROJECT_VERSION@
+Requires:
+Libs: -L${libdir} -l@PROJECT_NAME@
+Cflags: -I${includedir}
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/libbase_benchmark.c
@@ -0,0 +1,36 @@
+/* ========================================================================= */
+/**
+ * @file libbase_benchmark.c
+ * Benchmarks for libbase (in the form of unit test cases).
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <libbase/libbase.h>
+
+/** Unit tests. */
+const bs_test_set_t           libbase_benchmarks[] = {
+    { 1, "bs_gfxbuf", bs_gfxbuf_benchmarks },
+    { 0, NULL, NULL }
+};
+
+/** Main program, runs all unit tests. */
+int main(int argc, const char **argv)
+{
+    return bs_test(libbase_benchmarks, argc, argv, NULL);
+}
+
+/* == End of libbase_benchmark.c =========================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/libbase_test.c
@@ -0,0 +1,88 @@
+/* ========================================================================= */
+/**
+ * @file libbase_test.c
+ * Unit tests for libbase.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <libbase/libbase.h>
+
+static void test_assert(bs_test_t *test_ptr);
+static void test_def(bs_test_t *test_ptr);
+
+/** Further tests of definitions, without .c file. */
+static const bs_test_case_t bs_header_only_test_cases[] = {
+    { 1, "assert", test_assert },
+    { 1, "def", test_def },
+    { 0, NULL, NULL }
+};
+
+/** Unit tests. */
+const bs_test_set_t           libbase_tests[] = {
+    { 1, "atomic", bs_atomic_test_cases },
+    { 1, "arg", bs_arg_test_cases },
+    { 1, "avltree", bs_avltree_test_cases },
+    { 1, "dequeue", bs_dequeue_test_cases },
+    { 1, "dllist", bs_dllist_test_cases },
+    { 1, "file", bs_file_test_cases },
+    { 1, "gfxbuf", bs_gfxbuf_test_cases },
+    { 1, "gfxbuf_xpm", bs_gfxbuf_xpm_test_cases },
+    { 1, "header_only", bs_header_only_test_cases },
+    { 1, "log", bs_log_test_cases },
+    { 1, "ptr_set", bs_ptr_set_test_cases },
+    { 1, "ptr_stack", bs_ptr_stack_test_cases },
+    { 1, "ptr_vector", bs_ptr_vector_test_cases },
+    { 1, "subprocess", bs_subprocess_test_cases },
+    { 1, "strutil", bs_strutil_test_cases },
+    { 1, "test", bs_test_test_cases },
+    { 1, "time", bs_time_test_cases },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+/** Tests the functions of 'assert.h' */
+void test_assert(bs_test_t *test_ptr)
+{
+    void *ptr = test_assert;
+    BS_TEST_VERIFY_EQ(test_ptr, ptr, BS_ASSERT_NOTNULL(ptr));
+}
+
+/* ------------------------------------------------------------------------- */
+/** Tests the functions of 'def.h' */
+void test_def(bs_test_t *test_ptr)
+{
+    BS_TEST_VERIFY_EQ(test_ptr, 1, BS_MIN(1, 2));
+    BS_TEST_VERIFY_EQ(test_ptr, 2, BS_MAX(1, 2));
+}
+
+#if !defined(BS_TEST_DATA_DIR)
+/** Directory root for looking up test data. See @ref bs_test_resolve_path. */
+#define BS_TEST_DATA_DIR "./"
+#endif  // BS_TEST_DATA_DIR
+
+/* ========================================================================= */
+/** Main program, runs all unit tests. */
+int main(int argc, const char **argv)
+{
+    const bs_test_param_t params = {
+        .test_data_dir_ptr   = BS_TEST_DATA_DIR
+    };
+
+    return bs_test(libbase_tests, argc, argv, &params);
+}
+
+/* == End of libbase_test.c ================================================ */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/log.c
@@ -0,0 +1,306 @@
+/* ========================================================================= */
+/**
+ * @file log.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "assert.h"
+#include "log.h"
+#include "sock.h"
+#include "strutil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+/* == Data ================================================================= */
+
+bs_log_severity_t      bs_log_severity = BS_WARNING;
+
+static const char*     _severity_names[5] = {
+    "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"};
+
+static int             _log_fd = 2;
+
+static const char *_strip_prefix(const char *path_ptr);
+
+/* == Functions ============================================================ */
+
+/* ------------------------------------------------------------------------- */
+bool bs_log_init_file(const char *log_filename_ptr,
+                      bs_log_severity_t severity)
+{
+    int fd = open(log_filename_ptr, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
+    if (0 > fd) {
+        bs_log(BS_ERROR | BS_ERRNO,
+               "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)",
+               log_filename_ptr);
+        return false;
+    }
+    _log_fd = fd;
+    bs_log_severity = severity;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_log_write(bs_log_severity_t severity,
+                  const char *file_name_ptr,
+                  int line_num,
+                  const char *fmt_ptr, ...)
+{
+    va_list                   ap;
+    va_start(ap, fmt_ptr);
+    bs_log_vwrite(severity, file_name_ptr, line_num, fmt_ptr, ap);
+    va_end(ap);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Writes the log mesage, taking a va_list argument. */
+void bs_log_vwrite(bs_log_severity_t severity,
+                   const char *file_name_ptr,
+                   int line_num,
+                   const char *fmt_ptr, va_list ap)
+{
+    char buf[BS_LOG_MAX_BUF_SIZE + 1];
+    size_t pos = 0;
+
+    const char *color_attr_ptr = "";
+    switch (severity & 0x7f) {
+    case BS_DEBUG: color_attr_ptr = "\e[90m"; break;  // Dark gray foreground.
+    case BS_INFO: color_attr_ptr = "\e[37m"; break;  // Light gray foreground.
+    case BS_WARNING: color_attr_ptr = "\e[1;93m"; break;  // Yellow & bold.
+    case BS_ERROR: color_attr_ptr = "\e[1;91m"; break;  // Bright red & bold.
+    case BS_FATAL: color_attr_ptr = "\e[1;97;41m"; break; // White on red,bold.
+    }
+    char *reset_ptr = "";
+    if (*color_attr_ptr != '\0') {
+        reset_ptr = "\e[0m";
+    }
+
+    struct timeval tv;
+    if (0 != gettimeofday(&tv, NULL)) {
+        memset(&tv, 0, sizeof(tv));
+    }
+    struct tm *tm_ptr = localtime(&tv.tv_sec);
+    pos = bs_strappendf(
+        buf, BS_LOG_MAX_BUF_SIZE, pos,
+        "%04d-%02d-%02d %02d:%02d:%02d.%03d (%s%s%s) \e[90m%s:%d\e[0m ",
+        tm_ptr->tm_year + 1900, tm_ptr->tm_mon + 1, tm_ptr->tm_mday,
+        tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec,
+        (int)(tv.tv_usec / 1000),
+        color_attr_ptr,
+        _severity_names[severity & 0x7f],
+        reset_ptr,
+        _strip_prefix(file_name_ptr), line_num);
+
+    pos = bs_strappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, "%s", color_attr_ptr);
+    pos = bs_vstrappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, fmt_ptr, ap);
+    if (severity & BS_ERRNO) {
+        pos = bs_strappendf(
+            buf, BS_LOG_MAX_BUF_SIZE, pos,
+            ": errno(%d): %s", errno, strerror(errno));
+    }
+    pos = bs_strappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, "%s", reset_ptr);
+    if (pos >= BS_LOG_MAX_BUF_SIZE) {
+        pos = BS_LOG_MAX_BUF_SIZE;
+        buf[BS_LOG_MAX_BUF_SIZE - 3] = '.';
+        buf[BS_LOG_MAX_BUF_SIZE - 2] = '.';
+        buf[BS_LOG_MAX_BUF_SIZE - 1] = '.';
+    }
+    buf[pos++] = '\n';
+
+    size_t written_bytes = 0;
+    while (written_bytes < pos) {
+        ssize_t more_bytes = write(_log_fd, &buf[written_bytes],
+                                   pos - written_bytes);
+        if (0 > more_bytes) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+                continue;
+            }
+            abort();
+        }
+        written_bytes += more_bytes;
+    }
+
+    if (severity == BS_FATAL) BS_ABORT();
+}
+
+
+/* == Local (static) methods =============================================== */
+
+/** Strips leading relative (or absolute) path prefix. */
+const char *_strip_prefix(const char *path_ptr)
+{
+    if (*path_ptr == '.') ++path_ptr;
+    if (*path_ptr == '/') ++path_ptr;
+    return path_ptr;
+}
+
+/* == Test functions ======================================================= */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Helper method: Test that |expected_str| can be read from the descriptor
+ * |fd|. Will verify length and report to |test_ptr|.
+ *
+ * @param test_ptr
+ * @param fname_ptr
+ * @param line
+ * @param fd
+ * @param expected_str
+ */
+void verify_log_output_equals_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    int line,
+    int fd,
+    const char *expected_str)
+{
+    char buf[BS_LOG_MAX_BUF_SIZE + 1];
+    memset(buf, 0, sizeof(buf));
+
+    size_t timestamp_len = strlen("YYYY-MM-DD hh:mm:ss.ccc");
+    size_t expected_len = 0;
+    if (NULL != expected_str) {
+        expected_len = strlen(expected_str) + timestamp_len + 1;
+    } else {
+        expected_str = "";
+    }
+    if (BS_LOG_MAX_BUF_SIZE < expected_len || INT_MAX < expected_len) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Absurdly long string: %zu bytes!", expected_len);
+        return;
+    }
+
+    ssize_t available_len = bs_sock_read(fd, buf, expected_len, 10);
+    if (0 > available_len) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Failed reading \"%s\" from %d", expected_str, fd);
+        return;
+    }
+
+    if ((size_t)available_len != expected_len) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Only found %zd bytes, expected %zu (\"%.*s\" != \"%s\"",
+            available_len, expected_len, (int)available_len, buf,
+            expected_str);
+        return;
+    }
+
+    BS_TEST_VERIFY_STREQ(test_ptr, expected_str, &buf[timestamp_len + 1]);
+
+    if (0 != bs_sock_poll_read(fd, 10)) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Unexpected extra (> %zu) bytes found reading \"%s\"",
+            expected_len, expected_str);
+        return;
+    }
+}
+
+static void test_strip_prefix(bs_test_t *test_ptr);
+static void test_log(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_log_test_cases[] = {
+    { 1, "basename", test_strip_prefix },
+    { 1, "log", test_log },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+void test_strip_prefix(bs_test_t *test_ptr)
+{
+    BS_TEST_VERIFY_STREQ(test_ptr, "", _strip_prefix(""));
+    BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("base"));
+    BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("/base"));
+    BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("./base"));
+    BS_TEST_VERIFY_STREQ(test_ptr, "a/path/to/base", _strip_prefix("/a/path/to/base"));
+    BS_TEST_VERIFY_STREQ(test_ptr, "", _strip_prefix("./"));
+    BS_TEST_VERIFY_STREQ(test_ptr, ".", _strip_prefix("/."));
+}
+
+/* ------------------------------------------------------------------------- */
+void test_log(bs_test_t *test_ptr)
+{
+    char                      expected_output[BS_LOG_MAX_BUF_SIZE + 1];
+    bs_log_severity_t         backup_severity;
+
+    backup_severity = bs_log_severity;
+
+    int fds[2];
+    if (0 != pipe(fds)) {
+        BS_TEST_FAIL(
+            test_ptr,
+            "Failed pipe(%p): errno(%d): %s", fds, errno, strerror(errno));
+        return;
+    }
+    if (!bs_sock_set_blocking(fds[0], false)) {
+        BS_TEST_FAIL(
+            test_ptr,
+            "Failed bs_sock_set_blocking(%d, false)", fds[0]);
+        return;
+    }
+
+    _log_fd = fds[1];
+    snprintf(expected_output, sizeof(expected_output),
+             "(\e[1;93mWARNING\e[0m) \e[90mlog.c:%d\e[0m \e[1;93mtest 42\e[0m\n",
+             __LINE__ + 1);
+    bs_log(BS_WARNING, "test %d", 42);
+    verify_log_output_equals_at(
+        test_ptr, __FILE__, __LINE__, fds[0], expected_output);
+
+    snprintf(expected_output, sizeof(expected_output),
+             "(\e[1;91mERROR\e[0m) \e[90mlog.c:%d\e[0m \e[1;91mtest 43"
+             ": errno(%d): Permission denied\e[0m\n",
+             __LINE__ + 2, EACCES);
+    errno = EACCES;
+    bs_log(BS_ERROR | BS_ERRNO, "test %d", 43);
+    verify_log_output_equals_at(
+        test_ptr, __FILE__, __LINE__, fds[0], expected_output);
+
+    bs_log(BS_INFO, "test %d", 44);
+    verify_log_output_equals_at(
+        test_ptr, __FILE__, __LINE__, fds[0], NULL);
+
+    bs_log_severity = BS_INFO;
+    snprintf(expected_output, sizeof(expected_output),
+             "(\e[37mINFO\e[0m) \e[90mlog.c:%d\e[0m \e[37mtest 45\e[0m\n",
+             __LINE__ + 1);
+    bs_log(BS_INFO, "test %d", 45);
+    verify_log_output_equals_at(
+        test_ptr, __FILE__, __LINE__, fds[0], expected_output);
+
+    _log_fd = 2;
+
+    close(fds[0]);
+    close(fds[1]);
+    bs_log_severity = backup_severity;
+}
+
+/* == End of log.c ========================================================= */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/log.h
@@ -0,0 +1,95 @@
+/* ========================================================================= */
+/**
+ * @file log.h
+ * Logging library, to stderr or a log file. Used for verbose error reporting.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_LOG_H__
+#define __LIBBASE_LOG_H__
+
+#include "def.h"
+#include "test.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Maximum size of one log message, excluding terminating NUL. */
+#define BS_LOG_MAX_BUF_SIZE 4096
+
+/** Severity for logging. */
+typedef enum {
+    BS_DEBUG = 0,
+    BS_INFO = 1,
+    BS_WARNING = 2,
+    BS_ERROR = 3,
+    /** Always log. No matter the current severity. And: abort(). */
+    BS_FATAL = 4,
+
+    /** Can be OR-ed to above values, to report strerror & errno. */
+    BS_ERRNO = 0x80
+} bs_log_severity_t;
+
+/** Current severity for logging. Only equal-or-higher severity is logged. */
+extern bs_log_severity_t      bs_log_severity;
+
+/** Actually write a log message. Helper to bs_log. */
+void bs_log_write(bs_log_severity_t severity,
+                  const char *file_name_ptr,
+                  int line_num,
+                  const char *fmt_ptr, ...)
+    __ARG_PRINTF__(4, 5);
+
+/** Same as bs_log_write(), but with va_list arg. */
+void bs_log_vwrite(bs_log_severity_t severity,
+                   const char *file_name_ptr,
+                   int line_num,
+                   const char *fmt_ptr, va_list ap)
+    __ARG_PRINTF__(4, 0);
+
+/** Initialize logging system to write to |log_filename_ptr| at |severity|. */
+bool bs_log_init_file(const char *log_filename_ptr,
+                      bs_log_severity_t severity);
+
+/** Returns whether log outut will happen for `severity`. */
+static inline bool bs_will_log(bs_log_severity_t severity)
+{
+    return  ((severity & 0x7f) >= bs_log_severity ||
+             (severity & 0x7f) == BS_FATAL);
+}
+
+
+/** Write a log message, at specified severity. */
+#define bs_log(_severity, ...) {                                        \
+        bs_log_severity_t _tmp_sev = (_severity);                       \
+        if (bs_will_log(_tmp_sev)) {                                    \
+            bs_log_write(_tmp_sev, __FILE__, __LINE__, __VA_ARGS__);    \
+        }                                                               \
+    }
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_log_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_LOG_H__ */
+/* == End of log.h ========================================================= */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/log_wrappers.h
@@ -0,0 +1,87 @@
+/* ========================================================================= */
+/**
+ * @file log_wrappers.h
+ * Wraps common methods to conveniently log errors.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_LOG_WRAPPERS_H__
+#define __LIBBASE_LOG_WRAPPERS_H__
+
+#include "log.h"
+
+#include <stdlib.h>
+#include "c2x_compat.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Helper: Calls calloc(3), and wraps errors for the given file & line. */
+static inline void *_logged_calloc(
+    const char *filename_ptr, int line_no, size_t nmemb, size_t size)
+{
+    void *ptr = calloc(nmemb, size);
+    if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) {
+        bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr,
+                     line_no, "Failed calloc(%zu, %zu)", nmemb, size);
+    }
+    return ptr;
+}
+
+/** Calls calloc(3) and logs error with ERROR severity. */
+#define logged_calloc(nmemb, size)                      \
+    _logged_calloc(__FILE__, __LINE__, nmemb, size);
+
+/** Helper: Calls malloc(3) and logs errors for given file & line. */
+static inline void *_logged_malloc(
+    const char *filename_ptr, int line_no, size_t size)
+{
+    void *ptr = malloc(size);
+    if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) {
+        bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr,
+                     line_no, "Failed malloc(%zu)", size);
+    }
+    return ptr;
+}
+
+/** Calls malloc(3) and logs error with ERROR severity. */
+#define logged_malloc(size)                     \
+    _logged_malloc(__FILE__, __LINE__, size);
+
+/** Helper: Calls strdup(3) and logs errors for given file & line. */
+static inline char *_logged_strdup(
+    const char *filename_ptr, int line_no, const char *str)
+{
+    char *new_str = strdup(str);
+    if ((NULL == new_str) && (BS_ERROR >= bs_log_severity)) {
+        bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr,
+                     line_no, "Failed strdup(%s)", str);
+    }
+    return new_str;
+}
+
+/** Calls strdup(3) and logs error with ERROR severity. */
+#define logged_strdup(str)                      \
+    _logged_strdup(__FILE__, __LINE__, str);
+
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_LOG_WRAPPERS_H__ */
+/* == End of log_wrappers.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_set.c
@@ -0,0 +1,190 @@
+/* ========================================================================= */
+/**
+ * @file ptr_set.c
+ *
+ * Interface for a simple stack to store pointers.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "ptr_set.h"
+
+#include "avltree.h"
+#include "def.h"
+#include "log_wrappers.h"
+
+/* == Declarations ========================================================= */
+
+/** A set. */
+struct _bs_ptr_set_t {
+    /** Maps to the tree holding the set elements. */
+    bs_avltree_t              *tree_ptr;
+};
+
+/** Holder for one element. */
+typedef struct {
+    /** The tree node. */
+    bs_avltree_node_t         avlnode;
+    /** Element pointer. */
+    void                      *elem_ptr;
+} bs_ptr_set_elem_holder_t;
+
+static int elem_compare(
+    const bs_avltree_node_t *node_ptr,
+    const void *key_ptr);
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_ptr_set_t *bs_ptr_set_create(void)
+{
+    bs_ptr_set_t *set_ptr = logged_calloc(1, sizeof(bs_ptr_set_t));
+    if (NULL == set_ptr) return NULL;
+
+    set_ptr->tree_ptr = bs_avltree_create(elem_compare, NULL);
+    if (NULL == set_ptr->tree_ptr) {
+        bs_ptr_set_destroy(set_ptr);
+        return NULL;
+    }
+
+    return set_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_ptr_set_destroy(bs_ptr_set_t *set_ptr)
+{
+    if (NULL != set_ptr->tree_ptr) {
+        bs_avltree_destroy(set_ptr->tree_ptr);
+        set_ptr->tree_ptr = NULL;
+    }
+
+    free(set_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_set_insert(bs_ptr_set_t *set_ptr, void *elem_ptr)
+{
+    bs_ptr_set_elem_holder_t *holder_ptr = logged_calloc(
+        1, sizeof(bs_ptr_set_elem_holder_t));
+    if (NULL == holder_ptr) return false;
+
+    holder_ptr->elem_ptr = elem_ptr;
+    bool rv = bs_avltree_insert(
+        set_ptr->tree_ptr,
+        holder_ptr->elem_ptr,
+        &holder_ptr->avlnode,
+        false);
+    if (!rv) free(holder_ptr);
+    return rv;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_ptr_set_erase(bs_ptr_set_t *set_ptr, void *elem_ptr)
+{
+    bs_avltree_node_t *node_ptr = bs_avltree_delete(
+        set_ptr->tree_ptr, elem_ptr);
+    if (NULL != node_ptr) {
+        bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF(
+            node_ptr,  bs_ptr_set_elem_holder_t, avlnode);
+        free(holder_ptr);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_set_contains(bs_ptr_set_t *set_ptr, void *elem_ptr)
+{
+    return NULL != bs_avltree_lookup(set_ptr->tree_ptr, elem_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void *bs_ptr_set_any(bs_ptr_set_t *set_ptr)
+{
+    bs_avltree_node_t *avlnode_ptr = bs_avltree_min(set_ptr->tree_ptr);
+    if (NULL == avlnode_ptr) return NULL;
+
+    bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF(
+        avlnode_ptr, bs_ptr_set_elem_holder_t, avlnode);
+    return holder_ptr->elem_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_set_empty(bs_ptr_set_t *set_ptr)
+{
+    return 0 == bs_avltree_size(set_ptr->tree_ptr);
+}
+
+/* == Local (static) methods =============================================== */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Compares two element pointers.
+ *
+ * @param node_ptr
+ * @param key_ptr
+ *
+ * @return As specified by @ref bs_avltree_node_cmp_t.
+ */
+int elem_compare(const bs_avltree_node_t *node_ptr, const void *key_ptr)
+{
+    const bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF(
+        node_ptr, const bs_ptr_set_elem_holder_t, avlnode);
+    return (size_t)key_ptr - (size_t)holder_ptr->elem_ptr;
+}
+
+/* == Test Functions ======================================================= */
+
+static void bs_ptr_set_test(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_ptr_set_test_cases[] = {
+    { 1, "test", bs_ptr_set_test },
+    { 0, NULL, NULL }
+};
+
+void bs_ptr_set_test(bs_test_t *test_ptr)
+{
+    bs_ptr_set_t *set_ptr = bs_ptr_set_create();
+    BS_TEST_VERIFY_NEQ(test_ptr, set_ptr, NULL);
+    void *data1_ptr = (void*)-1;
+    void *data2_ptr = (void*)2;
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_empty(set_ptr));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_set_any(set_ptr));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_insert(set_ptr, data1_ptr));
+
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_empty(set_ptr));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, data1_ptr, bs_ptr_set_any(set_ptr));
+
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_insert(set_ptr, data1_ptr));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_insert(set_ptr, data2_ptr));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data2_ptr));
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, bs_ptr_set_any(set_ptr));
+
+    bs_ptr_set_erase(set_ptr, data1_ptr);
+    bs_ptr_set_erase(set_ptr, data2_ptr);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data2_ptr));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_empty(set_ptr));
+
+    bs_ptr_set_destroy(set_ptr);
+}
+
+/* == End of ptr_set.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_set.h
@@ -0,0 +1,107 @@
+/* ========================================================================= */
+/**
+ * @file ptr_set.h
+ *
+ * Interface for a simple stack to store pointers.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_PTR_SET_H__
+#define __LIBBASE_PTR_SET_H__
+
+#include <stdbool.h>
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Handle for a set. */
+typedef struct _bs_ptr_set_t bs_ptr_set_t;
+
+/**
+ * Creates the set.
+ *
+ * @return Pointer to the set, or NULL on error
+. Must be freed by calling
+ *     @ref bs_ptr_set_destroy.
+ */
+bs_ptr_set_t *bs_ptr_set_create(void);
+
+/**
+ * Destroys a set previously created with @ref bs_ptr_set_create.
+ *
+ * @param set_ptr
+ */
+void bs_ptr_set_destroy(bs_ptr_set_t *set_ptr);
+
+/**
+ * Inserts `elem_ptr` into `set_ptr`.
+ *
+ * @param set_ptr
+ * @param elem_ptr
+ *
+ * @return true iff the insert worked, false if there already exists the same
+ *     `elem_ptr` in the set or if there was an allocation error.
+ */
+bool bs_ptr_set_insert(bs_ptr_set_t *set_ptr, void *elem_ptr);
+
+/**
+ * Erases `elem_ptr` from `set_ptr`.
+ *
+ * @param set_ptr
+ * @param elem_ptr
+ */
+void bs_ptr_set_erase(bs_ptr_set_t *set_ptr, void *elem_ptr);
+
+/**
+ * Returns whether `set_ptr` contains `elem_ptr`.
+ *
+ * @param set_ptr
+ * @param elem_ptr
+ *
+ * @return whether `set_ptr` contains `elem_ptr`.
+ */
+bool bs_ptr_set_contains(bs_ptr_set_t *set_ptr, void *elem_ptr);
+
+/**
+ * Returns any of the contained pointers. Can be used for deletion.
+ *
+ * @param set_ptr
+ *
+ * @return Any of the contained pointers, or NULL on error.
+ */
+void *bs_ptr_set_any(bs_ptr_set_t *set_ptr);
+
+/**
+ * Returns whether `set_ptr` is empty.
+ *
+ * @param set_ptr
+ *
+ * @return Whether the set is empty.
+ */
+bool bs_ptr_set_empty(bs_ptr_set_t *set_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_ptr_set_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_PTR_SET_H__ */
+/* == End of ptr_set.h ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_stack.c
@@ -0,0 +1,201 @@
+/* ========================================================================= */
+/**
+ * @file ptr_stack.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "ptr_stack.h"
+
+#include "assert.h"
+#include "log_wrappers.h"
+
+/* == Declarations ========================================================= */
+
+/** Initial stack size, 1024 elements. */
+#define INITIAL_SIZE 1024
+
+static bool grow_stack(bs_ptr_stack_t *ptr_stack_ptr);
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_ptr_stack_t *bs_ptr_stack_create(void)
+{
+    bs_ptr_stack_t *ptr_stack_ptr = logged_calloc(1, sizeof(bs_ptr_stack_t));
+    if (NULL == ptr_stack_ptr) return NULL;
+
+    if (!bs_ptr_stack_init(ptr_stack_ptr)) {
+        bs_ptr_stack_destroy(ptr_stack_ptr);
+        return NULL;
+    }
+    return ptr_stack_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_ptr_stack_destroy(bs_ptr_stack_t *ptr_stack_ptr)
+{
+    bs_ptr_stack_fini(ptr_stack_ptr);
+    free(ptr_stack_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_stack_init(bs_ptr_stack_t *ptr_stack_ptr)
+{
+    ptr_stack_ptr->pos = 0;
+    ptr_stack_ptr->size = 0;
+    ptr_stack_ptr->data_ptr = NULL;
+    return grow_stack(ptr_stack_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_ptr_stack_fini(bs_ptr_stack_t *ptr_stack_ptr)
+{
+    if (NULL != ptr_stack_ptr->data_ptr) {
+        if (0 < ptr_stack_ptr->pos) {
+            bs_log(BS_WARNING, "Destroying non-empty ptr_stack at %p",
+                   ptr_stack_ptr);
+        }
+        free(ptr_stack_ptr->data_ptr);
+        ptr_stack_ptr->data_ptr = NULL;
+    }
+
+    ptr_stack_ptr->size = 0;
+    ptr_stack_ptr->pos = 0;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_stack_push(bs_ptr_stack_t *ptr_stack_ptr, void *elem_ptr)
+{
+    BS_ASSERT(NULL != elem_ptr);
+    if (ptr_stack_ptr->pos + 1 >= ptr_stack_ptr->size) {
+        if (!grow_stack(ptr_stack_ptr)) return false;
+    }
+
+    ptr_stack_ptr->data_ptr[ptr_stack_ptr->pos++] = elem_ptr;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void *bs_ptr_stack_pop(bs_ptr_stack_t *ptr_stack_ptr)
+{
+    if (0 >= ptr_stack_ptr->pos) return NULL;
+    return ptr_stack_ptr->data_ptr[--ptr_stack_ptr->pos];
+}
+
+/* ------------------------------------------------------------------------- */
+void *bs_ptr_stack_peek(bs_ptr_stack_t *ptr_stack_ptr,
+                        size_t index)
+{
+    if (index >= ptr_stack_ptr->pos) return NULL;
+    return ptr_stack_ptr->data_ptr[ptr_stack_ptr->pos - index - 1];
+}
+
+/* == Local (static) methods =============================================== */
+
+/* ------------------------------------------------------------------------- */
+/** Grows the stack by INITIAL_SIZE elements. */
+bool grow_stack(bs_ptr_stack_t *ptr_stack_ptr)
+{
+    size_t new_size = ptr_stack_ptr->size + INITIAL_SIZE;
+    void *new_data_ptr = realloc(
+        ptr_stack_ptr->data_ptr, new_size * sizeof(void*));
+    if (NULL == new_data_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)",
+               ptr_stack_ptr->data_ptr, new_size * sizeof(void*));
+        return false;
+    }
+    ptr_stack_ptr->size = new_size;
+    ptr_stack_ptr->data_ptr = new_data_ptr;
+    return true;
+}
+
+/* == Unit tests =========================================================== */
+
+static void basic_test(bs_test_t *test_ptr);
+static void large_test(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_ptr_stack_test_cases[] = {
+    { 1, "basic", basic_test },
+    { 1, "large", large_test },
+    { 0, NULL, NULL }  // sentinel.
+};
+
+/* ------------------------------------------------------------------------- */
+/** Basic functionality: init, empty pop, push, pop, empty pop, fini. */
+void basic_test(bs_test_t *test_ptr)
+{
+    bs_ptr_stack_t ptr_stack;
+    static void *elem_ptr = (void*)basic_test;  // Just anything, actually.
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_init(&ptr_stack));
+
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack));
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_push(&ptr_stack, elem_ptr));
+    BS_TEST_VERIFY_EQ(test_ptr, elem_ptr, bs_ptr_stack_peek(&ptr_stack, 0));
+    BS_TEST_VERIFY_EQ(test_ptr, elem_ptr, bs_ptr_stack_pop(&ptr_stack));
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack));
+
+    bs_ptr_stack_fini(&ptr_stack);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Basic functionality: init, empty pop, push, pop, empty pop, fini. */
+void large_test(bs_test_t *test_ptr)
+{
+    bs_ptr_stack_t ptr_stack;
+    char elem[2 * INITIAL_SIZE];
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_init(&ptr_stack));
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        BS_TEST_VERIFY_TRUE(test_ptr,
+                            bs_ptr_stack_push(&ptr_stack, &elem[i]));
+    }
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        BS_TEST_VERIFY_EQ(
+            test_ptr,
+            &elem[2 * INITIAL_SIZE - 1 - i],
+            bs_ptr_stack_peek(&ptr_stack, i));
+    }
+    BS_TEST_VERIFY_EQ(
+        test_ptr, NULL,
+        bs_ptr_stack_peek(&ptr_stack, 2 * INITIAL_SIZE));
+    BS_TEST_VERIFY_EQ(
+        test_ptr, NULL,
+        bs_ptr_stack_peek(&ptr_stack, 2 * INITIAL_SIZE + 1));
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        void *ptr = bs_ptr_stack_pop(&ptr_stack);
+
+        if (&elem[2 * INITIAL_SIZE - 1 - i] != ptr) {
+            bs_log(BS_ERROR, "%zu: (expected) %p != %p (stacked)", i,
+                   &elem[2 * INITIAL_SIZE - 1 - i], ptr);
+        }
+
+        BS_TEST_VERIFY_EQ(
+            test_ptr,
+            &elem[2 * INITIAL_SIZE - 1 - i],
+            ptr); //            bs_ptr_stack_pop(&ptr_stack));
+    }
+
+    BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack));
+
+    bs_ptr_stack_fini(&ptr_stack);
+}
+
+/* == End of ptr_stack.c =================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_stack.h
@@ -0,0 +1,115 @@
+/* ========================================================================= */
+/**
+ * @file ptr_stack.h
+ *
+ * Interface for a simple stack to store pointers.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_PTR_STACK_H__
+#define __LIBBASE_PTR_STACK_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** State of the pointer stack. */
+typedef struct {
+    /** Number of elements contained. Required for re-sizing. */
+    size_t                    size;
+    /** Current position within the stack. */
+    size_t                    pos;
+    /** Points to data array, holds @ref size elements of `void*`. */
+    void                      **data_ptr;
+} bs_ptr_stack_t;
+
+/**
+ * Creates a pointer stack.
+ *
+ * @return Pointer to the stack, or NULL on error. Must be destroyed by calling
+ *     @ref bs_ptr_stack_destroy.
+ */
+bs_ptr_stack_t *bs_ptr_stack_create(void);
+
+/**
+ * Destroys the pointer stack.
+ *
+ * @param ptr_stack_ptr
+ */
+void bs_ptr_stack_destroy(bs_ptr_stack_t *ptr_stack_ptr);
+
+/**
+ * Initializes the pointer stack. Use this for static allocations of
+ * @ref bs_ptr_stack_t. Associated resources need to be cleaned up by calling
+ * @ref bs_ptr_stack_fini.
+ *
+ * @param ptr_stack_ptr
+ *
+ * @return true on success.
+ */
+bool bs_ptr_stack_init(bs_ptr_stack_t *ptr_stack_ptr);
+
+/**
+ * Cleans up resources of associated `ptr_stack_ptr`.
+ *
+ * @param ptr_stack_ptr
+ */
+void bs_ptr_stack_fini(bs_ptr_stack_t *ptr_stack_ptr);
+
+/**
+ * Pushes `elem_ptr` to the stack.
+ *
+ * @param ptr_stack_ptr
+ * @param elem_ptr            Pointer to the element to be pushed. Must not be
+ *                            NULL.
+ *
+ * @return true on success.
+ */
+bool bs_ptr_stack_push(bs_ptr_stack_t *ptr_stack_ptr, void *elem_ptr);
+
+/**
+ * Pops the topmost element from the stack and returns it.
+ *
+ * @return Pointer to the popped element, or NULL if the stack was empty.
+ */
+void *bs_ptr_stack_pop(bs_ptr_stack_t *ptr_stack_ptr);
+
+/**
+ * Peeks at the stack value that is `index` items below the top.
+ *
+ * @param ptr_stack_ptr
+ * @param index
+ *
+ * @return The stacked pointer, or NULL if `index` is too large or the stack is
+ *     empty.
+ */
+void *bs_ptr_stack_peek(bs_ptr_stack_t *ptr_stack_ptr,
+                        size_t index);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_ptr_stack_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_PTR_STACK_H__ */
+/* == End of ptr_stack.h =================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_vector.c
@@ -0,0 +1,188 @@
+/* ========================================================================= */
+/**
+ * @file ptr_vector.h
+ *
+ * Interface for a simple vector to store pointers.
+ *
+ * @copyright
+ * Copyright 2024 Google LLC
+ *
+ * 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
+ *
+ * https://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 "ptr_vector.h"
+
+#include "assert.h"
+#include "log.h"
+
+/* == Declarations ========================================================= */
+
+/** Initial vector size, 1024 elements. */
+#define INITIAL_SIZE 1024
+
+static bool _bs_ptr_vector_grow(bs_ptr_vector_t *ptr_vector_ptr);
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_vector_init(bs_ptr_vector_t *ptr_vector_ptr)
+{
+    ptr_vector_ptr->capacity = 0;
+    ptr_vector_ptr->consumed = 0;
+    ptr_vector_ptr->elements_ptr = NULL;
+    _bs_ptr_vector_grow(ptr_vector_ptr);
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_ptr_vector_fini(bs_ptr_vector_t *ptr_vector_ptr)
+{
+    if (NULL != ptr_vector_ptr->elements_ptr) {
+        if (0 < ptr_vector_ptr->consumed) {
+            bs_log(BS_WARNING, "Un-initializing non-empty vector at %p",
+                   ptr_vector_ptr);
+        }
+        free(ptr_vector_ptr->elements_ptr);
+        ptr_vector_ptr->elements_ptr = NULL;
+    }
+    ptr_vector_ptr->capacity = 0;
+    ptr_vector_ptr->consumed = 0;
+}
+
+/* ------------------------------------------------------------------------- */
+size_t bs_ptr_vector_size(bs_ptr_vector_t *ptr_vector_ptr)
+{
+    return ptr_vector_ptr->consumed;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_vector_push_back(bs_ptr_vector_t *ptr_vector_ptr,
+                             void *data_ptr)
+{
+    while (ptr_vector_ptr->consumed >= ptr_vector_ptr->capacity) {
+        if (!_bs_ptr_vector_grow(ptr_vector_ptr)) return false;
+    }
+    ptr_vector_ptr->elements_ptr[ptr_vector_ptr->consumed++] = data_ptr;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_ptr_vector_erase(bs_ptr_vector_t *ptr_vector_ptr,
+                         size_t pos)
+{
+    if (pos >= ptr_vector_ptr->consumed) return false;
+
+    if (pos + 1 < ptr_vector_ptr->consumed) {
+        memmove(ptr_vector_ptr->elements_ptr + pos,
+                ptr_vector_ptr->elements_ptr + pos + 1,
+                (ptr_vector_ptr->consumed - pos - 1) * sizeof(void*));
+    }
+    --ptr_vector_ptr->consumed;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void* bs_ptr_vector_at(bs_ptr_vector_t *ptr_vector_ptr, size_t pos)
+{
+    BS_ASSERT(pos < ptr_vector_ptr->consumed);
+    return ptr_vector_ptr->elements_ptr[pos];
+}
+
+/* == Local (static) methods =============================================== */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Grows the vector, by INITIAL_SIZE elements.
+ *
+ * @param ptr_vector_ptr
+ *
+ * @return true on success.
+ */
+bool _bs_ptr_vector_grow(bs_ptr_vector_t *ptr_vector_ptr)
+{
+    size_t new_size = ptr_vector_ptr->capacity + INITIAL_SIZE;
+    void *new_elements_ptr = realloc(
+        ptr_vector_ptr->elements_ptr, new_size * sizeof(void*));
+    if (NULL == new_elements_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)",
+               ptr_vector_ptr->elements_ptr, new_size * sizeof(void*));
+        return false;
+    }
+    ptr_vector_ptr->capacity = new_size;
+    ptr_vector_ptr->elements_ptr = new_elements_ptr;
+    return true;
+}
+
+/* == Unit tests =========================================================== */
+
+static void basic_test(bs_test_t *test_ptr);
+static void large_test(bs_test_t *test_ptr);
+
+const bs_test_case_t bs_ptr_vector_test_cases[] = {
+    { 1, "basic", basic_test },
+    { 1, "large", large_test },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+/** Tests basic functionality. */
+void basic_test(bs_test_t *test_ptr)
+{
+    bs_ptr_vector_t ptr_vector;
+    char e = 'e';
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_init(&ptr_vector));
+    BS_TEST_VERIFY_EQ(test_ptr, 0, bs_ptr_vector_size(&ptr_vector));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_push_back(&ptr_vector, &e));
+    BS_TEST_VERIFY_EQ(test_ptr, 1, bs_ptr_vector_size(&ptr_vector));
+    BS_TEST_VERIFY_EQ(test_ptr, &e, bs_ptr_vector_at(&ptr_vector, 0));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_erase(&ptr_vector, 0));
+    BS_TEST_VERIFY_EQ(test_ptr, 0, bs_ptr_vector_size(&ptr_vector));
+
+    bs_ptr_vector_fini(&ptr_vector);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Tests with enough elements to trigger growth. */
+void large_test(bs_test_t *test_ptr)
+{
+    bs_ptr_vector_t vec;
+    char e[2 * INITIAL_SIZE];
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_init(&vec));
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_push_back(&vec, &e[i]));
+    }
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        BS_TEST_VERIFY_EQ(test_ptr, &e[i], bs_ptr_vector_at(&vec, i));
+    }
+
+    for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) {
+        BS_TEST_VERIFY_EQ(test_ptr, &e[i], bs_ptr_vector_at(&vec, 0));
+        BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_erase(&vec, 0));
+        if (0 < bs_ptr_vector_size(&vec)) {
+            BS_TEST_VERIFY_EQ(
+                test_ptr,
+                &e[2 * INITIAL_SIZE - 1],
+                bs_ptr_vector_at(&vec, bs_ptr_vector_size(&vec) - 1));
+        }
+    }
+
+    bs_ptr_vector_fini(&vec);
+}
+
+/* == End of ptr_vector.c ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/ptr_vector.h
@@ -0,0 +1,96 @@
+/* ========================================================================= */
+/**
+ * @file ptr_vector.h
+ *
+ * Interface for a simple vector to store pointers.
+ *
+ * @copyright
+ * Copyright 2024 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_PTR_VECTOR_H__
+#define __LIBBASE_PTR_VECTOR_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** State of a vector that stores pointers. */
+typedef struct {
+    /** Current capacity of the vector. Required for re-sizing. */
+    size_t                    capacity;
+    /** Currently consumed capacity of the bector. */
+    size_t                    consumed;
+    /** The elements. */
+    void                      **elements_ptr;
+} bs_ptr_vector_t;
+
+/**
+ * Initializes the vector.
+ *
+ * @param ptr_vector_ptr
+ *
+ * @return true on success.
+ */
+bool bs_ptr_vector_init(bs_ptr_vector_t *ptr_vector_ptr);
+
+/**
+ * Un-initializes the vector.
+ *
+ * @param ptr_vector_ptr
+ */
+void bs_ptr_vector_fini(bs_ptr_vector_t *ptr_vector_ptr);
+
+/** @return  the size of the vector, ie. @ref bs_ptr_vector_t::consumed. */
+size_t bs_ptr_vector_size(bs_ptr_vector_t *ptr_vector_ptr);
+
+/**
+ * Adds `data_ptr` at the end of the vector.
+ *
+ * @param ptr_vector_ptr
+ * @param data_ptr
+ *
+ * @return true on success.
+ */
+bool bs_ptr_vector_push_back(bs_ptr_vector_t *ptr_vector_ptr,
+                             void *data_ptr);
+
+/**
+ * Erases the element at pos.
+ *
+ * @param ptr_vector_ptr
+ * @param pos
+ *
+ * @return true if pos was valid.
+ */
+bool bs_ptr_vector_erase(bs_ptr_vector_t *ptr_vector_ptr,
+                         size_t pos);
+
+/** @return the element at `pos`. It must be `pos` < size. */
+void* bs_ptr_vector_at(bs_ptr_vector_t *ptr_vector_ptr, size_t pos);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_ptr_vector_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_PTR_VECTOR_H__ */
+/* == End of ptr_vector.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/sock.c
@@ -0,0 +1,148 @@
+/* ========================================================================= */
+/**
+ * @file sock.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "def.h"
+#include "log.h"
+#include "sock.h"
+#include "time.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <poll.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+/* == Methods ============================================================== */
+
+/* ------------------------------------------------------------------------- */
+bool bs_sock_set_blocking(int fd, bool blocking)
+{
+    int flags, rv;
+
+    flags = fcntl(fd, F_GETFL);
+    if (-1 == flags) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed fcntl(%d, F_GETFL)", fd);
+        return false;
+    }
+
+    if (blocking) {
+        flags &= ~O_NONBLOCK;
+    } else {
+        flags |= O_NONBLOCK;
+    }
+
+    rv = fcntl(fd, F_SETFL, flags);
+    if (0 != rv) {
+        bs_log(BS_ERROR | BS_ERRNO,
+               "Failed fcntl(%d, F_SETFL, 0x%x)", fd, flags);
+        return false;
+    }
+
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+int bs_sock_poll_read(int fd, int msec)
+{
+    struct pollfd             poll_fd;
+    int                       rv;
+
+    poll_fd.fd = fd;
+    poll_fd.events = POLLIN;
+    rv = poll(&poll_fd, 1, msec);
+    if (0 > rv) {
+        rv = errno;
+        bs_log(BS_ERROR | BS_ERRNO, "Failed poll(%d, 1, %d)", fd, msec);
+        errno = rv;
+        return -1;
+    }
+    return rv;
+}
+
+/* ------------------------------------------------------------------------- */
+ssize_t bs_sock_read(int fd, void *buf_ptr, size_t count, int msec)
+{
+    ssize_t                   consumed_bytes, read_bytes;
+    int                       rv;
+    uint8_t                   *byte_buf_ptr;
+    uint64_t                  start_msec;
+    int                       timeout;
+
+    /* catch boundary conditions on count */
+    if (INT32_MAX < count) {
+        errno = EINVAL;
+        return -1;
+    }
+    if (0 >= count) {
+        return 0;
+    }
+
+    if (0 > msec) {
+        start_msec = bs_usec() / 1000;
+    } else {
+        start_msec = 0;
+    }
+    timeout = msec;
+
+    consumed_bytes = 0;
+    byte_buf_ptr = (uint8_t*)buf_ptr;
+    while ((size_t)consumed_bytes < count) {
+        if (0 <= msec) {
+            uint64_t now_msec = bs_usec() / 1000;
+            if (start_msec + msec > now_msec) {
+                timeout = BS_MIN(now_msec - (start_msec + msec),
+                                 (uint64_t)INT32_MAX);
+            } else {
+                timeout = 0;
+            }
+        }
+
+        rv = bs_sock_poll_read(fd, timeout);
+        if (0 > rv) {
+            return -1;
+        } else if (0 == rv) {
+            /* no more data */
+            return consumed_bytes;
+        }
+
+        read_bytes = read(fd, &byte_buf_ptr[consumed_bytes],
+                          count - consumed_bytes);
+        if (0 == read_bytes) {
+            /* connection was closed, return prescribed error. */
+            errno = EPIPE;
+            return -1;
+        }
+        if ((0 > read_bytes) &&
+            (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
+            rv = errno;
+            bs_log(BS_ERROR | BS_ERRNO, "Failed read(%d, %p, %zu)",
+                   fd, &byte_buf_ptr[consumed_bytes], count - consumed_bytes);
+            errno = rv;
+            return -1;
+        }
+
+        consumed_bytes += read_bytes;
+    }
+    return consumed_bytes;
+}
+
+/* == End of sock.c ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/sock.h
@@ -0,0 +1,76 @@
+/* ========================================================================= */
+/**
+ * @file sock.h
+ * Some helper functions to work with sockets.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_SOCK_H__
+#define __LIBBASE_SOCK_H__
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Sets the blocking property of the file descriptor |fd|.
+ *
+ * @param fd
+ * @param blocking Have O_NONBLOCK cleared if true, and set if false.
+ *
+ * @return Whether the call succeeded.
+ */
+bool bs_sock_set_blocking(int fd, bool blocking);
+
+/**
+ * Waits up to |msec| until the file descriptor |fd| has data to read.
+ *
+ * @param fd
+ * @param msec
+ *
+ * @return a positive value if data is available, 0 if the call timed out, and a
+ * negative value if there was an error. The error is logged, and errno is set.
+ */
+int bs_sock_poll_read(int fd, int msec);
+
+/**
+ * Read |count| bytes from |fd| into |buf_ptr|, respecting the timeout given
+ * by |msec|.
+ *
+ * @param fd
+ * @param buf_ptr
+ * @param count
+ * @param msec                Timeout in milliseconds. A negative value
+ *                            specifies an infinite timeout.
+ *
+ * @return Number of bytes read. The return value will always be less or equal
+ * than |count|. Returns a negative value on error. Errors will be logged, but
+ * errno is retained.
+ * If |count| is non-positive, 0 is returned.
+ * If the connection was closed, a negative value is returned and errno is
+ * set to EPIPE.
+ */
+ssize_t bs_sock_read(int fd, void *buf_ptr, size_t count, int msec);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_SOCK_H__ */
+/* == End of sock.h ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/strutil.c
@@ -0,0 +1,272 @@
+/* ========================================================================= */
+/**
+ * @file strutil.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "strutil.h"
+
+#include "log.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+size_t bs_strappendf(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *fmt_ptr, ...)
+{
+    va_list ap;
+    va_start(ap, fmt_ptr);
+    size_t rv = bs_vstrappendf(buf, buf_size, buf_pos, fmt_ptr, ap);
+    va_end(ap);
+    return rv;
+}
+
+/* ------------------------------------------------------------------------- */
+size_t bs_vstrappendf(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *fmt_ptr,
+    va_list ap)
+{
+    if (buf_pos >= buf_size) return buf_pos;
+    ssize_t rv = vsnprintf(&buf[buf_pos], buf_size - buf_pos, fmt_ptr, ap);
+    return buf_pos + BS_MAX(0, rv);
+}
+
+/* ------------------------------------------------------------------------- */
+size_t bs_strappend(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *str_ptr)
+{
+    if (buf_pos >= buf_size) return buf_size;
+    size_t len = strlen(str_ptr);
+    if (buf_pos + len + 1 >= buf_size) len = buf_size - buf_pos - 1;
+    memmove(buf + buf_pos, str_ptr, len);
+    buf[buf_pos + len] = '\0';
+    return buf_pos + strlen(str_ptr);;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_strconvert_uint64(
+    const char *string_ptr,
+    uint64_t *value_ptr,
+    int base)
+{
+    unsigned long long        tmp_value;
+    char                      *invalid_ptr;
+
+    if ('-' == *string_ptr) {
+        bs_log(BS_ERROR, "Unexpected negative value \"%s\"", string_ptr);
+        return false;
+    }
+
+    invalid_ptr = NULL;
+    errno = 0;
+    tmp_value = strtoull(string_ptr, &invalid_ptr, base);
+    if (0 != errno) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed strtoull for value \"%s\"",
+               string_ptr);
+        return false;
+    }
+    if ('\0' != *invalid_ptr && !isspace(*invalid_ptr))  {
+        bs_log(BS_ERROR, "Failed strtoull for value \"%s\" at \"%s\"",
+               string_ptr, invalid_ptr);
+        return false;
+    }
+
+    *value_ptr = tmp_value;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_strconvert_int64(
+    const char *string_ptr,
+    int64_t *value_ptr,
+    int base)
+{
+    long long                 tmp_value;
+    char                      *invalid_ptr;
+
+    invalid_ptr = NULL;
+    errno = 0;
+    tmp_value = strtoll(string_ptr, &invalid_ptr, base);
+    if (0 != errno) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed strtoll for value \"%s\"",
+               string_ptr);
+        return false;
+    }
+    if ('\0' != *invalid_ptr && !isspace(*invalid_ptr))  {
+        bs_log(BS_ERROR, "Failed strtoll for value \"%s\" at \"%s\"",
+               string_ptr, invalid_ptr);
+        return false;
+    }
+
+    *value_ptr = tmp_value;
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_str_startswith(const char *string_ptr, const char *prefix_ptr)
+{
+    return 0 == strncmp(string_ptr, prefix_ptr, strlen(prefix_ptr));
+}
+
+/* == Test functions ======================================================= */
+
+static void test_strappend(bs_test_t *test_ptr);
+static void strconvert_uint64_test(bs_test_t *test_ptr);
+static void strconvert_int64_test(bs_test_t *test_ptr);
+static void test_startswith(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_strutil_test_cases[] = {
+    { 1, "strappend", test_strappend },
+    { 1, "strconvert_uint64", strconvert_uint64_test },
+    { 1, "strconvert_int64", strconvert_int64_test },
+    { 1, "startswith", test_startswith },
+    { 0, NULL, NULL }
+};
+
+/* -- Append to string buffer ---------------------------------------------- */
+void test_strappend(bs_test_t *test_ptr)
+{
+    char buf[10];
+    size_t in = 0, out;
+    out = bs_strappendf(buf, sizeof(buf), in, "asdf");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 4);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdf");
+
+    in = out;
+    out = bs_strappendf(buf, sizeof(buf), in, "qwer");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 8);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwer");
+
+    in = out;
+    out = bs_strappendf(buf, sizeof(buf), in, "j");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 9);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj");
+
+    out = bs_strappendf(buf, sizeof(buf), in, "jk");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 10);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj");
+
+    out = bs_strappendf(buf, sizeof(buf), in, "jkl");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 11);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj");
+
+    in = out;
+    out = bs_strappendf(buf, sizeof(buf), in, "uiop");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 11);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj");
+
+    in = 0;
+    out = bs_strappend(buf, sizeof(buf), in, "asdf");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 4);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdf");
+
+    in = out;
+    out = bs_strappend(buf, sizeof(buf), in, "qwer");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 8);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwer");
+
+    out = bs_strappend(buf, sizeof(buf), 8, "g");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 9);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg");
+
+    out = bs_strappend(buf, sizeof(buf), 8, "gh");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 10);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg");
+
+    out = bs_strappend(buf, sizeof(buf), 8, "ghv");
+    BS_TEST_VERIFY_EQ(test_ptr, out, 11);
+    BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg");
+}
+
+/* -- Convert uint64_t ----------------------------------------------------- */
+void strconvert_uint64_test(bs_test_t *test_ptr)
+{
+    uint64_t                  value;
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("42", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 42);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("43 ", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 43);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("44\n", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 44);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("0", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 0);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_strconvert_uint64("18446744073709551615", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 18446744073709551615u);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_strconvert_uint64("0xffffffffffffffff", &value, 16));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 18446744073709551615u);
+
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_strconvert_uint64("18446744073709551616", &value, 10));
+
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_uint64("42x", &value, 10));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_uint64("-42", &value, 10));
+}
+
+/* ------------------------------------------------------------------------- */
+void strconvert_int64_test(bs_test_t *test_ptr)
+{
+    int64_t                   value;
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_int64("0", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, 0);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_strconvert_int64("9223372036854775807", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, INT64_MAX);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_strconvert_int64("-9223372036854775808", &value, 10));
+    BS_TEST_VERIFY_EQ(test_ptr, value, INT64_MIN);
+
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_strconvert_int64("18446744073709551615", &value, 10));
+}
+
+/* ------------------------------------------------------------------------- */
+void test_startswith(bs_test_t *test_ptr)
+{
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_str_startswith("asdf", "asd"));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_str_startswith("asdf", "asdfe"));
+
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_str_startswith("asdf", ""));
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_str_startswith("", "asdf"));
+}
+
+/* == End of strutil.c ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/strutil.h
@@ -0,0 +1,124 @@
+/* ========================================================================= */
+/**
+ * @file strutil.h
+ * Utility functions for working with strings in C.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_STRUTIL_H__
+#define __LIBBASE_STRUTIL_H__
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <sys/types.h>
+
+#include "def.h"
+#include "test.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/**
+ * Appends a formatted string to `buf` at `buf_pos`, respecting `buf_size`.
+ *
+ * @param buf                 Points to the buffer destined to hold the result.
+ *                            The formatted string will be written to
+ *                            `&buf[buf_pos]`.
+ * @param buf_size            Size of `buf`. The written bytes, inculding the
+ *                            terminating NUL character, will not exceed
+ *                            `buf_size`.
+ * @param buf_pos             Position where to write to, within `buf`. Use
+ *                            this for conveniently chaining `strappendf`.
+ * @param fmt_ptr             Format string. See printf for documentation.
+ * @param ...                 Further arguments.
+ *
+ * @return The position of where the trailining NUL character was written to.
+ *     The number of written characters is `buf_size` minus the return value.
+ *     If the buffer was too small for holding all output (including the NUL
+ *     terminator), the return value will be larger or equal than buf_size.
+ *     It is undefined by how much larger the return value is.
+ */
+size_t bs_strappendf(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *fmt_ptr, ...) __ARG_PRINTF__(4, 5);
+
+/** Same as @ref bs_strappendf, with a va_list argument. */
+size_t bs_vstrappendf(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *fmt_ptr,
+    va_list ap) __ARG_PRINTF__(4, 0);
+
+/** Appends a non-formatted string. See @ref bs_strappendf for arguments. */
+size_t bs_strappend(
+    char *buf,
+    size_t buf_size,
+    size_t buf_pos,
+    const char *str_ptr);
+
+/**
+ * Converts a uint64_t from |string_ptr| with |base| and stores it in
+ * |value_ptr|.
+ *
+ * The string is considered valid if it is fully consumed, or if the conversion
+ * ends parsing at a whitespace character. Unlike stroull(3), a leading minus
+ * sign is detected and returned as failure.
+ *
+ * @param string_ptr
+ * @param value_ptr
+ * @param base
+ *
+ * @return true on success.
+ */
+bool bs_strconvert_uint64(
+    const char *string_ptr,
+    uint64_t *value_ptr,
+    int base);
+
+/**
+ * Converts a int64_t from |string_ptr| with |base| and stores it in
+ * |value_ptr|.
+ *
+ * The string is considered valid if it is fully consumed, or if the conversion
+ * ends parsing at a whitespace character.
+ *
+ * @param string_ptr
+ * @param value_ptr
+ * @param base
+ *
+ * @return true on success.
+ */
+bool bs_strconvert_int64(
+    const char *string_ptr,
+    int64_t *value_ptr,
+    int base);
+
+/** @return Whether `string_ptr` starts with `prefix_ptr`. */
+bool bs_str_startswith(const char *string_ptr, const char *prefix_ptr);
+
+/** Test cases. */
+extern const bs_test_case_t   bs_strutil_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_STRUTIL_H__ */
+/* == End of strutil.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess.c
@@ -0,0 +1,1154 @@
+/* ========================================================================= */
+/**
+ * @file subprocess.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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.
+ */
+
+/// kill(2) is a POSIX extension, and we can't do IPC without it.
+#define _POSIX_C_SOURCE 200809L
+
+#include "subprocess.h"
+
+#include <inttypes.h>
+#include <limits.h>
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "assert.h"
+#include "log.h"
+#include "log_wrappers.h"
+#include "sock.h"
+
+#undef _POSIX_C_SOURCE
+
+/* == Definitions ========================================================== */
+
+/** Local type, describes an environment variable. Non-const. */
+typedef struct {
+    /** Name of the variable. */
+    char *name_ptr;
+    /** Value of the variable. */
+    char *value_ptr;
+} _env_var_t;
+
+/** Subprocess handle. */
+struct _bs_subprocess_t {
+    /** Name of the executable to execute in the subprocess. */
+    char                      *file_ptr;
+    /**
+     * A NULL-terminated array of strings, holding the arguments.
+     * Note: argv_ptr[0] points to file_ptr.
+     */
+    char                      **argv_ptr;
+    /** Environment variables. */
+    _env_var_t                 *env_vars_ptr;
+
+    /** Will be non-zero when a child process is running. */
+    pid_t                     pid;
+
+    /** Will hold the exit status, if the process terminated normally. */
+    int                       exit_status;
+    /** Will hold the signal number, if the process was killed by a signal. */
+    int                       signal_number;
+
+    /** File descriptor for stdin (write) */
+    int                       stdin_write;
+    /** File descriptor for stdout (read) */
+    int                       stdout_read;
+    /** File descriptor for stderr (read) */
+    int                       stderr_read;
+    /** Points to the buffer for stdout */
+    char                      *stdout_buf_ptr;
+    /** Position within `stdout_buf_ptr`. */
+    size_t                    stdout_pos;
+    /** Size of `stdout_buf_ptr`. */
+    size_t                    stdout_size;
+    /** Points to the buffer for stderr */
+    char                      *stderr_buf_ptr;
+    /** Position within `stderr_buf_ptr`. */
+    size_t                    stderr_pos;
+    /** Size of `stderr_buf_ptr`. */
+    size_t                    stderr_size;
+};
+
+/** Deterministic Finite Automation transition. */
+typedef struct {
+    /** Next state, for the automaton. */
+    int8_t                     next_state;
+    /** Outcome: Add char to token (1), ignore (0) or terminate (-1). */
+    int8_t                     output;
+} dfa_transition_t;
+
+static bs_subprocess_t *_subprocess_create_argv(
+    char **argv_ptr,
+    _env_var_t *env_vars_ptr);
+static int _waitpid_nointr(bs_subprocess_t *subprocess_ptr, bool wait);
+static void _close_fd(int *fd_ptr);
+static bool _create_pipe_fds(int *read_fd_ptr, int *write_fd_ptr);
+static void _flush_stdout_stderr(bs_subprocess_t *subprocess_ptr);
+static void _subprocess_child(
+    bs_subprocess_t *subprocess_ptr,
+    int stdin_read, int stdout_write, int stderr_write);
+
+static char **_split_command(
+    const char *cmd_ptr,
+    _env_var_t **env_var_ptr_ptr);
+static char *_split_next_token(const char *word_ptr, const char **next_ptr);
+static void _free_argv_list(char **argv_ptr);
+static void _free_env_var_list(_env_var_t *env_var_ptr);
+static bool _populate_env_var(
+    _env_var_t *env_var_ptr,
+    const char *name_ptr,
+    size_t name_len,
+    const char *value_ptr);
+static bool _is_variable_assignment(
+    const char *word_ptr,
+    _env_var_t *env_variable_ptr);
+
+/* == Data ================================================================ */
+
+/** character types, for the DFA table. */
+static const int8_t            char_type_alpha = 0;
+static const int8_t            char_type_blank = 1;
+static const int8_t            char_type_escape = 2;
+static const int8_t            char_type_double_quote = 3;
+static const int8_t            char_type_end_of_string = 4;
+static const int8_t            char_type_single_quote = 5;
+
+/*
+ * Transition table and parsing is modelled closely after libdockapp.
+ *
+ * From https://repo.or.cz/dockapps.git/blob_plain/HEAD:/libdockapp/COPYING:
+ *
+ * Copyright (c) 1999 Alfredo K. Kojima
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+static const dfa_transition_t state_transition_table[9][7] = {
+    /* alpha     blank     escape   dblquote   NUL      sglquote */
+    /* initial state, token has not started yet */
+    { { 3, 1 }, { 0, 0 }, { 4, 0 }, { 1, 0 }, { 8, 0 }, { 6, 0 } },
+    /* double-quoted */
+    { { 1, 1 }, { 1, 1 }, { 2, 0 }, { 3, 0 }, { 5, 0 }, { 1, 1 } },
+    /* escaped while double-quoted */
+    { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 5, 0 }, { 1, 1 } },
+    /* processing token (has started) */
+    { { 3, 1 }, { 5, 0 }, { 4, 0 }, { 1, 0 }, { 5, 0 }, { 6, 0 } },
+    /* escaped, part of token */
+    { { 3, 1 }, { 3, 1 }, { 3, 1 }, { 3, 1 }, { 5, 0 }, { 3, 1 } },
+    /* final state */
+    { {-1,-1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
+    /* single-quoted */
+    { { 6, 1 }, { 6, 1 }, { 7, 0 }, { 6, 1 }, { 5, 0 }, { 3, 0 } },
+    /* escaped while single-quoted */
+    { { 6, 1 }, { 6, 1 }, { 6, 1 }, { 6, 1 }, { 5, 0 }, { 6, 1 } },
+    /* also final. */
+    { {-1,-1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, // final.
+};
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bs_subprocess_t *bs_subprocess_create(
+    const char *file_ptr,
+    const char *const *argv_ptr,
+    const bs_subprocess_environment_variable_t *env_vars_ptr)
+{
+    char **full_argv_ptr;
+
+    size_t argv_size = 0;
+    while (NULL != argv_ptr[argv_size]) ++argv_size;
+    // Don't forget to alloc space for the sentinel NULL and file_ptr.
+    full_argv_ptr = (char**)logged_calloc(argv_size + 2, sizeof(char*));
+    if (NULL == full_argv_ptr) return NULL;
+
+    full_argv_ptr[0] = logged_strdup(file_ptr);
+    if (NULL == full_argv_ptr[0]) {
+        _free_argv_list(full_argv_ptr);
+        return NULL;
+    }
+
+    for (size_t i = 0; i < argv_size; ++i) {
+        full_argv_ptr[i + 1] = logged_strdup(argv_ptr[i]);
+        if (NULL == full_argv_ptr[i + 1]) {
+            _free_argv_list(full_argv_ptr);
+            return NULL;
+        }
+    }
+
+    _env_var_t *env_var_ptr = NULL;
+    if (NULL != env_vars_ptr) {
+        size_t env_var_size = 0;
+        while (NULL != env_vars_ptr[env_var_size].name_ptr) ++env_var_size;
+        env_var_ptr = logged_calloc(env_var_size + 1, sizeof(_env_var_t));
+        if (NULL == env_var_ptr) {
+            _free_argv_list(full_argv_ptr);
+            return NULL;
+        }
+
+        for (size_t i = 0; i < env_var_size; ++i) {
+            if (!_populate_env_var(
+                    env_var_ptr + i,
+                    env_vars_ptr[i].name_ptr,
+                    strlen(env_vars_ptr[i].name_ptr),
+                    env_vars_ptr[i].value_ptr)) {
+                _free_env_var_list(env_var_ptr);
+                _free_argv_list(full_argv_ptr);
+                return NULL;
+            }
+        }
+    }
+
+
+    return _subprocess_create_argv(full_argv_ptr, env_var_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bs_subprocess_t *bs_subprocess_create_cmdline(
+    const char *cmdline_ptr)
+{
+    _env_var_t *env_var_ptr = NULL;
+    char **argv_ptr = _split_command(cmdline_ptr, &env_var_ptr);
+    if (NULL == argv_ptr) {
+        bs_log(BS_ERROR, "Failed _split_command(%s)", cmdline_ptr);
+        return NULL;
+    }
+
+    return _subprocess_create_argv(argv_ptr, env_var_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_subprocess_destroy(bs_subprocess_t *subprocess_ptr)
+{
+    if (0 != subprocess_ptr->pid) {
+        bs_subprocess_stop(subprocess_ptr);
+    }
+
+    if (NULL != subprocess_ptr->stderr_buf_ptr) {
+        free(subprocess_ptr->stderr_buf_ptr);
+    }
+    if (NULL != subprocess_ptr->stdout_buf_ptr) {
+        free(subprocess_ptr->stdout_buf_ptr);
+    }
+
+    if (NULL != subprocess_ptr->env_vars_ptr) {
+        _free_env_var_list(subprocess_ptr->env_vars_ptr);
+        subprocess_ptr->env_vars_ptr = NULL;
+    }
+
+    if (NULL != subprocess_ptr->argv_ptr) {
+        _free_argv_list(subprocess_ptr->argv_ptr);
+        // If argv_ptr was created, then file_ptr was argv_ptr[0], and thus it
+        // was already free()-ed.
+        subprocess_ptr->file_ptr = NULL;
+    }
+
+    if (NULL != subprocess_ptr->file_ptr) {
+        free(subprocess_ptr->file_ptr);
+    }
+
+    free(subprocess_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_subprocess_start(bs_subprocess_t *subprocess_ptr)
+{
+    int stdin_read = -1, stdout_write = -1, stderr_write = -1;
+
+    if (0 != subprocess_ptr->pid) {
+        bs_log(BS_ERROR, "Already a running subprocess, pid %d",
+               subprocess_ptr->pid);
+        return false;
+    }
+
+    if (_create_pipe_fds(&stdin_read, &subprocess_ptr->stdin_write) &&
+        _create_pipe_fds(&subprocess_ptr->stdout_read, &stdout_write) &&
+        _create_pipe_fds(&subprocess_ptr->stderr_read, &stderr_write)) {
+
+        subprocess_ptr->pid = fork();
+        if (0 == subprocess_ptr->pid) {
+            _subprocess_child(subprocess_ptr,
+                              stdin_read, stdout_write, stderr_write);
+
+            // Will never return, hence not reach this line.
+            abort();
+        }
+    }
+
+    // No matter what, we clean up descriptors for the child side.
+    if (-1 != stdin_read) _close_fd(&stdin_read);
+    if (-1 != stdout_write) _close_fd(&stdout_write);
+    if (-1 != stderr_write) _close_fd(&stderr_write);
+
+    subprocess_ptr->stdout_pos = 0;
+    subprocess_ptr->stderr_pos = 0;
+
+    // All worked -- go ahead.
+    if (0 < subprocess_ptr->pid) {
+        bs_sock_set_blocking(subprocess_ptr->stdout_read, false);
+        bs_sock_set_blocking(subprocess_ptr->stderr_read, false);
+        return true;
+    }
+
+    // There was a failure. Clean up descriptors on the parent side.
+    if (-1 != subprocess_ptr->stdin_write) {
+        _close_fd(&subprocess_ptr->stdin_write);
+    }
+    if (-1 != subprocess_ptr->stdout_read) {
+        _close_fd(&subprocess_ptr->stdout_read);
+    }
+    if (-1 != subprocess_ptr->stderr_read) {
+        _close_fd(&subprocess_ptr->stderr_read);
+    }
+    return false;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_subprocess_stop(bs_subprocess_t *subprocess_ptr)
+{
+    if (0 != subprocess_ptr->pid) {
+        // The child process is apparently still up. Send a KILL signal.
+        if (0 != kill(subprocess_ptr->pid, SIGKILL)) {
+            bs_log(BS_ERROR | BS_ERRNO, "Failed kill(%d, SIGKILL)",
+                   subprocess_ptr->pid);
+        }
+
+        // Now, wait for the child process to terminate. Shouldn't last long.
+        int rv = _waitpid_nointr(subprocess_ptr, true);
+        if (0 == rv) {
+            bs_log(BS_ERROR, "Process %d did unexpecedly NOT change status",
+                   subprocess_ptr->pid);
+        } else if (subprocess_ptr->pid != rv) {
+            bs_log(BS_ERROR, "Unexpected return value %d for waitpid(%d, ...)",
+                   rv, subprocess_ptr->pid);
+        }
+        subprocess_ptr->pid = 0;
+    }
+
+    // Here, the process is gone. Flush stdout & stderr, close sockets.
+    _flush_stdout_stderr(subprocess_ptr);
+    _close_fd(&subprocess_ptr->stdin_write);
+    _close_fd(&subprocess_ptr->stdout_read);
+    _close_fd(&subprocess_ptr->stderr_read);
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_subprocess_terminated(bs_subprocess_t *subprocess_ptr,
+                              int *exit_status_ptr,
+                              int *signal_number_ptr)
+{
+    if (0 != subprocess_ptr->pid) {
+        _flush_stdout_stderr(subprocess_ptr);
+        if (0 >= _waitpid_nointr(subprocess_ptr, false)) {
+            // An error or no signal. Assume it's still up.
+            return false;
+        }
+        subprocess_ptr->pid = 0;
+    }
+
+    if (NULL != exit_status_ptr) {
+        *exit_status_ptr = subprocess_ptr->exit_status;
+    }
+    if (NULL != signal_number_ptr) {
+        *signal_number_ptr = subprocess_ptr->signal_number;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_subprocess_get_fds(bs_subprocess_t *subprocess_ptr,
+                           int *stdin_write_fd_ptr,
+                           int *stdout_read_fd_ptr,
+                           int *stderr_read_fd_ptr)
+{
+    if (NULL != stdin_write_fd_ptr) {
+        *stdin_write_fd_ptr = subprocess_ptr->stdin_write;
+    }
+    if (NULL != stdout_read_fd_ptr) {
+        *stdout_read_fd_ptr = subprocess_ptr->stdout_read;
+    }
+    if (NULL != stderr_read_fd_ptr) {
+        *stderr_read_fd_ptr = subprocess_ptr->stderr_read;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+pid_t bs_subprocess_pid(bs_subprocess_t *subprocess_ptr)
+{
+    return subprocess_ptr->pid;
+}
+
+/* ------------------------------------------------------------------------- */
+const char *bs_subprocess_stdout(const bs_subprocess_t *subprocess_ptr)
+{
+    return subprocess_ptr->stdout_buf_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+const char *bs_subprocess_stderr(const bs_subprocess_t *subprocess_ptr)
+{
+    return subprocess_ptr->stderr_buf_ptr;
+}
+
+/* == Local methods ======================================================== */
+
+/* ------------------------------------------------------------------------- */
+/** Creates the subprocess from |argv_ptr|. argv_ptr[0] is the executable. */
+bs_subprocess_t *_subprocess_create_argv(
+    char **argv_ptr,
+    _env_var_t *env_vars_ptr)
+{
+    bs_subprocess_t *subprocess_ptr;
+
+    subprocess_ptr = (bs_subprocess_t*)logged_calloc(
+        1, sizeof(bs_subprocess_t));
+    if (NULL == subprocess_ptr) return NULL;
+    subprocess_ptr->file_ptr = argv_ptr[0];
+    subprocess_ptr->argv_ptr = argv_ptr;
+    subprocess_ptr->env_vars_ptr = env_vars_ptr;
+
+    // A buffer for stdout and stderr. Keep a char for a terminating NUL.
+    size_t buf_size = 4096;
+    subprocess_ptr->stdout_buf_ptr = malloc(buf_size + 1);
+    if (NULL == subprocess_ptr->stdout_buf_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", buf_size + 1);
+        bs_subprocess_destroy(subprocess_ptr);
+        return NULL;
+    }
+    subprocess_ptr->stdout_size = buf_size;
+    subprocess_ptr->stderr_buf_ptr = malloc(buf_size + 1);
+    if (NULL == subprocess_ptr->stderr_buf_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", buf_size + 1);
+        bs_subprocess_destroy(subprocess_ptr);
+        return NULL;
+    }
+    subprocess_ptr->stderr_size = buf_size;
+
+    subprocess_ptr->stdin_write = -1;
+    subprocess_ptr->stdout_read = -1;
+    subprocess_ptr->stderr_read = -1;
+
+    return subprocess_ptr;
+
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Runs waitpid() and updates |exit_status| and |signal_number| of
+ * |subprocess_ptr|. Waits for the job's termination if |wait| is set.
+ * Will re-try waitpid() when encountering EINTR, and logs all errors.
+ *
+ * @param subprocess_ptr
+ * @param wait
+ *
+ * @return See waitpid().
+ */
+int _waitpid_nointr(bs_subprocess_t *subprocess_ptr, bool wait)
+{
+    int options = 0, status = 0, rv = 0;
+    if (!wait) options |= WNOHANG;
+
+    // Catch EINTR and keep calling.
+    do {
+        rv = waitpid(subprocess_ptr->pid, &status, options);
+    } while (0 > rv && EINTR == errno);
+
+    if (0 > rv) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed waitpid(%d, %p, 0x%x)",
+               subprocess_ptr->pid, &status, options);
+    } else if (0 < rv) {
+        // Process did terminate. Update status accordingly.
+        BS_ASSERT(rv == subprocess_ptr->pid);
+        if (WIFEXITED(status)) {
+            subprocess_ptr->exit_status = WEXITSTATUS(status);
+            subprocess_ptr->signal_number = 0;
+        } else if (WIFSIGNALED(status)) {
+            subprocess_ptr->exit_status = INT_MIN;
+            subprocess_ptr->signal_number = WTERMSIG(status);
+        } else {
+            bs_log(BS_FATAL, "Unhandled waitpid() status for %d: 0x%x",
+                   subprocess_ptr->pid, status);
+        }
+    }
+    return rv;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Helper: Close socket, log error, and invalidate |*fd_ptr|. */
+void _close_fd(int *fd_ptr)
+{
+    if (0 != close(*fd_ptr)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", *fd_ptr);
+    }
+    *fd_ptr = -1;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Helper: Reads all of stdout and stderr into the buffers. */
+void _flush_stdout_stderr(bs_subprocess_t *subprocess_ptr)
+{
+    ssize_t read_bytes;
+
+    read_bytes = read(
+        subprocess_ptr->stdout_read,
+        subprocess_ptr->stdout_buf_ptr + subprocess_ptr->stdout_pos,
+        subprocess_ptr->stdout_size - subprocess_ptr->stdout_pos);
+    if (0 > read_bytes && (EAGAIN != errno || EWOULDBLOCK != errno)) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed read(%d, %p, %zu)",
+               subprocess_ptr->stdout_read,
+               subprocess_ptr->stdout_buf_ptr + subprocess_ptr->stdout_pos,
+               subprocess_ptr->stdout_size - subprocess_ptr->stdout_pos);
+    } else if (0 <= read_bytes) {
+        subprocess_ptr->stdout_pos += read_bytes;
+        subprocess_ptr->stdout_buf_ptr[subprocess_ptr->stdout_pos] = '\0';
+    }
+    read_bytes = read(
+        subprocess_ptr->stderr_read,
+        subprocess_ptr->stderr_buf_ptr + subprocess_ptr->stderr_pos,
+        subprocess_ptr->stderr_size - subprocess_ptr->stderr_pos);
+    if (0 > read_bytes && (EAGAIN != errno || EWOULDBLOCK != errno)) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed read(%d, %p, %zu)",
+               subprocess_ptr->stderr_read,
+               subprocess_ptr->stderr_buf_ptr + subprocess_ptr->stderr_pos,
+               subprocess_ptr->stderr_size - subprocess_ptr->stderr_pos);
+    } else if (0 <= read_bytes) {
+        subprocess_ptr->stderr_pos += read_bytes;
+        subprocess_ptr->stderr_buf_ptr[subprocess_ptr->stderr_pos] = '\0';
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+/** Helper: Creates a pipe, with errors logged & file descriptors stored */
+bool _create_pipe_fds(int *read_fd_ptr, int *write_fd_ptr) {
+    int fds[2];
+
+    if (0 != pipe(fds)) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed pipe(%p)", fds);
+        return false;
+    }
+    *read_fd_ptr = fds[0];
+    *write_fd_ptr = fds[1];
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Wraps the child process: setup stdin, stdout, stderr and launches job. */
+void _subprocess_child(
+    bs_subprocess_t *subprocess_ptr,
+    int stdin_read, int stdout_write, int stderr_write)
+{
+    // Close the parent's descriptors for stdin, stdout & stderr.
+    _close_fd(&subprocess_ptr->stdin_write);
+    _close_fd(&subprocess_ptr->stdout_read);
+    _close_fd(&subprocess_ptr->stderr_read);
+
+    // Setup stdin, stderr and stdout proper.
+    if (0 != dup2(stdin_read, 0)) {
+        bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 0)", stdin_read);
+    }
+    if (0 != close(stdin_read)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stdin_read);
+    }
+    if (1 != dup2(stdout_write, 1)) {
+        bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 1)", stdout_write);
+    }
+    if (0 != close(stdout_write)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stdout_write);
+    }
+    if (2 != dup2(stderr_write, 2)) {
+        bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 2)", stderr_write);
+    }
+    if (0 != close(stderr_write)) {
+        bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stderr_write);
+    }
+
+    for (const _env_var_t *var_ptr = subprocess_ptr->env_vars_ptr;
+         NULL != var_ptr && NULL != var_ptr->name_ptr;
+         ++var_ptr) {
+        if (0 != setenv(var_ptr->name_ptr, var_ptr->value_ptr, 1)) {
+            bs_log(BS_WARNING | BS_ERRNO, "Failed setenv(\"%s\", \"%s\")",
+                   var_ptr->name_ptr, var_ptr->value_ptr);
+            abort();
+        }
+    }
+
+    if (0 > execvp(subprocess_ptr->file_ptr, subprocess_ptr->argv_ptr)) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed execvp(%s, %p)",
+               subprocess_ptr->file_ptr, subprocess_ptr->argv_ptr);
+    }
+    // We only get here on error, and cannot do anything about.
+    abort();
+}
+
+/* ------------------------------------------------------------------------- */
+/** Splits the commandline into a NULL-terminated list of strings. */
+char **_split_command(const char *cmd_ptr, _env_var_t **env_var_ptr_ptr)
+{
+    char *token_ptr;
+    const char*line_ptr;
+    char **argv_ptr;
+
+    size_t argv_size = 0;
+    argv_ptr = (char**)logged_calloc(1, (argv_size + 1) * sizeof(char*));
+    if (NULL == argv_ptr) return NULL;
+
+    _env_var_t *env_var_ptr;
+    env_var_ptr = logged_calloc(1, sizeof(_env_var_t));
+    if (NULL == env_var_ptr) {
+        free(argv_ptr);
+        return NULL;
+    }
+    size_t env_var_size = 0;
+
+    line_ptr = cmd_ptr;
+    do {
+        token_ptr = _split_next_token(line_ptr, &line_ptr);
+        if (NULL != token_ptr) {
+
+            if (0 == argv_size &&
+                _is_variable_assignment(token_ptr, &env_var_ptr[env_var_size])) {
+                free(token_ptr);
+
+                void *tmp_ptr = realloc(
+                    env_var_ptr,
+                    (env_var_size + 2) * sizeof(_env_var_t));
+                if (NULL == tmp_ptr) {
+                    bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)",
+                           env_var_ptr,
+                           (env_var_size + 2) * sizeof(_env_var_t));
+                    _free_env_var_list(env_var_ptr);
+                    _free_argv_list(argv_ptr);
+                    return NULL;
+                }
+                env_var_ptr = tmp_ptr;
+                ++env_var_size;
+                memset(&env_var_ptr[env_var_size], 0, sizeof(_env_var_t));
+            } else {
+                argv_ptr[argv_size++] = token_ptr;
+                void *tmp_ptr = realloc(argv_ptr, (argv_size + 1) * sizeof(char*));
+                if (NULL == tmp_ptr) {
+                    bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)",
+                           argv_ptr, (argv_size + 1) * sizeof(char*));
+                    _free_env_var_list(env_var_ptr);
+                    _free_argv_list(argv_ptr);
+                    return NULL;
+                }
+                argv_ptr = (char**)tmp_ptr;
+                argv_ptr[argv_size] = NULL;
+            }
+        }
+    } while (NULL != token_ptr && NULL != line_ptr);
+
+    *env_var_ptr_ptr = env_var_ptr;
+    return argv_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Returns a copy of the next token from |word_ptr|. Must be free()-ed. */
+char *_split_next_token(const char *word_ptr, const char **next_ptr)
+{
+    char *token_ptr, *orig_token_ptr;
+    int state, char_type;
+
+    if (*word_ptr == '\0') return NULL;
+    token_ptr = malloc(strlen(word_ptr) + 1);
+    if (NULL == token_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", strlen(word_ptr) + 1);
+        return NULL;
+    }
+    orig_token_ptr = token_ptr;
+
+    state = 0;
+    *token_ptr = '\0';
+    while (true) {
+        switch (*word_ptr) {
+        case '\0': char_type = char_type_end_of_string; break;
+        case '\\': char_type = char_type_escape; break;
+        case '"' : char_type = char_type_double_quote; break;
+        case '\'': char_type = char_type_single_quote; break;
+        case ' ' :
+        case '\t': char_type = char_type_blank; break;
+        default: char_type = char_type_alpha; break;
+        }
+
+        if (state_transition_table[state][char_type].output) {
+            *token_ptr = *word_ptr;
+            ++token_ptr;
+            *token_ptr = '\0';
+        }
+        state = state_transition_table[state][char_type].next_state;
+        word_ptr++;
+        if (0 > state_transition_table[state][0].output) break;
+    }
+
+    // The token might be shorter than what we had reserved, update that.
+    token_ptr = logged_strdup(orig_token_ptr);
+    free(orig_token_ptr);
+
+    if (char_type == char_type_end_of_string) {
+        *next_ptr = NULL;
+    } else {
+        *next_ptr = word_ptr;
+    }
+
+    return token_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+/** Frees up strings and the list of a NULL-terminated list of strings. */
+void _free_argv_list(char **argv_ptr) {
+    for (char **iter_ptr = argv_ptr; *iter_ptr != NULL; ++iter_ptr) {
+        free(*iter_ptr);
+    }
+    free(argv_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Frees up the variable names and values, as well as the list itself. */
+void _free_env_var_list(_env_var_t *env_var_ptr)
+{
+    for (_env_var_t *iter_ptr = env_var_ptr;
+         iter_ptr->name_ptr != NULL;
+         ++iter_ptr) {
+        free(iter_ptr->name_ptr);
+        free(iter_ptr->value_ptr);
+    }
+    free(env_var_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Populates the entry from name and value. */
+bool _populate_env_var(_env_var_t *env_var_ptr,
+                       const char *name_ptr,
+                       size_t name_len,
+                       const char *value_ptr)
+{
+    env_var_ptr->name_ptr = logged_malloc(name_len + 1);
+    if (NULL == env_var_ptr->name_ptr) return false;
+
+    memcpy(env_var_ptr->name_ptr, name_ptr, name_len);
+    env_var_ptr->name_ptr[name_len] = '\0';
+
+    env_var_ptr->value_ptr = logged_strdup(value_ptr);
+    if (NULL == env_var_ptr->value_ptr) {
+        free(env_var_ptr->name_ptr);
+        env_var_ptr->name_ptr = NULL;
+        return false;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Returns if `word_ptr` is a variable assignmend and gives name and value.
+ *
+ * A variable assignment will start with the variable name, located at the
+ * beginning of `word_ptr`. The variable name must start with a letter or
+ * underscore, and may only contain only letters, numbers or underscore.
+ * The variable name must be succeeded by a '=' sign. All that follows will be
+ * considered the value.
+ *
+ * @param word_ptr            The token to analyse.
+ * @param name_ptr_ptr        If `word_ptr` is a variable assignment, then
+ *     *name_ptr_ptr will be set to point to a newly allocated copy of the
+ *     variable name. Will only be set if the function returns true. If so,
+ *     the allocated memory must be free()-ed.
+ * @param value_ptr_ptr       If `word_ptr` is a variable assignent, then
+ *     *value_ptr_ptr will be set to point to a newly allocated copy of the
+ *     variable's value. Will only be set if the function return true. I fos,
+ *     the allocated memory must be free()-ed.
+ *
+ * @return true if `word_ptr` is a variable, and false if it's not a varaible
+ *     or if an error occurred. *name_ptr_ptr and *value_ptr_ptr will be set
+ *     only on success.
+ */
+bool _is_variable_assignment(
+    const char *word_ptr,
+    _env_var_t *env_variable_ptr)
+{
+    static const char *name_regexp_ptr = "^[a-zA-Z_][a-zA-Z_0-9]*=";
+    regex_t regex;
+    regmatch_t matches[1];
+    int rv;
+
+    if (0 != (rv = regcomp(&regex, name_regexp_ptr, REG_EXTENDED))) {
+        bs_log(BS_ERROR, "Failed regcomp(%p, \"%s\", REG_EXTENDED): %d",
+               &regex, name_regexp_ptr, rv);
+        return false;
+    }
+
+    rv = regexec(&regex, word_ptr, 1, matches, 0);
+    regfree(&regex);
+    if (REG_NOMATCH == rv) return false;
+    if (0 != rv) {
+        bs_log(BS_ERROR, "Failed regexec(%p, \"%s\", 1, %p): %d",
+               &regex, word_ptr, matches, rv);
+        return false;
+    }
+
+    // A valid match must start at the beginning of the word, and it must at
+    // least hold two chars: the variable name, and the '=' sign.
+    if (matches[0].rm_so != 0 || matches[0].rm_eo < 2) return false;
+
+    if (!_populate_env_var(env_variable_ptr,
+                           word_ptr,
+                           matches[0].rm_eo - matches[0].rm_so - 1,
+                           word_ptr + matches[0].rm_eo)) {
+        return false;
+    }
+    return true;
+}
+
+/* == Unit tests =========================================================== */
+/** @cond TEST */
+
+static void test_is_variable_assignment(bs_test_t *test_ptr);
+static void test_split_command(bs_test_t *test_ptr);
+static void test_failure(bs_test_t *test_ptr);
+static void test_hang(bs_test_t *test_ptr);
+static void test_nonexisting(bs_test_t *test_ptr);
+static void test_sigpipe(bs_test_t *test_ptr);
+static void test_success(bs_test_t *test_ptr);
+static void test_success_cmdline(bs_test_t *test_ptr);
+static void test_success_twice(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_subprocess_test_cases[] = {
+    { 1, "is_variable_assignment", test_is_variable_assignment },
+    { 1, "split_command", test_split_command },
+    { 1, "failure", test_failure },
+    { 1, "hang", test_hang },
+    { 1, "nonexisting", test_nonexisting },
+    { 1, "sigpipe", test_sigpipe },
+    { 1, "success", test_success },
+    { 1, "success_cmdline", test_success_cmdline },
+    { 1, "success_twice", test_success_twice },
+    { 0, NULL, NULL }
+};
+
+const char                    *test_args[] = { "alpha", NULL };
+
+/* ------------------------------------------------------------------------- */
+/** Helper: Verify two NULL-terminated pointer string lists are equal. */
+void test_verify_eq_arglist(
+    bs_test_t *test_ptr, char **a1_ptr, char **a2_ptr)
+{
+    for (; *a1_ptr != NULL && *a2_ptr != NULL; a1_ptr++, a2_ptr++) {
+        BS_TEST_VERIFY_STREQ(test_ptr, *a1_ptr, *a2_ptr);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, *a1_ptr, *a2_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/** Helper: Verify two env variable lists are equal. */
+void test_verify_eq_envlist(
+    bs_test_t *test_ptr,
+    const bs_subprocess_environment_variable_t *exp_ptr,
+    _env_var_t *var_ptr)
+{
+    for (;
+         exp_ptr->name_ptr != NULL && var_ptr->name_ptr != NULL;
+         exp_ptr++, var_ptr++) {
+        BS_TEST_VERIFY_STREQ(test_ptr, exp_ptr->value_ptr, var_ptr->value_ptr);
+        BS_TEST_VERIFY_STREQ(test_ptr, exp_ptr->value_ptr, var_ptr->value_ptr);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, exp_ptr->name_ptr, var_ptr->name_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_is_variable_assignment(bs_test_t *test_ptr)
+{
+    _env_var_t var;
+    BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a=value", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a1=value", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a1");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("_=value", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "_");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _is_variable_assignment("SILLY_2_LONG_VARIABLE_42=value", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "SILLY_2_LONG_VARIABLE_42");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        _is_variable_assignment("a=value\" with more\"", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value\" with more\"");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a= value", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, " value");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a=", &var));
+    BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a");
+    BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "");
+    free(var.name_ptr);
+    free(var.value_ptr);
+
+    BS_TEST_VERIFY_FALSE(test_ptr, _is_variable_assignment("a", &var));
+    BS_TEST_VERIFY_FALSE(test_ptr, _is_variable_assignment("1a=b", &var));
+}
+
+
+/* ------------------------------------------------------------------------- */
+void test_split_command(bs_test_t *test_ptr)
+{
+    char **argv_ptr;
+    _env_var_t *env_var_ptr;
+
+    char *expected1[] = {"command", "arg1", "arg2", NULL };
+    argv_ptr = _split_command("command arg1 arg2", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected1, argv_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+
+    char *expected2[] = {"command", "arg1 arg2", "arg3", NULL };
+    argv_ptr = _split_command("command \"arg1 arg2\" arg3", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected2, argv_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+
+    char *expected3[] = {"command", "arg1 arg2", "arg3", NULL };
+    argv_ptr = _split_command("command \'arg1 arg2\' arg3", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected3, argv_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+
+    char *expected4[] = {"command", "arg1 \'arg2", "arg3", NULL };
+    argv_ptr = _split_command("command \"arg1 \'arg2\" arg3\'", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected4, argv_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+
+    char *expected5[] = {"command", "\"arg1", "arg2\"", "arg3", NULL };
+    argv_ptr = _split_command("command \\\"arg1 arg2\\\" arg3", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected5, argv_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+
+    char *expected6[] = {"command", "arg1", NULL };
+    const bs_subprocess_environment_variable_t expected_env6[] = {
+        { "var1", "1" }, { "var2", "2" }, { NULL, NULL }
+    };
+    argv_ptr = _split_command("var1=1 var2=2 command arg1", &env_var_ptr);
+    test_verify_eq_arglist(test_ptr, expected6, argv_ptr);
+    test_verify_eq_envlist(test_ptr, expected_env6, env_var_ptr);
+    _free_argv_list(argv_ptr);
+    _free_env_var_list(env_var_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_failure(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_failure", test_args, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+        // Don't busy-loop -- just wait a little.
+        poll(NULL, 0, 10);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, 42, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number);
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_hang(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_hang", test_args, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    BS_TEST_VERIFY_FALSE(
+        test_ptr,
+        bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number));
+    bs_subprocess_stop(sp_ptr);
+    BS_TEST_VERIFY_TRUE(
+        test_ptr,
+        bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number));
+    BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, SIGKILL, signal_number);
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_nonexisting(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_does_not_exist", test_args, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+        // Don't busy-loop -- just wait a little.
+        poll(NULL, 0, 10);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, SIGABRT, signal_number);
+
+    BS_TEST_VERIFY_STRMATCH(
+        test_ptr, bs_subprocess_stderr(sp_ptr),
+        ".*ERROR.*Failed execvp\\(\\./subprocess_test_does_not_exist");
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_sigpipe(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_sigpipe", test_args, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+        // Don't busy-loop -- just wait a little.
+        poll(NULL, 0, 10);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, SIGPIPE, signal_number);
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_success(bs_test_t *test_ptr)
+{
+    const bs_subprocess_environment_variable_t envs[] = {
+        { "SUBPROCESS_ENV", "WORKS" },
+        { NULL, NULL }
+    };
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_success", test_args, envs);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+        // Don't busy-loop -- just wait a little.
+        poll(NULL, 0, 10);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number);
+    BS_TEST_VERIFY_STREQ(
+        test_ptr, "test stdout: ./subprocess_test_success\nenv: WORKS\n",
+        bs_subprocess_stdout(sp_ptr));
+    BS_TEST_VERIFY_STREQ(
+        test_ptr, "test stderr: alpha\n",
+        bs_subprocess_stderr(sp_ptr));
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_success_cmdline(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create_cmdline(
+        "SUBPROCESS_ENV=CMD ./subprocess_test_success alpha");
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+    int exit_status, signal_number;
+    while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+        // Don't busy-loop -- just wait a little.
+        poll(NULL, 0, 10);
+    }
+    BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status);
+    BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number);
+    BS_TEST_VERIFY_STREQ(
+        test_ptr, "test stdout: ./subprocess_test_success\nenv: CMD\n",
+        bs_subprocess_stdout(sp_ptr));
+    BS_TEST_VERIFY_STREQ(
+        test_ptr, "test stderr: alpha\n",
+        bs_subprocess_stderr(sp_ptr));
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+void test_success_twice(bs_test_t *test_ptr)
+{
+    bs_subprocess_t *sp_ptr = bs_subprocess_create(
+        "./subprocess_test_success", test_args, NULL);
+    BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr);
+
+    for (int i = 0; i < 2; ++i) {
+        // Verify that starting the process again works.
+        BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr));
+
+        int exit_status, signal_number;
+        while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) {
+            // Don't busy-loop -- just wait a little.
+            poll(NULL, 0, 10);
+        }
+        BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status);
+        BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number);
+        BS_TEST_VERIFY_STREQ(
+            test_ptr, "test stdout: ./subprocess_test_success\nenv: (null)\n",
+            bs_subprocess_stdout(sp_ptr));
+        BS_TEST_VERIFY_STREQ(
+            test_ptr, "test stderr: alpha\n",
+            bs_subprocess_stderr(sp_ptr));
+    }
+    bs_subprocess_destroy(sp_ptr);
+}
+
+/** @endcond */
+/* == End of subprocess.c ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess.h
@@ -0,0 +1,164 @@
+/* ========================================================================= */
+/**
+ * @file subprocess.h
+ * Methods to conveniently create sub-processes and handle I/O in a non-
+ * blocking fashion.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_SUBPROCESS_H__
+#define __LIBBASE_SUBPROCESS_H__
+
+#include "test.h"
+
+#include <stdbool.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Handle for a sub-process */
+typedef struct _bs_subprocess_t bs_subprocess_t;
+
+/** Descriptor for an environment variable. */
+typedef struct {
+    /** Name for the environment variable. */
+    const char                *name_ptr;
+    /** Value of the environment variable. */
+    const char                *value_ptr;
+} bs_subprocess_environment_variable_t;
+
+/**
+ * Creates a sub-process. Does not start it.
+ *
+ * @param file_ptr Name of the executable file. The subprocess module will
+ *     invoke execvp() for execution, hence this will lookup the executable
+ *     from PATHs as the shell would.
+ * @param argv_ptr Pointer to a NULL-terminated list of args.
+ *     Note: Unlike the convention to execvp(), this expects the real sequence
+ *     of arguments. file_ptr will be inserted in position [0] when calling
+ *     execvp().
+ * @param env_vars_ptr Points to an array of @ref bs_subprocess_environment_variable_t
+ *     describing additional environment variables to set for the sub-process.
+ *     The array must be concluded with a sentinel element, where key_ptr is
+ *     NULL. `env_vars_ptr` may be NULL to indicate no envinroment variables
+ *     need to be set.
+ *
+ * @return A handle to the subprocess, to be cleaned by calling
+ *     |bs_subprocess_destroy|.
+ */
+bs_subprocess_t *bs_subprocess_create(
+    const char *file_ptr,
+    const char *const *argv_ptr,
+    const bs_subprocess_environment_variable_t *env_vars_ptr);
+
+/**
+ * Creates a sub-process. Does not start it.
+ *
+ * @param cmdline_ptr Commandline to execute. Will be tokenized. The leading
+ *     tokens may be environment variables. The first token thereafter is
+ *     is expected to identify an executable file; the rest of the tokens
+ *     will be handled as arguments.
+ *
+ * @return A handle to the subprocess, to be cleaned by calling
+ *     |bs_subprocess_destroy|.
+ */
+bs_subprocess_t *bs_subprocess_create_cmdline(
+    const char *cmdline_ptr);
+
+/**
+ * Destroys a sub-process. Will stop (|bs_subprocess_stop|), if still running.
+ *
+ * @param subprocess_ptr
+ */
+void bs_subprocess_destroy(bs_subprocess_t *subprocess_ptr);
+
+/**
+ * Starts the sub-process.
+ *
+ * A started sub-process can be stopped by calling |bs_subprocess_stop| (and
+ * then can be started again), or by |bs_subprocess_destroy|.
+ *
+ * @param subprocess_ptr
+ *
+ * @return Whether the call succeeded.
+ */
+bool bs_subprocess_start(bs_subprocess_t *subprocess_ptr);
+
+/**
+ * Stops the sub-process. Will SIGKILL the process, if not terminated
+ * already.
+ *
+ * @param subprocess_ptr
+ */
+void bs_subprocess_stop(bs_subprocess_t *subprocess_ptr);
+
+/**
+ * Checks whether |subprocess_ptr| has terminated, and provide the information
+ * in |*exit_status_ptr|, respectively |*signal_number_ptr|.
+ *
+ * @param subprocess_ptr
+ * @param exit_status_ptr If the process terminated normally, this will hold
+ *     the exit status of the program. It will be INT_MIN, if the process
+ *     terminated abnormally.
+ * @param signal_number_ptr If the process terminated abnormally, this will
+ *     hold the number of the signal that caused the abnormal termination. A
+ *     value of 0 indicates normal termination.
+ *
+ * @return Whether the process has terminated.
+ */
+bool bs_subprocess_terminated(bs_subprocess_t *subprocess_ptr,
+                              int *exit_status_ptr,
+                              int *signal_number_ptr);
+
+/**
+ * Retrieves the parent's file descriptors for stdin, stdout and stderr.
+ *
+ * @param subprocess_ptr
+ * @param stdin_write_fd_ptr  May be NULL.
+ * @param stdout_read_fd_ptr  May be NULL.
+ * @param stderr_read_fd_ptr  May be NULL.
+ */
+void bs_subprocess_get_fds(bs_subprocess_t *subprocess_ptr,
+                           int *stdin_write_fd_ptr,
+                           int *stdout_read_fd_ptr,
+                           int *stderr_read_fd_ptr);
+
+/**
+ * Returns the PID of the given subprocess. Will be 0 if not started or
+ * terminated.
+ *
+ * @param subprocess_ptr
+ *
+ * @return PID, or 0.
+ */
+pid_t bs_subprocess_pid(bs_subprocess_t *subprocess_ptr);
+
+/** Provides a pointer to the stdout buffer for |subprocess_ptr|. */
+const char *bs_subprocess_stdout(const bs_subprocess_t *subprocess_ptr);
+/** Provides a pointer to the stderr buffer for |subprocess_ptr|. */
+const char *bs_subprocess_stderr(const bs_subprocess_t *subprocess_ptr);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_subprocess_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_SUBPROCESS_H__ */
+/* == End of subprocess.h ================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess_test_failure.c
@@ -0,0 +1,27 @@
+/* ========================================================================= */
+/**
+ * @file subprocess_test_failure.c
+ * Test executable for subprocess module: Exit with failure code.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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.
+ */
+
+/** Returns an error status. */
+int main() {
+    return 42;
+}
+
+/* == End of subprocess_test_failure.c ===================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess_test_hang.c
@@ -0,0 +1,33 @@
+/* ========================================================================= */
+/**
+ * @file subprocess_test_hang.c
+ * Test executable for subprocess module: Keep looping, does not exit.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <poll.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+/** A program that will never exit. */
+int main() {
+    while (true) {
+        poll(NULL, 0, 100);
+    }
+}
+
+/* == End of subprocess_test_hang.c ======================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess_test_sigpipe.c
@@ -0,0 +1,30 @@
+/* ========================================================================= */
+/**
+ * @file subprocess_test_sigpipe.c
+ * Test executable for subprocess module: Killed by (arbitrarily chosen)
+ * SIGPIPE.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <signal.h>
+
+/** A test program that raises a signal. */
+int main() {
+    raise(SIGPIPE);
+}
+
+/* == End of subprocess_test_sigpipe.c ===================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/subprocess_test_success.c
@@ -0,0 +1,39 @@
+/* ========================================================================= */
+/**
+ * @file subprocess_test_success.c
+ * Test executable for subprocess module: Exit successfully, with stdout/err.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <stdio.h>
+#include <stdlib.h>
+
+/** A test program that returns a successful code, if args were all correct. */
+int main(int argc, char** argv) {
+    if (2 != argc) {
+        fprintf(stderr, "Expecting 1 arguments.\n");
+        return EXIT_FAILURE;
+    }
+
+    const char *value_ptr = getenv("SUBPROCESS_ENV");
+    fprintf(stdout, "test stdout: %s\nenv: %s\n",
+            argv[0], value_ptr ? value_ptr : "(null)");
+    fprintf(stderr, "test stderr: %s\n", argv[1]);
+    return EXIT_SUCCESS;
+}
+
+/* == End of subprocess_test_success.c ===================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/test.c
@@ -0,0 +1,760 @@
+/* ========================================================================= */
+/**
+ * @file test.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 <curses.h>
+#include <stdarg.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <term.h>
+#include <threads.h>
+
+#include "arg.h"
+#include "assert.h"
+#include "dllist.h"
+#include "file.h"
+#include "log_wrappers.h"
+#include "test.h"
+
+/** Information on current test case. */
+struct _bs_test_t {
+    /** Index of current test case (for information only). */
+    int                       case_idx;
+    /** Current test case descriptor. */
+    const bs_test_case_t      *case_ptr;
+
+    /** Test outcome: Failed? */
+    bool                      failed;
+    /** Test report summary (through bs_test_succeed, bs_test_fail) */
+    char                      report[256];
+};
+
+/** Terminal codes for commands we're requiring. */
+struct _bs_test_tcode {
+    /** Buffer for storing all codes. */
+    char                      all_codes[2048];
+    /** Code for setting the foreground color. */
+    const char                *code_set_foreground;
+    /** Code for setting the background color. */
+    const char                *code_set_background;
+    /** Sets default colour-pair to the original one. */
+    const char                *code_orig_pair;
+    /** Code for entering bold mode. */
+    const char                *code_enter_bold_mode;
+    /** Code for clearing attributes. */
+    const char                *code_exit_attribute_mode;
+};
+
+/** Attributes for setting terminal attributes. */
+enum _bs_test_attributes {
+    BS_TEST_ATTR_SKIP = 0x0201,
+    BS_TEST_ATTR_FAIL = 0x020C,
+    BS_TEST_ATTR_SUCCESS = 0x020A,
+    BS_TEST_ATTR_RESET = -1,
+};
+
+/** Double-linked-list node, information about a failed test case. */
+struct bs_test_fail_node {
+    /** Node of the double-linked list. */
+    bs_dllist_node_t          dlnode;
+    /** Full name of the test (including test suite)*. */
+    char                      *full_name_ptr;
+};
+
+/** Summary on a test run (either a case or a set). */
+struct bs_test_report {
+    /** Number of failed tests. */
+    int                      failed;
+    /** Number of succeeded tests. */
+    int                      succeeded;
+    /** Number of skipped tests. */
+    int                      skipped;
+    /** Total number of tests. */
+    int                      total;
+};
+
+/* Other helpers */
+static void bs_test_tcode_init(void);
+static int bs_test_putc(int c);
+static void bs_test_puts(const char *fmt_ptr, ...) __ARG_PRINTF__(1, 2);
+static void bs_test_attr(int attr);
+
+/* Set helpers. */
+static void bs_test_set_report(const bs_test_set_t *set_ptr,
+                               const struct bs_test_report *set_report_ptr);
+static int bs_test_set(const bs_test_set_t *set_ptr,
+                       const char *pattern_ptr,
+                       bs_dllist_t *failed_tests_ptr);
+
+/* Testcase helpers. */
+static void bs_test_case_prepare(bs_test_t *test_ptr,
+                                 const bs_test_set_t *set_ptr,
+                                 int case_idx);
+static void bs_test_case_report(const bs_test_t *test_ptr, bool enabled);
+
+static struct bs_test_fail_node *bs_test_case_fail_node_create(
+    char *full_name_ptr);
+static void bs_test_case_fail_node_destroy(
+    struct bs_test_fail_node *fnode_ptr);
+static char *bs_test_case_create_full_name(
+    const bs_test_set_t *set_ptr,
+    const bs_test_case_t *case_ptr);
+
+/* == Data ================================================================= */
+
+/** Terminal codes. */
+static struct _bs_test_tcode  bs_test_tcode;
+/** Line separator between test cases. */
+static const char             *bs_test_linesep_ptr =
+    "----------------------------------------"
+    "---------------------------------------";
+/** Line separator between test sets. */
+static const char             *bs_test_report_separator_ptr =
+    "========================================"
+    "=======================================";
+
+static char                  *bs_test_filter_ptr = NULL;
+static char                  *bs_test_data_dir_ptr = NULL;
+
+static const bs_arg_t        bs_test_args[] = {
+    BS_ARG_STRING(
+        "test_filter",
+        "Filter to apply for selecting tests. Uses fnmatch on the full name.",
+        "*",
+        &bs_test_filter_ptr),
+    BS_ARG_STRING(
+        "test_data_directory",
+        "Directory to use for test data. Setting this flag takes precedence "
+        "over the parameter specified through the `bs_test_param_t` arg to "
+        "bs_test().",
+        NULL,
+        &bs_test_data_dir_ptr),
+    BS_ARG_SENTINEL()
+};
+
+/* == Exported Functions =================================================== */
+
+/* ------------------------------------------------------------------------- */
+int bs_test(
+    const bs_test_set_t *test_sets,
+    int argc,
+    const char **argv,
+    const bs_test_param_t *param_ptr)
+{
+    struct bs_test_report     report;
+    int                       i;
+    bool                      run_set;
+    bs_dllist_t               failed_tests;
+
+    if (NULL == test_sets) return 0;
+    if (!bs_arg_parse(bs_test_args, BS_ARG_MODE_NO_EXTRA, &argc, argv)) {
+        bs_arg_print_usage(stderr, bs_test_args);
+        return -1;
+    }
+
+    if (NULL == bs_test_data_dir_ptr &&
+        NULL != param_ptr &&
+        NULL != param_ptr->test_data_dir_ptr) {
+        bs_test_data_dir_ptr = logged_strdup(param_ptr->test_data_dir_ptr);
+        if (NULL == bs_test_data_dir_ptr) return -1;
+    }
+
+    bs_test_tcode_init();
+
+    memset(&report, 0, sizeof(report));
+    memset(&failed_tests, 0, sizeof(failed_tests));
+    /** List of all failed tests. */
+    while (NULL != test_sets->name_ptr) {
+
+        /* If any args are given, check if the name matches. */
+        run_set = 1 >= argc;
+        for (i = 1; i < argc; i++) {
+            if (0 == strcmp(argv[i], test_sets->name_ptr)) {
+                run_set = true;;
+            }
+        }
+
+        if (run_set && test_sets->enabled) {
+            if (bs_test_set(test_sets, bs_test_filter_ptr, &failed_tests)) {
+                report.failed++;
+            } else {
+                report.succeeded++;
+            }
+        } else {
+            report.skipped++;
+        }
+
+        report.total++;
+        test_sets++;
+    }
+
+    if (report.failed) {
+        bs_test_attr(BS_TEST_ATTR_FAIL);
+        bs_test_puts("FAILED: % 66d/% 3d\n",
+                     report.failed, report.total);
+
+        /* Print all failed tests. */
+        struct bs_test_fail_node *fnode_ptr;
+        while (NULL != (fnode_ptr = (struct bs_test_fail_node*)
+                        bs_dllist_pop_front(&failed_tests))) {
+            bs_test_puts(" %s\n", fnode_ptr->full_name_ptr);
+            bs_test_case_fail_node_destroy(fnode_ptr);
+        }
+        bs_test_attr(BS_TEST_ATTR_RESET);
+    } else if (report.succeeded) {
+        bs_test_attr(BS_TEST_ATTR_SUCCESS);
+        bs_test_puts("SUCCESS: % 65d/% 3d\n",
+                     report.succeeded, report.total);
+        bs_test_attr(BS_TEST_ATTR_RESET);
+    }
+    if (report.skipped) {
+        bs_test_attr(BS_TEST_ATTR_SKIP);
+        bs_test_puts("SKIPPED: % 65d/% 3d\n", report.skipped, report.total);
+        bs_test_attr(BS_TEST_ATTR_RESET);
+    }
+
+    BS_ASSERT(0 == bs_dllist_size(&failed_tests));
+    bs_arg_cleanup(bs_test_args);
+    return report.failed;
+}
+
+
+/* ------------------------------------------------------------------------- */
+void bs_test_succeed(bs_test_t *test, const char *fmt_ptr, ...)
+{
+    va_list                   ap;
+
+    if ((!test->failed) && ('\0' == test->report[0])) {
+        va_start(ap, fmt_ptr);
+        vsnprintf(test->report, sizeof(test->report), fmt_ptr, ap);
+        va_end(ap);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_test_fail_at(
+    bs_test_t *test,
+    const char *fname_ptr,
+    int line,
+    const char *fmt_ptr, ...)
+{
+    va_list                   ap;
+
+    if (!test->failed) {
+        test->failed = true;
+        int pos = snprintf(&test->report[0], sizeof(test->report),
+                           "%s(%d): ", fname_ptr, line);
+        if (0 > pos || (size_t)pos >= sizeof(test->report)) return;
+
+        va_start(ap, fmt_ptr);
+        vsnprintf(&test->report[pos], sizeof(test->report) - pos, fmt_ptr, ap);
+        va_end(ap);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_test_failed(bs_test_t *test_ptr)
+{
+    return test_ptr->failed;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_test_verify_streq_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const char *a_ptr,
+    const char *hash_a_ptr,
+    const char *b_ptr,
+    const char *hash_b_ptr)
+{
+    if (0 == strcmp(a_ptr, b_ptr)) {
+        // TODO: report bs_test_succeed.
+        return;
+    }
+
+    size_t pos = 0;
+    while (a_ptr[pos] && b_ptr[pos] && a_ptr[pos] == b_ptr[pos]) {
+        ++pos;
+    }
+    bs_test_fail_at(
+        test_ptr, fname_ptr, line,
+        "%s (\"%s\") not equal %s (\"%s\") at %zu (0x%02x != 0x%02x)",
+        hash_a_ptr, a_ptr, hash_b_ptr, b_ptr, pos, a_ptr[pos], b_ptr[pos]);
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_test_verify_strmatch_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const char *a_ptr,
+    const char *hash_a_ptr,
+    const char *regex_ptr)
+{
+    regex_t regex;
+    int rv = regcomp(&regex, regex_ptr, REG_EXTENDED);
+    if (0 != rv) {
+        char err_buf[512];
+        regerror(rv, &regex, err_buf, sizeof(err_buf));
+        BS_TEST_FAIL(test_ptr, "Failed regcomp(\"%s\"): %s",
+                     regex_ptr, err_buf);
+        return;
+    }
+
+    regmatch_t matches[1];
+    rv = regexec(&regex, a_ptr, 1, matches, 0);
+    regfree(&regex);
+    if (REG_NOMATCH == rv) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "%s (\"%s\") does not match \"%s\".",
+            hash_a_ptr, a_ptr, regex_ptr);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_test_verify_memeq_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const void *a_ptr,
+    const char *hash_a_ptr,
+    const void *b_ptr,
+    const char *hash_b_ptr,
+    const size_t size)
+{
+    if (0 != memcmp(a_ptr, b_ptr, size)) {
+        bs_test_fail_at(
+            test_ptr, fname_ptr, line,
+            "Buffer at %p (%s) != at %p (%s) for %zu",
+            a_ptr, hash_a_ptr, b_ptr, hash_b_ptr, size);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+const char *bs_test_resolve_path(const char *fname_ptr)
+{
+    // POSIX doesn't really say whether the terminating NUL would fit.
+    // Add a spare byte for that.
+    static thread_local char resolved_path[PATH_MAX + 1];
+    const char *resolved_path_ptr = bs_file_join_resolve_path(
+        bs_test_data_dir_ptr, fname_ptr, resolved_path);
+    if (NULL == resolved_path_ptr) {
+        bs_log(BS_ERROR | BS_ERRNO,
+               "Failed bs_file_join_resolve_path(%s, %s, %p)",
+               bs_test_data_dir_ptr, fname_ptr, resolved_path);
+    }
+    return resolved_path_ptr;
+}
+/* == Static (Local) Functions ============================================= */
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Initializes terminal escape codes.
+ *
+ * @param tcode_ptr
+ */
+void bs_test_tcode_init(void)
+{
+    char                      *code_buf_ptr;
+    char                      tc_buf[4096];
+
+    char *term_ptr = getenv("TERM");
+    if (NULL == term_ptr) {
+        // Fallback: Use xterm, provides colors also on github workflows...
+        term_ptr = "xterm";
+    } else if (0 == strcmp(term_ptr, "xterm-color") ||
+               0 == strcmp(term_ptr, "xterm-256color")) {
+        // Hack: Some Unixes use xterm-color, but fail to provide a termcap.
+        term_ptr = "xterm";
+    }
+    BS_ASSERT(0 < tgetent(tc_buf, term_ptr));
+
+    memset(&bs_test_tcode, 0, sizeof(struct _bs_test_tcode));
+    code_buf_ptr = bs_test_tcode.all_codes;
+
+    // https://pubs.opengroup.org/onlinepubs/007908799/xcurses/terminfo.html.
+    bs_test_tcode.code_set_background = tgetstr((char*)"Sb", &code_buf_ptr);
+    bs_test_tcode.code_set_foreground = tgetstr((char*)"Sf", &code_buf_ptr);
+    bs_test_tcode.code_orig_pair = tgetstr((char*)"op", &code_buf_ptr);
+
+    bs_test_tcode.code_enter_bold_mode =
+        tgetstr((char*)"md", &code_buf_ptr);
+    bs_test_tcode.code_exit_attribute_mode =
+        tgetstr((char*)"me", &code_buf_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Print a character to terminal. Wrapper for terminal-code functions.
+ *
+ * @param c
+ *
+ * @return 0 on success.
+ */
+int bs_test_putc(int c)
+{
+    char x = c;
+    /* return 0 on success */
+    return 1 - write(1, &x, 1);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Print to terminal.
+ *
+ * @param fmt_ptr             Format string, as vsnprintf takes.
+ * @param ...                 Additional arguments.
+ */
+void bs_test_puts(const char *fmt_ptr, ...)
+{
+    char                      line_buf[1024];
+    int                       len;
+    va_list                   ap;
+
+    va_start(ap, fmt_ptr);
+    len = vsnprintf(line_buf, sizeof(line_buf), fmt_ptr, ap);
+    if ((size_t)len > sizeof(line_buf)) len = sizeof(line_buf);
+    va_end(ap);
+
+    int written_len = 0;
+    for (written_len = 0;
+         written_len < len;
+         written_len = write(1, &line_buf[written_len], len)) ;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Set terminal attributes.
+ *
+ * @param attr:               - bit 0..2 : 7-bit foreground
+ *                            - bit 3 : toggle bold mode
+ *                            - bit 4..6 : 7-bit background
+ *                            - bit 7 : ignored.
+ *                            - bit 8 : do not set foreground
+ *                            - bit 9 : do not set background
+ *                            - a value of -1 resets attributes
+ */
+void bs_test_attr(int attr)
+{
+    if (-1 == attr) {
+        tputs(bs_test_tcode.code_orig_pair, 0, bs_test_putc);
+        tputs(bs_test_tcode.code_exit_attribute_mode, 0, bs_test_putc);
+        return;
+    }
+
+    if (0x100 != (0x100 & attr)) {
+        tputs(tparm((char*)bs_test_tcode.code_set_foreground,
+                    attr & 0x07, 0, 0, 0, 0, 0, 0, 0, 0),
+              0, bs_test_putc);
+    }
+    if (attr & 0x08) {
+        tputs(bs_test_tcode.code_enter_bold_mode, 0, bs_test_putc);
+    }
+
+    if (0x0200 != (0x0200 & attr)) {
+        tputs(tparm((char*)bs_test_tcode.code_set_background,
+                    (attr & 0x70) >> 4, 0, 0, 0, 0, 0, 0, 0, 0),
+              0, bs_test_putc);
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Print summary of test set.
+ *
+ * @param test_ptr            Test state.
+ * @param set_report_ptr      Report.
+ */
+void bs_test_set_report(const bs_test_set_t *set_ptr,
+                        const struct bs_test_report *set_report_ptr)
+{
+    bs_test_puts("%s\n %-65.65s ",
+                 bs_test_report_separator_ptr,
+                 set_ptr->name_ptr);
+    bs_test_puts("OK");
+    bs_test_puts(": % 3d/% 3d\n%s\n",
+                 set_report_ptr->succeeded,
+                 set_report_ptr->total,
+                 bs_test_report_separator_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Runs the test cases of the specified test set.
+ *
+ * @param test_ptr            Test state.
+ * @param pattern_ptr         Which tests to run, using fnmatch.
+ * @param set_ptr             Set to run.
+ *
+ * @return 0, if all tests of the test reported success, or the number of
+ *     failed test cases.
+ */
+int bs_test_set(const bs_test_set_t *set_ptr,
+                const char *pattern_ptr,
+                bs_dllist_t *failed_tests_ptr)
+{
+    const bs_test_case_t      *case_ptr;
+    struct bs_test_fail_node  *fnode_ptr;
+    struct bs_test_report     set_report;
+    int                       case_idx;
+    bs_test_t                 test;
+
+    bs_test_puts("%s\n Set: %-73.73s\n",
+                 bs_test_report_separator_ptr,
+                 set_ptr->name_ptr);
+    memset(&set_report, 0, sizeof(set_report));
+
+    /* step through descriptors and run each test */
+    for (case_idx = 0;
+         NULL != set_ptr->case_ptr[case_idx].name_ptr;
+         set_report.total++, case_idx++) {
+
+        case_ptr = set_ptr->case_ptr + case_idx;
+        bs_test_puts("%s\n", bs_test_linesep_ptr);
+        bs_test_case_prepare(&test, set_ptr, case_idx);
+        bool enabled = case_ptr->enabled;
+
+        char *full_name_ptr = bs_test_case_create_full_name(set_ptr, case_ptr);
+        if (NULL == full_name_ptr) {
+            test.failed = true;
+        } else {
+            if (fnmatch(pattern_ptr, full_name_ptr, 0)) {
+                enabled = false;
+            }
+            if (enabled) {
+                case_ptr->test_fn(&test);
+            }
+        }
+
+        if (!enabled) {
+            set_report.skipped++;
+        } else if (!test.failed) {
+            set_report.succeeded++;
+        } else {
+            set_report.failed++;
+            fnode_ptr = bs_test_case_fail_node_create(full_name_ptr);
+            if (fnode_ptr) {
+                bs_dllist_push_back(failed_tests_ptr, &fnode_ptr->dlnode);
+                full_name_ptr = NULL;
+            }
+        }
+
+        if (NULL != full_name_ptr) free(full_name_ptr);
+
+        bs_test_case_report(&test, enabled);
+    }
+
+    bs_test_set_report(set_ptr, &set_report);
+    return set_report.failed;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Prepares test_ptr for this test case.
+ *
+ * @param test_ptr            Test state.
+ * @param set_ptr             Test set.
+ * @param case_idx            Index of the test case within the set.
+ */
+void bs_test_case_prepare(bs_test_t *test_ptr,
+                          const bs_test_set_t *set_ptr,
+                          int case_idx)
+{
+    test_ptr->case_idx = case_idx;
+    test_ptr->case_ptr = set_ptr->case_ptr + case_idx;
+    test_ptr->failed = false;
+    memset(test_ptr->report, 0, sizeof(test_ptr->report));
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Reports the outcome of this particular testcase.
+ *
+ * @param test_ptr
+ */
+void bs_test_case_report(const bs_test_t *test_ptr, bool enabled)
+{
+    const char                *outcome_ptr;
+    int                       attr;
+
+    if (!enabled) {
+        attr = BS_TEST_ATTR_SKIP;
+        outcome_ptr = "SKIP";
+    } else if (test_ptr->failed) {
+        attr = BS_TEST_ATTR_FAIL;
+        outcome_ptr = "FAIL";
+    } else {
+        attr = BS_TEST_ATTR_SUCCESS;
+        outcome_ptr = "SUCCESS";
+    }
+
+    bs_test_puts(" % 3d: %-63.63s ",
+                 test_ptr->case_idx,
+                 test_ptr->case_ptr->name_ptr);
+    bs_test_attr(attr);
+    bs_test_puts("%8.8s", outcome_ptr);
+    bs_test_attr(BS_TEST_ATTR_RESET);
+    bs_test_puts("\n");
+
+    if ((enabled) && ('\0' != test_ptr->report[0])) {
+        bs_test_puts("  %s\n", test_ptr->report);
+    }
+    bs_test_attr(BS_TEST_ATTR_RESET);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Create a double-linked-list node with information on a failed test.
+ *
+ * @param full_name_ptr
+ *
+ * @return Pointer to the node or NULL on error.
+ */
+struct bs_test_fail_node *bs_test_case_fail_node_create(char *full_name_ptr)
+{
+    struct bs_test_fail_node  *fnode_ptr;
+
+    fnode_ptr = calloc(1, sizeof(struct bs_test_fail_node));
+    if (NULL == fnode_ptr) return NULL;
+
+    fnode_ptr->full_name_ptr = full_name_ptr;
+    return fnode_ptr;
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Destroy a double-linked-list node with failed-test information.
+ *
+ * @see bs_test_case_fail_node_create
+ *
+ * @param fnode_ptr
+ */
+void bs_test_case_fail_node_destroy(struct bs_test_fail_node *fnode_ptr)
+{
+    if (NULL != fnode_ptr->full_name_ptr) {
+        free(fnode_ptr->full_name_ptr);
+    }
+    free(fnode_ptr);
+}
+
+/* ------------------------------------------------------------------------- */
+/**
+ * Creates the fully-qualified test name from the set and the case.
+ *
+ * @param set_ptr
+ * @param case_ptr
+ *
+ * @return Pointer to the full name, or NULL on error.
+ */
+char *bs_test_case_create_full_name(
+    const bs_test_set_t *set_ptr,
+    const bs_test_case_t *case_ptr)
+{
+    char *full_name_ptr = logged_malloc(
+        strlen(set_ptr->name_ptr) + strlen(case_ptr->name_ptr) + 2);
+    if (NULL == full_name_ptr) return NULL;
+
+    strcpy(full_name_ptr, set_ptr->name_ptr);
+    full_name_ptr[strlen(set_ptr->name_ptr)] = '.';
+    strcpy(full_name_ptr + strlen(set_ptr->name_ptr) + 1, case_ptr->name_ptr);
+    return full_name_ptr;
+}
+
+/* == Unit self-tests ====================================================== */
+/** @cond TEST */
+
+static void bs_test_test_report(bs_test_t *test_ptr);
+static void bs_test_eq_neq_tests(bs_test_t *test_ptr);
+
+const bs_test_case_t bs_test_test_cases[] = {
+    { 1, "succeed/fail reporting", bs_test_test_report },
+    { 1, "eq/neq tests", bs_test_eq_neq_tests },
+    { 0, NULL, NULL }  /* sentinel. */
+};
+
+void bs_test_test_fail(bs_test_t *test_ptr)
+{
+    BS_TEST_FAIL(test_ptr, "fail");
+}
+
+void bs_test_test_succeed(bs_test_t *test_ptr)
+{
+    bs_test_succeed(test_ptr, "success");
+}
+
+void bs_test_test_succeed_fail(bs_test_t *test_ptr)
+{
+    bs_test_succeed(test_ptr, "success");
+    BS_TEST_FAIL(test_ptr, "fail");
+}
+
+void bs_test_test_fail_succeed(bs_test_t *test_ptr)
+{
+    BS_TEST_FAIL(test_ptr, "fail");
+    bs_test_succeed(test_ptr, "success");
+}
+
+/**
+ * Tests bs_test_fail and bs_test_succeed.
+ */
+void bs_test_test_report(bs_test_t *test_ptr)
+{
+    bs_test_t                 sub_test;
+
+    memset(&sub_test, 0, sizeof(bs_test_t));
+    bs_test_test_fail(&sub_test);
+    BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test));
+    memset(&sub_test, 0, sizeof(bs_test_t));
+    bs_test_test_succeed(&sub_test);
+    BS_TEST_VERIFY_FALSE(test_ptr, sub_test.failed);
+    BS_TEST_VERIFY_FALSE(test_ptr, bs_test_failed(&sub_test));
+
+    /** bs_test_fail takes precedence over bs_test_succeed. */
+    memset(&sub_test, 0, sizeof(bs_test_t));
+    bs_test_test_succeed_fail(&sub_test);
+    BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test));
+    memset(&sub_test, 0, sizeof(bs_test_t));
+    bs_test_test_fail_succeed(&sub_test);
+    BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed);
+    BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test));
+}
+
+/**
+ * Tests the EQ / NEQ macros.
+ */
+void bs_test_eq_neq_tests(bs_test_t *test_ptr)
+{
+    BS_TEST_VERIFY_EQ(test_ptr, 1, 1);
+    BS_TEST_VERIFY_NEQ(test_ptr, 1, 2);
+
+    BS_TEST_VERIFY_STREQ(test_ptr, "a", "a");
+    BS_TEST_VERIFY_STRMATCH(test_ptr, "asdf", "^[a-z]+$");
+
+    BS_TEST_VERIFY_MEMEQ(test_ptr, "asdf", "asdf", 4);
+}
+
+/** @endcond */
+/* == End of test.c ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/test.h
@@ -0,0 +1,357 @@
+/* ========================================================================= */
+/**
+ * @file test.h
+ * Declarations for building unit tests.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_TEST_H__
+#define __LIBBASE_TEST_H__
+
+#include <regex.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "def.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Overall state of test. */
+typedef struct _bs_test_t     bs_test_t;
+/** State of unit test. */
+typedef struct _bs_test_case_t bs_test_case_t;
+/**  A test set. */
+typedef struct _bs_test_set_t bs_test_set_t;
+
+/** Test function. */
+typedef void (*bs_test_fn_t)(bs_test_t *test_ptr);
+
+/** Descriptor for a test case. */
+struct _bs_test_case_t {
+    /** 0 if the test is disabled */
+    int                       enabled;
+    /** Name of the test, for informational purpose. */
+    const char                *name_ptr;
+    /** Test function for this testcase. */
+    bs_test_fn_t              test_fn;
+};
+
+/** Test set. */
+struct _bs_test_set_t {
+    /** 0 if the set is disabled. */
+    int                       enabled;
+    /** Name of the test set. */
+    const char                *name_ptr;
+    /** Array of test cases for that set. */
+    const bs_test_case_t      *case_ptr;
+};
+
+/** Test parameters. */
+typedef struct {
+    /** Directory to data files. */
+    const char                *test_data_dir_ptr;
+} bs_test_param_t;
+
+/**
+ * Reports that the given test succeeded, and print the format string and
+ * arguments to the test's report. Calling bs_test_succeed is optional, if
+ * neither bs_test_fail or bs_test_succeed are called, the test counts as
+ * succeeded. bs_test_fail takes precedence.
+ *
+ * @param test_ptr            Test state.
+ * @param fmt_ptr             Format string for report message.
+ * @param ...                 Additional arguments to format string.
+ */
+void bs_test_succeed(bs_test_t *test_ptr, const char *fmt_ptr, ...)
+    __ARG_PRINTF__(2, 3);
+
+/**
+ * Reports the given test as failed for the given position.
+ *
+ * @param test_ptr            Test state.
+ * @param fname_ptr           Filename to report the failure for.
+ * @param line                line number.
+ * @param fmt_ptr             Format string for report message.
+ * @param ...                 Additional arguments to format string.
+ */
+void bs_test_fail_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    int line,
+    const char *fmt_ptr, ...)
+    __ARG_PRINTF__(4, 5);
+
+/**
+ * Check failure status of test.
+ *
+ * @return Whether the test has failed.
+ */
+bool bs_test_failed(bs_test_t *test_ptr);
+
+/**
+ * Runs test sets.
+ *
+ * @param test_sets
+ * @param argc
+ * @param argv
+ * @param param_ptr           Optional, points to a @ref bs_test_param_t and
+ *                            specifies parameters for the test environment.
+ *
+ * @return 0 on success or the number of failed test sets.
+ */
+int bs_test(
+    const bs_test_set_t *test_sets,
+    int argc,
+    const char **argv,
+    const bs_test_param_t *param_ptr);
+
+/**
+ * Tests whether the strings at `a_ptr` and `b_ptr` are equal.
+ *
+ * Helper method, you should use the BS_TEST_VERIFY_STREQ macro instead.
+ *
+ * @param test_ptr
+ * @param fname_ptr
+ * @param line
+ * @param a_ptr
+ * @param hash_a_ptr
+ * @param b_ptr
+ * @param hash_b_ptr
+ */
+void bs_test_verify_streq_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const char *a_ptr,
+    const char *hash_a_ptr,
+    const char *b_ptr,
+    const char *hash_b_ptr);
+
+/**
+ * Tests whether the string at `a_ptr` matches the regular expression.
+ *
+ * Helper method, you should use the BS_TEST_VERIFY_STRMATCH macro instead.
+ *
+ * @param fname_ptr
+ * @param line
+ * @param test_ptr
+ * @param a_ptr
+ * @param hash_a_ptr
+ * @param regex_ptr
+ */
+void bs_test_verify_strmatch_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const char *a_ptr,
+    const char *hash_a_ptr,
+    const char *regex_ptr);
+
+/**
+ * Tests whether the memory buffers at `a_ptr` and `b_ptr` are equal.
+ *
+ * Helper method, you should use the BS_TEST_VERIFY_MEMEQ macro instead.
+ *
+ * @param test_ptr
+ * @param fname_ptr
+ * @param line
+ * @param a_ptr
+ * @param hash_a_ptr
+ * @param b_ptr
+ * @param hash_b_ptr
+ * @param size
+ */
+void bs_test_verify_memeq_at(
+    bs_test_t *test_ptr,
+    const char *fname_ptr,
+    const int line,
+    const void *a_ptr,
+    const char *hash_a_ptr,
+    const void *b_ptr,
+    const char *hash_b_ptr,
+    const size_t size);
+
+/**
+ * Resolves a relative path into an absolute, with configured test directory.
+ *
+ * If fname_ptr is already an absolute path, this will return the resolved path
+ * using realpath(3). Otherwise, it is joined with the configured test
+ * directory, and resolved using realpath(3).
+ *
+ * @param fname_ptr
+ *
+ * @return A pointer to the resolved path or NULL on error. It points to a
+ *     thread-local static store and does not need to be free()-ed. It may get
+ *     overwritten by the next call to @ref bs_test_resolve_path.
+ */
+const char *bs_test_resolve_path(const char *fname_ptr);
+
+/* == Verification macros ================================================== */
+
+/**
+ * Reports the test as failed, at current position.
+ *
+ * @param _test
+ */
+#define BS_TEST_FAIL(_test, ...) {                                      \
+        bs_test_fail_at((_test), __FILE__, __LINE__, __VA_ARGS__);      \
+    }
+
+/** Verifies that _expr is true, and returns (stops tests) if not. */
+#define BS_TEST_VERIFY_TRUE_OR_RETURN(_test, _expr) { \
+        BS_TEST_VERIFY_TRUE(_test, _expr);            \
+        if (bs_test_failed(test_ptr)) return;         \
+    }
+
+/**
+ * Verifies that _expr is true.
+ *
+ * @param _test
+ * @param _expr
+ */
+#define BS_TEST_VERIFY_TRUE(_test, _expr) {                             \
+        if (!(_expr)) {                                                 \
+            bs_test_fail_at((_test), __FILE__, __LINE__,                \
+                            "%s not true.", #_expr);                    \
+        }                                                               \
+    }
+
+/** Verifies that _expr is false, and reutrns (stop tests) if not. */
+#define BS_TEST_VERIFY_FALSE_OR_RETURN(_test, _expr) {  \
+        BS_TEST_VERIFY_FALSE(_test, _expr);             \
+        if (bs_test_failed(test_ptr)) return;           \
+    }
+
+/**
+ * Verifies that _expr is false.
+ *
+ * @param _test
+ * @param _expr
+ */
+#define BS_TEST_VERIFY_FALSE(_test, _expr) {                            \
+        if (_expr) {                                                    \
+            bs_test_fail_at((_test), __FILE__, __LINE__,                \
+                            "%s not false.", #_expr);                   \
+        }                                                               \
+    }
+
+/** Verifies that _a == _b, and reutrns (stop tests) if not. */
+#define BS_TEST_VERIFY_EQ_OR_RETURN(_test, _a, _b) {    \
+        BS_TEST_VERIFY_EQ(_test, _a, _b);               \
+        if (bs_test_failed(test_ptr)) return;           \
+    }
+
+/**
+ * Verifies that _a == _b
+ *
+ * @param _test
+ * @param _a
+ * @param _b
+ */
+#define BS_TEST_VERIFY_EQ(_test, _a, _b) {                              \
+        if (!((_a) == (_b))) {                                          \
+            bs_test_fail_at((_test), __FILE__, __LINE__,                \
+                            "%s not equal %s.", #_a, #_b);              \
+        }                                                               \
+    }
+
+/** Verifies that _a != _b, and reutrns (stop tests) if not. */
+#define BS_TEST_VERIFY_NEQ_OR_RETURN(_test, _a, _b) { \
+        BS_TEST_VERIFY_NEQ(_test, _a, _b);            \
+        if (bs_test_failed(test_ptr)) return;         \
+    }
+
+/**
+ * Verifies that _a != _b
+ *
+ * @param _test
+ * @param _a
+ * @param _b
+ */
+#define BS_TEST_VERIFY_NEQ(_test, _a, _b) {                             \
+        if ((_a) == (_b)) {                                             \
+            bs_test_fail_at((_test), __FILE__, __LINE__,                \
+                            "%s equal %s.", #_a, #_b);                  \
+        }                                                               \
+    }
+
+/** Verifies that the strings _a == _b. Returns (stop test) if not. */
+#define BS_TEST_VERIFY_STREQ_OR_RETURN(_test, _a, _b) { \
+        BS_TEST_VERIFY_STREQ(_test, _a, _b);            \
+        if (bs_test_failed(test_ptr)) return;           \
+    }
+
+/**
+ * Verifies that the strings _a == _b.
+ *
+ * @param _test
+ * @param _a
+ * @param _b
+ */
+#define BS_TEST_VERIFY_STREQ(_test, _a, _b) {                           \
+        bs_test_verify_streq_at(                                        \
+            (_test), __FILE__, __LINE__, _a, #_a, _b, #_b);             \
+    }
+
+/** Verifies that the string _a matches _regex.. Returns (stop test) if not. */
+#define BS_TEST_VERIFY_STRMATCH_OR_RETURN(_test, _a, _regex) { \
+        BS_TEST_VERIFY_STRMATCH(_test, _a, _regex);            \
+        if (bs_test_failed(test_ptr)) return;                  \
+    }
+
+/**
+ * Verifies that the string _a matches the regular expression _regex.
+ *
+ * @param _test
+ * @param _a
+ * @param _regex
+ */
+#define BS_TEST_VERIFY_STRMATCH(_test, _a, _regex) {                    \
+        bs_test_verify_strmatch_at(                                     \
+            (_test), __FILE__, __LINE__, _a, #_a, _regex);              \
+    }
+
+/** Verifies that the memory buffers _a == _b. Returns (stop tests) if not. */
+#define BS_TEST_VERIFY_MEMEQ_OR_RETURN(_test, _a, _b, _size) {  \
+        BS_TEST_VERIFY_MEMEQ(_test, _a, _b, _size);             \
+        if (bs_test_failed(test_ptr)) return;                   \
+    }
+
+/**
+ * Verifies that the memory buffers _a == _b.
+ *
+ * @param _test
+ * @param _a
+ * @param _b
+ * @param _size
+ */
+#define BS_TEST_VERIFY_MEMEQ(_test, _a, _b, _size) {                    \
+        bs_test_verify_memeq_at(                                        \
+            (_test), __FILE__, __LINE__, _a, #_a, _b, #_b, _size);      \
+    }
+
+/** Test cases. */
+extern const bs_test_case_t   bs_test_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_TEST_H__ */
+/* == End of test.h ===================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/thread.c
@@ -0,0 +1,144 @@
+/* ========================================================================= */
+/**
+ * @file thread.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 "thread.h"
+
+#include "assert.h"
+#include "log.h"
+
+#include <errno.h>
+#include <sys/time.h>
+
+/* == Exported methods ===================================================== */
+
+/* ------------------------------------------------------------------------- */
+bool bs_mutex_init(pthread_mutex_t *mutex_ptr)
+{
+    int rv = pthread_mutex_init(mutex_ptr, NULL);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_mutex_init(%p, NULL)",
+               mutex_ptr);
+        return false;
+    }
+    return true;
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_mutex_destroy(pthread_mutex_t *mutex_ptr)
+{
+    int rv = pthread_mutex_destroy(mutex_ptr);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_mutex_destroy(%p)",
+               mutex_ptr);
+        BS_ABORT();
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_mutex_lock(pthread_mutex_t *mutex_ptr)
+{
+    int rv = pthread_mutex_lock(mutex_ptr);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_mutex_lock(%p)",
+               mutex_ptr);
+        BS_ABORT();
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_mutex_unlock(pthread_mutex_t *mutex_ptr)
+{
+    int rv = pthread_mutex_unlock(mutex_ptr);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_mutex_unlock(%p)",
+               mutex_ptr);
+        BS_ABORT();
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_cond_init(pthread_cond_t *condition_ptr)
+{
+    int rv = pthread_cond_init(condition_ptr, NULL);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_cond_init(%p)",
+               condition_ptr);
+        return false;
+    }
+    return true;
+ }
+
+/* ------------------------------------------------------------------------- */
+void bs_cond_destroy(pthread_cond_t *condition_ptr)
+{
+    int rv = pthread_cond_destroy(condition_ptr);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_cond_destroy(%p)",
+               condition_ptr);
+        BS_ABORT();
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_cond_broadcast(pthread_cond_t *condition_ptr)
+{
+    int rv = pthread_cond_broadcast(condition_ptr);
+    if (0 != rv) {
+        errno = rv;
+        bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_cond_broadcast(%p)",
+               condition_ptr);
+        BS_ABORT();
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+bool bs_cond_timedwait(pthread_cond_t *condition_ptr,
+                       pthread_mutex_t *mutex_ptr,
+                       uint64_t usec)
+{
+    struct                    timeval tv;
+    struct                    timespec ts;
+    int                       rv;
+
+    if (0 != gettimeofday(&tv, NULL)) {
+        bs_log(BS_FATAL | BS_ERRNO, "Failed gettimeofday(%p, NULL)", &tv);
+        BS_ABORT();
+    }
+    ts.tv_sec = tv.tv_sec + (tv.tv_usec + usec) / 1000000;
+    ts.tv_nsec = ((tv.tv_usec + usec) % 1000000) * 1000;
+
+    rv = pthread_cond_timedwait(condition_ptr, mutex_ptr, &ts);
+    if (0 != rv && ETIMEDOUT != rv) {
+        errno = rv;
+        bs_log(BS_FATAL | BS_ERRNO,
+               "Failed pthread_cond_timedwait(%p, %p, %"PRIu64")",
+               condition_ptr, mutex_ptr, usec);
+        BS_ABORT();
+    }
+    return rv == 0;
+}
+
+/* == End of thread.c ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/thread.h
@@ -0,0 +1,62 @@
+/* ========================================================================= */
+/**
+ * @file thread.h
+ * Wrappers around libpthread, to consolidate logging and error handling.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_THREAD_H__
+#define __LIBBASE_THREAD_H__
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Initializes as PTHREAD_MUTEX_NORMAL mutex, with error handling. */
+bool bs_mutex_init(pthread_mutex_t *mutex_ptr);
+/** Destroys the mutex, with error handling. Aborts on error. */
+void bs_mutex_destroy(pthread_mutex_t *mutex_ptr);
+
+/** Locks the mutex, with error handling: aborts on error. */
+void bs_mutex_lock(pthread_mutex_t *mutex_ptr);
+/** Unlocks the mutex, with error handling: aborts on error. */
+void bs_mutex_unlock(pthread_mutex_t *mutex_ptr);
+
+/** Initializes the condition, with default attributes. With error handling. */
+bool bs_cond_init(pthread_cond_t *condition_ptr);
+/** Destroys the condition, with error handling: abors on error. */
+void bs_cond_destroy(pthread_cond_t *condition_ptr);
+
+/** Broadcasts the condition. */
+void bs_cond_broadcast(pthread_cond_t *condition_ptr);
+/**
+ * Waits for condition, with error handling. Returns true, if the condition
+ * was signalled or broadcasted, and false if it timed out.
+ */
+bool bs_cond_timedwait(pthread_cond_t *condition_ptr,
+                       pthread_mutex_t *mutex_ptr,
+                       uint64_t usec);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_THREAD_H__ */
+/* == End of thread.h ====================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/time.c
@@ -0,0 +1,92 @@
+/* ========================================================================= */
+/**
+ * @file time.c
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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.
+ */
+
+/// clock_gettime(2) is a POSIX extension, at 199309L. _XOPEN_SOURCE 500
+/// defines _POSIX_C_SOURCE at 199506L.
+/// gettimeofday(2) is a XSI extension, needing _XOPEN_SOURCE 500.
+#define _XOPEN_SOURCE 500
+
+#include "log.h"
+#include "time.h"
+#include "test.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/time.h>
+
+#undef _XOPEN_SOURCE
+
+/* == Methods ============================================================== */
+
+/* ------------------------------------------------------------------------- */
+uint64_t bs_usec(void)
+{
+    struct timeval tv;
+    if (0 != gettimeofday(&tv, NULL)) {
+        bs_log(BS_ERROR | BS_ERRNO, "Failed gettimeofday(%p,  NULL)\n",
+               (void*)&tv);
+        return 0;
+    }
+    return (uint64_t)tv.tv_sec * UINT64_C(1000000) +
+        (uint64_t)tv.tv_usec % UINT64_C(1000000);
+}
+
+/* ------------------------------------------------------------------------- */
+uint64_t bs_mono_nsec(void)
+{
+    struct timespec timespec;
+    if (0 != clock_gettime(CLOCK_MONOTONIC, &timespec)) {
+        bs_log(BS_ERROR | BS_ERRNO,
+               "Failed clock_gettime(CLOCK_MONOTONIC, %p)",
+               &timespec);
+        return 0;
+    }
+
+    return timespec.tv_sec * UINT64_C(1000000000) +  + timespec.tv_nsec;
+}
+
+/* == Unit tests =========================================================== */
+
+static void bs_time_test_usec(bs_test_t *test_ptr);
+static void bs_time_test_nsec(bs_test_t *test_ptr);
+
+const bs_test_case_t          bs_time_test_cases[] = {
+    { 1, "time usec", bs_time_test_usec },
+    { 1, "time_mono_nsec", bs_time_test_nsec },
+    { 0, NULL, NULL }
+};
+
+/* ------------------------------------------------------------------------- */
+void bs_time_test_usec(bs_test_t *test_ptr)
+{
+    BS_TEST_VERIFY_NEQ(test_ptr, 0, bs_usec());
+}
+
+/* ------------------------------------------------------------------------- */
+void bs_time_test_nsec(bs_test_t *test_ptr)
+{
+    uint64_t v1, v2;
+    v1 = bs_mono_nsec();
+    v2 = bs_mono_nsec();
+    BS_TEST_VERIFY_TRUE(test_ptr, v1 <= v2);
+}
+
+/* == End of time.c ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/time.h
@@ -0,0 +1,46 @@
+/* ========================================================================= */
+/**
+ * @file time.h
+ * Methods for retrieving system time, respectively clock counter.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_TIME_H__
+#define __LIBBASE_TIME_H__
+
+#include "test.h"
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Returns the current time, in microseconds since epoch. */
+uint64_t bs_usec(void);
+
+/** Returns a monotonous time counter in nsec, as CLOCK_MONOTONIC. */
+uint64_t bs_mono_nsec(void);
+
+/** Unit tests. */
+extern const bs_test_case_t   bs_time_test_cases[];
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_TIME_H__ */
+/* == End of time.h ======================================================== */
--- /dev/null
+++ wlmaker-0.3/submodules/libbase/vector.h
@@ -0,0 +1,72 @@
+/* ========================================================================= */
+/**
+ * @file vector.h
+ * Methods and definitions to work a bit more conveniently with vectors in C.
+ *
+ * @copyright
+ * Copyright 2023 Google LLC
+ *
+ * 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
+ *
+ * https://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 __LIBBASE_VECTOR_H__
+#define __LIBBASE_VECTOR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+/** Two-dimension vector with floating point dimensions. */
+typedef struct {
+    /** X dimension. */
+    double                    x;
+    /** Y dimension. */
+    double                    y;
+} bs_vector_2f_t;
+
+/** Initializer for the 2-dimensional vector with floating points. */
+#define BS_VECTOR_2F(_x, _y) ((bs_vector_2f_t){ .x = _x, .y = _y })
+
+/**
+ * Adds two vectors.
+ *
+ * @param v1
+ * @param v2
+ *
+ * @return v1 + v2.
+ */
+static inline bs_vector_2f_t bs_vec_add_2f(const bs_vector_2f_t v1,
+                                           const bs_vector_2f_t v2)
+{
+    return BS_VECTOR_2F(v1.x + v2.x, v1.y + v2.y);
+}
+
+/**
+ * Scalar multiplication of a vector.
+ *
+ * @param scale
+ * @param v
+ *
+ * @return scale * v.
+ */
+static inline bs_vector_2f_t bs_vec_mul_2f(const double scale,
+                                           const bs_vector_2f_t v)
+{
+    return BS_VECTOR_2F(scale * v.x, scale * v.y);
+}
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif /* __LIBBASE_VECTOR_H__ */
+/* == End of vector.h ====================================================== */
