diff --git a/.gitignore b/.gitignore
index 1f62fb87aeac8e9a7e9cfb4572dc49a2a4bee886..eedfb36fac1f95dfd83a7a5fc4692fdfb89361d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,9 +11,11 @@ bin/*
 .*.sw?
 .*.pyc
 
-.project 
-.cproject 
-.settings 
+.project
+.cproject
+.settings
+
+compile_flags.txt
 STM32F429zi_Discovery.xml
 
 ._*
@@ -25,4 +27,9 @@ build
 .vscode/*
 
 store.json
-scripts/homeone/event_header_generator/generated/
+**/generated/
+core
+
+__pycache__
+/scripts/generators/generated
+/scripts/generators/scxmls
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e594a367a6e9c46c5d42ba386c78ef1dc0fe1a29..7bb4feafb39ac38529aa0e8a452288f7ca546dbb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,35 +7,28 @@
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
-before_script:
-  - echo "Before script section"
-  - echo "For example you might run an update here or install a build dependency"
-  - echo "Or perhaps you might print out some debugging details"
+# before_script:
+#   - echo "Before script section"
+#   - echo "For example you might run an update here or install a build dependency"
+#   - echo "Or perhaps you might print out some debugging details"
    
-after_script:
-  - echo "After script section"
-  - echo "For example you might do some cleanup here"
+# after_script:
+#   - echo "After script section"
+#   - echo "For example you might do some cleanup here"
    
-build1:
+build:
   stage: build
   only:
     - master
     - testing
   script:
-    - echo "Do your build here"
+    - echo "Running complete build"
     - python3 sbs
    
-test1:
+linter:
   stage: test
   script: 
-    - echo "Do a test here"
-    - echo "For example run a test suite"
-   
-test2:
-  stage: test
-  script: 
-    - echo "Do another parallel test here"
-    - echo "For example run a lint test"
+    - echo "Running linter"
     - ./scripts/linter.sh
 
 documentation:
@@ -48,7 +41,7 @@ documentation:
     - mkdir -p /srv/code_documentation/${CI_PROJECT_NAME}/
     - mv doc/output/html /srv/code_documentation/${CI_PROJECT_NAME}/${CI_COMMIT_REF_NAME}
 
-deploy1:
-  stage: deploy
-  script:
-    - echo "Do your deploy here"
+# deploy1:
+#   stage: deploy
+#   script:
+#     - echo "Do your deploy here"
diff --git a/.gitmodules b/.gitmodules
index e6d4a9415a0bc8bff6682c1980d7f092eee019bf..178056d3aa2116ce8cfe956cdcbd2bc9c67ba6ab 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -11,3 +11,9 @@
 [submodule "libs/simple-template-matrix"]
 	path = libs/simple-template-matrix
 	url = https://github.com/fedetft/simple-template-matrix.git
+[submodule "libs/eigen"]
+	path = libs/eigen
+	url = https://gitlab.com/libeigen/eigen.git
+[submodule "libs/mxgui"]
+	path = libs/mxgui
+	url = ../../scs/mxgui.git
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
index 145d9c1878b407d0eccd1fb657e44bd0e510723c..ec8b4984d562776585652543cecae42e177f0bbb 100644
--- a/.ycm_extra_conf.py
+++ b/.ycm_extra_conf.py
@@ -55,6 +55,7 @@ flags = [
     '-Ilibs/miosix-kernel/miosix/arch/common',
     '-Ilibs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common',
     '-Ilibs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_stm32f4discovery',
+    '-Ilibs/eigen',
     '-Imiosix',
     '-Imiosix-kernel',
     '-Isrc/shared',
diff --git a/Makefile.template b/Makefile.template
index 368ff88bd7deae79c0757d0ae9792fe323fa17ff..b9b7c91c62ba592b0a35dc5c153943d3a9b7a22a 100644
--- a/Makefile.template
+++ b/Makefile.template
@@ -3,7 +3,7 @@ SBS_BASE = {SBS_BASE_PATH}
 ##
 ## Makefile for Miosix embedded OS
 ##
-MAKEFILE_VERSION := 1.07S
+MAKEFILE_VERSION := 1.09S
 ## Path to kernel directory (edited by init_project_out_of_git_repo.pl)
 KPATH := $(SBS_BASE)/libs/miosix-kernel/miosix
 ## Path to config directory (edited by init_project_out_of_git_repo.pl)
@@ -15,7 +15,7 @@ include $(CONFPATH)/config/Makefile.inc
 ##
 ## List here subdirectories which contains makefiles
 ##
-SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS}
+SUBDIRS := $(KPATH) {SBS_PROJECT_SUBDIRS} {SBS_LIB_SUBDIR}
 
 ##
 ## List here your source files (both .s, .c and .cpp)
@@ -25,13 +25,13 @@ SRC := {SBS_SOURCE_FILES}
 ##
 ## List here additional static libraries with relative path
 ##
-LIBS := {SBS_PROJECT_LIBS}
+LIBS := {SBS_PROJECT_LIBS} {SBS_LIB_LIBS}
 
 ##
 ## List here additional include directories (in the form -Iinclude_dir)
 ##
 INCLUDE_DIRS := -I$(SBS_BASE)/libs -I$(SBS_BASE)/libs/miosix-kernel/miosix \
-                {SBS_PROJECT_INCLUDES}
+                {SBS_PROJECT_INCLUDES} {SBS_LIB_INCLUDES}
 
 ##############################################################################
 ## You should not need to modify anything below                             ##
@@ -54,10 +54,12 @@ OBJ := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(basename $(SRC))))
 ## Always include CONFPATH first, as it overrides the config file location
 CXXFLAGS := $(CXXFLAGS_BASE) -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC)  \
             -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC)      \
-            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic
+            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \
+			-pedantic
 CFLAGS   := $(CFLAGS_BASE)   -I$(CONFPATH) -I$(CONFPATH)/config/$(BOARD_INC)  \
             -I. -I$(KPATH) -I$(KPATH)/arch/common -I$(KPATH)/$(ARCH_INC)      \
-            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -pedantic
+            -I$(KPATH)/$(BOARD_INC) $(INCLUDE_DIRS) -Wall -Wextra -Wcast-align \
+			-pedantic
 AFLAGS   := $(AFLAGS_BASE)
 LFLAGS   := $(LFLAGS_BASE) -Wl,--gc-sections,-Map,$(BINDIR)/${SBS_BIN_NAME}.map
 DFLAGS   := -MMD -MP
@@ -109,11 +111,11 @@ $(OBJDIR)/%.o: %.s
 
 $(OBJDIR)/%.o : %.c
 	$(ECHO) "[CC]  " $<
-	$(Q) $(CC)  $(DFLAGS) $(CFLAGS) {SBS_CUSTOM_DEFINES} $< -o $@
+	$(Q) $(CC)  $(DFLAGS) $(CFLAGS) {SBS_CUSTOM_DEFINES} {SBS_LIB_DEFINES} $< -o $@
 
 $(OBJDIR)/%.o : %.cpp
 	$(ECHO) "[CXX] " $<
-	$(Q) $(CXX) $(DFLAGS) $(CXXFLAGS) {SBS_CUSTOM_DEFINES} $< -o $@
+	$(Q) $(CXX) $(DFLAGS) $(CXXFLAGS) {SBS_CUSTOM_DEFINES} {SBS_LIB_DEFINES} $< -o $@
 
 folders:
 	$(Q) mkdir -pv $(dir $(OBJ)) $(BINDIR)
diff --git a/data/gdb/stm32f4-stlinv2.cfg b/data/gdb/stm32f4-stlinv2.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..4d4718023ed13220fa7b779294ca87824d2bd0f8
--- /dev/null
+++ b/data/gdb/stm32f4-stlinv2.cfg
@@ -0,0 +1,9 @@
+# This is an STM32F4 discovery board with a single STM32F407VGT6 chip.
+# http://www.st.com/internet/evalboard/product/252419.jsp
+
+source [find interface/stlink-v2-1.cfg]
+
+source [find target/stm32f4x.cfg]
+
+# use hardware reset, connect under reset
+reset_config srst_only srst_nogate
\ No newline at end of file
diff --git a/ide/vscode/c_cpp_properties.json b/ide/vscode/c_cpp_properties.json
index abf33c87fab5ad668eab706beecadcea033a4e45..d33c461d53fc16f1c5f8110d0e6816c49e48197e 100755
--- a/ide/vscode/c_cpp_properties.json
+++ b/ide/vscode/c_cpp_properties.json
@@ -8,6 +8,7 @@
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/eigen",
                 "${workspaceFolder}/libs",
                 "${workspaceFolder}/src/shared",
                 "${workspaceFolder}/src/tests",
@@ -46,9 +47,9 @@
                     "${workspaceFolder}/libs/miosix-kernel/miosix/*",
                     "${workspaceFolder}/libs/mavlink_skyward_lib",               
                     "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/eigen",
                     "${workspaceFolder}/src/shared",
                     "${workspaceFolder}/src/tests",
-                    "${workspaceFolder}/*",
                     "${workspaceFolder}/src",
                     "${workspaceFolder}/*"
                 ],
@@ -63,6 +64,7 @@
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/eigen",
                 "${workspaceFolder}/libs",
                 "${workspaceFolder}/src/shared",
                 "${workspaceFolder}/src/tests",
@@ -101,10 +103,11 @@
                     "${workspaceFolder}/libs/miosix-kernel/miosix/*",
                     "${workspaceFolder}/libs/mavlink_skyward_lib",               
                     "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/eigen",
                     "${workspaceFolder}/src/shared",
                     "${workspaceFolder}/src/tests",
-                    "${workspaceFolder}/*",
-                    "${workspaceFolder}/src"
+                    "${workspaceFolder}/src",
+                    "${workspaceFolder}/*"
                 ],
                 "limitSymbolsToIncludedHeaders": true
             }
@@ -117,6 +120,7 @@
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
                 "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/eigen",
                 "${workspaceFolder}/libs",
                 "${workspaceFolder}/src/shared",
                 "${workspaceFolder}/src/tests",
@@ -156,17 +160,72 @@
                     "${workspaceFolder}/libs/mavlink_skyward_lib",   
                     "${workspaceFolder}/libs/mxgui",                   
                     "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/eigen",
                     "${workspaceFolder}/libs/*",     
                     "${workspaceFolder}/src/shared",
                     "${workspaceFolder}/src/tests",
-                    "${workspaceFolder}/*",
                     "${workspaceFolder}/src",
                     "${workspaceFolder}/*"
                 ],
                 "limitSymbolsToIncludedHeaders": true
             }
-        }
+        },
+        {
+            "name": "stm32f407vg_skyward_tortellino",
+            "includePath": [
+                "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                "${workspaceFolder}/libs/miosix-kernel/miosix",
+                "${workspaceFolder}/libs/eigen",
+                "${workspaceFolder}/libs",
+                "${workspaceFolder}/src/shared",
+                "${workspaceFolder}/src/tests",
+                "${workspaceFolder}"
+            ],
+            "compilerPath": "/opt/arm-miosix-eabi/bin/arm-miosix-eabi-g++",
+        
+            "defines": [
+                "DEBUG",
+                "_ARCH_CORTEXM4_STM32F4",
+                "_BOARD_STM32F407VG_SKYWARD_TORTELLINO",
+                "_MIOSIX_BOARDNAME=stm32f407vg_skyward_tortellino",
+                "HSE_VALUE=8000000",
+                "SYSCLK_FREQ_168MHz=168000000",
+                "_MIOSIX",
+                "__cplusplus=201103L"
+            ],
         
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+        
+            "browse": {
+                "path" : [
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino/interfaces-impl",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/config/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f407vg_skyward_tortellino",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/arch/common",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/interfaces",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/kernel",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/util",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/e20",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/filesystem",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/stdlib_integration",
+                    "${workspaceFolder}/libs/miosix-kernel/miosix/*",
+                    "${workspaceFolder}/libs/mavlink_skyward_lib",               
+                    "${workspaceFolder}/libs/tscpp",
+                    "${workspaceFolder}/libs/eigen",
+                    "${workspaceFolder}/src/shared",
+                    "${workspaceFolder}/src/tests",
+                    "${workspaceFolder}/src",
+                    "${workspaceFolder}/*"
+                ],
+                "limitSymbolsToIncludedHeaders": true
+            }
+        } 
     ],
     "version": 4
 }
\ No newline at end of file
diff --git a/ide/vscode/launch.json b/ide/vscode/launch.json
index f07981bfab285ff228026edb5840654b0d08f3f9..3e9272fde8f6474f08b9803f2c3e3cf547050d0f 100644
--- a/ide/vscode/launch.json
+++ b/ide/vscode/launch.json
@@ -9,9 +9,10 @@
             "type": "cortex-debug",
             "servertype": "openocd",
             "device": "STM32F429ZI",
-            "svdFile": "/home/luca/drive/Skyward/STM32F429.svd",
+            "armToolchainPath": "/opt/arm-miosix-eabi/bin",
+            "toolchainPrefix": "arm-miosix-eabi",
             "configFiles": [
-                "/home/luca/test.cfg"
+                "${workspaceFolder}/data/gdb/stm32f4-stlinv2.cfg"
             ]
         },
         {
@@ -22,6 +23,8 @@
             "type": "cortex-debug",
             "servertype": "openocd",
             "device": "STM32F429ZI",
+            "armToolchainPath": "/opt/arm-miosix-eabi/bin",
+            "toolchainPrefix": "arm-miosix-eabi",
             "configFiles": [
                 "${workspaceFolder}/libs/miosix-kernel/miosix/arch/cortexM4_stm32f4/stm32f429zi_skyward_death_stack/death_stack.cfg"
             ]
diff --git a/libs/eigen b/libs/eigen
new file mode 160000
index 0000000000000000000000000000000000000000..0dd9643ad547d3dd2e23ded1d3376d0f7bdc8ada
--- /dev/null
+++ b/libs/eigen
@@ -0,0 +1 @@
+Subproject commit 0dd9643ad547d3dd2e23ded1d3376d0f7bdc8ada
diff --git a/libs/fmt/.clang-format b/libs/fmt/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..df55d340f018f5faae470f58fb06507148728e2c
--- /dev/null
+++ b/libs/fmt/.clang-format
@@ -0,0 +1,8 @@
+# Run manually to reformat a file:
+# clang-format -i --style=file <file>
+Language: Cpp
+BasedOnStyle: Google
+IndentPPDirectives: AfterHash
+IndentCaseLabels: false
+AlwaysBreakTemplateDeclarations: false
+DerivePointerAlignment: false
diff --git a/libs/fmt/.gitignore b/libs/fmt/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..7fcbb0e3771b4d12a12a4c529143b9cb85021300
--- /dev/null
+++ b/libs/fmt/.gitignore
@@ -0,0 +1,36 @@
+.vscode/
+.vs/
+
+*.iml
+.idea/
+.externalNativeBuild/
+.gradle/
+gradle/
+gradlew*
+local.properties
+build/
+support/.cxx
+
+bin/
+/_CPack_Packages
+/CMakeScripts
+/doc/doxyxml
+/doc/html
+/doc/node_modules
+virtualenv
+/Testing
+/install_manifest.txt
+*~
+*.a
+*.so*
+*.xcodeproj
+*.zip
+cmake_install.cmake
+CPack*.cmake
+fmt-*.cmake
+CTestTestfile.cmake
+CMakeCache.txt
+CMakeFiles
+FMT.build
+run-msbuild.bat
+fmt.pc
diff --git a/libs/fmt/CMakeLists.txt b/libs/fmt/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ff6d009373d22a6b008c7d7c64087a920e0b188b
--- /dev/null
+++ b/libs/fmt/CMakeLists.txt
@@ -0,0 +1,365 @@
+cmake_minimum_required(VERSION 3.1...3.18)
+
+# Fallback for using newer policies on CMake <3.12.
+if(${CMAKE_VERSION} VERSION_LESS 3.12)
+  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+endif()
+
+# Determine if fmt is built as a subproject (using add_subdirectory)
+# or if it is the master project.
+if (NOT DEFINED FMT_MASTER_PROJECT)
+  set(FMT_MASTER_PROJECT OFF)
+  if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
+    set(FMT_MASTER_PROJECT ON)
+    message(STATUS "CMake version: ${CMAKE_VERSION}")
+  endif ()
+endif ()
+
+# Joins arguments and places the results in ${result_var}.
+function(join result_var)
+  set(result )
+  foreach (arg ${ARGN})
+    set(result "${result}${arg}")
+  endforeach ()
+  set(${result_var} "${result}" PARENT_SCOPE)
+endfunction()
+
+include(CMakeParseArguments)
+
+# Sets a cache variable with a docstring joined from multiple arguments:
+#   set(<variable> <value>... CACHE <type> <docstring>...)
+# This allows splitting a long docstring for readability.
+function(set_verbose)
+  # cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use
+  # list instead.
+  list(GET ARGN 0 var)
+  list(REMOVE_AT ARGN 0)
+  list(GET ARGN 0 val)
+  list(REMOVE_AT ARGN 0)
+  list(REMOVE_AT ARGN 0)
+  list(GET ARGN 0 type)
+  list(REMOVE_AT ARGN 0)
+  join(doc ${ARGN})
+  set(${var} ${val} CACHE ${type} ${doc})
+endfunction()
+
+# Set the default CMAKE_BUILD_TYPE to Release.
+# This should be done before the project command since the latter can set
+# CMAKE_BUILD_TYPE itself (it does so for nmake).
+if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
+  set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
+              "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
+              "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
+endif ()
+
+project(FMT CXX)
+include(GNUInstallDirs)
+set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
+            "Installation directory for include files, a relative path that "
+            "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
+
+option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
+option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
+       OFF)
+
+# Options that control generation of various targets.
+option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
+option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
+option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
+option(FMT_FUZZ "Generate the fuzz target." OFF)
+option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
+option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
+
+# Get version from core.h
+file(READ include/fmt/core.h core_h)
+if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
+  message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.")
+endif ()
+# Use math to skip leading zeros if any.
+math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
+math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
+math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
+join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
+                 ${CPACK_PACKAGE_VERSION_PATCH})
+message(STATUS "Version: ${FMT_VERSION}")
+
+message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
+
+if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
+  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+endif ()
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
+  "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
+
+include(cxx14)
+include(CheckCXXCompilerFlag)
+include(JoinPaths)
+
+list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
+if (${index} GREATER -1)
+  # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
+  # compatibility with older CMake versions.
+  set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
+endif ()
+message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
+
+if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+  set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
+      -Wold-style-cast -Wundef
+      -Wredundant-decls -Wwrite-strings -Wpointer-arith
+      -Wcast-qual -Wformat=2 -Wmissing-include-dirs
+      -Wcast-align
+      -Wctor-dtor-privacy -Wdisabled-optimization
+      -Winvalid-pch -Woverloaded-virtual
+      -Wconversion -Wswitch-enum -Wundef
+      -Wno-ctor-dtor-privacy -Wno-format-nonliteral)
+  if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
+      set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
+         -Wno-dangling-else -Wno-unused-local-typedefs)
+  endif ()
+  if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
+      set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
+          -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
+          -Wvector-operation-performance -Wsized-deallocation -Wshadow)
+  endif ()
+  if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
+      set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
+          -Wnull-dereference -Wduplicated-cond)
+  endif ()
+  set(WERROR_FLAG -Werror)
+endif ()
+
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
+      -Wdeprecated -Wweak-vtables -Wshadow
+      -Wno-gnu-zero-variadic-macro-arguments)
+  check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
+  if (HAS_NULLPTR_WARNING)
+    set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
+        -Wzero-as-null-pointer-constant)
+  endif ()
+  set(WERROR_FLAG -Werror)
+endif ()
+
+if (MSVC)
+  set(PEDANTIC_COMPILE_FLAGS /W3)
+  set(WERROR_FLAG /WX)
+endif ()
+
+if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
+  # If Microsoft SDK is installed create script run-msbuild.bat that
+  # calls SetEnv.cmd to set up build environment and runs msbuild.
+  # It is useful when building Visual Studio projects with the SDK
+  # toolchain rather than Visual Studio.
+  include(FindSetEnv)
+  if (WINSDK_SETENV)
+    set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"")
+  endif ()
+  # Set FrameworkPathOverride to get rid of MSB3644 warnings.
+  join(netfxpath
+       "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\"
+       ".NETFramework\\v4.0")
+  file(WRITE run-msbuild.bat "
+    ${MSBUILD_SETUP}
+    ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
+endif ()
+
+set(strtod_l_headers stdlib.h)
+if (APPLE)
+  set(strtod_l_headers ${strtod_l_headers} xlocale.h)
+endif ()
+
+include(CheckSymbolExists)
+if (WIN32)
+  check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
+else ()
+  check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
+endif ()
+
+function(add_headers VAR)
+  set(headers ${${VAR}})
+  foreach (header ${ARGN})
+    set(headers ${headers} include/fmt/${header})
+  endforeach()
+  set(${VAR} ${headers} PARENT_SCOPE)
+endfunction()
+
+# Define the fmt library, its includes and the needed defines.
+add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
+                        format-inl.h locale.h os.h ostream.h printf.h ranges.h)
+if (FMT_OS)
+  set(FMT_SOURCES src/format.cc src/os.cc)
+else()
+  set(FMT_SOURCES src/format.cc)
+endif ()
+
+add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
+add_library(fmt::fmt ALIAS fmt)
+
+if (HAVE_STRTOD_L)
+  target_compile_definitions(fmt PUBLIC FMT_LOCALE)
+endif ()
+
+if (MINGW)
+  check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
+  if (${FMT_HAS_MBIG_OBJ})
+    target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
+  endif()
+endif ()
+
+if (FMT_WERROR)
+  target_compile_options(fmt PRIVATE ${WERROR_FLAG})
+endif ()
+if (FMT_PEDANTIC)
+  target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+endif ()
+
+target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
+
+target_include_directories(fmt PUBLIC
+  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
+
+set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
+
+set_target_properties(fmt PROPERTIES
+  VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
+  DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
+
+# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
+# property because it's not set by default.
+set(FMT_LIB_NAME fmt)
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
+endif ()
+
+if (BUILD_SHARED_LIBS)
+  if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
+      NOT EMSCRIPTEN)
+    # Fix rpmlint warning:
+    # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
+    target_link_libraries(fmt -Wl,--as-needed)
+  endif ()
+  target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
+endif ()
+if (FMT_SAFE_DURATION_CAST)
+  target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
+endif()
+
+add_library(fmt-header-only INTERFACE)
+add_library(fmt::fmt-header-only ALIAS fmt-header-only)
+
+target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
+target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
+
+target_include_directories(fmt-header-only INTERFACE
+  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
+
+# Install targets.
+if (FMT_INSTALL)
+  include(CMakePackageConfigHelpers)
+  set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
+              "Installation directory for cmake files, a relative path that "
+              "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
+              "path.")
+  set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
+  set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
+  set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
+  set(targets_export_name fmt-targets)
+
+  set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
+              "Installation directory for libraries, a relative path that "
+              "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
+
+  set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
+              "Installation directory for pkgconfig (.pc) files, a relative "
+              "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
+              "absolute path.")
+
+  # Generate the version, config and target files into the build directory.
+  write_basic_package_version_file(
+    ${version_config}
+    VERSION ${FMT_VERSION}
+    COMPATIBILITY AnyNewerVersion)
+
+  join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}")
+  join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}")
+
+  configure_file(
+    "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
+    "${pkgconfig}"
+    @ONLY)
+  configure_package_config_file(
+    ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
+    ${project_config}
+    INSTALL_DESTINATION ${FMT_CMAKE_DIR})
+
+  set(INSTALL_TARGETS fmt fmt-header-only)
+
+  # Install the library and headers.
+  install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
+          LIBRARY DESTINATION ${FMT_LIB_DIR}
+          ARCHIVE DESTINATION ${FMT_LIB_DIR}
+          RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+  # Use a namespace because CMake provides better diagnostics for namespaced
+  # imported targets.
+  export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
+         FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
+
+  # Install version, config and target files.
+  install(
+    FILES ${project_config} ${version_config}
+    DESTINATION ${FMT_CMAKE_DIR})
+  install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
+          NAMESPACE fmt::)
+
+  install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
+          DESTINATION ${FMT_LIB_DIR} OPTIONAL)
+  install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
+  install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
+endif ()
+
+if (FMT_DOC)
+  add_subdirectory(doc)
+endif ()
+
+if (FMT_TEST)
+  enable_testing()
+  add_subdirectory(test)
+endif ()
+
+# Control fuzzing independent of the unit tests.
+if (FMT_FUZZ)
+  add_subdirectory(test/fuzzing)
+
+  # The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
+  # mode and make fuzzing practically possible. It is similar to
+  # FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
+  # avoid interfering with fuzzing of projects that use {fmt}.
+  # See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
+  target_compile_definitions(fmt PUBLIC FMT_FUZZ)
+endif ()
+
+set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
+if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
+  # Get the list of ignored files from .gitignore.
+  file (STRINGS ${gitignore} lines)
+  list(REMOVE_ITEM lines /doc/html)
+  foreach (line ${lines})
+    string(REPLACE "." "[.]" line "${line}")
+    string(REPLACE "*" ".*" line "${line}")
+    set(ignored_files ${ignored_files} "${line}$" "${line}/")
+  endforeach ()
+  set(ignored_files ${ignored_files}
+    /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
+
+  set(CPACK_SOURCE_GENERATOR ZIP)
+  set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
+  set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
+  set(CPACK_PACKAGE_NAME fmt)
+  set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
+  include(CPack)
+endif ()
\ No newline at end of file
diff --git a/libs/fmt/CONTRIBUTING.md b/libs/fmt/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..b82f145069a8c61601ce12604b00fd06a8050488
--- /dev/null
+++ b/libs/fmt/CONTRIBUTING.md
@@ -0,0 +1,20 @@
+Contributing to {fmt}
+=====================
+
+By submitting a pull request or a patch, you represent that you have the right
+to license your contribution to the {fmt} project owners and the community,
+agree that your contributions are licensed under the {fmt} license, and agree
+to future changes to the licensing.
+
+All C++ code must adhere to [Google C++ Style Guide](
+https://google.github.io/styleguide/cppguide.html) with the following
+exceptions:
+
+* Exceptions are permitted
+* snake_case should be used instead of UpperCamelCase for function and type
+  names
+
+All documentation must adhere to the [Google Developer Documentation Style
+Guide](https://developers.google.com/style).
+
+Thanks for contributing!
diff --git a/libs/fmt/ChangeLog.rst b/libs/fmt/ChangeLog.rst
new file mode 100644
index 0000000000000000000000000000000000000000..9c171af0505955ecabd835f76d1a48ed2c17c9d8
--- /dev/null
+++ b/libs/fmt/ChangeLog.rst
@@ -0,0 +1,3665 @@
+7.1.3 - 2020-11-24
+------------------
+
+* Fixed handling of buffer boundaries in ``format_to_n``
+  (`#1996 <https://github.com/fmtlib/fmt/issues/1996>`_,
+  `#2029 <https://github.com/fmtlib/fmt/issues/2029>`_).
+
+* Fixed linkage errors when linking with a shared library
+  (`#2011 <https://github.com/fmtlib/fmt/issues/2011>`_).
+
+* Reintroduced ostream support to range formatters
+  (`#2014 <https://github.com/fmtlib/fmt/issues/2014>`_).
+
+* Worked around an issue with mixing std versions in gcc
+  (`#2017 <https://github.com/fmtlib/fmt/issues/2017>`_).
+
+7.1.2 - 2020-11-04
+------------------
+
+* Fixed floating point formatting with large precision
+  (`#1976 <https://github.com/fmtlib/fmt/issues/1976>`_).
+
+7.1.1 - 2020-11-01
+------------------
+
+* Fixed ABI compatibility with 7.0.x
+  (`#1961 <https://github.com/fmtlib/fmt/issues/1961>`_).
+
+* Added the ``FMT_ARM_ABI_COMPATIBILITY`` macro to work around ABI
+  incompatibility between GCC and Clang on ARM
+  (`#1919 <https://github.com/fmtlib/fmt/issues/1919>`_).
+
+* Worked around a SFINAE bug in GCC 8
+  (`#1957 <https://github.com/fmtlib/fmt/issues/1957>`_).
+
+* Fixed linkage errors when building with GCC's LTO
+  (`#1955 <https://github.com/fmtlib/fmt/issues/1955>`_).
+
+* Fixed a compilation error when building without ``__builtin_clz`` or equivalent
+  (`#1968 <https://github.com/fmtlib/fmt/pull/1968>`_).
+  Thanks `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_.
+
+* Fixed a sign conversion warning
+  (`#1964 <https://github.com/fmtlib/fmt/pull/1964>`_).
+  Thanks `@OptoCloud <https://github.com/OptoCloud>`_.
+
+7.1.0 - 2020-10-25
+------------------
+
+* Switched from `Grisu3
+  <https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf>`_
+  to `Dragonbox <https://github.com/jk-jeon/dragonbox>`_ for the default
+  floating-point formatting which gives the shortest decimal representation
+  with round-trip guarantee and correct rounding
+  (`#1882 <https://github.com/fmtlib/fmt/pull/1882>`_,
+  `#1887 <https://github.com/fmtlib/fmt/pull/1887>`_,
+  `#1894 <https://github.com/fmtlib/fmt/pull/1894>`_). This makes {fmt} up to
+  20-30x faster than common implementations of ``std::ostringstream`` and
+  ``sprintf`` on `dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_
+  and faster than double-conversion and Ryū:
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
+
+  It is possible to get even better performance at the cost of larger binary
+  size by compiling with the ``FMT_USE_FULL_CACHE_DRAGONBOX`` macro set to 1.
+
+  Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
+
+* Added an experimental unsynchronized file output API which, together with
+  `format string compilation <https://fmt.dev/latest/api.html#compile-api>`_,
+  can give `5-9 times speed up compared to fprintf
+  <https://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_
+  on common platforms (`godbolt <https://godbolt.org/z/nsTcG8>`__):
+
+  .. code:: c++
+
+     #include <fmt/os.h>
+
+     int main() {
+       auto f = fmt::output_file("guide");
+       f.print("The answer is {}.", 42);
+     }
+
+* Added a formatter for ``std::chrono::time_point<system_clock>``
+  (`#1819 <https://github.com/fmtlib/fmt/issues/1819>`_,
+  `#1837 <https://github.com/fmtlib/fmt/pull/1837>`_). For example
+  (`godbolt <https://godbolt.org/z/c4M6fh>`__):
+
+  .. code:: c++
+
+     #include <fmt/chrono.h>
+
+     int main() {
+       auto now = std::chrono::system_clock::now();
+       fmt::print("The time is {:%H:%M:%S}.\n", now);
+     }
+
+  Thanks `@adamburgess (Adam Burgess) <https://github.com/adamburgess>`_.
+
+* Added support for ranges with non-const ``begin``/``end`` to ``fmt::join``
+  (`#1784 <https://github.com/fmtlib/fmt/issues/1784>`_,
+  `#1786 <https://github.com/fmtlib/fmt/pull/1786>`_). For example
+  (`godbolt <https://godbolt.org/z/jP63Tv>`__):
+
+  .. code:: c++
+
+     #include <fmt/ranges.h>
+     #include <range/v3/view/filter.hpp>
+
+     int main() {
+       using std::literals::string_literals::operator""s;
+       auto strs = std::array{"a"s, "bb"s, "ccc"s};
+       auto range = strs | ranges::views::filter(
+         [] (const std::string &x) { return x.size() != 2; }
+       );
+       fmt::print("{}\n", fmt::join(range, ""));
+     }
+
+  prints "accc".
+
+  Thanks `@tonyelewis (Tony E Lewis) <https://github.com/tonyelewis>`_.
+
+* Added a ``memory_buffer::append`` overload that takes a range
+  (`#1806 <https://github.com/fmtlib/fmt/pull/1806>`_).
+  Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
+
+* Improved handling of single code units in ``FMT_COMPILE``. For example:
+
+  .. code:: c++
+
+     #include <fmt/compile.h>
+
+     char* f(char* buf) {
+       return fmt::format_to(buf, FMT_COMPILE("x{}"), 42);
+     }
+
+  compiles to just (`godbolt <https://godbolt.org/z/5vncz3>`__):
+
+  .. code:: asm
+
+     _Z1fPc:
+       movb $120, (%rdi)
+       xorl %edx, %edx
+       cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip)
+       movl $3, %eax
+       seta %dl
+       subl %edx, %eax
+       movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx
+       cltq
+       addq %rdi, %rax
+       movw %dx, -2(%rax)
+       ret
+
+  Here a single ``mov`` instruction writes ``'x'`` (``$120``) to the output
+  buffer.
+
+* Added dynamic width support to format string compilation
+  (`#1809 <https://github.com/fmtlib/fmt/issues/1809>`_).
+
+* Improved error reporting for unformattable types: now you'll get the type name
+  directly in the error message instead of the note:
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     struct how_about_no {};
+
+     int main() {
+       fmt::print("{}", how_about_no());
+     }
+
+  Error (`godbolt <https://godbolt.org/z/GoxM4e>`__):
+
+  ``fmt/core.h:1438:3: error: static_assert failed due to requirement
+  'fmt::v7::formattable<how_about_no>()' "Cannot format an argument.
+  To make type T formattable provide a formatter<T> specialization:
+  https://fmt.dev/latest/api.html#udt"
+  ...``
+
+* Added the `make_args_checked <https://fmt.dev/7.1.0/api.html#argument-lists>`_
+  function template that allows you to write formatting functions with
+  compile-time format string checks and avoid binary code bloat
+  (`godbolt <https://godbolt.org/z/PEf9qr>`__):
+
+  .. code:: c++
+
+     void vlog(const char* file, int line, fmt::string_view format,
+               fmt::format_args args) {
+       fmt::print("{}: {}: ", file, line);
+       fmt::vprint(format, args);
+     }
+
+     template <typename S, typename... Args>
+     void log(const char* file, int line, const S& format, Args&&... args) {
+       vlog(file, line, format,
+           fmt::make_args_checked<Args...>(format, args...));
+     }
+
+     #define MY_LOG(format, ...) \
+       log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
+
+     MY_LOG("invalid squishiness: {}", 42);
+
+* Replaced ``snprintf`` fallback with a faster internal IEEE 754 ``float`` and
+  ``double`` formatter for arbitrary precision. For example
+  (`godbolt <https://godbolt.org/z/dPhWvj>`__):
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     int main() {
+       fmt::print("{:.500}\n", 4.9406564584124654E-324);
+     }
+
+  prints
+
+  ``4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324``.
+
+* Made ``format_to_n`` and ``formatted_size`` part of the `core API
+  <https://fmt.dev/latest/api.html#core-api>`__
+  (`godbolt <https://godbolt.org/z/sPjY1K>`__):
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     int main() {
+       char buffer[10];
+       auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42);
+     }
+
+* Added ``fmt::format_to_n`` overload with format string compilation
+  (`#1764 <https://github.com/fmtlib/fmt/issues/1764>`_,
+  `#1767 <https://github.com/fmtlib/fmt/pull/1767>`_,
+  `#1869 <https://github.com/fmtlib/fmt/pull/1869>`_). For example
+  (`godbolt <https://godbolt.org/z/93h86q>`__):
+
+  .. code:: c++
+
+     #include <fmt/compile.h>
+
+     int main() {
+       char buffer[8];
+       fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42);
+     }
+
+  Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_,
+  `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
+
+* Added ``fmt::format_to`` overload that take ``text_style``
+  (`#1593 <https://github.com/fmtlib/fmt/issues/1593>`_,
+  `#1842 <https://github.com/fmtlib/fmt/issues/1842>`_,
+  `#1843 <https://github.com/fmtlib/fmt/pull/1843>`_). For example
+  (`godbolt <https://godbolt.org/z/91153r>`__):
+
+  .. code:: c++
+
+     #include <fmt/color.h>
+
+     int main() {
+       std::string out;
+       fmt::format_to(std::back_inserter(out),
+                      fmt::emphasis::bold | fg(fmt::color::red),
+                      "The answer is {}.", 42);
+     }
+
+  Thanks `@Naios (Denis Blank) <https://github.com/Naios>`_.
+
+* Made the ``#`` specifier emit trailing zeros in addition to the decimal point
+  (`#1797 <https://github.com/fmtlib/fmt/issues/1797>`_). For example
+  (`godbolt <https://godbolt.org/z/bhdcW9>`__):
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     int main() {
+       fmt::print("{:#.2g}", 0.5);
+     }
+
+  prints ``0.50``.
+
+* Changed the default floating point format to not include ``.0`` for
+  consistency with ``std::format`` and ``std::to_chars``
+  (`#1893 <https://github.com/fmtlib/fmt/issues/1893>`_,
+  `#1943 <https://github.com/fmtlib/fmt/issues/1943>`_). It is possible to get
+  the decimal point and trailing zero with the ``#`` specifier.
+
+* Fixed an issue with floating-point formatting that could result in addition of
+  a non-significant trailing zero in rare cases e.g. ``1.00e-34`` instead of
+  ``1.0e-34`` (`#1873 <https://github.com/fmtlib/fmt/issues/1873>`_,
+  `#1917 <https://github.com/fmtlib/fmt/issues/1917>`_).
+
+* Made ``fmt::to_string`` fallback on ``ostream`` insertion operator if
+  the ``formatter`` specialization is not provided
+  (`#1815 <https://github.com/fmtlib/fmt/issues/1815>`_,
+  `#1829 <https://github.com/fmtlib/fmt/pull/1829>`_).
+  Thanks `@alexezeder (Alexey Ochapov) <https://github.com/alexezeder>`_.
+
+* Added support for the append mode to the experimental file API and
+  improved ``fcntl.h`` detection.
+  (`#1847 <https://github.com/fmtlib/fmt/pull/1847>`_,
+  `#1848 <https://github.com/fmtlib/fmt/pull/1848>`_).
+  Thanks `@t-wiser <https://github.com/t-wiser>`_.
+
+* Fixed handling of types that have both an implicit conversion operator and
+  an overloaded ``ostream`` insertion operator
+  (`#1766 <https://github.com/fmtlib/fmt/issues/1766>`_).
+
+* Fixed a slicing issue in an internal iterator type
+  (`#1822 <https://github.com/fmtlib/fmt/pull/1822>`_).
+  Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
+
+* Fixed an issue in locale-specific integer formatting
+  (`#1927 <https://github.com/fmtlib/fmt/issues/1927>`_).
+
+* Fixed handling of exotic code unit types
+  (`#1870 <https://github.com/fmtlib/fmt/issues/1870>`_,
+  `#1932 <https://github.com/fmtlib/fmt/issues/1932>`_).
+
+* Improved ``FMT_ALWAYS_INLINE``
+  (`#1878 <https://github.com/fmtlib/fmt/pull/1878>`_).
+  Thanks `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_.
+
+* Removed dependency on ``windows.h``
+  (`#1900 <https://github.com/fmtlib/fmt/pull/1900>`_).
+  Thanks `@bernd5 (Bernd Baumanns) <https://github.com/bernd5>`_.
+
+* Optimized counting of decimal digits on MSVC
+  (`#1890 <https://github.com/fmtlib/fmt/pull/1890>`_).
+  Thanks `@mwinterb <https://github.com/mwinterb>`_.
+
+* Improved documentation
+  (`#1772 <https://github.com/fmtlib/fmt/issues/1772>`_,
+  `#1775 <https://github.com/fmtlib/fmt/pull/1775>`_,
+  `#1792 <https://github.com/fmtlib/fmt/pull/1792>`_,
+  `#1838 <https://github.com/fmtlib/fmt/pull/1838>`_,
+  `#1888 <https://github.com/fmtlib/fmt/pull/1888>`_,
+  `#1918 <https://github.com/fmtlib/fmt/pull/1918>`_,
+  `#1939 <https://github.com/fmtlib/fmt/pull/1939>`_).
+  Thanks `@leolchat (Léonard Gérard) <https://github.com/leolchat>`_,
+  `@pepsiman (Malcolm Parsons) <https://github.com/pepsiman>`_,
+  `@Klaim (Joël Lamotte) <https://github.com/Klaim>`_,
+  `@ravijanjam (Ravi J) <https://github.com/ravijanjam>`_,
+  `@francesco-st <https://github.com/francesco-st>`_,
+  `@udnaan (Adnan) <https://github.com/udnaan>`_.
+
+* Added the ``FMT_REDUCE_INT_INSTANTIATIONS`` CMake option that reduces the
+  binary code size at the cost of some integer formatting performance. This can
+  be useful for extremely memory-constrained embedded systems
+  (`#1778 <https://github.com/fmtlib/fmt/issues/1778>`_,
+  `#1781 <https://github.com/fmtlib/fmt/pull/1781>`_).
+  Thanks `@kammce (Khalil Estell) <https://github.com/kammce>`_.
+
+* Added the ``FMT_USE_INLINE_NAMESPACES`` macro to control usage of inline
+  namespaces (`#1945 <https://github.com/fmtlib/fmt/pull/1945>`_).
+  Thanks `@darklukee <https://github.com/darklukee>`_.
+
+* Improved build configuration
+  (`#1760 <https://github.com/fmtlib/fmt/pull/1760>`_,
+  `#1770 <https://github.com/fmtlib/fmt/pull/1770>`_,
+  `#1779 <https://github.com/fmtlib/fmt/issues/1779>`_,
+  `#1783 <https://github.com/fmtlib/fmt/pull/1783>`_,
+  `#1823 <https://github.com/fmtlib/fmt/pull/1823>`_).
+  Thanks `@dvetutnev (Dmitriy Vetutnev) <https://github.com/dvetutnev>`_,
+  `@xvitaly (Vitaly Zaitsev) <https://github.com/xvitaly>`_,
+  `@tambry (Raul Tambre) <https://github.com/tambry>`_,
+  `@medithe <https://github.com/medithe>`_,
+  `@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_.
+
+* Fixed various warnings and compilation issues
+  (`#1790 <https://github.com/fmtlib/fmt/pull/1790>`_,
+  `#1802 <https://github.com/fmtlib/fmt/pull/1802>`_,
+  `#1808 <https://github.com/fmtlib/fmt/pull/1808>`_,
+  `#1810 <https://github.com/fmtlib/fmt/issues/1810>`_,
+  `#1811 <https://github.com/fmtlib/fmt/issues/1811>`_,
+  `#1812 <https://github.com/fmtlib/fmt/pull/1812>`_,
+  `#1814 <https://github.com/fmtlib/fmt/pull/1814>`_,
+  `#1816 <https://github.com/fmtlib/fmt/pull/1816>`_,
+  `#1817 <https://github.com/fmtlib/fmt/pull/1817>`_,
+  `#1818 <https://github.com/fmtlib/fmt/pull/1818>`_,
+  `#1825 <https://github.com/fmtlib/fmt/issues/1825>`_,
+  `#1836 <https://github.com/fmtlib/fmt/pull/1836>`_,
+  `#1855 <https://github.com/fmtlib/fmt/pull/1855>`_,
+  `#1856 <https://github.com/fmtlib/fmt/pull/1856>`_,
+  `#1860 <https://github.com/fmtlib/fmt/pull/1860>`_,
+  `#1877 <https://github.com/fmtlib/fmt/pull/1877>`_,
+  `#1879 <https://github.com/fmtlib/fmt/pull/1879>`_,
+  `#1880 <https://github.com/fmtlib/fmt/pull/1880>`_,
+  `#1896 <https://github.com/fmtlib/fmt/issues/1896>`_,
+  `#1897 <https://github.com/fmtlib/fmt/pull/1897>`_,
+  `#1898 <https://github.com/fmtlib/fmt/pull/1898>`_,
+  `#1904 <https://github.com/fmtlib/fmt/issues/1904>`_,
+  `#1908 <https://github.com/fmtlib/fmt/pull/1908>`_,
+  `#1911 <https://github.com/fmtlib/fmt/issues/1911>`_,
+  `#1912 <https://github.com/fmtlib/fmt/issues/1912>`_,
+  `#1928 <https://github.com/fmtlib/fmt/issues/1928>`_,
+  `#1929 <https://github.com/fmtlib/fmt/pull/1929>`_,
+  `#1935 <https://github.com/fmtlib/fmt/issues/1935>`_
+  `#1937 <https://github.com/fmtlib/fmt/pull/1937>`_,
+  `#1942 <https://github.com/fmtlib/fmt/pull/1942>`_,
+  `#1949 <https://github.com/fmtlib/fmt/issues/1949>`_).
+  Thanks `@TheQwertiest <https://github.com/TheQwertiest>`_,
+  `@medithe <https://github.com/medithe>`_,
+  `@martinwuehrer (Martin Wührer) <https://github.com/martinwuehrer>`_,
+  `@n16h7hunt3r <https://github.com/n16h7hunt3r>`_,
+  `@Othereum (Seokjin Lee) <https://github.com/Othereum>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@AlexanderLanin (Alexander Lanin) <https://github.com/AlexanderLanin>`_,
+  `@gcerretani (Giovanni Cerretani) <https://github.com/gcerretani>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@noizefloor (Jan Schwers) <https://github.com/noizefloor>`_,
+  `@akohlmey (Axel Kohlmeyer) <https://github.com/akohlmey>`_,
+  `@jk-jeon (Junekey Jeon) <https://github.com/jk-jeon>`_,
+  `@rimathia <https://github.com/rimathia>`_,
+  `@rglarix (Riccardo Ghetta (larix)) <https://github.com/rglarix>`_,
+  `@moiwi <https://github.com/moiwi>`_,
+  `@heckad (Kazantcev Andrey) <https://github.com/heckad>`_,
+  `@MarcDirven <https://github.com/MarcDirven>`_.
+  `@BartSiwek (Bart Siwek) <https://github.com/BartSiwek>`_,
+  `@darklukee <https://github.com/darklukee>`_.
+
+7.0.3 - 2020-08-06
+------------------
+
+* Worked around broken ``numeric_limits`` for 128-bit integers
+  (`#1787 <https://github.com/fmtlib/fmt/issues/1787>`_).
+
+* Added error reporting on missing named arguments
+  (`#1796 <https://github.com/fmtlib/fmt/issues/1796>`_).
+
+* Stopped using 128-bit integers with clang-cl
+  (`#1800 <https://github.com/fmtlib/fmt/pull/1800>`_).
+  Thanks `@Kingcom <https://github.com/Kingcom>`_.
+
+* Fixed issues in locale-specific integer formatting
+  (`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_,
+  `#1801 <https://github.com/fmtlib/fmt/issues/1801>`_).
+
+7.0.2 - 2020-07-29
+------------------
+
+* Worked around broken ``numeric_limits`` for 128-bit integers
+  (`#1725 <https://github.com/fmtlib/fmt/issues/1725>`_).
+
+* Fixed compatibility with CMake 3.4
+  (`#1779 <https://github.com/fmtlib/fmt/issues/1779>`_).
+
+* Fixed handling of digit separators in locale-specific formatting
+  (`#1782 <https://github.com/fmtlib/fmt/issues/1782>`_).
+
+7.0.1 - 2020-07-07
+------------------
+
+* Updated the inline version namespace name.
+
+* Worked around a gcc bug in mangling of alias templates
+  (`#1753 <https://github.com/fmtlib/fmt/issues/1753>`_).
+
+* Fixed a linkage error on Windows
+  (`#1757 <https://github.com/fmtlib/fmt/issues/1757>`_).
+  Thanks `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_.
+
+* Fixed minor issues with the documentation.
+
+7.0.0 - 2020-07-05
+------------------
+
+* Reduced the library size. For example, on macOS a stripped test binary
+  statically linked with {fmt} `shrank from ~368k to less than 100k
+  <http://www.zverovich.net/2020/05/21/reducing-library-size.html>`_.
+
+* Added a simpler and more efficient `format string compilation API
+  <https://fmt.dev/7.0.0/api.html#compile-api>`_:
+
+  .. code:: c++
+
+     #include <fmt/compile.h>
+
+     // Converts 42 into std::string using the most efficient method and no
+     // runtime format string processing.
+     std::string s = fmt::format(FMT_COMPILE("{}"), 42);
+
+  The old ``fmt::compile`` API is now deprecated.
+
+* Optimized integer formatting: ``format_to`` with format string compilation
+  and a stack-allocated buffer is now `faster than to_chars on both
+  libc++ and libstdc++
+  <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
+
+* Optimized handling of small format strings. For example,
+
+  .. code:: c++
+
+      fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5)
+
+  is now ~40% faster (`#1685 <https://github.com/fmtlib/fmt/issues/1685>`_).
+
+* Applied extern templates to improve compile times when using the core API
+  and ``fmt/format.h`` (`#1452 <https://github.com/fmtlib/fmt/issues/1452>`_).
+  For example, on macOS with clang the compile time of a test translation unit
+  dropped from 2.3s to 0.3s with ``-O2`` and from 0.6s to 0.3s with the default
+  settings (``-O0``).
+
+  Before (``-O2``)::
+
+    % time c++ -c test.cc -I include -std=c++17 -O2
+    c++ -c test.cc -I include -std=c++17 -O2  2.22s user 0.08s system 99% cpu 2.311 total
+
+  After (``-O2``)::
+
+    % time c++ -c test.cc -I include -std=c++17 -O2
+    c++ -c test.cc -I include -std=c++17 -O2  0.26s user 0.04s system 98% cpu 0.303 total
+
+  Before (default)::
+
+    % time c++ -c test.cc -I include -std=c++17
+    c++ -c test.cc -I include -std=c++17  0.53s user 0.06s system 98% cpu 0.601 total
+
+  After (default)::
+
+    % time c++ -c test.cc -I include -std=c++17
+    c++ -c test.cc -I include -std=c++17  0.24s user 0.06s system 98% cpu 0.301 total
+
+  It is still recommended to use ``fmt/core.h`` instead of ``fmt/format.h`` but
+  the compile time difference is now smaller. Thanks
+  `@alex3d <https://github.com/alex3d>`_ for the suggestion.
+
+* Named arguments are now stored on stack (no dynamic memory allocations) and
+  the compiled code is more compact and efficient. For example
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     int main() {
+       fmt::print("The answer is {answer}\n", fmt::arg("answer", 42));
+     }
+
+  compiles to just (`godbolt <https://godbolt.org/z/NcfEp_>`__)
+
+  .. code:: asm
+
+      .LC0:
+              .string "answer"
+      .LC1:
+              .string "The answer is {answer}\n"
+      main:
+              sub     rsp, 56
+              mov     edi, OFFSET FLAT:.LC1
+              mov     esi, 23
+              movabs  rdx, 4611686018427387905
+              lea     rax, [rsp+32]
+              lea     rcx, [rsp+16]
+              mov     QWORD PTR [rsp+8], 1
+              mov     QWORD PTR [rsp], rax
+              mov     DWORD PTR [rsp+16], 42
+              mov     QWORD PTR [rsp+32], OFFSET FLAT:.LC0
+              mov     DWORD PTR [rsp+40], 0
+              call    fmt::v6::vprint(fmt::v6::basic_string_view<char>,
+                                      fmt::v6::format_args)
+              xor     eax, eax
+              add     rsp, 56
+              ret
+
+          .L.str.1:
+                  .asciz  "answer"
+
+* Implemented compile-time checks for dynamic width and precision
+  (`#1614 <https://github.com/fmtlib/fmt/issues/1614>`_):
+
+  .. code:: c++
+
+     #include <fmt/format.h>
+
+     int main() {
+       fmt::print(FMT_STRING("{0:{1}}"), 42);
+     }
+
+  now gives a compilation error because argument 1 doesn't exist::
+
+    In file included from test.cc:1:
+    include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be
+    initialized by a constant expression
+      FMT_CONSTEXPR_DECL bool invalid_format =
+                              ^
+    ...
+    include/fmt/core.h:569:26: note: in call to
+    '&checker(s, {}).context_->on_error(&"argument not found"[0])'
+        if (id >= num_args_) on_error("argument not found");
+                            ^
+
+* Added sentinel support to ``fmt::join``
+  (`#1689 <https://github.com/fmtlib/fmt/pull/1689>`_)
+
+  .. code:: c++
+
+    struct zstring_sentinel {};
+    bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
+    bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
+
+    struct zstring {
+      const char* p;
+      const char* begin() const { return p; }
+      zstring_sentinel end() const { return {}; }
+    };
+
+    auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_"));
+    // s == "h_e_l_l_o"
+
+  Thanks `@BRevzin (Barry Revzin) <https://github.com/BRevzin>`_.
+
+* Added support for named arguments, ``clear`` and ``reserve`` to
+  ``dynamic_format_arg_store``
+  (`#1655 <https://github.com/fmtlib/fmt/issues/1655>`_,
+  `#1663 <https://github.com/fmtlib/fmt/pull/1663>`_,
+  `#1674 <https://github.com/fmtlib/fmt/pull/1674>`_,
+  `#1677 <https://github.com/fmtlib/fmt/pull/1677>`_).
+  Thanks `@vsolontsov-ll (Vladimir Solontsov)
+  <https://github.com/vsolontsov-ll>`_.
+
+* Added support for the ``'c'`` format specifier to integral types for
+  compatibility with ``std::format``
+  (`#1652 <https://github.com/fmtlib/fmt/issues/1652>`_).
+
+* Replaced the ``'n'`` format specifier with ``'L'`` for compatibility with
+  ``std::format`` (`#1624 <https://github.com/fmtlib/fmt/issues/1624>`_).
+  The ``'n'`` specifier can be enabled via the ``FMT_DEPRECATED_N_SPECIFIER``
+  macro.
+
+* The ``'='`` format specifier is now disabled by default for compatibility with
+  ``std::format``. It can be enabled via the ``FMT_DEPRECATED_NUMERIC_ALIGN``
+  macro.
+
+* Removed the following deprecated APIs:
+
+  * ``FMT_STRING_ALIAS`` and ``fmt`` macros - replaced by ``FMT_STRING``
+  * ``fmt::basic_string_view::char_type`` - replaced by
+    ``fmt::basic_string_view::value_type``
+  * ``convert_to_int``
+  * ``format_arg_store::types``
+  * ``*parse_context`` - replaced by ``*format_parse_context``
+  * ``FMT_DEPRECATED_INCLUDE_OS``
+  * ``FMT_DEPRECATED_PERCENT`` - incompatible with ``std::format``
+  * ``*writer`` - replaced by compiled format API
+
+* Renamed the ``internal`` namespace to ``detail``
+  (`#1538 <https://github.com/fmtlib/fmt/issues/1538>`_). The former is still
+  provided as an alias if the ``FMT_USE_INTERNAL`` macro is defined.
+
+* Improved compatibility between ``fmt::printf`` with the standard specs
+  (`#1595 <https://github.com/fmtlib/fmt/issues/1595>`_,
+  `#1682 <https://github.com/fmtlib/fmt/pull/1682>`_,
+  `#1683 <https://github.com/fmtlib/fmt/pull/1683>`_,
+  `#1687 <https://github.com/fmtlib/fmt/pull/1687>`_,
+  `#1699 <https://github.com/fmtlib/fmt/pull/1699>`_).
+  Thanks `@rimathia <https://github.com/rimathia>`_.
+
+* Fixed handling of ``operator<<`` overloads that use ``copyfmt``
+  (`#1666 <https://github.com/fmtlib/fmt/issues/1666>`_).
+
+* Added the ``FMT_OS`` CMake option to control inclusion of OS-specific APIs
+  in the fmt target. This can be useful for embedded platforms
+  (`#1654 <https://github.com/fmtlib/fmt/issues/1654>`_,
+  `#1656 <https://github.com/fmtlib/fmt/pull/1656>`_).
+  Thanks `@kwesolowski (Krzysztof Wesolowski)
+  <https://github.com/kwesolowski>`_.
+
+* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ``
+  macro to prevent interferring with fuzzing of projects using {fmt}
+  (`#1650 <https://github.com/fmtlib/fmt/pull/1650>`_).
+  Thanks `@asraa (Asra Ali) <https://github.com/asraa>`_.
+
+* Fixed compatibility with emscripten
+  (`#1636 <https://github.com/fmtlib/fmt/issues/1636>`_,
+  `#1637 <https://github.com/fmtlib/fmt/pull/1637>`_).
+  Thanks `@ArthurSonzogni (Arthur Sonzogni)
+  <https://github.com/ArthurSonzogni>`_.
+
+* Improved documentation
+  (`#704 <https://github.com/fmtlib/fmt/issues/704>`_,
+  `#1643 <https://github.com/fmtlib/fmt/pull/1643>`_,
+  `#1660 <https://github.com/fmtlib/fmt/pull/1660>`_,
+  `#1681 <https://github.com/fmtlib/fmt/pull/1681>`_,
+  `#1691 <https://github.com/fmtlib/fmt/pull/1691>`_,
+  `#1706 <https://github.com/fmtlib/fmt/pull/1706>`_,
+  `#1714 <https://github.com/fmtlib/fmt/pull/1714>`_,
+  `#1721 <https://github.com/fmtlib/fmt/pull/1721>`_,
+  `#1739 <https://github.com/fmtlib/fmt/pull/1739>`_,
+  `#1740 <https://github.com/fmtlib/fmt/pull/1740>`_,
+  `#1741 <https://github.com/fmtlib/fmt/pull/1741>`_,
+  `#1751 <https://github.com/fmtlib/fmt/pull/1751>`_).
+  Thanks `@senior7515 (Alexander Gallego) <https://github.com/senior7515>`_,
+  `@lsr0 (Lindsay Roberts) <https://github.com/lsr0>`_,
+  `@puetzk (Kevin Puetz) <https://github.com/puetzk>`_,
+  `@fpelliccioni (Fernando Pelliccioni) <https://github.com/fpelliccioni>`_,
+  Alexey Kuzmenko, `@jelly (jelle van der Waa) <https://github.com/jelly>`_,
+  `@claremacrae (Clare Macrae) <https://github.com/claremacrae>`_,
+  `@jiapengwen (文佳鹏) <https://github.com/jiapengwen>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@alexey-milovidov <https://github.com/alexey-milovidov>`_.
+
+* Implemented various build configuration fixes and improvements
+  (`#1603 <https://github.com/fmtlib/fmt/pull/1603>`_,
+  `#1657 <https://github.com/fmtlib/fmt/pull/1657>`_,
+  `#1702 <https://github.com/fmtlib/fmt/pull/1702>`_,
+  `#1728 <https://github.com/fmtlib/fmt/pull/1728>`_).
+  Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_,
+  `@jtojnar (Jan Tojnar) <https://github.com/jtojnar>`_,
+  `@orivej (Orivej Desh) <https://github.com/orivej>`_,
+  `@flagarde <https://github.com/flagarde>`_.
+
+* Fixed various warnings and compilation issues
+  (`#1616 <https://github.com/fmtlib/fmt/pull/1616>`_,
+  `#1620 <https://github.com/fmtlib/fmt/issues/1620>`_,
+  `#1622 <https://github.com/fmtlib/fmt/issues/1622>`_,
+  `#1625 <https://github.com/fmtlib/fmt/issues/1625>`_,
+  `#1627 <https://github.com/fmtlib/fmt/pull/1627>`_,
+  `#1628 <https://github.com/fmtlib/fmt/issues/1628>`_,
+  `#1629 <https://github.com/fmtlib/fmt/pull/1629>`_,
+  `#1631 <https://github.com/fmtlib/fmt/issues/1631>`_,
+  `#1633 <https://github.com/fmtlib/fmt/pull/1633>`_,
+  `#1649 <https://github.com/fmtlib/fmt/pull/1649>`_,
+  `#1658 <https://github.com/fmtlib/fmt/issues/1658>`_,
+  `#1661 <https://github.com/fmtlib/fmt/pull/1661>`_,
+  `#1667 <https://github.com/fmtlib/fmt/pull/1667>`_,
+  `#1668 <https://github.com/fmtlib/fmt/issues/1668>`_,
+  `#1669 <https://github.com/fmtlib/fmt/pull/1669>`_,
+  `#1692 <https://github.com/fmtlib/fmt/issues/1692>`_,
+  `#1696 <https://github.com/fmtlib/fmt/pull/1696>`_,
+  `#1697 <https://github.com/fmtlib/fmt/pull/1697>`_,
+  `#1707 <https://github.com/fmtlib/fmt/issues/1707>`_,
+  `#1712 <https://github.com/fmtlib/fmt/pull/1712>`_,
+  `#1716 <https://github.com/fmtlib/fmt/pull/1716>`_,
+  `#1722 <https://github.com/fmtlib/fmt/pull/1722>`_,
+  `#1724 <https://github.com/fmtlib/fmt/issues/1724>`_,
+  `#1729 <https://github.com/fmtlib/fmt/pull/1729>`_,
+  `#1738 <https://github.com/fmtlib/fmt/pull/1738>`_,
+  `#1742 <https://github.com/fmtlib/fmt/issues/1742>`_,
+  `#1743 <https://github.com/fmtlib/fmt/issues/1743>`_,
+  `#1744 <https://github.com/fmtlib/fmt/pull/1744>`_,
+  `#1747 <https://github.com/fmtlib/fmt/issues/1747>`_,
+  `#1750 <https://github.com/fmtlib/fmt/pull/1750>`_).
+  Thanks `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@johnor (Johan) <https://github.com/johnor>`_,
+  `@Kurkin (Dmitry Kurkin) <https://github.com/Kurkin>`_,
+  `@invexed (James Beach) <https://github.com/invexed>`_,
+  `@peterbell10 <https://github.com/peterbell10>`_,
+  `@daixtrose (Markus Werle) <https://github.com/daixtrose>`_,
+  `@petrutlucian94 (Lucian Petrut) <https://github.com/petrutlucian94>`_,
+  `@Neargye (Daniil Goncharov) <https://github.com/Neargye>`_,
+  `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@erthink (Leonid Yuriev) <https://github.com/erthink>`_,
+  `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_,
+  `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_.
+
+6.2.1 - 2020-05-09
+------------------
+
+* Fixed ostream support in ``sprintf``
+  (`#1631 <https://github.com/fmtlib/fmt/issues/1631>`_).
+
+* Fixed type detection when using implicit conversion to ``string_view`` and
+  ostream ``operator<<`` inconsistently
+  (`#1662 <https://github.com/fmtlib/fmt/issues/1662>`_).
+
+6.2.0 - 2020-04-05
+------------------
+
+* Improved error reporting when trying to format an object of a non-formattable
+  type:
+
+  .. code:: c++
+
+     fmt::format("{}", S());
+
+  now gives::
+
+    include/fmt/core.h:1015:5: error: static_assert failed due to requirement
+    'formattable' "Cannot format argument. To make type T formattable provide a
+    formatter<T> specialization:
+    https://fmt.dev/latest/api.html#formatting-user-defined-types"
+        static_assert(
+        ^
+    ...
+    note: in instantiation of function template specialization
+    'fmt::v6::format<char [3], S, char>' requested here
+      fmt::format("{}", S());
+           ^
+
+  if ``S`` is not formattable.
+
+* Reduced the library size by ~10%.
+
+* Always print decimal point if ``#`` is specified
+  (`#1476 <https://github.com/fmtlib/fmt/issues/1476>`_,
+  `#1498 <https://github.com/fmtlib/fmt/issues/1498>`_):
+
+  .. code:: c++
+
+     fmt::print("{:#.0f}", 42.0);
+
+  now prints ``42.``
+
+* Implemented the ``'L'`` specifier for locale-specific numeric formatting to
+  improve compatibility with ``std::format``. The ``'n'`` specifier is now
+  deprecated and will be removed in the next major release.
+
+* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to
+  ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically
+  include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be
+  disabled in the next major release.
+
+* Added precision overflow detection in floating-point formatting.
+
+* Implemented detection of invalid use of ``fmt::arg``.
+
+* Used ``type_identity`` to block unnecessary template argument deduction.
+  Thanks Tim Song.
+
+* Improved UTF-8 handling
+  (`#1109 <https://github.com/fmtlib/fmt/issues/1109>`_):
+
+  .. code:: c++
+
+     fmt::print("┌{0:─^{2}}┐\n"
+                "│{1: ^{2}}│\n"
+                "└{0:─^{2}}┘\n", "", "Привет, мир!", 20);
+
+  now prints::
+
+     ┌────────────────────┐
+     │    Привет, мир!    │
+     └────────────────────┘
+
+  on systems that support Unicode.
+
+* Added experimental dynamic argument storage
+  (`#1170 <https://github.com/fmtlib/fmt/issues/1170>`_,
+  `#1584 <https://github.com/fmtlib/fmt/pull/1584>`_):
+
+  .. code:: c++
+
+     fmt::dynamic_format_arg_store<fmt::format_context> store;
+     store.push_back("answer");
+     store.push_back(42);
+     fmt::vprint("The {} is {}.\n", store);
+  
+  prints::
+
+     The answer is 42.
+
+  Thanks `@vsolontsov-ll (Vladimir Solontsov)
+  <https://github.com/vsolontsov-ll>`_.
+
+* Made ``fmt::join`` accept ``initializer_list``
+  (`#1591 <https://github.com/fmtlib/fmt/pull/1591>`_).
+  Thanks `@Rapotkinnik (Nikolay Rapotkin) <https://github.com/Rapotkinnik>`_.
+
+* Fixed handling of empty tuples
+  (`#1588 <https://github.com/fmtlib/fmt/issues/1588>`_).
+
+* Fixed handling of output iterators in ``format_to_n``
+  (`#1506 <https://github.com/fmtlib/fmt/issues/1506>`_).
+
+* Fixed formatting of ``std::chrono::duration`` types to wide output
+  (`#1533 <https://github.com/fmtlib/fmt/pull/1533>`_).
+  Thanks `@zeffy (pilao) <https://github.com/zeffy>`_.
+
+* Added const ``begin`` and ``end`` overload to buffers
+  (`#1553 <https://github.com/fmtlib/fmt/pull/1553>`_).
+  Thanks `@dominicpoeschko <https://github.com/dominicpoeschko>`_.
+
+* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``,
+  ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely
+  memory-constrained embedded system
+  (`#1590 <https://github.com/fmtlib/fmt/pull/1590>`_).
+  Thanks `@albaguirre (Alberto Aguirre) <https://github.com/albaguirre>`_.
+
+* Made ``FMT_STRING`` work with ``constexpr`` ``string_view``
+  (`#1589 <https://github.com/fmtlib/fmt/pull/1589>`_).
+  Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
+
+* Implemented a minor optimization in the format string parser
+  (`#1560 <https://github.com/fmtlib/fmt/pull/1560>`_).
+  Thanks `@IkarusDeveloper <https://github.com/IkarusDeveloper>`_.
+
+* Improved attribute detection
+  (`#1469 <https://github.com/fmtlib/fmt/pull/1469>`_,
+  `#1475 <https://github.com/fmtlib/fmt/pull/1475>`_,
+  `#1576 <https://github.com/fmtlib/fmt/pull/1576>`_).
+  Thanks `@federico-busato (Federico) <https://github.com/federico-busato>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@refnum <https://github.com/refnum>`_.
+
+* Improved documentation
+  (`#1481 <https://github.com/fmtlib/fmt/pull/1481>`_,
+  `#1523 <https://github.com/fmtlib/fmt/pull/1523>`_).
+  Thanks `@JackBoosY (Jack·Boos·Yu) <https://github.com/JackBoosY>`_,
+  `@imba-tjd (谭九鼎) <https://github.com/imba-tjd>`_.
+
+* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden``
+  (`#1535 <https://github.com/fmtlib/fmt/pull/1535>`_).
+  Thanks `@milianw (Milian Wolff) <https://github.com/milianw>`_.
+
+* Implemented various build configuration fixes and improvements
+  (`#1264 <https://github.com/fmtlib/fmt/issues/1264>`_,
+  `#1460 <https://github.com/fmtlib/fmt/issues/1460>`_,
+  `#1534 <https://github.com/fmtlib/fmt/pull/1534>`_,
+  `#1536 <https://github.com/fmtlib/fmt/issues/1536>`_,
+  `#1545 <https://github.com/fmtlib/fmt/issues/1545>`_,
+  `#1546 <https://github.com/fmtlib/fmt/pull/1546>`_,
+  `#1566 <https://github.com/fmtlib/fmt/issues/1566>`_,
+  `#1582 <https://github.com/fmtlib/fmt/pull/1582>`_,
+  `#1597 <https://github.com/fmtlib/fmt/issues/1597>`_,
+  `#1598 <https://github.com/fmtlib/fmt/pull/1598>`_).
+  Thanks `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_,
+  `@jwillikers (Jordan Williams) <https://github.com/jwillikers>`_,
+  `@stac47 (Laurent Stacul) <https://github.com/stac47>`_.
+
+* Fixed various warnings and compilation issues
+  (`#1433 <https://github.com/fmtlib/fmt/pull/1433>`_,
+  `#1461 <https://github.com/fmtlib/fmt/issues/1461>`_,
+  `#1470 <https://github.com/fmtlib/fmt/pull/1470>`_,
+  `#1480 <https://github.com/fmtlib/fmt/pull/1480>`_,
+  `#1485 <https://github.com/fmtlib/fmt/pull/1485>`_,
+  `#1492 <https://github.com/fmtlib/fmt/pull/1492>`_,
+  `#1493 <https://github.com/fmtlib/fmt/issues/1493>`_,
+  `#1504 <https://github.com/fmtlib/fmt/issues/1504>`_,
+  `#1505 <https://github.com/fmtlib/fmt/pull/1505>`_,
+  `#1512 <https://github.com/fmtlib/fmt/pull/1512>`_,
+  `#1515 <https://github.com/fmtlib/fmt/issues/1515>`_,
+  `#1516 <https://github.com/fmtlib/fmt/pull/1516>`_,
+  `#1518 <https://github.com/fmtlib/fmt/pull/1518>`_,
+  `#1519 <https://github.com/fmtlib/fmt/pull/1519>`_,
+  `#1520 <https://github.com/fmtlib/fmt/pull/1520>`_,
+  `#1521 <https://github.com/fmtlib/fmt/pull/1521>`_,
+  `#1522 <https://github.com/fmtlib/fmt/pull/1522>`_,
+  `#1524 <https://github.com/fmtlib/fmt/issues/1524>`_,
+  `#1530 <https://github.com/fmtlib/fmt/pull/1530>`_,
+  `#1531 <https://github.com/fmtlib/fmt/issues/1531>`_,
+  `#1532 <https://github.com/fmtlib/fmt/pull/1532>`_,
+  `#1539 <https://github.com/fmtlib/fmt/issues/1539>`_,
+  `#1547 <https://github.com/fmtlib/fmt/issues/1547>`_,
+  `#1548 <https://github.com/fmtlib/fmt/issues/1548>`_,
+  `#1554 <https://github.com/fmtlib/fmt/pull/1554>`_,
+  `#1567 <https://github.com/fmtlib/fmt/issues/1567>`_,
+  `#1568 <https://github.com/fmtlib/fmt/pull/1568>`_,
+  `#1569 <https://github.com/fmtlib/fmt/pull/1569>`_,
+  `#1571 <https://github.com/fmtlib/fmt/pull/1571>`_,
+  `#1573 <https://github.com/fmtlib/fmt/pull/1573>`_,
+  `#1575 <https://github.com/fmtlib/fmt/pull/1575>`_,
+  `#1581 <https://github.com/fmtlib/fmt/pull/1581>`_,
+  `#1583 <https://github.com/fmtlib/fmt/issues/1583>`_,
+  `#1586 <https://github.com/fmtlib/fmt/issues/1586>`_,
+  `#1587 <https://github.com/fmtlib/fmt/issues/1587>`_,
+  `#1594 <https://github.com/fmtlib/fmt/issues/1594>`_,
+  `#1596 <https://github.com/fmtlib/fmt/pull/1596>`_,
+  `#1604 <https://github.com/fmtlib/fmt/issues/1604>`_,
+  `#1606 <https://github.com/fmtlib/fmt/pull/1606>`_,
+  `#1607 <https://github.com/fmtlib/fmt/issues/1607>`_,
+  `#1609 <https://github.com/fmtlib/fmt/issues/1609>`_).
+  Thanks `@marti4d (Chris Martin) <https://github.com/marti4d>`_,
+  `@iPherian <https://github.com/iPherian>`_,
+  `@parkertomatoes <https://github.com/parkertomatoes>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
+  `@torsten48 <https://github.com/torsten48>`_,
+  `@tohammer (Tobias Hammer) <https://github.com/tohammer>`_,
+  `@lefticus (Jason Turner) <https://github.com/lefticus>`_,
+  `@ryusakki (Haise) <https://github.com/ryusakki>`_,
+  `@adnsv (Alex Denisov) <https://github.com/adnsv>`_,
+  `@fghzxm <https://github.com/fghzxm>`_,
+  `@refnum <https://github.com/refnum>`_,
+  `@pramodk (Pramod Kumbhar) <https://github.com/pramodk>`_,
+  `@Spirrwell <https://github.com/Spirrwell>`_,
+  `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
+
+6.1.2 - 2019-12-11
+------------------
+
+* Fixed ABI compatibility with ``libfmt.so.6.0.0``
+  (`#1471 <https://github.com/fmtlib/fmt/issues/1471>`_).
+
+* Fixed handling types convertible to ``std::string_view``
+  (`#1451 <https://github.com/fmtlib/fmt/pull/1451>`_).
+  Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_.
+
+* Made CUDA test an opt-in enabled via the ``FMT_CUDA_TEST`` CMake option.
+
+* Fixed sign conversion warnings
+  (`#1440 <https://github.com/fmtlib/fmt/pull/1440>`_).
+  Thanks `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_.
+
+6.1.1 - 2019-12-04
+------------------
+
+* Fixed shared library build on Windows
+  (`#1443 <https://github.com/fmtlib/fmt/pull/1443>`_,
+  `#1445 <https://github.com/fmtlib/fmt/issues/1445>`_,
+  `#1446 <https://github.com/fmtlib/fmt/pull/1446>`_,
+  `#1450 <https://github.com/fmtlib/fmt/issues/1450>`_).
+  Thanks `@egorpugin (Egor Pugin) <https://github.com/egorpugin>`_,
+  `@bbolli (Beat Bolli) <https://github.com/bbolli>`_.
+
+* Added a missing decimal point in exponent notation with trailing zeros.
+
+* Removed deprecated ``format_arg_store::TYPES``.
+
+6.1.0 - 2019-12-01
+------------------
+
+* {fmt} now formats IEEE 754 ``float`` and ``double`` using the shortest decimal
+  representation with correct rounding by default:
+
+  .. code:: c++
+
+     #include <cmath>
+     #include <fmt/core.h>
+
+     int main() {
+       fmt::print("{}", M_PI);
+     }
+
+  prints ``3.141592653589793``.
+
+* Made the fast binary to decimal floating-point formatter the default,
+  simplified it and improved performance. {fmt} is now 15 times faster than
+  libc++'s ``std::ostringstream``, 11 times faster than ``printf`` and 10%
+  faster than double-conversion on `dtoa-benchmark
+  <https://github.com/fmtlib/dtoa-benchmark>`_:
+
+  ==================  =========  =======
+  Function            Time (ns)  Speedup
+  ==================  =========  =======
+  ostringstream        1,346.30    1.00x
+  ostrstream           1,195.74    1.13x
+  sprintf                995.08    1.35x
+  doubleconv              99.10   13.59x
+  fmt                     88.34   15.24x
+  ==================  =========  =======
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
+
+* {fmt} no longer converts ``float`` arguments to ``double``. In particular this
+  improves the default (shortest) representation of floats and makes
+  ``fmt::format`` consistent with ``std::format`` specs
+  (`#1336 <https://github.com/fmtlib/fmt/issues/1336>`_,
+  `#1353 <https://github.com/fmtlib/fmt/issues/1353>`_,
+  `#1360 <https://github.com/fmtlib/fmt/pull/1360>`_,
+  `#1361 <https://github.com/fmtlib/fmt/pull/1361>`_):
+
+  .. code:: c++
+
+     fmt::print("{}", 0.1f);
+
+  prints ``0.1`` instead of ``0.10000000149011612``.
+
+  Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_.
+
+* Made floating-point formatting output consistent with ``printf``/iostreams
+  (`#1376 <https://github.com/fmtlib/fmt/issues/1376>`_,
+  `#1417 <https://github.com/fmtlib/fmt/issues/1417>`_).
+
+* Added support for 128-bit integers
+  (`#1287 <https://github.com/fmtlib/fmt/pull/1287>`_):
+
+  .. code:: c++
+
+     fmt::print("{}", std::numeric_limits<__int128_t>::max());
+
+  prints ``170141183460469231731687303715884105727``.
+
+  Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_.
+
+* The overload of ``print`` that takes ``text_style`` is now atomic, i.e. the
+  output from different threads doesn't interleave
+  (`#1351 <https://github.com/fmtlib/fmt/pull/1351>`_).
+  Thanks `@tankiJong (Tanki Zhang) <https://github.com/tankiJong>`_.
+
+* Made compile time in the header-only mode ~20% faster by reducing the number
+  of template instantiations. ``wchar_t`` overload of ``vprint`` was moved from
+  ``fmt/core.h`` to ``fmt/format.h``.
+
+* Added an overload of ``fmt::join`` that works with tuples
+  (`#1322 <https://github.com/fmtlib/fmt/issues/1322>`_,
+  `#1330 <https://github.com/fmtlib/fmt/pull/1330>`_):
+
+  .. code:: c++
+
+     #include <tuple>
+     #include <fmt/ranges.h>
+
+     int main() {
+       std::tuple<char, int, float> t{'a', 1, 2.0f};
+       fmt::print("{}", t);
+     }
+
+  prints ``('a', 1, 2.0)``.
+
+  Thanks `@jeremyong (Jeremy Ong) <https://github.com/jeremyong>`_.
+
+* Changed formatting of octal zero with prefix from "00" to "0":
+
+  .. code:: c++
+
+     fmt::print("{:#o}", 0);
+
+  prints ``0``.
+
+* The locale is now passed to ostream insertion (``<<``) operators
+  (`#1406 <https://github.com/fmtlib/fmt/pull/1406>`_):
+
+  .. code:: c++
+
+     #include <fmt/locale.h>
+     #include <fmt/ostream.h>
+
+     struct S {
+       double value;
+     };
+
+     std::ostream& operator<<(std::ostream& os, S s) {
+       return os << s.value;
+     }
+
+     int main() {
+       auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42});
+       // s == "0,42"
+     }
+
+  Thanks `@dlaugt (Daniel Laügt) <https://github.com/dlaugt>`_.
+
+* Locale-specific number formatting now uses grouping
+  (`#1393 <https://github.com/fmtlib/fmt/issues/1393>`_
+  `#1394 <https://github.com/fmtlib/fmt/pull/1394>`_).
+  Thanks `@skrdaniel <https://github.com/skrdaniel>`_.
+
+* Fixed handling of types with deleted implicit rvalue conversion to
+  ``const char**`` (`#1421 <https://github.com/fmtlib/fmt/issues/1421>`_):
+
+  .. code:: c++
+
+     struct mystring {
+       operator const char*() const&;
+       operator const char*() &;
+       operator const char*() const&& = delete;
+       operator const char*() && = delete;
+     };
+     mystring str;
+     fmt::print("{}", str); // now compiles
+
+* Enums are now mapped to correct underlying types instead of ``int``
+  (`#1286 <https://github.com/fmtlib/fmt/pull/1286>`_).
+  Thanks `@agmt (Egor Seredin) <https://github.com/agmt>`_.
+
+* Enum classes are no longer implicitly converted to ``int``
+  (`#1424 <https://github.com/fmtlib/fmt/issues/1424>`_).
+
+* Added ``basic_format_parse_context`` for consistency with C++20
+  ``std::format`` and deprecated ``basic_parse_context``.
+
+* Fixed handling of UTF-8 in precision
+  (`#1389 <https://github.com/fmtlib/fmt/issues/1389>`_,
+  `#1390 <https://github.com/fmtlib/fmt/pull/1390>`_).
+  Thanks `@tajtiattila (Attila Tajti) <https://github.com/tajtiattila>`_.
+
+* {fmt} can now be installed on Linux, macOS and Windows with
+  `Conda <https://docs.conda.io/en/latest/>`__ using its
+  `conda-forge <https://conda-forge.org>`__
+  `package <https://github.com/conda-forge/fmt-feedstock>`__
+  (`#1410 <https://github.com/fmtlib/fmt/pull/1410>`_)::
+
+    conda install -c conda-forge fmt
+
+  Thanks `@tdegeus (Tom de Geus) <https://github.com/tdegeus>`_.
+
+* Added a CUDA test (`#1285 <https://github.com/fmtlib/fmt/pull/1285>`_,
+  `#1317 <https://github.com/fmtlib/fmt/pull/1317>`_).
+  Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_ and
+  `@risa2000 <https://github.com/risa2000>`_.
+
+* Improved documentation (`#1276 <https://github.com/fmtlib/fmt/pull/1276>`_,
+  `#1291 <https://github.com/fmtlib/fmt/issues/1291>`_,
+  `#1296 <https://github.com/fmtlib/fmt/issues/1296>`_,
+  `#1315 <https://github.com/fmtlib/fmt/pull/1315>`_,
+  `#1332 <https://github.com/fmtlib/fmt/pull/1332>`_,
+  `#1337 <https://github.com/fmtlib/fmt/pull/1337>`_,
+  `#1395 <https://github.com/fmtlib/fmt/issues/1395>`_
+  `#1418 <https://github.com/fmtlib/fmt/pull/1418>`_).
+  Thanks
+  `@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_,
+  `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_,
+  `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_.
+
+* Various code improvements
+  (`#1358 <https://github.com/fmtlib/fmt/pull/1358>`_,
+  `#1407 <https://github.com/fmtlib/fmt/pull/1407>`_).
+  Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_,
+  `@dpacbach (David P. Sicilia) <https://github.com/dpacbach>`_,
+
+* Fixed compile-time format string checks for user-defined types
+  (`#1292 <https://github.com/fmtlib/fmt/issues/1292>`_).
+
+* Worked around a false positive in ``unsigned-integer-overflow`` sanitizer
+  (`#1377 <https://github.com/fmtlib/fmt/issues/1377>`_).
+
+* Fixed various warnings and compilation issues
+  (`#1273 <https://github.com/fmtlib/fmt/issues/1273>`_,
+  `#1278 <https://github.com/fmtlib/fmt/pull/1278>`_,
+  `#1280 <https://github.com/fmtlib/fmt/pull/1280>`_,
+  `#1281 <https://github.com/fmtlib/fmt/issues/1281>`_,
+  `#1288 <https://github.com/fmtlib/fmt/issues/1288>`_,
+  `#1290 <https://github.com/fmtlib/fmt/pull/1290>`_,
+  `#1301 <https://github.com/fmtlib/fmt/pull/1301>`_,
+  `#1305 <https://github.com/fmtlib/fmt/issues/1305>`_,
+  `#1306 <https://github.com/fmtlib/fmt/issues/1306>`_,
+  `#1309 <https://github.com/fmtlib/fmt/issues/1309>`_,
+  `#1312 <https://github.com/fmtlib/fmt/pull/1312>`_,
+  `#1313 <https://github.com/fmtlib/fmt/issues/1313>`_,
+  `#1316 <https://github.com/fmtlib/fmt/issues/1316>`_,
+  `#1319 <https://github.com/fmtlib/fmt/issues/1319>`_,
+  `#1320 <https://github.com/fmtlib/fmt/pull/1320>`_,
+  `#1326 <https://github.com/fmtlib/fmt/pull/1326>`_,
+  `#1328 <https://github.com/fmtlib/fmt/pull/1328>`_,
+  `#1344 <https://github.com/fmtlib/fmt/issues/1344>`_,
+  `#1345 <https://github.com/fmtlib/fmt/pull/1345>`_,
+  `#1347 <https://github.com/fmtlib/fmt/pull/1347>`_,
+  `#1349 <https://github.com/fmtlib/fmt/pull/1349>`_,
+  `#1354 <https://github.com/fmtlib/fmt/issues/1354>`_,
+  `#1362 <https://github.com/fmtlib/fmt/issues/1362>`_,
+  `#1366 <https://github.com/fmtlib/fmt/issues/1366>`_,
+  `#1364 <https://github.com/fmtlib/fmt/pull/1364>`_,
+  `#1370 <https://github.com/fmtlib/fmt/pull/1370>`_,
+  `#1371 <https://github.com/fmtlib/fmt/pull/1371>`_,
+  `#1385 <https://github.com/fmtlib/fmt/issues/1385>`_,
+  `#1388 <https://github.com/fmtlib/fmt/issues/1388>`_,
+  `#1397 <https://github.com/fmtlib/fmt/pull/1397>`_,
+  `#1414 <https://github.com/fmtlib/fmt/pull/1414>`_,
+  `#1416 <https://github.com/fmtlib/fmt/pull/1416>`_,
+  `#1422 <https://github.com/fmtlib/fmt/issues/1422>`_
+  `#1427 <https://github.com/fmtlib/fmt/pull/1427>`_,
+  `#1431 <https://github.com/fmtlib/fmt/issues/1431>`_,
+  `#1433 <https://github.com/fmtlib/fmt/pull/1433>`_).
+  Thanks `@hhb <https://github.com/hhb>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@neheb (Rosen Penev) <https://github.com/neheb>`_,
+  `@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_,
+  `@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_,
+  `@mwinterb <https://github.com/mwinterb>`_,
+  `@orivej (Orivej Desh) <https://github.com/orivej>`_,
+  `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_
+  `@leonklingele <https://github.com/leonklingele>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@kent-tri <https://github.com/kent-tri>`_,
+  `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_,
+  `@marti4d (Chris Martin) <https://github.com/marti4d>`_.
+
+6.0.0 - 2019-08-26
+------------------
+
+* Switched to the `MIT license
+  <https://github.com/fmtlib/fmt/blob/5a4b24613ba16cc689977c3b5bd8274a3ba1dd1f/LICENSE.rst>`_
+  with an optional exception that allows distributing binary code without
+  attribution.
+
+* Floating-point formatting is now locale-independent by default:
+
+  .. code:: c++
+
+     #include <locale>
+     #include <fmt/core.h>
+
+     int main() {
+       std::locale::global(std::locale("ru_RU.UTF-8"));
+       fmt::print("value = {}", 4.2);
+     }
+
+  prints "value = 4.2" regardless of the locale.
+
+  For locale-specific formatting use the ``n`` specifier:
+
+  .. code:: c++
+
+     std::locale::global(std::locale("ru_RU.UTF-8"));
+     fmt::print("value = {:n}", 4.2);
+
+  prints "value = 4,2".
+
+* Added an experimental Grisu floating-point formatting algorithm
+  implementation (disabled by default). To enable it compile with the
+  ``FMT_USE_GRISU`` macro defined to 1:
+
+  .. code:: c++
+
+     #define FMT_USE_GRISU 1
+     #include <fmt/format.h>
+
+     auto s = fmt::format("{}", 4.2); // formats 4.2 using Grisu
+
+  With Grisu enabled, {fmt} is 13x faster than ``std::ostringstream`` (libc++)
+  and 10x faster than ``sprintf`` on `dtoa-benchmark
+  <https://github.com/fmtlib/dtoa-benchmark>`_ (`full results
+  <https://fmt.dev/unknown_mac64_clang10.0.html>`_):
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg
+
+* Separated formatting and parsing contexts for consistency with
+  `C++20 std::format <http://eel.is/c++draft/format>`_, removing the
+  undocumented ``basic_format_context::parse_context()`` function.
+
+* Added `oss-fuzz <https://github.com/google/oss-fuzz>`_ support
+  (`#1199 <https://github.com/fmtlib/fmt/pull/1199>`_).
+  Thanks `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_.
+
+* ``formatter`` specializations now always take precedence over ``operator<<``
+  (`#952 <https://github.com/fmtlib/fmt/issues/952>`_):
+
+  .. code:: c++
+
+     #include <iostream>
+     #include <fmt/ostream.h>
+
+     struct S {};
+
+     std::ostream& operator<<(std::ostream& os, S) {
+       return os << 1;
+     }
+
+     template <>
+     struct fmt::formatter<S> : fmt::formatter<int> {
+       auto format(S, format_context& ctx) {
+         return formatter<int>::format(2, ctx);
+       }
+     };
+
+     int main() {
+       std::cout << S() << "\n"; // prints 1 using operator<<
+       fmt::print("{}\n", S());  // prints 2 using formatter
+     }
+
+* Introduced the experimental ``fmt::compile`` function that does format string
+  compilation (`#618 <https://github.com/fmtlib/fmt/issues/618>`_,
+  `#1169 <https://github.com/fmtlib/fmt/issues/1169>`_,
+  `#1171 <https://github.com/fmtlib/fmt/pull/1171>`_):
+
+  .. code:: c++
+
+     #include <fmt/compile.h>
+
+     auto f = fmt::compile<int>("{}");
+     std::string s = fmt::format(f, 42); // can be called multiple times to
+                                         // format different values
+     // s == "42"
+
+  It moves the cost of parsing a format string outside of the format function
+  which can be beneficial when identically formatting many objects of the same
+  types. Thanks `@stryku (Mateusz Janek) <https://github.com/stryku>`_.
+
+* Added experimental ``%`` format specifier that formats floating-point values
+  as percentages (`#1060 <https://github.com/fmtlib/fmt/pull/1060>`_,
+  `#1069 <https://github.com/fmtlib/fmt/pull/1069>`_,
+  `#1071 <https://github.com/fmtlib/fmt/pull/1071>`_):
+
+  .. code:: c++
+
+     auto s = fmt::format("{:.1%}", 0.42); // s == "42.0%"
+
+  Thanks `@gawain-bolton (Gawain Bolton) <https://github.com/gawain-bolton>`_.
+
+* Implemented precision for floating-point durations
+  (`#1004 <https://github.com/fmtlib/fmt/issues/1004>`_,
+  `#1012 <https://github.com/fmtlib/fmt/pull/1012>`_):
+
+  .. code:: c++
+
+     auto s = fmt::format("{:.1}", std::chrono::duration<double>(1.234));
+     // s == 1.2s
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Implemented ``chrono`` format specifiers ``%Q`` and ``%q`` that give the value
+  and the unit respectively (`#1019 <https://github.com/fmtlib/fmt/pull/1019>`_):
+
+  .. code:: c++
+
+     auto value = fmt::format("{:%Q}", 42s); // value == "42"
+     auto unit  = fmt::format("{:%q}", 42s); // unit == "s"
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Fixed handling of dynamic width in chrono formatter:
+
+  .. code:: c++
+
+     auto s = fmt::format("{0:{1}%H:%M:%S}", std::chrono::seconds(12345), 12);
+     //                        ^ width argument index                     ^ width
+     // s == "03:25:45    "
+
+  Thanks Howard Hinnant.
+
+* Removed deprecated ``fmt/time.h``. Use ``fmt/chrono.h`` instead.
+
+* Added ``fmt::format`` and ``fmt::vformat`` overloads that take ``text_style``
+  (`#993 <https://github.com/fmtlib/fmt/issues/993>`_,
+  `#994 <https://github.com/fmtlib/fmt/pull/994>`_):
+
+  .. code:: c++
+
+     #include <fmt/color.h>
+
+     std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
+                                       "The answer is {}.", 42);
+
+  Thanks `@Naios (Denis Blank) <https://github.com/Naios>`_.
+
+* Removed the deprecated color API (``print_colored``). Use the new API, namely
+  ``print`` overloads that take ``text_style`` instead.
+
+* Made ``std::unique_ptr`` and ``std::shared_ptr`` formattable as pointers via
+  ``fmt::ptr`` (`#1121 <https://github.com/fmtlib/fmt/pull/1121>`_):
+
+  .. code:: c++
+
+     std::unique_ptr<int> p = ...;
+     fmt::print("{}", fmt::ptr(p)); // prints p as a pointer
+
+  Thanks `@sighingnow (Tao He) <https://github.com/sighingnow>`_.
+
+* Made ``print`` and ``vprint`` report I/O errors
+  (`#1098 <https://github.com/fmtlib/fmt/issues/1098>`_,
+  `#1099 <https://github.com/fmtlib/fmt/pull/1099>`_).
+  Thanks `@BillyDonahue (Billy Donahue) <https://github.com/BillyDonahue>`_.
+
+* Marked deprecated APIs with the ``[[deprecated]]`` attribute and removed
+  internal uses of deprecated APIs
+  (`#1022 <https://github.com/fmtlib/fmt/pull/1022>`_).
+  Thanks `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_.
+
+* Modernized the codebase using more C++11 features and removing workarounds.
+  Most importantly, ``buffer_context`` is now an alias template, so
+  use ``buffer_context<T>`` instead of ``buffer_context<T>::type``.
+  These features require GCC 4.8 or later.
+
+* ``formatter`` specializations now always take precedence over implicit
+  conversions to ``int`` and the undocumented ``convert_to_int`` trait
+  is now deprecated.
+
+* Moved the undocumented ``basic_writer``, ``writer``, and ``wwriter`` types
+  to the ``internal`` namespace.
+
+* Removed deprecated ``basic_format_context::begin()``. Use ``out()`` instead.
+
+* Disallowed passing the result of ``join`` as an lvalue to prevent misuse.
+
+* Refactored the undocumented structs that represent parsed format specifiers
+  to simplify the API and allow multibyte fill.
+
+* Moved SFINAE to template parameters to reduce symbol sizes.
+
+* Switched to ``fputws`` for writing wide strings so that it's no longer
+  required to call ``_setmode`` on Windows
+  (`#1229 <https://github.com/fmtlib/fmt/issues/1229>`_,
+  `#1243 <https://github.com/fmtlib/fmt/pull/1243>`_).
+  Thanks `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_.
+
+* Improved literal-based API
+  (`#1254 <https://github.com/fmtlib/fmt/pull/1254>`_).
+  Thanks `@sylveon (Charles Milette) <https://github.com/sylveon>`_.
+
+* Added support for exotic platforms without ``uintptr_t`` such as IBM i
+  (AS/400) which has 128-bit pointers and only 64-bit integers
+  (`#1059 <https://github.com/fmtlib/fmt/issues/1059>`_).
+
+* Added `Sublime Text syntax highlighting config
+  <https://github.com/fmtlib/fmt/blob/master/support/C%2B%2B.sublime-syntax>`_
+  (`#1037 <https://github.com/fmtlib/fmt/issues/1037>`_).
+  Thanks `@Kronuz (Germán Méndez Bravo) <https://github.com/Kronuz>`_.
+
+* Added the ``FMT_ENFORCE_COMPILE_STRING`` macro to enforce the use of
+  compile-time format strings
+  (`#1231 <https://github.com/fmtlib/fmt/pull/1231>`_).
+  Thanks `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_.
+
+* Stopped setting ``CMAKE_BUILD_TYPE`` if {fmt} is a subproject
+  (`#1081 <https://github.com/fmtlib/fmt/issues/1081>`_).
+
+* Various build improvements
+  (`#1039 <https://github.com/fmtlib/fmt/pull/1039>`_,
+  `#1078 <https://github.com/fmtlib/fmt/pull/1078>`_,
+  `#1091 <https://github.com/fmtlib/fmt/pull/1091>`_,
+  `#1103 <https://github.com/fmtlib/fmt/pull/1103>`_,
+  `#1177 <https://github.com/fmtlib/fmt/pull/1177>`_).
+  Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_,
+  `@jasonszang (Jason Shuo Zang) <https://github.com/jasonszang>`_,
+  `@olafhering (Olaf Hering) <https://github.com/olafhering>`_,
+  `@Lecetem <https://github.com/Lectem>`_,
+  `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_.
+
+* Improved documentation
+  (`#1049 <https://github.com/fmtlib/fmt/issues/1049>`_,
+  `#1051 <https://github.com/fmtlib/fmt/pull/1051>`_,
+  `#1083 <https://github.com/fmtlib/fmt/pull/1083>`_,
+  `#1113 <https://github.com/fmtlib/fmt/pull/1113>`_,
+  `#1114 <https://github.com/fmtlib/fmt/pull/1114>`_,
+  `#1146 <https://github.com/fmtlib/fmt/issues/1146>`_,
+  `#1180 <https://github.com/fmtlib/fmt/issues/1180>`_,
+  `#1250 <https://github.com/fmtlib/fmt/pull/1250>`_,
+  `#1252 <https://github.com/fmtlib/fmt/pull/1252>`_,
+  `#1265 <https://github.com/fmtlib/fmt/pull/1265>`_).
+  Thanks `@mikelui (Michael Lui) <https://github.com/mikelui>`_,
+  `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_,
+  `@BillyDonahue (Billy Donahue) <https://github.com/BillyDonahue>`_,
+  `@jwakely (Jonathan Wakely) <https://github.com/jwakely>`_,
+  `@kaisbe (Kais Ben Salah) <https://github.com/kaisbe>`_,
+  `@sdebionne (Samuel Debionne) <https://github.com/sdebionne>`_.
+
+* Fixed ambiguous formatter specialization in ``fmt/ranges.h``
+  (`#1123 <https://github.com/fmtlib/fmt/issues/1123>`_).
+
+* Fixed formatting of a non-empty ``std::filesystem::path`` which is an
+  infinitely deep range of its components
+  (`#1268 <https://github.com/fmtlib/fmt/issues/1268>`_).
+
+* Fixed handling of general output iterators when formatting characters
+  (`#1056 <https://github.com/fmtlib/fmt/issues/1056>`_,
+  `#1058 <https://github.com/fmtlib/fmt/pull/1058>`_).
+  Thanks `@abolz (Alexander Bolz) <https://github.com/abolz>`_.
+
+* Fixed handling of output iterators in ``formatter`` specialization for
+  ranges (`#1064 <https://github.com/fmtlib/fmt/issues/1064>`_).
+
+* Fixed handling of exotic character types
+  (`#1188 <https://github.com/fmtlib/fmt/issues/1188>`_).
+
+* Made chrono formatting work with exceptions disabled
+  (`#1062 <https://github.com/fmtlib/fmt/issues/1062>`_).
+
+* Fixed DLL visibility issues
+  (`#1134 <https://github.com/fmtlib/fmt/pull/1134>`_,
+  `#1147 <https://github.com/fmtlib/fmt/pull/1147>`_).
+  Thanks `@denchat <https://github.com/denchat>`_.
+
+* Disabled the use of UDL template extension on GCC 9
+  (`#1148 <https://github.com/fmtlib/fmt/issues/1148>`_).
+
+* Removed misplaced ``format`` compile-time checks from ``printf``
+  (`#1173 <https://github.com/fmtlib/fmt/issues/1173>`_).
+
+* Fixed issues in the experimental floating-point formatter
+  (`#1072 <https://github.com/fmtlib/fmt/issues/1072>`_,
+  `#1129 <https://github.com/fmtlib/fmt/issues/1129>`_,
+  `#1153 <https://github.com/fmtlib/fmt/issues/1153>`_,
+  `#1155 <https://github.com/fmtlib/fmt/pull/1155>`_,
+  `#1210 <https://github.com/fmtlib/fmt/issues/1210>`_,
+  `#1222 <https://github.com/fmtlib/fmt/issues/1222>`_).
+  Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
+
+* Fixed bugs discovered by fuzzing or during fuzzing integration
+  (`#1124 <https://github.com/fmtlib/fmt/issues/1124>`_,
+  `#1127 <https://github.com/fmtlib/fmt/issues/1127>`_,
+  `#1132 <https://github.com/fmtlib/fmt/issues/1132>`_,
+  `#1135 <https://github.com/fmtlib/fmt/pull/1135>`_,
+  `#1136 <https://github.com/fmtlib/fmt/issues/1136>`_,
+  `#1141 <https://github.com/fmtlib/fmt/issues/1141>`_,
+  `#1142 <https://github.com/fmtlib/fmt/issues/1142>`_,
+  `#1178 <https://github.com/fmtlib/fmt/issues/1178>`_,
+  `#1179 <https://github.com/fmtlib/fmt/issues/1179>`_,
+  `#1194 <https://github.com/fmtlib/fmt/issues/1194>`_).
+  Thanks `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_.
+
+* Fixed building tests on FreeBSD and Hurd
+  (`#1043 <https://github.com/fmtlib/fmt/issues/1043>`_).
+  Thanks `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_.
+
+* Fixed various warnings and compilation issues
+  (`#998 <https://github.com/fmtlib/fmt/pull/998>`_,
+  `#1006 <https://github.com/fmtlib/fmt/pull/1006>`_,
+  `#1008 <https://github.com/fmtlib/fmt/issues/1008>`_,
+  `#1011 <https://github.com/fmtlib/fmt/issues/1011>`_,
+  `#1025 <https://github.com/fmtlib/fmt/issues/1025>`_,
+  `#1027 <https://github.com/fmtlib/fmt/pull/1027>`_,
+  `#1028 <https://github.com/fmtlib/fmt/pull/1028>`_,
+  `#1029 <https://github.com/fmtlib/fmt/pull/1029>`_,
+  `#1030 <https://github.com/fmtlib/fmt/pull/1030>`_,
+  `#1031 <https://github.com/fmtlib/fmt/pull/1031>`_,
+  `#1054 <https://github.com/fmtlib/fmt/pull/1054>`_,
+  `#1063 <https://github.com/fmtlib/fmt/issues/1063>`_,
+  `#1068 <https://github.com/fmtlib/fmt/pull/1068>`_,
+  `#1074 <https://github.com/fmtlib/fmt/pull/1074>`_,
+  `#1075 <https://github.com/fmtlib/fmt/pull/1075>`_,
+  `#1079 <https://github.com/fmtlib/fmt/pull/1079>`_,
+  `#1086 <https://github.com/fmtlib/fmt/pull/1086>`_,
+  `#1088 <https://github.com/fmtlib/fmt/issues/1088>`_,
+  `#1089 <https://github.com/fmtlib/fmt/pull/1089>`_,
+  `#1094 <https://github.com/fmtlib/fmt/pull/1094>`_,
+  `#1101 <https://github.com/fmtlib/fmt/issues/1101>`_,
+  `#1102 <https://github.com/fmtlib/fmt/pull/1102>`_,
+  `#1105 <https://github.com/fmtlib/fmt/issues/1105>`_,
+  `#1107 <https://github.com/fmtlib/fmt/pull/1107>`_,
+  `#1115 <https://github.com/fmtlib/fmt/issues/1115>`_,
+  `#1117 <https://github.com/fmtlib/fmt/issues/1117>`_,
+  `#1118 <https://github.com/fmtlib/fmt/issues/1118>`_,
+  `#1120 <https://github.com/fmtlib/fmt/issues/1120>`_,
+  `#1123 <https://github.com/fmtlib/fmt/issues/1123>`_,
+  `#1139 <https://github.com/fmtlib/fmt/pull/1139>`_,
+  `#1140 <https://github.com/fmtlib/fmt/issues/1140>`_,
+  `#1143 <https://github.com/fmtlib/fmt/issues/1143>`_,
+  `#1144 <https://github.com/fmtlib/fmt/pull/1144>`_,
+  `#1150 <https://github.com/fmtlib/fmt/pull/1150>`_,
+  `#1151 <https://github.com/fmtlib/fmt/pull/1151>`_,
+  `#1152 <https://github.com/fmtlib/fmt/issues/1152>`_,
+  `#1154 <https://github.com/fmtlib/fmt/issues/1154>`_,
+  `#1156 <https://github.com/fmtlib/fmt/issues/1156>`_,
+  `#1159 <https://github.com/fmtlib/fmt/pull/1159>`_,
+  `#1175 <https://github.com/fmtlib/fmt/issues/1175>`_,
+  `#1181 <https://github.com/fmtlib/fmt/issues/1181>`_,
+  `#1186 <https://github.com/fmtlib/fmt/issues/1186>`_,
+  `#1187 <https://github.com/fmtlib/fmt/pull/1187>`_,
+  `#1191 <https://github.com/fmtlib/fmt/pull/1191>`_,
+  `#1197 <https://github.com/fmtlib/fmt/issues/1197>`_,
+  `#1200 <https://github.com/fmtlib/fmt/issues/1200>`_,
+  `#1203 <https://github.com/fmtlib/fmt/issues/1203>`_,
+  `#1205 <https://github.com/fmtlib/fmt/issues/1205>`_,
+  `#1206 <https://github.com/fmtlib/fmt/pull/1206>`_,
+  `#1213 <https://github.com/fmtlib/fmt/issues/1213>`_,
+  `#1214 <https://github.com/fmtlib/fmt/issues/1214>`_,
+  `#1217 <https://github.com/fmtlib/fmt/pull/1217>`_,
+  `#1228 <https://github.com/fmtlib/fmt/issues/1228>`_,
+  `#1230 <https://github.com/fmtlib/fmt/pull/1230>`_,
+  `#1232 <https://github.com/fmtlib/fmt/issues/1232>`_,
+  `#1235 <https://github.com/fmtlib/fmt/pull/1235>`_,
+  `#1236 <https://github.com/fmtlib/fmt/pull/1236>`_,
+  `#1240 <https://github.com/fmtlib/fmt/issues/1240>`_).
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
+  `@mwinterb <https://github.com/mwinterb>`_,
+  `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_,
+  `@morinmorin <https://github.com/morinmorin>`_,
+  `@ricco19 (Brian Ricciardelli) <https://github.com/ricco19>`_,
+  `@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@remyabel <https://github.com/remyabel>`_,
+  `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@rcane (Ronny Krüger) <https://github.com/rcane>`_,
+  `@mocabe <https://github.com/mocabe>`_,
+  `@denchat <https://github.com/denchat>`_,
+  `@cjdb (Christopher Di Bella) <https://github.com/cjdb>`_,
+  `@HazardyKnusperkeks (Björn Schäpers) <https://github.com/HazardyKnusperkeks>`_,
+  `@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_,
+  `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_,
+  `@DaanDeMeyer (Daan De Meyer) <https://github.com/DaanDeMeyer>`_,
+  `@starkmapper (Mark Stapper) <https://github.com/starkmapper>`_.
+
+5.3.0 - 2018-12-28
+------------------
+
+* Introduced experimental chrono formatting support:
+
+  .. code:: c++
+
+     #include <fmt/chrono.h>
+
+     int main() {
+       using namespace std::literals::chrono_literals;
+       fmt::print("Default format: {} {}\n", 42s, 100ms);
+       fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
+     }
+
+  prints::
+
+     Default format: 42s 100ms
+     strftime-like format: 03:15:30
+
+* Added experimental support for emphasis (bold, italic, underline,
+  strikethrough), colored output to a file stream, and improved colored
+  formatting API
+  (`#961 <https://github.com/fmtlib/fmt/pull/961>`_,
+  `#967 <https://github.com/fmtlib/fmt/pull/967>`_,
+  `#973 <https://github.com/fmtlib/fmt/pull/973>`_):
+
+  .. code:: c++
+
+     #include <fmt/color.h>
+
+     int main() {
+       print(fg(fmt::color::crimson) | fmt::emphasis::bold,
+             "Hello, {}!\n", "world");
+       print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
+             fmt::emphasis::underline, "Hello, {}!\n", "мир");
+       print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
+             "Hello, {}!\n", "世界");
+     }
+
+  prints the following on modern terminals with RGB color support:
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             50405788-b66e7500-076e-11e9-9592-7324d1f951d8.png
+
+  Thanks `@Rakete1111 (Nicolas) <https://github.com/Rakete1111>`_.
+
+* Added support for 4-bit terminal colors
+  (`#968 <https://github.com/fmtlib/fmt/issues/968>`_,
+  `#974 <https://github.com/fmtlib/fmt/pull/974>`_)
+
+  .. code:: c++
+
+     #include <fmt/color.h>
+
+     int main() {
+       print(fg(fmt::terminal_color::red), "stop\n");
+     }
+
+  Note that these colors vary by terminal:
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             50405925-dbfc7e00-0770-11e9-9b85-333fab0af9ac.png
+
+  Thanks `@Rakete1111 (Nicolas) <https://github.com/Rakete1111>`_.
+
+* Parameterized formatting functions on the type of the format string
+  (`#880 <https://github.com/fmtlib/fmt/issues/880>`_,
+  `#881 <https://github.com/fmtlib/fmt/pull/881>`_,
+  `#883 <https://github.com/fmtlib/fmt/pull/883>`_,
+  `#885 <https://github.com/fmtlib/fmt/pull/885>`_,
+  `#897 <https://github.com/fmtlib/fmt/pull/897>`_,
+  `#920 <https://github.com/fmtlib/fmt/issues/920>`_).
+  Any object of type ``S`` that has an overloaded ``to_string_view(const S&)``
+  returning ``fmt::string_view`` can be used as a format string:
+
+  .. code:: c++
+
+     namespace my_ns {
+     inline string_view to_string_view(const my_string& s) {
+       return {s.data(), s.length()};
+     }
+     }
+
+     std::string message = fmt::format(my_string("The answer is {}."), 42);
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Made ``std::string_view`` work as a format string
+  (`#898 <https://github.com/fmtlib/fmt/pull/898>`_):
+
+  .. code:: c++
+
+     auto message = fmt::format(std::string_view("The answer is {}."), 42);
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Added wide string support to compile-time format string checks
+  (`#924 <https://github.com/fmtlib/fmt/pull/924>`_):
+
+  .. code:: c++
+
+     print(fmt(L"{:f}"), 42); // compile-time error: invalid type specifier
+
+  Thanks `@XZiar <https://github.com/XZiar>`_.
+
+* Made colored print functions work with wide strings
+  (`#867 <https://github.com/fmtlib/fmt/pull/867>`_):
+
+  .. code:: c++
+
+     #include <fmt/color.h>
+
+     int main() {
+       print(fg(fmt::color::red), L"{}\n", 42);
+     }
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Introduced experimental Unicode support
+  (`#628 <https://github.com/fmtlib/fmt/issues/628>`_,
+  `#891 <https://github.com/fmtlib/fmt/pull/891>`_):
+
+  .. code:: c++
+
+     using namespace fmt::literals;
+     auto s = fmt::format("{:*^5}"_u, "🤡"_u); // s == "**🤡**"_u
+
+* Improved locale support:
+
+  .. code:: c++
+
+     #include <fmt/locale.h>
+
+     struct numpunct : std::numpunct<char> {
+      protected:
+       char do_thousands_sep() const override { return '~'; }
+     };
+
+     std::locale loc;
+     auto s = fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567);
+     // s == "1~234~567"
+
+* Constrained formatting functions on proper iterator types
+  (`#921 <https://github.com/fmtlib/fmt/pull/921>`_).
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Added ``make_printf_args`` and ``make_wprintf_args`` functions
+  (`#934 <https://github.com/fmtlib/fmt/pull/934>`_).
+  Thanks `@tnovotny <https://github.com/tnovotny>`_.
+
+* Deprecated ``fmt::visit``, ``parse_context``, and ``wparse_context``.
+  Use ``fmt::visit_format_arg``, ``format_parse_context``, and
+  ``wformat_parse_context`` instead.
+
+* Removed undocumented ``basic_fixed_buffer`` which has been superseded by the
+  iterator-based API
+  (`#873 <https://github.com/fmtlib/fmt/issues/873>`_,
+  `#902 <https://github.com/fmtlib/fmt/pull/902>`_).
+  Thanks `@superfunc (hollywood programmer) <https://github.com/superfunc>`_.
+
+* Disallowed repeated leading zeros in an argument ID:
+
+  .. code:: c++
+
+     fmt::print("{000}", 42); // error
+
+* Reintroduced support for gcc 4.4.
+
+* Fixed compilation on platforms with exotic ``double``
+  (`#878 <https://github.com/fmtlib/fmt/issues/878>`_).
+
+* Improved documentation
+  (`#164 <https://github.com/fmtlib/fmt/issues/164>`_,
+  `#877 <https://github.com/fmtlib/fmt/issues/877>`_,
+  `#901 <https://github.com/fmtlib/fmt/pull/901>`_,
+  `#906 <https://github.com/fmtlib/fmt/pull/906>`_,
+  `#979 <https://github.com/fmtlib/fmt/pull/979>`_).
+  Thanks `@kookjr (Mathew Cucuzella) <https://github.com/kookjr>`_,
+  `@DarkDimius (Dmitry Petrashko) <https://github.com/DarkDimius>`_,
+  `@HecticSerenity <https://github.com/HecticSerenity>`_.
+
+* Added pkgconfig support which makes it easier to consume the library from
+  meson and other build systems
+  (`#916 <https://github.com/fmtlib/fmt/pull/916>`_).
+  Thanks `@colemickens (Cole Mickens) <https://github.com/colemickens>`_.
+
+* Various build improvements
+  (`#909 <https://github.com/fmtlib/fmt/pull/909>`_,
+  `#926 <https://github.com/fmtlib/fmt/pull/926>`_,
+  `#937 <https://github.com/fmtlib/fmt/pull/937>`_,
+  `#953 <https://github.com/fmtlib/fmt/pull/953>`_,
+  `#959 <https://github.com/fmtlib/fmt/pull/959>`_).
+  Thanks `@tchaikov (Kefu Chai) <https://github.com/tchaikov>`_,
+  `@luncliff (Park DongHa) <https://github.com/luncliff>`_,
+  `@AndreasSchoenle (Andreas Schönle) <https://github.com/AndreasSchoenle>`_,
+  `@hotwatermorning <https://github.com/hotwatermorning>`_,
+  `@Zefz (JohanJansen) <https://github.com/Zefz>`_.
+
+* Improved ``string_view`` construction performance
+  (`#914 <https://github.com/fmtlib/fmt/pull/914>`_).
+  Thanks `@gabime (Gabi Melman) <https://github.com/gabime>`_.
+
+* Fixed non-matching char types
+  (`#895 <https://github.com/fmtlib/fmt/pull/895>`_).
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Fixed ``format_to_n`` with ``std::back_insert_iterator``
+  (`#913 <https://github.com/fmtlib/fmt/pull/913>`_).
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Fixed locale-dependent formatting
+  (`#905 <https://github.com/fmtlib/fmt/issues/905>`_).
+
+* Fixed various compiler warnings and errors
+  (`#882 <https://github.com/fmtlib/fmt/pull/882>`_,
+  `#886 <https://github.com/fmtlib/fmt/pull/886>`_,
+  `#933 <https://github.com/fmtlib/fmt/pull/933>`_,
+  `#941 <https://github.com/fmtlib/fmt/pull/941>`_,
+  `#931 <https://github.com/fmtlib/fmt/issues/931>`_,
+  `#943 <https://github.com/fmtlib/fmt/pull/943>`_,
+  `#954 <https://github.com/fmtlib/fmt/pull/954>`_,
+  `#956 <https://github.com/fmtlib/fmt/pull/956>`_,
+  `#962 <https://github.com/fmtlib/fmt/pull/962>`_,
+  `#965 <https://github.com/fmtlib/fmt/issues/965>`_,
+  `#977 <https://github.com/fmtlib/fmt/issues/977>`_,
+  `#983 <https://github.com/fmtlib/fmt/pull/983>`_,
+  `#989 <https://github.com/fmtlib/fmt/pull/989>`_).
+  Thanks `@Luthaf (Guillaume Fraux) <https://github.com/Luthaf>`_,
+  `@stevenhoving (Steven Hoving) <https://github.com/stevenhoving>`_,
+  `@christinaa (Kristina Brooks) <https://github.com/christinaa>`_,
+  `@lgritz (Larry Gritz) <https://github.com/lgritz>`_,
+  `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
+  `@0x8000-0000 (Sign Bit) <https://github.com/0x8000-0000>`_,
+  `@liuping1997 <https://github.com/liuping1997>`_.
+
+5.2.1 - 2018-09-21
+------------------
+
+* Fixed ``visit`` lookup issues on gcc 7 & 8
+  (`#870 <https://github.com/fmtlib/fmt/pull/870>`_).
+  Thanks `@medithe <https://github.com/medithe>`_.
+
+* Fixed linkage errors on older gcc.
+
+* Prevented ``fmt/range.h`` from specializing ``fmt::basic_string_view``
+  (`#865 <https://github.com/fmtlib/fmt/issues/865>`_,
+  `#868 <https://github.com/fmtlib/fmt/pull/868>`_).
+  Thanks `@hhggit (dual) <https://github.com/hhggit>`_.
+
+* Improved error message when formatting unknown types
+  (`#872 <https://github.com/fmtlib/fmt/pull/872>`_).
+  Thanks `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_,
+
+* Disabled templated user-defined literals when compiled under nvcc
+  (`#875 <https://github.com/fmtlib/fmt/pull/875>`_).
+  Thanks `@CandyGumdrop (Candy Gumdrop) <https://github.com/CandyGumdrop>`_,
+
+* Fixed ``format_to`` formatting to ``wmemory_buffer``
+  (`#874 <https://github.com/fmtlib/fmt/issues/874>`_).
+
+5.2.0 - 2018-09-13
+------------------
+
+* Optimized format string parsing and argument processing which resulted in up
+  to 5x speed up on long format strings and significant performance boost on
+  various benchmarks. For example, version 5.2 is 2.22x faster than 5.1 on
+  decimal integer formatting with ``format_to`` (macOS, clang-902.0.39.2):
+
+  ==================  =======  =======
+  Method              Time, s  Speedup
+  ==================  =======  =======
+  fmt::format 5.1      0.58
+  fmt::format 5.2      0.35     1.66x
+  fmt::format_to 5.1   0.51
+  fmt::format_to 5.2   0.23     2.22x
+  sprintf              0.71
+  std::to_string       1.01
+  std::stringstream    1.73
+  ==================  =======  =======
+
+* Changed the ``fmt`` macro from opt-out to opt-in to prevent name collisions.
+  To enable it define the ``FMT_STRING_ALIAS`` macro to 1 before including
+  ``fmt/format.h``:
+
+  .. code:: c++
+
+     #define FMT_STRING_ALIAS 1
+     #include <fmt/format.h>
+     std::string answer = format(fmt("{}"), 42);
+
+* Added compile-time format string checks to ``format_to`` overload that takes
+  ``fmt::memory_buffer`` (`#783 <https://github.com/fmtlib/fmt/issues/783>`_):
+
+  .. code:: c++
+
+     fmt::memory_buffer buf;
+     // Compile-time error: invalid type specifier.
+     fmt::format_to(buf, fmt("{:d}"), "foo");
+
+* Moved experimental color support to ``fmt/color.h`` and enabled the
+  new API by default. The old API can be enabled by defining the
+  ``FMT_DEPRECATED_COLORS`` macro.
+
+* Added formatting support for types explicitly convertible to
+  ``fmt::string_view``:
+
+  .. code:: c++
+
+     struct foo {
+       explicit operator fmt::string_view() const { return "foo"; }
+     };
+     auto s = format("{}", foo());
+
+  In particular, this makes formatting function work with
+  ``folly::StringPiece``.
+
+* Implemented preliminary support for ``char*_t`` by replacing the ``format``
+  function overloads with a single function template parameterized on the string
+  type.
+
+* Added support for dynamic argument lists
+  (`#814 <https://github.com/fmtlib/fmt/issues/814>`_,
+  `#819 <https://github.com/fmtlib/fmt/pull/819>`_).
+  Thanks `@MikePopoloski (Michael Popoloski)
+  <https://github.com/MikePopoloski>`_.
+
+* Reduced executable size overhead for embedded targets using newlib nano by
+  making locale dependency optional
+  (`#839 <https://github.com/fmtlib/fmt/pull/839>`_).
+  Thanks `@teajay-fr (Thomas Benard) <https://github.com/teajay-fr>`_.
+
+* Keep ``noexcept`` specifier when exceptions are disabled
+  (`#801 <https://github.com/fmtlib/fmt/issues/801>`_,
+  `#810 <https://github.com/fmtlib/fmt/pull/810>`_).
+  Thanks `@qis (Alexej Harm) <https://github.com/qis>`_.
+
+* Fixed formatting of user-defined types providing ``operator<<`` with
+  ``format_to_n``
+  (`#806 <https://github.com/fmtlib/fmt/pull/806>`_).
+  Thanks `@mkurdej (Marek Kurdej) <https://github.com/mkurdej>`_.
+
+* Fixed dynamic linkage of new symbols
+  (`#808 <https://github.com/fmtlib/fmt/issues/808>`_).
+
+* Fixed global initialization issue
+  (`#807 <https://github.com/fmtlib/fmt/issues/807>`_):
+
+  .. code:: c++
+
+     // This works on compilers with constexpr support.
+     static const std::string answer = fmt::format("{}", 42);
+
+* Fixed various compiler warnings and errors
+  (`#804 <https://github.com/fmtlib/fmt/pull/804>`_,
+  `#809 <https://github.com/fmtlib/fmt/issues/809>`_,
+  `#811 <https://github.com/fmtlib/fmt/pull/811>`_,
+  `#822 <https://github.com/fmtlib/fmt/issues/822>`_,
+  `#827 <https://github.com/fmtlib/fmt/pull/827>`_,
+  `#830 <https://github.com/fmtlib/fmt/issues/830>`_,
+  `#838 <https://github.com/fmtlib/fmt/pull/838>`_,
+  `#843 <https://github.com/fmtlib/fmt/issues/843>`_,
+  `#844 <https://github.com/fmtlib/fmt/pull/844>`_,
+  `#851 <https://github.com/fmtlib/fmt/issues/851>`_,
+  `#852 <https://github.com/fmtlib/fmt/pull/852>`_,
+  `#854 <https://github.com/fmtlib/fmt/pull/854>`_).
+  Thanks `@henryiii (Henry Schreiner) <https://github.com/henryiii>`_,
+  `@medithe <https://github.com/medithe>`_, and
+  `@eliasdaler (Elias Daler) <https://github.com/eliasdaler>`_.
+
+5.1.0 - 2018-07-05
+------------------
+
+* Added experimental support for RGB color output enabled with
+  the ``FMT_EXTENDED_COLORS`` macro:
+
+  .. code:: c++
+
+     #define FMT_EXTENDED_COLORS
+     #define FMT_HEADER_ONLY // or compile fmt with FMT_EXTENDED_COLORS defined
+     #include <fmt/format.h>
+
+     fmt::print(fmt::color::steel_blue, "Some beautiful text");
+
+  The old API (the ``print_colored`` and ``vprint_colored`` functions and the
+  ``color`` enum) is now deprecated.
+  (`#762 <https://github.com/fmtlib/fmt/issues/762>`_
+  `#767 <https://github.com/fmtlib/fmt/pull/767>`_).
+  thanks `@Remotion (Remo) <https://github.com/Remotion>`_.
+
+* Added quotes to strings in ranges and tuples
+  (`#766 <https://github.com/fmtlib/fmt/pull/766>`_).
+  Thanks `@Remotion (Remo) <https://github.com/Remotion>`_.
+
+* Made ``format_to`` work with ``basic_memory_buffer``
+  (`#776 <https://github.com/fmtlib/fmt/issues/776>`_).
+
+* Added ``vformat_to_n`` and ``wchar_t`` overload of ``format_to_n``
+  (`#764 <https://github.com/fmtlib/fmt/issues/764>`_,
+  `#769 <https://github.com/fmtlib/fmt/issues/769>`_).
+
+* Made ``is_range`` and ``is_tuple_like`` part of public (experimental) API
+  to allow specialization for user-defined types
+  (`#751 <https://github.com/fmtlib/fmt/issues/751>`_,
+  `#759 <https://github.com/fmtlib/fmt/pull/759>`_).
+  Thanks `@drrlvn (Dror Levin) <https://github.com/drrlvn>`_.
+
+* Added more compilers to continuous integration and increased ``FMT_PEDANTIC``
+  warning levels
+  (`#736 <https://github.com/fmtlib/fmt/pull/736>`_).
+  Thanks `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_.
+
+* Fixed compilation with MSVC 2013.
+
+* Fixed handling of user-defined types in ``format_to``
+  (`#793 <https://github.com/fmtlib/fmt/issues/793>`_).
+
+* Forced linking of inline ``vformat`` functions into the library
+  (`#795 <https://github.com/fmtlib/fmt/issues/795>`_).
+
+* Fixed incorrect call to on_align in ``'{:}='``
+  (`#750 <https://github.com/fmtlib/fmt/issues/750>`_).
+
+* Fixed floating-point formatting to a non-back_insert_iterator with sign &
+  numeric alignment specified
+  (`#756 <https://github.com/fmtlib/fmt/issues/756>`_).
+
+* Fixed formatting to an array with ``format_to_n``
+  (`#778 <https://github.com/fmtlib/fmt/issues/778>`_).
+
+* Fixed formatting of more than 15 named arguments
+  (`#754 <https://github.com/fmtlib/fmt/issues/754>`_).
+
+* Fixed handling of compile-time strings when including ``fmt/ostream.h``.
+  (`#768 <https://github.com/fmtlib/fmt/issues/768>`_).
+
+* Fixed various compiler warnings and errors
+  (`#742 <https://github.com/fmtlib/fmt/issues/742>`_,
+  `#748 <https://github.com/fmtlib/fmt/issues/748>`_,
+  `#752 <https://github.com/fmtlib/fmt/issues/752>`_,
+  `#770 <https://github.com/fmtlib/fmt/issues/770>`_,
+  `#775 <https://github.com/fmtlib/fmt/pull/775>`_,
+  `#779 <https://github.com/fmtlib/fmt/issues/779>`_,
+  `#780 <https://github.com/fmtlib/fmt/pull/780>`_,
+  `#790 <https://github.com/fmtlib/fmt/pull/790>`_,
+  `#792 <https://github.com/fmtlib/fmt/pull/792>`_,
+  `#800 <https://github.com/fmtlib/fmt/pull/800>`_).
+  Thanks `@Remotion (Remo) <https://github.com/Remotion>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_,
+  `@Dark-Passenger (Dhruv Paranjape) <https://github.com/Dark-Passenger>`_, and
+  `@0x8000-0000 (Sign Bit) <https://github.com/0x8000-0000>`_.
+
+5.0.0 - 2018-05-21
+------------------
+
+* Added a requirement for partial C++11 support, most importantly variadic
+  templates and type traits, and dropped ``FMT_VARIADIC_*`` emulation macros.
+  Variadic templates are available since GCC 4.4, Clang 2.9 and MSVC 18.0 (2013).
+  For older compilers use {fmt} `version 4.x
+  <https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
+  maintained and works with C++98 compilers.
+
+* Renamed symbols to follow standard C++ naming conventions and proposed a subset
+  of the library for standardization in `P0645R2 Text Formatting
+  <https://wg21.link/P0645>`_.
+
+* Implemented ``constexpr`` parsing of format strings and `compile-time format
+  string checks
+  <https://fmt.dev/latest/api.html#compile-time-format-string-checks>`_. For
+  example
+
+  .. code:: c++
+
+     #include <fmt/format.h>
+
+     std::string s = format(fmt("{:d}"), "foo");
+
+  gives a compile-time error because ``d`` is an invalid specifier for strings
+  (`godbolt <https://godbolt.org/g/rnCy9Q>`__)::
+
+     ...
+     <source>:4:19: note: in instantiation of function template specialization 'fmt::v5::format<S, char [4]>' requested here
+       std::string s = format(fmt("{:d}"), "foo");
+                       ^
+     format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression
+         handler.on_error("invalid type specifier");
+
+  Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If
+  the latter is not available, checks will be performed at runtime.
+
+* Separated format string parsing and formatting in the extension API to enable
+  compile-time format string processing. For example
+
+  .. code:: c++
+
+     struct Answer {};
+
+     namespace fmt {
+     template <>
+     struct formatter<Answer> {
+       constexpr auto parse(parse_context& ctx) {
+         auto it = ctx.begin();
+         spec = *it;
+         if (spec != 'd' && spec != 's')
+           throw format_error("invalid specifier");
+         return ++it;
+       }
+
+       template <typename FormatContext>
+       auto format(Answer, FormatContext& ctx) {
+         return spec == 's' ?
+           format_to(ctx.begin(), "{}", "fourty-two") :
+           format_to(ctx.begin(), "{}", 42);
+       }
+
+       char spec = 0;
+     };
+     }
+
+     std::string s = format(fmt("{:x}"), Answer());
+
+  gives a compile-time error due to invalid format specifier (`godbolt
+  <https://godbolt.org/g/2jQ1Dv>`__)::
+
+     ...
+     <source>:12:45: error: expression '<throw-expression>' is not a constant expression
+            throw format_error("invalid specifier");
+
+* Added `iterator support
+  <https://fmt.dev/latest/api.html#output-iterator-support>`_:
+
+  .. code:: c++
+
+     #include <vector>
+     #include <fmt/format.h>
+
+     std::vector<char> out;
+     fmt::format_to(std::back_inserter(out), "{}", 42);
+
+* Added the `format_to_n
+  <https://fmt.dev/latest/api.html#_CPPv2N3fmt11format_to_nE8OutputItNSt6size_tE11string_viewDpRK4Args>`_
+  function that restricts the output to the specified number of characters
+  (`#298 <https://github.com/fmtlib/fmt/issues/298>`_):
+
+  .. code:: c++
+
+     char out[4];
+     fmt::format_to_n(out, sizeof(out), "{}", 12345);
+     // out == "1234" (without terminating '\0')
+
+* Added the `formatted_size
+  <https://fmt.dev/latest/api.html#_CPPv2N3fmt14formatted_sizeE11string_viewDpRK4Args>`_
+  function for computing the output size:
+
+  .. code:: c++
+
+     #include <fmt/format.h>
+
+     auto size = fmt::formatted_size("{}", 12345); // size == 5
+
+* Improved compile times by reducing dependencies on standard headers and
+  providing a lightweight `core API <https://fmt.dev/latest/api.html#core-api>`_:
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+
+     fmt::print("The answer is {}.", 42);
+
+  See `Compile time and code bloat
+  <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_.
+
+* Added the `make_format_args
+  <https://fmt.dev/latest/api.html#_CPPv2N3fmt16make_format_argsEDpRK4Args>`_
+  function for capturing formatting arguments:
+
+  .. code:: c++
+  
+     // Prints formatted error message.
+     void vreport_error(const char *format, fmt::format_args args) {
+       fmt::print("Error: ");
+       fmt::vprint(format, args);
+     }
+     template <typename... Args>
+     void report_error(const char *format, const Args & ... args) {
+       vreport_error(format, fmt::make_format_args(args...));
+     }
+
+* Added the ``make_printf_args`` function for capturing ``printf`` arguments
+  (`#687 <https://github.com/fmtlib/fmt/issues/687>`_,
+  `#694 <https://github.com/fmtlib/fmt/pull/694>`_).
+  Thanks `@Kronuz (Germán Méndez Bravo) <https://github.com/Kronuz>`_.
+
+* Added prefix ``v`` to non-variadic functions taking ``format_args`` to
+  distinguish them from variadic ones:
+
+  .. code:: c++
+
+     std::string vformat(string_view format_str, format_args args);
+     
+     template <typename... Args>
+     std::string format(string_view format_str, const Args & ... args);
+
+* Added experimental support for formatting ranges, containers and tuple-like
+  types in ``fmt/ranges.h`` (`#735 <https://github.com/fmtlib/fmt/pull/735>`_):
+
+  .. code:: c++
+
+     #include <fmt/ranges.h>
+
+     std::vector<int> v = {1, 2, 3};
+     fmt::print("{}", v); // prints {1, 2, 3}
+
+  Thanks `@Remotion (Remo) <https://github.com/Remotion>`_.
+
+* Implemented ``wchar_t`` date and time formatting
+  (`#712 <https://github.com/fmtlib/fmt/pull/712>`_):
+
+  .. code:: c++
+
+     #include <fmt/time.h>
+
+     std::time_t t = std::time(nullptr);
+     auto s = fmt::format(L"The date is {:%Y-%m-%d}.", *std::localtime(&t));
+
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Provided more wide string overloads
+  (`#724 <https://github.com/fmtlib/fmt/pull/724>`_).
+  Thanks `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_.
+
+* Switched from a custom null-terminated string view class to ``string_view``
+  in the format API and provided ``fmt::string_view`` which implements a subset
+  of ``std::string_view`` API for pre-C++17 systems.
+
+* Added support for ``std::experimental::string_view``
+  (`#607 <https://github.com/fmtlib/fmt/pull/607>`_):
+
+  .. code:: c++
+
+     #include <fmt/core.h>
+     #include <experimental/string_view>
+
+     fmt::print("{}", std::experimental::string_view("foo"));
+
+  Thanks `@virgiliofornazin (Virgilio Alexandre Fornazin)
+  <https://github.com/virgiliofornazin>`__.
+
+* Allowed mixing named and automatic arguments:
+
+  .. code:: c++
+
+     fmt::format("{} {two}", 1, fmt::arg("two", 2));
+
+* Removed the write API in favor of the `format API
+  <https://fmt.dev/latest/api.html#format-api>`_ with compile-time handling of
+  format strings.
+
+* Disallowed formatting of multibyte strings into a wide character target
+  (`#606 <https://github.com/fmtlib/fmt/pull/606>`_).
+
+* Improved documentation
+  (`#515 <https://github.com/fmtlib/fmt/pull/515>`_,
+  `#614 <https://github.com/fmtlib/fmt/issues/614>`_,
+  `#617 <https://github.com/fmtlib/fmt/pull/617>`_,
+  `#661 <https://github.com/fmtlib/fmt/pull/661>`_,
+  `#680 <https://github.com/fmtlib/fmt/pull/680>`_).
+  Thanks `@ibell (Ian Bell) <https://github.com/ibell>`_,
+  `@mihaitodor (Mihai Todor) <https://github.com/mihaitodor>`_, and
+  `@johnthagen <https://github.com/johnthagen>`_.
+
+* Implemented more efficient handling of large number of format arguments.
+
+* Introduced an inline namespace for symbol versioning.
+
+* Added debug postfix ``d`` to the ``fmt`` library name
+  (`#636 <https://github.com/fmtlib/fmt/issues/636>`_).
+
+* Removed unnecessary ``fmt/`` prefix in includes
+  (`#397 <https://github.com/fmtlib/fmt/pull/397>`_).
+  Thanks `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_.
+
+* Moved ``fmt/*.h`` to ``include/fmt/*.h`` to prevent irrelevant files and
+  directories appearing on the include search paths when fmt is used as a
+  subproject and moved source files to the ``src`` directory.
+
+* Added qmake project file ``support/fmt.pro``
+  (`#641 <https://github.com/fmtlib/fmt/pull/641>`_).
+  Thanks `@cowo78 (Giuseppe Corbelli) <https://github.com/cowo78>`_.
+
+* Added Gradle build file ``support/build.gradle``
+  (`#649 <https://github.com/fmtlib/fmt/pull/649>`_).
+  Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_.
+
+* Removed ``FMT_CPPFORMAT`` CMake option.
+
+* Fixed a name conflict with the macro ``CHAR_WIDTH`` in glibc
+  (`#616 <https://github.com/fmtlib/fmt/pull/616>`_).
+  Thanks `@aroig (Abdó Roig-Maranges) <https://github.com/aroig>`_.
+
+* Fixed handling of nested braces in ``fmt::join``
+  (`#638 <https://github.com/fmtlib/fmt/issues/638>`_).
+
+* Added ``SOURCELINK_SUFFIX`` for compatibility with Sphinx 1.5
+  (`#497 <https://github.com/fmtlib/fmt/pull/497>`_).
+  Thanks `@ginggs (Graham Inggs) <https://github.com/ginggs>`_.
+
+* Added a missing ``inline`` in the header-only mode
+  (`#626 <https://github.com/fmtlib/fmt/pull/626>`_).
+  Thanks `@aroig (Abdó Roig-Maranges) <https://github.com/aroig>`_.
+
+* Fixed various compiler warnings
+  (`#640 <https://github.com/fmtlib/fmt/pull/640>`_,
+  `#656 <https://github.com/fmtlib/fmt/pull/656>`_,
+  `#679 <https://github.com/fmtlib/fmt/pull/679>`_,
+  `#681 <https://github.com/fmtlib/fmt/pull/681>`_,
+  `#705 <https://github.com/fmtlib/fmt/pull/705>`__,
+  `#715 <https://github.com/fmtlib/fmt/issues/715>`_,
+  `#717 <https://github.com/fmtlib/fmt/pull/717>`_,
+  `#720 <https://github.com/fmtlib/fmt/pull/720>`_,
+  `#723 <https://github.com/fmtlib/fmt/pull/723>`_,
+  `#726 <https://github.com/fmtlib/fmt/pull/726>`_,
+  `#730 <https://github.com/fmtlib/fmt/pull/730>`_,
+  `#739 <https://github.com/fmtlib/fmt/pull/739>`_).
+  Thanks `@peterbell10 <https://github.com/peterbell10>`_,
+  `@LarsGullik <https://github.com/LarsGullik>`_,
+  `@foonathan (Jonathan Müller) <https://github.com/foonathan>`_,
+  `@eliaskosunen (Elias Kosunen) <https://github.com/eliaskosunen>`_,
+  `@christianparpart (Christian Parpart) <https://github.com/christianparpart>`_,
+  `@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
+  and `@mwinterb <https://github.com/mwinterb>`_.
+
+* Worked around an MSVC bug and fixed several warnings
+  (`#653 <https://github.com/fmtlib/fmt/pull/653>`_).
+  Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
+
+* Worked around GCC bug 67371
+  (`#682 <https://github.com/fmtlib/fmt/issues/682>`_).
+
+* Fixed compilation with ``-fno-exceptions``
+  (`#655 <https://github.com/fmtlib/fmt/pull/655>`_).
+  Thanks `@chenxiaolong (Andrew Gunnerson) <https://github.com/chenxiaolong>`_.
+
+* Made ``constexpr remove_prefix`` gcc version check tighter
+  (`#648 <https://github.com/fmtlib/fmt/issues/648>`_).
+
+* Renamed internal type enum constants to prevent collision with poorly written
+  C libraries (`#644 <https://github.com/fmtlib/fmt/issues/644>`_).
+
+* Added detection of ``wostream operator<<``
+  (`#650 <https://github.com/fmtlib/fmt/issues/650>`_).
+
+* Fixed compilation on OpenBSD
+  (`#660 <https://github.com/fmtlib/fmt/pull/660>`_).
+  Thanks `@hubslave <https://github.com/hubslave>`_.
+
+* Fixed compilation on FreeBSD 12
+  (`#732 <https://github.com/fmtlib/fmt/pull/732>`_).
+  Thanks `@dankm <https://github.com/dankm>`_.
+
+* Fixed compilation when there is a mismatch between ``-std`` options between
+  the library and user code
+  (`#664 <https://github.com/fmtlib/fmt/issues/664>`_).
+
+* Fixed compilation with GCC 7 and ``-std=c++11``
+  (`#734 <https://github.com/fmtlib/fmt/issues/734>`_).
+
+* Improved generated binary code on GCC 7 and older
+  (`#668 <https://github.com/fmtlib/fmt/issues/668>`_).
+
+* Fixed handling of numeric alignment with no width 
+  (`#675 <https://github.com/fmtlib/fmt/issues/675>`_).
+
+* Fixed handling of empty strings in UTF8/16 converters
+  (`#676 <https://github.com/fmtlib/fmt/pull/676>`_).
+  Thanks `@vgalka-sl (Vasili Galka) <https://github.com/vgalka-sl>`_.
+
+* Fixed formatting of an empty ``string_view``
+  (`#689 <https://github.com/fmtlib/fmt/issues/689>`_).
+
+* Fixed detection of ``string_view`` on libc++ 
+  (`#686 <https://github.com/fmtlib/fmt/issues/686>`_).
+
+* Fixed DLL issues (`#696 <https://github.com/fmtlib/fmt/pull/696>`_).
+  Thanks `@sebkoenig <https://github.com/sebkoenig>`_.
+
+* Fixed compile checks for mixing narrow and wide strings
+  (`#690 <https://github.com/fmtlib/fmt/issues/690>`_).
+
+* Disabled unsafe implicit conversion to ``std::string``
+  (`#729 <https://github.com/fmtlib/fmt/issues/729>`_).
+
+* Fixed handling of reused format specs (as in ``fmt::join``) for pointers
+  (`#725 <https://github.com/fmtlib/fmt/pull/725>`_).
+  Thanks `@mwinterb <https://github.com/mwinterb>`_.
+
+* Fixed installation of ``fmt/ranges.h``
+  (`#738 <https://github.com/fmtlib/fmt/pull/738>`_).
+  Thanks `@sv1990 <https://github.com/sv1990>`_.
+
+4.1.0 - 2017-12-20
+------------------
+
+* Added ``fmt::to_wstring()`` in addition to ``fmt::to_string()``
+  (`#559 <https://github.com/fmtlib/fmt/pull/559>`_).
+  Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
+
+* Added support for C++17 ``std::string_view``
+  (`#571 <https://github.com/fmtlib/fmt/pull/571>`_ and
+  `#578 <https://github.com/fmtlib/fmt/pull/578>`_).
+  Thanks `@thelostt (Mário Feroldi) <https://github.com/thelostt>`_ and
+  `@mwinterb <https://github.com/mwinterb>`_.
+
+* Enabled stream exceptions to catch errors
+  (`#581 <https://github.com/fmtlib/fmt/issues/581>`_).
+  Thanks `@crusader-mike <https://github.com/crusader-mike>`_.
+
+* Allowed formatting of class hierarchies with ``fmt::format_arg()``
+  (`#547 <https://github.com/fmtlib/fmt/pull/547>`_).
+  Thanks `@rollbear (Björn Fahller) <https://github.com/rollbear>`_.
+
+* Removed limitations on character types
+  (`#563 <https://github.com/fmtlib/fmt/pull/563>`_).
+  Thanks `@Yelnats321 (Elnar Dakeshov) <https://github.com/Yelnats321>`_.
+
+* Conditionally enabled use of ``std::allocator_traits``
+  (`#583 <https://github.com/fmtlib/fmt/pull/583>`_).
+  Thanks `@mwinterb <https://github.com/mwinterb>`_.
+
+* Added support for ``const`` variadic member function emulation with
+  ``FMT_VARIADIC_CONST`` (`#591 <https://github.com/fmtlib/fmt/pull/591>`_).
+  Thanks `@ludekvodicka (Ludek Vodicka) <https://github.com/ludekvodicka>`_.
+
+* Various bugfixes: bad overflow check, unsupported implicit type conversion
+  when determining formatting function, test segfaults
+  (`#551 <https://github.com/fmtlib/fmt/issues/551>`_), ill-formed macros
+  (`#542 <https://github.com/fmtlib/fmt/pull/542>`_) and ambiguous overloads
+  (`#580 <https://github.com/fmtlib/fmt/issues/580>`_).
+  Thanks `@xylosper (Byoung-young Lee) <https://github.com/xylosper>`_.
+
+* Prevented warnings on MSVC (`#605 <https://github.com/fmtlib/fmt/pull/605>`_,
+  `#602 <https://github.com/fmtlib/fmt/pull/602>`_, and
+  `#545 <https://github.com/fmtlib/fmt/pull/545>`_),
+  clang (`#582 <https://github.com/fmtlib/fmt/pull/582>`_),
+  GCC (`#573 <https://github.com/fmtlib/fmt/issues/573>`_),
+  various conversion warnings (`#609 <https://github.com/fmtlib/fmt/pull/609>`_,
+  `#567 <https://github.com/fmtlib/fmt/pull/567>`_,
+  `#553 <https://github.com/fmtlib/fmt/pull/553>`_ and
+  `#553 <https://github.com/fmtlib/fmt/pull/553>`_), and added ``override`` and
+  ``[[noreturn]]`` (`#549 <https://github.com/fmtlib/fmt/pull/549>`_ and
+  `#555 <https://github.com/fmtlib/fmt/issues/555>`_).
+  Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_,
+  `@virgiliofornazin (Virgilio Alexandre Fornazin)
+  <https://gihtub.com/virgiliofornazin>`_,
+  `@alexanderbock (Alexander Bock) <https://github.com/alexanderbock>`_,
+  `@yumetodo <https://github.com/yumetodo>`_,
+  `@VaderY (Császár Mátyás) <https://github.com/VaderY>`_,
+  `@jpcima (JP Cimalando) <https://github.com/jpcima>`_,
+  `@thelostt (Mário Feroldi) <https://github.com/thelostt>`_, and
+  `@Manu343726 (Manu Sánchez) <https://github.com/Manu343726>`_.
+
+* Improved CMake: Used ``GNUInstallDirs`` to set installation location
+  (`#610 <https://github.com/fmtlib/fmt/pull/610>`_) and fixed warnings
+  (`#536 <https://github.com/fmtlib/fmt/pull/536>`_ and
+  `#556 <https://github.com/fmtlib/fmt/pull/556>`_).
+  Thanks `@mikecrowe (Mike Crowe) <https://github.com/mikecrowe>`_,
+  `@evgen231 <https://github.com/evgen231>`_ and
+  `@henryiii (Henry Schreiner) <https://github.com/henryiii>`_.
+
+4.0.0 - 2017-06-27
+------------------
+
+* Removed old compatibility headers ``cppformat/*.h`` and CMake options
+  (`#527 <https://github.com/fmtlib/fmt/pull/527>`_).
+  Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_.
+
+* Added ``string.h`` containing ``fmt::to_string()`` as alternative to
+  ``std::to_string()`` as well as other string writer functionality
+  (`#326 <https://github.com/fmtlib/fmt/issues/326>`_ and
+  `#441 <https://github.com/fmtlib/fmt/pull/441>`_):
+
+  .. code:: c++
+
+    #include "fmt/string.h"
+  
+    std::string answer = fmt::to_string(42);
+
+  Thanks to `@glebov-andrey (Andrey Glebov)
+  <https://github.com/glebov-andrey>`_.
+
+* Moved ``fmt::printf()`` to new ``printf.h`` header and allowed ``%s`` as
+  generic specifier (`#453 <https://github.com/fmtlib/fmt/pull/453>`_),
+  made ``%.f`` more conformant to regular ``printf()``
+  (`#490 <https://github.com/fmtlib/fmt/pull/490>`_), added custom writer
+  support (`#476 <https://github.com/fmtlib/fmt/issues/476>`_) and implemented
+  missing custom argument formatting
+  (`#339 <https://github.com/fmtlib/fmt/pull/339>`_ and
+  `#340 <https://github.com/fmtlib/fmt/pull/340>`_):
+
+  .. code:: c++
+
+    #include "fmt/printf.h"
+ 
+    // %s format specifier can be used with any argument type.
+    fmt::printf("%s", 42);
+
+  Thanks `@mojoBrendan <https://github.com/mojoBrendan>`_,
+  `@manylegged (Arthur Danskin) <https://github.com/manylegged>`_ and
+  `@spacemoose (Glen Stark) <https://github.com/spacemoose>`_.
+  See also `#360 <https://github.com/fmtlib/fmt/issues/360>`_,
+  `#335 <https://github.com/fmtlib/fmt/issues/335>`_ and
+  `#331 <https://github.com/fmtlib/fmt/issues/331>`_.
+
+* Added ``container.h`` containing a ``BasicContainerWriter``
+  to write to containers like ``std::vector``
+  (`#450 <https://github.com/fmtlib/fmt/pull/450>`_).
+  Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_.
+
+* Added ``fmt::join()`` function that takes a range and formats
+  its elements separated by a given string
+  (`#466 <https://github.com/fmtlib/fmt/pull/466>`_):
+
+  .. code:: c++
+
+    #include "fmt/format.h"
+ 
+    std::vector<double> v = {1.2, 3.4, 5.6};
+    // Prints "(+01.20, +03.40, +05.60)".
+    fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", "));
+
+  Thanks `@olivier80 <https://github.com/olivier80>`_.
+
+* Added support for custom formatting specifications to simplify customization
+  of built-in formatting (`#444 <https://github.com/fmtlib/fmt/pull/444>`_).
+  Thanks `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_.
+  See also `#439 <https://github.com/fmtlib/fmt/issues/439>`_.
+
+* Added ``fmt::format_system_error()`` for error code formatting
+  (`#323 <https://github.com/fmtlib/fmt/issues/323>`_ and
+  `#526 <https://github.com/fmtlib/fmt/pull/526>`_).
+  Thanks `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_.
+
+* Added thread-safe ``fmt::localtime()`` and ``fmt::gmtime()``
+  as replacement   for the standard version to ``time.h``
+  (`#396 <https://github.com/fmtlib/fmt/pull/396>`_).
+  Thanks `@codicodi <https://github.com/codicodi>`_.
+
+* Internal improvements to ``NamedArg`` and ``ArgLists``
+  (`#389 <https://github.com/fmtlib/fmt/pull/389>`_ and
+  `#390 <https://github.com/fmtlib/fmt/pull/390>`_).
+  Thanks `@chronoxor <https://github.com/chronoxor>`_.
+
+* Fixed crash due to bug in ``FormatBuf``
+  (`#493 <https://github.com/fmtlib/fmt/pull/493>`_).
+  Thanks `@effzeh <https://github.com/effzeh>`_. See also
+  `#480 <https://github.com/fmtlib/fmt/issues/480>`_ and
+  `#491 <https://github.com/fmtlib/fmt/issues/491>`_.
+
+* Fixed handling of wide strings in ``fmt::StringWriter``.
+
+* Improved compiler error messages
+  (`#357 <https://github.com/fmtlib/fmt/issues/357>`_).
+
+* Fixed various warnings and issues with various compilers
+  (`#494 <https://github.com/fmtlib/fmt/pull/494>`_,
+  `#499 <https://github.com/fmtlib/fmt/pull/499>`_,
+  `#483 <https://github.com/fmtlib/fmt/pull/483>`_,
+  `#485 <https://github.com/fmtlib/fmt/pull/485>`_,
+  `#482 <https://github.com/fmtlib/fmt/pull/482>`_,
+  `#475 <https://github.com/fmtlib/fmt/pull/475>`_,
+  `#473 <https://github.com/fmtlib/fmt/pull/473>`_ and
+  `#414 <https://github.com/fmtlib/fmt/pull/414>`_).
+  Thanks `@chronoxor <https://github.com/chronoxor>`_,
+  `@zhaohuaxishi <https://github.com/zhaohuaxishi>`_,
+  `@pkestene (Pierre Kestener) <https://github.com/pkestene>`_,
+  `@dschmidt (Dominik Schmidt) <https://github.com/dschmidt>`_ and
+  `@0x414c (Alexey Gorishny) <https://github.com/0x414c>`_ .
+
+* Improved CMake: targets are now namespaced
+  (`#511 <https://github.com/fmtlib/fmt/pull/511>`_ and
+  `#513 <https://github.com/fmtlib/fmt/pull/513>`_), supported header-only
+  ``printf.h`` (`#354 <https://github.com/fmtlib/fmt/pull/354>`_), fixed issue
+  with minimal supported library subset
+  (`#418 <https://github.com/fmtlib/fmt/issues/418>`_,
+  `#419 <https://github.com/fmtlib/fmt/pull/419>`_ and
+  `#420 <https://github.com/fmtlib/fmt/pull/420>`_).
+  Thanks `@bjoernthiel (Bjoern Thiel) <https://github.com/bjoernthiel>`_,
+  `@niosHD (Mario Werner) <https://github.com/niosHD>`_,
+  `@LogicalKnight (Sean LK) <https://github.com/LogicalKnight>`_ and
+  `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
+
+* Improved documentation. Thanks to
+  `@pwm1234 (Phil) <https://github.com/pwm1234>`_ for
+  `#393 <https://github.com/fmtlib/fmt/pull/393>`_.
+
+3.0.2 - 2017-06-14
+------------------
+
+* Added ``FMT_VERSION`` macro
+  (`#411 <https://github.com/fmtlib/fmt/issues/411>`_).
+
+* Used ``FMT_NULL`` instead of literal ``0``
+  (`#409 <https://github.com/fmtlib/fmt/pull/409>`_).
+  Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
+
+* Added extern templates for ``format_float``
+  (`#413 <https://github.com/fmtlib/fmt/issues/413>`_).
+
+* Fixed implicit conversion issue
+  (`#507 <https://github.com/fmtlib/fmt/issues/507>`_).
+
+* Fixed signbit detection (`#423 <https://github.com/fmtlib/fmt/issues/423>`_).
+
+* Fixed naming collision (`#425 <https://github.com/fmtlib/fmt/issues/425>`_).
+
+* Fixed missing intrinsic for C++/CLI
+  (`#457 <https://github.com/fmtlib/fmt/pull/457>`_).
+  Thanks `@calumr (Calum Robinson) <https://github.com/calumr>`_
+
+* Fixed Android detection (`#458 <https://github.com/fmtlib/fmt/pull/458>`_).
+  Thanks `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_.
+
+* Use lean ``windows.h`` if not in header-only mode
+  (`#503 <https://github.com/fmtlib/fmt/pull/503>`_).
+  Thanks `@Quentin01 (Quentin Buathier) <https://github.com/Quentin01>`_.
+
+* Fixed issue with CMake exporting C++11 flag
+  (`#445 <https://github.com/fmtlib/fmt/pull/455>`_).
+  Thanks `@EricWF (Eric) <https://github.com/EricWF>`_.
+
+* Fixed issue with nvcc and MSVC compiler bug and MinGW
+  (`#505 <https://github.com/fmtlib/fmt/issues/505>`_).
+
+* Fixed DLL issues (`#469 <https://github.com/fmtlib/fmt/pull/469>`_ and
+  `#502 <https://github.com/fmtlib/fmt/pull/502>`_).
+  Thanks `@richardeakin (Richard Eakin) <https://github.com/richardeakin>`_ and
+  `@AndreasSchoenle (Andreas Schönle) <https://github.com/AndreasSchoenle>`_.
+
+* Fixed test compilation under FreeBSD
+  (`#433 <https://github.com/fmtlib/fmt/issues/433>`_).
+
+* Fixed various warnings (`#403 <https://github.com/fmtlib/fmt/pull/403>`_,
+  `#410 <https://github.com/fmtlib/fmt/pull/410>`_ and
+  `#510 <https://github.com/fmtlib/fmt/pull/510>`_).
+  Thanks `@Lecetem <https://github.com/Lectem>`_,
+  `@chenhayat (Chen Hayat) <https://github.com/chenhayat>`_ and
+  `@trozen <https://github.com/trozen>`_.
+
+* Worked around a broken ``__builtin_clz`` in clang with MS codegen
+  (`#519 <https://github.com/fmtlib/fmt/issues/519>`_).
+
+* Removed redundant include
+  (`#479 <https://github.com/fmtlib/fmt/issues/479>`_).
+
+* Fixed documentation issues.
+
+3.0.1 - 2016-11-01
+------------------
+* Fixed handling of thousands separator
+  (`#353 <https://github.com/fmtlib/fmt/issues/353>`_).
+
+* Fixed handling of ``unsigned char`` strings
+  (`#373 <https://github.com/fmtlib/fmt/issues/373>`_).
+
+* Corrected buffer growth when formatting time
+  (`#367 <https://github.com/fmtlib/fmt/issues/367>`_).
+
+* Removed warnings under MSVC and clang
+  (`#318 <https://github.com/fmtlib/fmt/issues/318>`_,
+  `#250 <https://github.com/fmtlib/fmt/issues/250>`_, also merged
+  `#385 <https://github.com/fmtlib/fmt/pull/385>`_ and
+  `#361 <https://github.com/fmtlib/fmt/pull/361>`_).
+  Thanks `@jcelerier (Jean-Michaël Celerier) <https://github.com/jcelerier>`_
+  and `@nmoehrle (Nils Moehrle) <https://github.com/nmoehrle>`_.
+
+* Fixed compilation issues under Android
+  (`#327 <https://github.com/fmtlib/fmt/pull/327>`_,
+  `#345 <https://github.com/fmtlib/fmt/issues/345>`_ and
+  `#381 <https://github.com/fmtlib/fmt/pull/381>`_),
+  FreeBSD (`#358 <https://github.com/fmtlib/fmt/pull/358>`_),
+  Cygwin (`#388 <https://github.com/fmtlib/fmt/issues/388>`_),
+  MinGW (`#355 <https://github.com/fmtlib/fmt/issues/355>`_) as well as other
+  issues (`#350 <https://github.com/fmtlib/fmt/issues/350>`_,
+  `#366 <https://github.com/fmtlib/fmt/issues/355>`_,
+  `#348 <https://github.com/fmtlib/fmt/pull/348>`_,
+  `#402 <https://github.com/fmtlib/fmt/pull/402>`_,
+  `#405 <https://github.com/fmtlib/fmt/pull/405>`_).
+  Thanks to `@dpantele (Dmitry) <https://github.com/dpantele>`_,
+  `@hghwng (Hugh Wang) <https://github.com/hghwng>`_,
+  `@arvedarved (Tilman Keskinöz) <https://github.com/arvedarved>`_,
+  `@LogicalKnight (Sean) <https://github.com/LogicalKnight>`_ and
+  `@JanHellwig (Jan Hellwig) <https://github.com/janhellwig>`_.
+
+* Fixed some documentation issues and extended specification
+  (`#320 <https://github.com/fmtlib/fmt/issues/320>`_,
+  `#333 <https://github.com/fmtlib/fmt/pull/333>`_,
+  `#347 <https://github.com/fmtlib/fmt/issues/347>`_,
+  `#362 <https://github.com/fmtlib/fmt/pull/362>`_).
+  Thanks to `@smellman (Taro Matsuzawa aka. btm)
+  <https://github.com/smellman>`_.
+
+3.0.0 - 2016-05-07
+------------------
+
+* The project has been renamed from C++ Format (cppformat) to fmt for
+  consistency with the used namespace and macro prefix
+  (`#307 <https://github.com/fmtlib/fmt/issues/307>`_).
+  Library headers are now located in the ``fmt`` directory:
+
+  .. code:: c++
+
+    #include "fmt/format.h"
+
+  Including ``format.h`` from the ``cppformat`` directory is deprecated
+  but works via a proxy header which will be removed in the next major version.
+  
+  The documentation is now available at https://fmt.dev.
+
+* Added support for `strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like
+  `date and time formatting <https://fmt.dev/3.0.0/api.html#date-and-time-formatting>`_
+  (`#283 <https://github.com/fmtlib/fmt/issues/283>`_):
+
+  .. code:: c++
+
+    #include "fmt/time.h"
+
+    std::time_t t = std::time(nullptr);
+    // Prints "The date is 2016-04-29." (with the current date)
+    fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t));
+
+* ``std::ostream`` support including formatting of user-defined types that provide
+  overloaded ``operator<<`` has been moved to ``fmt/ostream.h``:
+
+  .. code:: c++
+
+    #include "fmt/ostream.h"
+
+    class Date {
+      int year_, month_, day_;
+    public:
+      Date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
+
+      friend std::ostream &operator<<(std::ostream &os, const Date &d) {
+        return os << d.year_ << '-' << d.month_ << '-' << d.day_;
+      }
+    };
+
+    std::string s = fmt::format("The date is {}", Date(2012, 12, 9));
+    // s == "The date is 2012-12-9"
+
+* Added support for `custom argument formatters
+  <https://fmt.dev/3.0.0/api.html#argument-formatters>`_
+  (`#235 <https://github.com/fmtlib/fmt/issues/235>`_).
+
+* Added support for locale-specific integer formatting with the ``n`` specifier
+  (`#305 <https://github.com/fmtlib/fmt/issues/305>`_):
+
+  .. code:: c++
+
+    std::setlocale(LC_ALL, "en_US.utf8");
+    fmt::print("cppformat: {:n}\n", 1234567); // prints 1,234,567
+
+* Sign is now preserved when formatting an integer with an incorrect ``printf``
+  format specifier (`#265 <https://github.com/fmtlib/fmt/issues/265>`_):
+
+  .. code:: c++
+
+    fmt::printf("%lld", -42); // prints -42
+
+  Note that it would be an undefined behavior in ``std::printf``.
+
+* Length modifiers such as ``ll`` are now optional in printf formatting
+  functions and the correct type is determined automatically
+  (`#255 <https://github.com/fmtlib/fmt/issues/255>`_):
+
+  .. code:: c++
+
+    fmt::printf("%d", std::numeric_limits<long long>::max());
+
+  Note that it would be an undefined behavior in ``std::printf``.
+
+* Added initial support for custom formatters
+  (`#231 <https://github.com/fmtlib/fmt/issues/231>`_).
+
+* Fixed detection of user-defined literal support on Intel C++ compiler
+  (`#311 <https://github.com/fmtlib/fmt/issues/311>`_,
+  `#312 <https://github.com/fmtlib/fmt/pull/312>`_).
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_ and
+  `@speth (Ray Speth) <https://github.com/speth>`_.
+
+* Reduced compile time
+  (`#243 <https://github.com/fmtlib/fmt/pull/243>`_,
+  `#249 <https://github.com/fmtlib/fmt/pull/249>`_,
+  `#317 <https://github.com/fmtlib/fmt/issues/317>`_):
+
+  .. image:: https://cloud.githubusercontent.com/assets/4831417/11614060/
+             b9e826d2-9c36-11e5-8666-d4131bf503ef.png
+
+  .. image:: https://cloud.githubusercontent.com/assets/4831417/11614080/
+             6ac903cc-9c37-11e5-8165-26df6efae364.png
+
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_.
+
+* Compile test fixes (`#313 <https://github.com/fmtlib/fmt/pull/313>`_).
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_.
+
+* Documentation fixes (`#239 <https://github.com/fmtlib/fmt/pull/239>`_,
+  `#248 <https://github.com/fmtlib/fmt/issues/248>`_,
+  `#252 <https://github.com/fmtlib/fmt/issues/252>`_,
+  `#258 <https://github.com/fmtlib/fmt/pull/258>`_,
+  `#260 <https://github.com/fmtlib/fmt/issues/260>`_,
+  `#301 <https://github.com/fmtlib/fmt/issues/301>`_,
+  `#309 <https://github.com/fmtlib/fmt/pull/309>`_).
+  Thanks to `@ReadmeCritic <https://github.com/ReadmeCritic>`_
+  `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_ and
+  `@jwilk (Jakub Wilk) <https://github.com/jwilk>`_.
+
+* Fixed compiler and sanitizer warnings
+  (`#244 <https://github.com/fmtlib/fmt/issues/244>`_,
+  `#256 <https://github.com/fmtlib/fmt/pull/256>`_,
+  `#259 <https://github.com/fmtlib/fmt/pull/259>`_,
+  `#263 <https://github.com/fmtlib/fmt/issues/263>`_,
+  `#274 <https://github.com/fmtlib/fmt/issues/274>`_,
+  `#277 <https://github.com/fmtlib/fmt/pull/277>`_,
+  `#286 <https://github.com/fmtlib/fmt/pull/286>`_,
+  `#291 <https://github.com/fmtlib/fmt/issues/291>`_,
+  `#296 <https://github.com/fmtlib/fmt/issues/296>`_,
+  `#308 <https://github.com/fmtlib/fmt/issues/308>`_)
+  Thanks to `@mwinterb <https://github.com/mwinterb>`_,
+  `@pweiskircher (Patrik Weiskircher) <https://github.com/pweiskircher>`_,
+  `@Naios <https://github.com/Naios>`_.
+
+* Improved compatibility with Windows Store apps
+  (`#280 <https://github.com/fmtlib/fmt/issues/280>`_,
+  `#285 <https://github.com/fmtlib/fmt/pull/285>`_)
+  Thanks to `@mwinterb <https://github.com/mwinterb>`_.
+
+* Added tests of compatibility with older C++ standards
+  (`#273 <https://github.com/fmtlib/fmt/pull/273>`_).
+  Thanks to `@niosHD <https://github.com/niosHD>`_.
+
+* Fixed Android build (`#271 <https://github.com/fmtlib/fmt/pull/271>`_).
+  Thanks to `@newnon <https://github.com/newnon>`_.
+
+* Changed ``ArgMap`` to be backed by a vector instead of a map.
+  (`#261 <https://github.com/fmtlib/fmt/issues/261>`_,
+  `#262 <https://github.com/fmtlib/fmt/pull/262>`_).
+  Thanks to `@mwinterb <https://github.com/mwinterb>`_.
+
+* Added ``fprintf`` overload that writes to a ``std::ostream``
+  (`#251 <https://github.com/fmtlib/fmt/pull/251>`_).
+  Thanks to `nickhutchinson (Nicholas Hutchinson) <https://github.com/nickhutchinson>`_.
+
+* Export symbols when building a Windows DLL
+  (`#245 <https://github.com/fmtlib/fmt/pull/245>`_).
+  Thanks to `macdems (Maciek Dems) <https://github.com/macdems>`_.
+
+* Fixed compilation on Cygwin (`#304 <https://github.com/fmtlib/fmt/issues/304>`_).
+
+* Implemented a workaround for a bug in Apple LLVM version 4.2 of clang
+  (`#276 <https://github.com/fmtlib/fmt/issues/276>`_).
+
+* Implemented a workaround for Google Test bug
+  `#705 <https://github.com/google/googletest/issues/705>`_ on gcc 6
+  (`#268 <https://github.com/fmtlib/fmt/issues/268>`_).
+  Thanks to `octoploid <https://github.com/octoploid>`_.
+
+* Removed Biicode support because the latter has been discontinued.
+
+2.1.1 - 2016-04-11
+------------------
+
+* The install location for generated CMake files is now configurable via
+  the ``FMT_CMAKE_DIR`` CMake variable
+  (`#299 <https://github.com/fmtlib/fmt/pull/299>`_).
+  Thanks to `@niosHD <https://github.com/niosHD>`_.
+
+* Documentation fixes (`#252 <https://github.com/fmtlib/fmt/issues/252>`_).
+
+2.1.0 - 2016-03-21
+------------------
+
+* Project layout and build system improvements
+  (`#267 <https://github.com/fmtlib/fmt/pull/267>`_):
+
+  * The code have been moved to the ``cppformat`` directory.
+    Including ``format.h`` from the top-level directory is deprecated
+    but works via a proxy header which will be removed in the next
+    major version.
+
+  * C++ Format CMake targets now have proper interface definitions.
+
+  * Installed version of the library now supports the header-only
+    configuration.
+
+  * Targets ``doc``, ``install``, and ``test`` are now disabled if C++ Format
+    is included as a CMake subproject. They can be enabled by setting
+    ``FMT_DOC``, ``FMT_INSTALL``, and ``FMT_TEST`` in the parent project.
+
+  Thanks to `@niosHD <https://github.com/niosHD>`_.
+
+2.0.1 - 2016-03-13
+------------------
+
+* Improved CMake find and package support
+  (`#264 <https://github.com/fmtlib/fmt/issues/264>`_).
+  Thanks to `@niosHD <https://github.com/niosHD>`_.
+
+* Fix compile error with Android NDK and mingw32
+  (`#241 <https://github.com/fmtlib/fmt/issues/241>`_).
+  Thanks to `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_.
+
+* Documentation fixes
+  (`#248 <https://github.com/fmtlib/fmt/issues/248>`_,
+  `#260 <https://github.com/fmtlib/fmt/issues/260>`_).
+
+2.0.0 - 2015-12-01
+------------------
+
+General
+~~~~~~~
+
+* [Breaking] Named arguments
+  (`#169 <https://github.com/fmtlib/fmt/pull/169>`_,
+  `#173 <https://github.com/fmtlib/fmt/pull/173>`_,
+  `#174 <https://github.com/fmtlib/fmt/pull/174>`_):
+
+  .. code:: c++
+
+    fmt::print("The answer is {answer}.", fmt::arg("answer", 42));
+
+  Thanks to `@jamboree <https://github.com/jamboree>`_.
+
+* [Experimental] User-defined literals for format and named arguments
+  (`#204 <https://github.com/fmtlib/fmt/pull/204>`_,
+  `#206 <https://github.com/fmtlib/fmt/pull/206>`_,
+  `#207 <https://github.com/fmtlib/fmt/pull/207>`_):
+
+  .. code:: c++
+
+    using namespace fmt::literals;
+    fmt::print("The answer is {answer}.", "answer"_a=42);
+
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_.
+
+* [Breaking] Formatting of more than 16 arguments is now supported when using
+  variadic templates
+  (`#141 <https://github.com/fmtlib/fmt/issues/141>`_).
+  Thanks to `@Shauren <https://github.com/Shauren>`_.
+
+* Runtime width specification
+  (`#168 <https://github.com/fmtlib/fmt/pull/168>`_):
+
+  .. code:: c++
+
+    fmt::format("{0:{1}}", 42, 5); // gives "   42"
+
+  Thanks to `@jamboree <https://github.com/jamboree>`_.
+
+* [Breaking] Enums are now formatted with an overloaded ``std::ostream`` insertion
+  operator (``operator<<``) if available
+  (`#232 <https://github.com/fmtlib/fmt/issues/232>`_).
+
+* [Breaking] Changed default ``bool`` format to textual, "true" or "false"
+  (`#170 <https://github.com/fmtlib/fmt/issues/170>`_):
+
+  .. code:: c++
+  
+    fmt::print("{}", true); // prints "true"
+
+  To print ``bool`` as a number use numeric format specifier such as ``d``:
+
+  .. code:: c++
+
+    fmt::print("{:d}", true); // prints "1"
+
+* ``fmt::printf`` and ``fmt::sprintf`` now support formatting of ``bool`` with the
+  ``%s`` specifier giving textual output, "true" or "false"
+  (`#223 <https://github.com/fmtlib/fmt/pull/223>`_):
+
+  .. code:: c++
+
+    fmt::printf("%s", true); // prints "true"
+
+  Thanks to `@LarsGullik <https://github.com/LarsGullik>`_.
+
+* [Breaking] ``signed char`` and ``unsigned char`` are now formatted as integers by default
+  (`#217 <https://github.com/fmtlib/fmt/pull/217>`_).
+
+* [Breaking] Pointers to C strings can now be formatted with the ``p`` specifier
+  (`#223 <https://github.com/fmtlib/fmt/pull/223>`_):
+
+  .. code:: c++
+
+    fmt::print("{:p}", "test"); // prints pointer value
+
+  Thanks to `@LarsGullik <https://github.com/LarsGullik>`_.
+
+* [Breaking] ``fmt::printf`` and ``fmt::sprintf`` now print null pointers as ``(nil)``
+  and null strings as ``(null)`` for consistency with glibc
+  (`#226 <https://github.com/fmtlib/fmt/pull/226>`_).
+  Thanks to `@LarsGullik <https://github.com/LarsGullik>`_.
+
+* [Breaking] ``fmt::(s)printf`` now supports formatting of objects of user-defined types
+  that provide an overloaded ``std::ostream`` insertion operator (``operator<<``)
+  (`#201 <https://github.com/fmtlib/fmt/issues/201>`_):
+
+  .. code:: c++
+
+    fmt::printf("The date is %s", Date(2012, 12, 9));
+
+* [Breaking] The ``Buffer`` template is now part of the public API and can be used
+  to implement custom memory buffers
+  (`#140 <https://github.com/fmtlib/fmt/issues/140>`_).
+  Thanks to `@polyvertex (Jean-Charles Lefebvre) <https://github.com/polyvertex>`_.
+
+* [Breaking] Improved compatibility between ``BasicStringRef`` and
+  `std::experimental::basic_string_view
+  <http://en.cppreference.com/w/cpp/experimental/basic_string_view>`_
+  (`#100 <https://github.com/fmtlib/fmt/issues/100>`_,
+  `#159 <https://github.com/fmtlib/fmt/issues/159>`_,
+  `#183 <https://github.com/fmtlib/fmt/issues/183>`_):
+
+  - Comparison operators now compare string content, not pointers
+  - ``BasicStringRef::c_str`` replaced by ``BasicStringRef::data``
+  - ``BasicStringRef`` is no longer assumed to be null-terminated
+
+  References to null-terminated strings are now represented by a new class,
+  ``BasicCStringRef``.
+
+* Dependency on pthreads introduced by Google Test is now optional
+  (`#185 <https://github.com/fmtlib/fmt/issues/185>`_).
+
+* New CMake options ``FMT_DOC``, ``FMT_INSTALL`` and ``FMT_TEST`` to control
+  generation of ``doc``, ``install`` and ``test`` targets respectively, on by default
+  (`#197 <https://github.com/fmtlib/fmt/issues/197>`_,
+  `#198 <https://github.com/fmtlib/fmt/issues/198>`_,
+  `#200 <https://github.com/fmtlib/fmt/issues/200>`_).
+  Thanks to `@maddinat0r (Alex Martin) <https://github.com/maddinat0r>`_.
+
+* ``noexcept`` is now used when compiling with MSVC2015
+  (`#215 <https://github.com/fmtlib/fmt/pull/215>`_).
+  Thanks to `@dmkrepo (Dmitriy) <https://github.com/dmkrepo>`_.
+
+* Added an option to disable use of ``windows.h`` when ``FMT_USE_WINDOWS_H``
+  is defined as 0 before including ``format.h``
+  (`#171 <https://github.com/fmtlib/fmt/issues/171>`_).
+  Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_.
+
+* [Breaking] ``windows.h`` is now included with ``NOMINMAX`` unless
+  ``FMT_WIN_MINMAX`` is defined. This is done to prevent breaking code using
+  ``std::min`` and ``std::max`` and only affects the header-only configuration
+  (`#152 <https://github.com/fmtlib/fmt/issues/152>`_,
+  `#153 <https://github.com/fmtlib/fmt/pull/153>`_,
+  `#154 <https://github.com/fmtlib/fmt/pull/154>`_).
+  Thanks to `@DevO2012 <https://github.com/DevO2012>`_.
+
+* Improved support for custom character types
+  (`#171 <https://github.com/fmtlib/fmt/issues/171>`_).
+  Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_.
+
+* Added an option to disable use of IOStreams when ``FMT_USE_IOSTREAMS``
+  is defined as 0 before including ``format.h``
+  (`#205 <https://github.com/fmtlib/fmt/issues/205>`_,
+  `#208 <https://github.com/fmtlib/fmt/pull/208>`_).
+  Thanks to `@JodiTheTigger <https://github.com/JodiTheTigger>`_.
+
+* Improved detection of ``isnan``, ``isinf`` and ``signbit``.
+
+Optimization
+~~~~~~~~~~~~
+
+* Made formatting of user-defined types more efficient with a custom stream buffer
+  (`#92 <https://github.com/fmtlib/fmt/issues/92>`_,
+  `#230 <https://github.com/fmtlib/fmt/pull/230>`_).
+  Thanks to `@NotImplemented <https://github.com/NotImplemented>`_.
+
+* Further improved performance of ``fmt::Writer`` on integer formatting
+  and fixed a minor regression. Now it is ~7% faster than ``karma::generate``
+  on Karma's benchmark
+  (`#186 <https://github.com/fmtlib/fmt/issues/186>`_).
+
+* [Breaking] Reduced `compiled code size
+  <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_
+  (`#143 <https://github.com/fmtlib/fmt/issues/143>`_,
+  `#149 <https://github.com/fmtlib/fmt/pull/149>`_).
+
+Distribution
+~~~~~~~~~~~~
+
+* [Breaking] Headers are now installed in
+  ``${CMAKE_INSTALL_PREFIX}/include/cppformat``
+  (`#178 <https://github.com/fmtlib/fmt/issues/178>`_).
+  Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_.
+
+* [Breaking] Changed the library name from ``format`` to ``cppformat``
+  for consistency with the project name and to avoid potential conflicts
+  (`#178 <https://github.com/fmtlib/fmt/issues/178>`_).
+  Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_.
+
+* C++ Format is now available in `Debian <https://www.debian.org/>`_ GNU/Linux
+  (`stretch <https://packages.debian.org/source/stretch/cppformat>`_,
+  `sid <https://packages.debian.org/source/sid/cppformat>`_) and 
+  derived distributions such as
+  `Ubuntu <https://launchpad.net/ubuntu/+source/cppformat>`_ 15.10 and later
+  (`#155 <https://github.com/fmtlib/fmt/issues/155>`_)::
+
+    $ sudo apt-get install libcppformat1-dev
+
+  Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_.
+
+* `Packages for Fedora and RHEL <https://admin.fedoraproject.org/pkgdb/package/cppformat/>`_
+  are now available. Thanks to Dave Johansen.
+  
+* C++ Format can now be installed via `Homebrew <http://brew.sh/>`_ on OS X
+  (`#157 <https://github.com/fmtlib/fmt/issues/157>`_)::
+
+    $ brew install cppformat
+
+  Thanks to `@ortho <https://github.com/ortho>`_, Anatoliy Bulukin.
+
+Documentation
+~~~~~~~~~~~~~
+
+* Migrated from ReadTheDocs to GitHub Pages for better responsiveness
+  and reliability
+  (`#128 <https://github.com/fmtlib/fmt/issues/128>`_).
+  New documentation address is http://cppformat.github.io/.
+
+
+* Added `Building the documentation
+  <https://fmt.dev/2.0.0/usage.html#building-the-documentation>`_
+  section to the documentation.
+
+* Documentation build script is now compatible with Python 3 and newer pip versions.
+  (`#189 <https://github.com/fmtlib/fmt/pull/189>`_,
+  `#209 <https://github.com/fmtlib/fmt/issues/209>`_).
+  Thanks to `@JodiTheTigger <https://github.com/JodiTheTigger>`_ and
+  `@xentec <https://github.com/xentec>`_.
+  
+* Documentation fixes and improvements
+  (`#36 <https://github.com/fmtlib/fmt/issues/36>`_,
+  `#75 <https://github.com/fmtlib/fmt/issues/75>`_,
+  `#125 <https://github.com/fmtlib/fmt/issues/125>`_,
+  `#160 <https://github.com/fmtlib/fmt/pull/160>`_,
+  `#161 <https://github.com/fmtlib/fmt/pull/161>`_,
+  `#162 <https://github.com/fmtlib/fmt/issues/162>`_,
+  `#165 <https://github.com/fmtlib/fmt/issues/165>`_,
+  `#210 <https://github.com/fmtlib/fmt/issues/210>`_).
+  Thanks to `@syohex (Syohei YOSHIDA) <https://github.com/syohex>`_ and
+  bug reporters.
+
+* Fixed out-of-tree documentation build
+  (`#177 <https://github.com/fmtlib/fmt/issues/177>`_).
+  Thanks to `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_.
+
+Fixes
+~~~~~
+
+* Fixed ``initializer_list`` detection
+  (`#136 <https://github.com/fmtlib/fmt/issues/136>`_).
+  Thanks to `@Gachapen (Magnus Bjerke Vik) <https://github.com/Gachapen>`_.
+
+* [Breaking] Fixed formatting of enums with numeric format specifiers in
+  ``fmt::(s)printf`` 
+  (`#131 <https://github.com/fmtlib/fmt/issues/131>`_,
+  `#139 <https://github.com/fmtlib/fmt/issues/139>`_):
+
+  .. code:: c++
+
+    enum { ANSWER = 42 };
+    fmt::printf("%d", ANSWER);
+
+  Thanks to `@Naios <https://github.com/Naios>`_.
+
+* Improved compatibility with old versions of MinGW
+  (`#129 <https://github.com/fmtlib/fmt/issues/129>`_,
+  `#130 <https://github.com/fmtlib/fmt/pull/130>`_,
+  `#132 <https://github.com/fmtlib/fmt/issues/132>`_).
+  Thanks to `@cstamford (Christopher Stamford) <https://github.com/cstamford>`_.
+
+* Fixed a compile error on MSVC with disabled exceptions
+  (`#144 <https://github.com/fmtlib/fmt/issues/144>`_).
+
+* Added a workaround for broken implementation of variadic templates in MSVC2012
+  (`#148 <https://github.com/fmtlib/fmt/issues/148>`_).
+
+* Placed the anonymous namespace within ``fmt`` namespace for the header-only
+  configuration
+  (`#171 <https://github.com/fmtlib/fmt/issues/171>`_).
+  Thanks to `@alfps (Alf P. Steinbach) <https://github.com/alfps>`_.
+
+* Fixed issues reported by Coverity Scan
+  (`#187 <https://github.com/fmtlib/fmt/issues/187>`_,
+  `#192 <https://github.com/fmtlib/fmt/issues/192>`_).
+
+* Implemented a workaround for a name lookup bug in MSVC2010
+  (`#188 <https://github.com/fmtlib/fmt/issues/188>`_).
+
+* Fixed compiler warnings
+  (`#95 <https://github.com/fmtlib/fmt/issues/95>`_,
+  `#96 <https://github.com/fmtlib/fmt/issues/96>`_,
+  `#114 <https://github.com/fmtlib/fmt/pull/114>`_,
+  `#135 <https://github.com/fmtlib/fmt/issues/135>`_,
+  `#142 <https://github.com/fmtlib/fmt/issues/142>`_,
+  `#145 <https://github.com/fmtlib/fmt/issues/145>`_,
+  `#146 <https://github.com/fmtlib/fmt/issues/146>`_,
+  `#158 <https://github.com/fmtlib/fmt/issues/158>`_,
+  `#163 <https://github.com/fmtlib/fmt/issues/163>`_,
+  `#175 <https://github.com/fmtlib/fmt/issues/175>`_,
+  `#190 <https://github.com/fmtlib/fmt/issues/190>`_,
+  `#191 <https://github.com/fmtlib/fmt/pull/191>`_,
+  `#194 <https://github.com/fmtlib/fmt/issues/194>`_,
+  `#196 <https://github.com/fmtlib/fmt/pull/196>`_,
+  `#216 <https://github.com/fmtlib/fmt/issues/216>`_,
+  `#218 <https://github.com/fmtlib/fmt/pull/218>`_,
+  `#220 <https://github.com/fmtlib/fmt/pull/220>`_,
+  `#229 <https://github.com/fmtlib/fmt/pull/229>`_,
+  `#233 <https://github.com/fmtlib/fmt/issues/233>`_,
+  `#234 <https://github.com/fmtlib/fmt/issues/234>`_,
+  `#236 <https://github.com/fmtlib/fmt/pull/236>`_,
+  `#281 <https://github.com/fmtlib/fmt/issues/281>`_,
+  `#289 <https://github.com/fmtlib/fmt/issues/289>`_).
+  Thanks to `@seanmiddleditch (Sean Middleditch) <https://github.com/seanmiddleditch>`_,
+  `@dixlorenz (Dix Lorenz) <https://github.com/dixlorenz>`_,
+  `@CarterLi (李通洲) <https://github.com/CarterLi>`_,
+  `@Naios <https://github.com/Naios>`_,
+  `@fmatthew5876 (Matthew Fioravante) <https://github.com/fmatthew5876>`_,
+  `@LevskiWeng (Levski Weng) <https://github.com/LevskiWeng>`_,
+  `@rpopescu <https://github.com/rpopescu>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@cubicool (Jeremy Moles) <https://github.com/cubicool>`_,
+  `@jkflying (Julian Kent) <https://github.com/jkflying>`_,
+  `@LogicalKnight (Sean L) <https://github.com/LogicalKnight>`_,
+  `@inguin (Ingo van Lil) <https://github.com/inguin>`_ and
+  `@Jopie64 (Johan) <https://github.com/Jopie64>`_.
+
+* Fixed portability issues (mostly causing test failures) on ARM, ppc64, ppc64le,
+  s390x and SunOS 5.11 i386
+  (`#138 <https://github.com/fmtlib/fmt/issues/138>`_,
+  `#179 <https://github.com/fmtlib/fmt/issues/179>`_,
+  `#180 <https://github.com/fmtlib/fmt/issues/180>`_,
+  `#202 <https://github.com/fmtlib/fmt/issues/202>`_,
+  `#225 <https://github.com/fmtlib/fmt/issues/225>`_,
+  `Red Hat Bugzilla Bug 1260297 <https://bugzilla.redhat.com/show_bug.cgi?id=1260297>`_).
+  Thanks to `@Naios <https://github.com/Naios>`_,
+  `@jackyf (Eugene V. Lyubimkin) <https://github.com/jackyf>`_ and Dave Johansen.
+
+* Fixed a name conflict with macro ``free`` defined in
+  ``crtdbg.h`` when ``_CRTDBG_MAP_ALLOC`` is set
+  (`#211 <https://github.com/fmtlib/fmt/issues/211>`_).
+
+* Fixed shared library build on OS X
+  (`#212 <https://github.com/fmtlib/fmt/pull/212>`_).
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_.
+
+* Fixed an overload conflict on MSVC when ``/Zc:wchar_t-`` option is specified
+  (`#214 <https://github.com/fmtlib/fmt/pull/214>`_).
+  Thanks to `@slavanap (Vyacheslav Napadovsky) <https://github.com/slavanap>`_.
+
+* Improved compatibility with MSVC 2008
+  (`#236 <https://github.com/fmtlib/fmt/pull/236>`_).
+  Thanks to `@Jopie64 (Johan) <https://github.com/Jopie64>`_.
+
+* Improved compatibility with bcc32
+  (`#227 <https://github.com/fmtlib/fmt/issues/227>`_).
+
+* Fixed ``static_assert`` detection on Clang
+  (`#228 <https://github.com/fmtlib/fmt/pull/228>`_).
+  Thanks to `@dean0x7d (Dean Moldovan) <https://github.com/dean0x7d>`_.
+
+1.1.0 - 2015-03-06
+------------------
+
+* Added ``BasicArrayWriter``, a class template that provides operations for
+  formatting and writing data into a fixed-size array
+  (`#105 <https://github.com/fmtlib/fmt/issues/105>`_ and
+  `#122 <https://github.com/fmtlib/fmt/issues/122>`_):
+
+  .. code:: c++
+  
+    char buffer[100];
+    fmt::ArrayWriter w(buffer);
+    w.write("The answer is {}", 42);
+
+* Added `0 A.D. <http://play0ad.com/>`_ and `PenUltima Online (POL)
+  <http://www.polserver.com/>`_ to the list of notable projects using C++ Format.
+
+* C++ Format now uses MSVC intrinsics for better formatting performance
+  (`#115 <https://github.com/fmtlib/fmt/pull/115>`_,
+  `#116 <https://github.com/fmtlib/fmt/pull/116>`_,
+  `#118 <https://github.com/fmtlib/fmt/pull/118>`_ and
+  `#121 <https://github.com/fmtlib/fmt/pull/121>`_).
+  Previously these optimizations where only used on GCC and Clang.
+  Thanks to `@CarterLi <https://github.com/CarterLi>`_ and
+  `@objectx <https://github.com/objectx>`_.
+
+* CMake install target (`#119 <https://github.com/fmtlib/fmt/pull/119>`_).
+  Thanks to `@TrentHouliston <https://github.com/TrentHouliston>`_.
+
+  You can now install C++ Format with ``make install`` command.
+
+* Improved `Biicode <http://www.biicode.com/>`_ support
+  (`#98 <https://github.com/fmtlib/fmt/pull/98>`_ and
+  `#104 <https://github.com/fmtlib/fmt/pull/104>`_). Thanks to
+  `@MariadeAnton <https://github.com/MariadeAnton>`_ and
+  `@franramirez688 <https://github.com/franramirez688>`_.
+
+* Improved support for building with `Android NDK
+  <https://developer.android.com/tools/sdk/ndk/index.html>`_
+  (`#107 <https://github.com/fmtlib/fmt/pull/107>`_).
+  Thanks to `@newnon <https://github.com/newnon>`_.
+  
+  The `android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
+  repository provides and example of using C++ Format with Android NDK:
+
+  .. image:: https://raw.githubusercontent.com/fmtlib/android-ndk-example/
+            master/screenshot.png
+
+* Improved documentation of ``SystemError`` and ``WindowsError``
+  (`#54 <https://github.com/fmtlib/fmt/issues/54>`_).
+
+* Various code improvements
+  (`#110 <https://github.com/fmtlib/fmt/pull/110>`_,
+  `#111 <https://github.com/fmtlib/fmt/pull/111>`_
+  `#112 <https://github.com/fmtlib/fmt/pull/112>`_).
+  Thanks to `@CarterLi <https://github.com/CarterLi>`_.
+
+* Improved compile-time errors when formatting wide into narrow strings
+  (`#117 <https://github.com/fmtlib/fmt/issues/117>`_).
+
+* Fixed ``BasicWriter::write`` without formatting arguments when C++11 support
+  is disabled (`#109 <https://github.com/fmtlib/fmt/issues/109>`_).
+
+* Fixed header-only build on OS X with GCC 4.9
+  (`#124 <https://github.com/fmtlib/fmt/issues/124>`_).
+
+* Fixed packaging issues (`#94 <https://github.com/fmtlib/fmt/issues/94>`_).
+
+* Added `changelog <https://github.com/fmtlib/fmt/blob/master/ChangeLog.rst>`_
+  (`#103 <https://github.com/fmtlib/fmt/issues/103>`_).
+
+1.0.0 - 2015-02-05
+------------------
+
+* Add support for a header-only configuration when ``FMT_HEADER_ONLY`` is
+  defined before including ``format.h``:
+
+  .. code:: c++
+
+    #define FMT_HEADER_ONLY
+    #include "format.h"
+
+* Compute string length in the constructor of ``BasicStringRef``
+  instead of the ``size`` method
+  (`#79 <https://github.com/fmtlib/fmt/issues/79>`_).
+  This eliminates size computation for string literals on reasonable optimizing
+  compilers.
+
+* Fix formatting of types with overloaded ``operator <<`` for ``std::wostream``
+  (`#86 <https://github.com/fmtlib/fmt/issues/86>`_):
+
+  .. code:: c++
+
+    fmt::format(L"The date is {0}", Date(2012, 12, 9));
+
+* Fix linkage of tests on Arch Linux
+  (`#89 <https://github.com/fmtlib/fmt/issues/89>`_).
+
+* Allow precision specifier for non-float arguments
+  (`#90 <https://github.com/fmtlib/fmt/issues/90>`_):
+
+  .. code:: c++
+
+    fmt::print("{:.3}\n", "Carpet"); // prints "Car"
+
+* Fix build on Android NDK
+  (`#93 <https://github.com/fmtlib/fmt/issues/93>`_)
+
+* Improvements to documentation build procedure.
+
+* Remove ``FMT_SHARED`` CMake variable in favor of standard `BUILD_SHARED_LIBS
+  <http://www.cmake.org/cmake/help/v3.0/variable/BUILD_SHARED_LIBS.html>`_.
+
+* Fix error handling in ``fmt::fprintf``.
+
+* Fix a number of warnings.
+
+0.12.0 - 2014-10-25
+-------------------
+
+* [Breaking] Improved separation between formatting and buffer management.
+  ``Writer`` is now a base class that cannot be instantiated directly.
+  The new ``MemoryWriter`` class implements the default buffer management
+  with small allocations done on stack. So ``fmt::Writer`` should be replaced
+  with ``fmt::MemoryWriter`` in variable declarations.
+
+  Old code:
+
+  .. code:: c++
+
+    fmt::Writer w;
+
+  New code: 
+
+  .. code:: c++
+
+    fmt::MemoryWriter w;
+
+  If you pass ``fmt::Writer`` by reference, you can continue to do so:
+
+  .. code:: c++
+
+      void f(fmt::Writer &w);
+
+  This doesn't affect the formatting API.
+
+* Support for custom memory allocators
+  (`#69 <https://github.com/fmtlib/fmt/issues/69>`_)
+
+* Formatting functions now accept `signed char` and `unsigned char` strings as
+  arguments (`#73 <https://github.com/fmtlib/fmt/issues/73>`_):
+
+  .. code:: c++
+
+    auto s = format("GLSL version: {}", glGetString(GL_VERSION));
+
+* Reduced code bloat. According to the new `benchmark results
+  <https://github.com/fmtlib/fmt#compile-time-and-code-bloat>`_,
+  cppformat is close to ``printf`` and by the order of magnitude better than
+  Boost Format in terms of compiled code size.
+
+* Improved appearance of the documentation on mobile by using the `Sphinx
+  Bootstrap theme <http://ryan-roemer.github.io/sphinx-bootstrap-theme/>`_:
+
+  .. |old| image:: https://cloud.githubusercontent.com/assets/576385/4792130/
+                   cd256436-5de3-11e4-9a62-c077d0c2b003.png
+
+  .. |new| image:: https://cloud.githubusercontent.com/assets/576385/4792131/
+                   cd29896c-5de3-11e4-8f59-cac952942bf0.png
+  
+  +-------+-------+
+  |  Old  |  New  |
+  +-------+-------+
+  | |old| | |new| |
+  +-------+-------+
+
+0.11.0 - 2014-08-21
+-------------------
+
+* Safe printf implementation with a POSIX extension for positional arguments:
+
+  .. code:: c++
+
+    fmt::printf("Elapsed time: %.2f seconds", 1.23);
+    fmt::printf("%1$s, %3$d %2$s", weekday, month, day);
+
+* Arguments of ``char`` type can now be formatted as integers
+  (Issue `#55 <https://github.com/fmtlib/fmt/issues/55>`_):
+
+  .. code:: c++
+
+    fmt::format("0x{0:02X}", 'a');
+
+* Deprecated parts of the API removed.
+
+* The library is now built and tested on MinGW with Appveyor in addition to
+  existing test platforms Linux/GCC, OS X/Clang, Windows/MSVC.
+
+0.10.0 - 2014-07-01
+-------------------
+
+**Improved API**
+
+* All formatting methods are now implemented as variadic functions instead
+  of using ``operator<<`` for feeding arbitrary arguments into a temporary
+  formatter object. This works both with C++11 where variadic templates are
+  used and with older standards where variadic functions are emulated by
+  providing lightweight wrapper functions defined with the ``FMT_VARIADIC``
+  macro. You can use this macro for defining your own portable variadic
+  functions:
+
+  .. code:: c++
+
+    void report_error(const char *format, const fmt::ArgList &args) {
+      fmt::print("Error: {}");
+      fmt::print(format, args);
+    }
+    FMT_VARIADIC(void, report_error, const char *)
+
+    report_error("file not found: {}", path);
+
+  Apart from a more natural syntax, this also improves performance as there
+  is no need to construct temporary formatter objects and control arguments'
+  lifetimes. Because the wrapper functions are very lightweight, this doesn't
+  cause code bloat even in pre-C++11 mode.
+
+* Simplified common case of formatting an ``std::string``. Now it requires a
+  single function call:
+
+  .. code:: c++
+
+    std::string s = format("The answer is {}.", 42);
+
+  Previously it required 2 function calls:
+
+  .. code:: c++
+
+    std::string s = str(Format("The answer is {}.") << 42);
+
+  Instead of unsafe ``c_str`` function, ``fmt::Writer`` should be used directly
+  to bypass creation of ``std::string``:
+
+  .. code:: c++
+
+    fmt::Writer w;
+    w.write("The answer is {}.", 42);
+    w.c_str();  // returns a C string
+
+  This doesn't do dynamic memory allocation for small strings and is less error
+  prone as the lifetime of the string is the same as for ``std::string::c_str``
+  which is well understood (hopefully).
+
+* Improved consistency in naming functions that are a part of the public API.
+  Now all public functions are lowercase following the standard library
+  conventions. Previously it was a combination of lowercase and
+  CapitalizedWords.
+  Issue `#50 <https://github.com/fmtlib/fmt/issues/50>`_.
+
+* Old functions are marked as deprecated and will be removed in the next
+  release.
+
+**Other Changes**
+
+* Experimental support for printf format specifications (work in progress):
+
+  .. code:: c++
+
+    fmt::printf("The answer is %d.", 42);
+    std::string s = fmt::sprintf("Look, a %s!", "string");
+
+* Support for hexadecimal floating point format specifiers ``a`` and ``A``:
+
+  .. code:: c++
+
+    print("{:a}", -42.0); // Prints -0x1.5p+5
+    print("{:A}", -42.0); // Prints -0X1.5P+5
+
+* CMake option ``FMT_SHARED`` that specifies whether to build format as a
+  shared library (off by default).
+
+0.9.0 - 2014-05-13
+------------------
+
+* More efficient implementation of variadic formatting functions.
+
+* ``Writer::Format`` now has a variadic overload:
+
+  .. code:: c++
+
+    Writer out;
+    out.Format("Look, I'm {}!", "variadic");
+
+* For efficiency and consistency with other overloads, variadic overload of
+  the ``Format`` function now returns ``Writer`` instead of ``std::string``.
+  Use the ``str`` function to convert it to ``std::string``:
+
+  .. code:: c++
+
+    std::string s = str(Format("Look, I'm {}!", "variadic"));
+
+* Replaced formatter actions with output sinks: ``NoAction`` -> ``NullSink``,
+  ``Write`` -> ``FileSink``, ``ColorWriter`` -> ``ANSITerminalSink``.
+  This improves naming consistency and shouldn't affect client code unless
+  these classes are used directly which should be rarely needed.
+
+* Added ``ThrowSystemError`` function that formats a message and throws
+  ``SystemError`` containing the formatted message and system-specific error
+  description. For example, the following code
+
+  .. code:: c++
+
+    FILE *f = fopen(filename, "r");
+    if (!f)
+      ThrowSystemError(errno, "Failed to open file '{}'") << filename;
+
+  will throw ``SystemError`` exception with description
+  "Failed to open file '<filename>': No such file or directory" if file
+  doesn't exist.
+
+* Support for AppVeyor continuous integration platform.
+
+* ``Format`` now throws ``SystemError`` in case of I/O errors.
+
+* Improve test infrastructure. Print functions are now tested by redirecting
+  the output to a pipe.
+
+0.8.0 - 2014-04-14
+------------------
+
+* Initial release
diff --git a/libs/fmt/LICENSE.rst b/libs/fmt/LICENSE.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f0ec3db4d2a03368a9f1357b38e79486bdf41076
--- /dev/null
+++ b/libs/fmt/LICENSE.rst
@@ -0,0 +1,27 @@
+Copyright (c) 2012 - present, Victor Zverovich
+
+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 AUTHORS OR COPYRIGHT HOLDERS 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.
+
+--- Optional exception to the license ---
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into a machine-executable object form of such
+source code, you may redistribute such embedded portions in such object form
+without including the above copyright and permission notices.
diff --git a/libs/fmt/Makefile b/libs/fmt/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3572ff166fe02c07e4066002af5b0cee90765758
--- /dev/null
+++ b/libs/fmt/Makefile
@@ -0,0 +1,12 @@
+all: libfmt.a
+
+libfmt.a: format.o
+	arm-miosix-eabi-ar rvs libfmt.a format.o
+
+format.o: src/format.cc
+	arm-miosix-eabi-g++ -s -c -o format.o src/format.cc -O2 \
+		-D_GLIBCXX_USE_WCHAR_T \
+		-DFMT_UNICODE=0 -DFMT_STATIC_THOUSANDS_SEPARATOR=0\
+		-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Iinclude
+clean:
+	rm -f format.o libfmt.a
\ No newline at end of file
diff --git a/libs/fmt/README.rst b/libs/fmt/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4f33b627686ca8cd55859725e7a4b6c681cc1df6
--- /dev/null
+++ b/libs/fmt/README.rst
@@ -0,0 +1,524 @@
+{fmt}
+=====
+
+.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
+   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
+
+.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
+   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
+
+.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
+   :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
+
+.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
+   :target: https://ci.appveyor.com/project/vitaut/fmt
+
+.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
+   :alt: fmt is continuously fuzzed at oss-fuzz
+   :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
+            colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
+            Summary&q=proj%3Dfmt&can=1
+
+.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
+   :alt: Ask questions at StackOverflow with the tag fmt
+   :target: https://stackoverflow.com/questions/tagged/fmt
+
+**{fmt}** is an open-source formatting library providing a fast and safe
+alternative to C stdio and C++ iostreams.
+
+If you like this project, please consider donating to BY_Help,
+an initiative to help victims of political repressions in Belarus:
+https://www.facebook.com/donate/199475051809330/.
+
+`Documentation <https://fmt.dev>`__
+
+Q&A: ask questions on `StackOverflow with the tag fmt
+<https://stackoverflow.com/questions/tagged/fmt>`_.
+
+Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
+
+Features
+--------
+
+* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
+  for localization
+* Implementation of `C++20 std::format
+  <https://en.cppreference.com/w/cpp/utility/format>`__
+* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
+  `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
+* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
+  round-trip guarantees
+* Safe `printf implementation
+  <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
+  extension for positional arguments
+* Extensibility: `support for user-defined types
+  <https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
+* High performance: faster than common standard library implementations of
+  ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
+  and `Converting a hundred million integers to strings per second
+  <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
+* Small code size both in terms of source code with the minimum configuration
+  consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
+  and compiled code; see `Compile time and code bloat`_
+* Reliability: the library has an extensive set of `tests
+  <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
+  <https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
+  Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
+* Safety: the library is fully type safe, errors in format strings can be
+  reported at compile time, automatic memory management prevents buffer overflow
+  errors
+* Ease of use: small self-contained code base, no external dependencies,
+  permissive MIT `license
+  <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
+* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
+  consistent output across platforms and support for older compilers
+* Clean warning-free codebase even on high warning levels such as
+  ``-Wall -Wextra -pedantic``
+* Locale-independence by default
+* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
+
+See the `documentation <https://fmt.dev>`_ for more details.
+
+Examples
+--------
+
+**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
+
+.. code:: c++
+
+    #include <fmt/core.h>
+    
+    int main() {
+      fmt::print("Hello, world!\n");
+    }
+
+**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
+
+.. code:: c++
+
+    std::string s = fmt::format("The answer is {}.", 42);
+    // s == "The answer is 42."
+
+**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
+
+.. code:: c++
+
+    std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
+    // s == "I'd rather be happy than right."
+
+**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
+
+.. code:: c++
+
+    #include <fmt/chrono.h>
+
+    int main() {
+      using namespace std::literals::chrono_literals;
+      fmt::print("Default format: {} {}\n", 42s, 100ms);
+      fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
+    }
+
+Output::
+
+    Default format: 42s 100ms
+    strftime-like format: 03:15:30
+
+**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
+
+.. code:: c++
+
+    #include <vector>
+    #include <fmt/ranges.h>
+
+    int main() {
+      std::vector<int> v = {1, 2, 3};
+      fmt::print("{}\n", v);
+    }
+
+Output::
+
+    {1, 2, 3}
+
+**Check a format string at compile time**
+
+.. code:: c++
+
+    std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number");
+
+This gives a compile-time error because ``d`` is an invalid format specifier for
+a string.
+
+**Write a file from a single thread**
+
+.. code:: c++
+
+    #include <fmt/os.h>
+
+    int main() {
+      auto out = fmt::output_file("guide.txt");
+      out.print("Don't {}", "Panic");
+    }
+
+This can be `5 to 9 times faster than fprintf
+<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
+
+**Print with colors and text styles**
+
+.. code:: c++
+
+    #include <fmt/color.h>
+
+    int main() {
+      fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
+                 "Hello, {}!\n", "world");
+      fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
+                 fmt::emphasis::underline, "Hello, {}!\n", "мир");
+      fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
+                 "Hello, {}!\n", "世界");
+    }
+
+Output on a modern terminal:
+
+.. image:: https://user-images.githubusercontent.com/
+           576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
+
+Benchmarks
+----------
+
+Speed tests
+~~~~~~~~~~~
+
+================= ============= ===========
+Library           Method        Run Time, s
+================= ============= ===========
+libc              printf          1.04
+libc++            std::ostream    3.05
+{fmt} 6.1.1       fmt::print      0.75
+Boost Format 1.67 boost::format   7.24
+Folly Format      folly::format   2.23
+================= ============= ===========
+
+{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
+
+The above results were generated by building ``tinyformat_test.cpp`` on macOS
+10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
+best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
+or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
+further details refer to the `source
+<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
+
+{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
+floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
+and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
+`ryu <https://github.com/ulfjack/ryu>`_:
+
+.. image:: https://user-images.githubusercontent.com/576385/
+           95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
+   :target: https://fmt.dev/unknown_mac64_clang12.0.html
+
+Compile time and code bloat
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The script `bloat-test.py
+<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
+from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
+tests compile time and code bloat for nontrivial projects.
+It generates 100 translation units and uses ``printf()`` or its alternative
+five times in each to simulate a medium sized project.  The resulting
+executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
+macOS Sierra, best of three) is shown in the following tables.
+
+**Optimized build (-O3)**
+
+============= =============== ==================== ==================
+Method        Compile Time, s Executable size, KiB Stripped size, KiB
+============= =============== ==================== ==================
+printf                    2.6                   29                 26
+printf+string            16.4                   29                 26
+iostreams                31.1                   59                 55
+{fmt}                    19.0                   37                 34
+Boost Format             91.9                  226                203
+Folly Format            115.7                  101                 88
+============= =============== ==================== ==================
+
+As you can see, {fmt} has 60% less overhead in terms of resulting binary code
+size compared to iostreams and comes pretty close to ``printf``. Boost Format
+and Folly Format have the largest overheads.
+
+``printf+string`` is the same as ``printf`` but with extra ``<string>``
+include to measure the overhead of the latter.
+
+**Non-optimized build**
+
+============= =============== ==================== ==================
+Method        Compile Time, s Executable size, KiB Stripped size, KiB
+============= =============== ==================== ==================
+printf                    2.2                   33                 30
+printf+string            16.0                   33                 30
+iostreams                28.3                   56                 52
+{fmt}                    18.2                   59                 50
+Boost Format             54.1                  365                303
+Folly Format             79.9                  445                430
+============= =============== ==================== ==================
+
+``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
+compare formatting function overhead only. Boost Format is a
+header-only library so it doesn't provide any linkage options.
+
+Running the tests
+~~~~~~~~~~~~~~~~~
+
+Please refer to `Building the library`__ for the instructions on how to build
+the library and run the unit tests.
+
+__ https://fmt.dev/latest/usage.html#building-the-library
+
+Benchmarks reside in a separate repository,
+`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
+so to run the benchmarks you first need to clone this repository and
+generate Makefiles with CMake::
+
+    $ git clone --recursive https://github.com/fmtlib/format-benchmark.git
+    $ cd format-benchmark
+    $ cmake .
+
+Then you can run the speed test::
+
+    $ make speed-test
+
+or the bloat test::
+
+    $ make bloat-test
+    
+Migrating code
+--------------
+
+`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
+tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
+``fmt::print``.
+
+Projects using this library
+---------------------------
+
+* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
+  real-time strategy game
+
+* `2GIS <https://2gis.ru/>`_: free business listings with a city map
+
+* `AMPL/MP <https://github.com/ampl/mp>`_:
+  an open-source library for mathematical programming
+
+* `Aseprite <https://github.com/aseprite/aseprite>`_:
+  animated sprite editor & pixel art tool 
+
+* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
+  operations suite
+  
+* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
+  
+* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
+
+* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
+
+* `ccache <https://ccache.dev/>`_: a compiler cache
+
+* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
+  management system
+
+* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
+  vehicle
+
+* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
+  for nonlinear dynamical systems (MIT)
+
+* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
+  (Lyft)
+
+* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
+
+* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
+  logging library with latency in nanoseconds
+
+* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
+
+* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
+  Player vs Player Gaming Network with tweaks
+
+* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
+  engine
+
+* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
+
+* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
+
+* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
+
+* `Microsoft Verona <https://github.com/microsoft/verona>`_:
+  research programming language for concurrent ownership
+
+* `MongoDB <https://mongodb.com/>`_: distributed document database
+
+* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
+  generate randomized datasets
+
+* `OpenSpace <https://openspaceproject.com/>`_: an open-source
+  astrovisualization framework
+
+* `PenUltima Online (POL) <https://www.polserver.com/>`_:
+  an MMO server, compatible with most Ultima Online clients
+
+* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
+  learning library
+
+* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
+  associative database
+  
+* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
+
+* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
+  navigation, and executing complex multi-line terminal command sequences
+
+* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
+  proxy
+
+* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
+  for mission critical systems written in C++
+
+* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
+  library
+
+* `Salesforce Analytics Cloud
+  <https://www.salesforce.com/analytics-cloud/overview/>`_:
+  business intelligence software
+
+* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
+  that can handle 1 million transactions per second on a single server
+
+* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
+  framework for high-performance server applications on modern hardware
+
+* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
+
+* `Stellar <https://www.stellar.org/>`_: financial platform
+
+* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
+
+* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
+  MMORPG framework
+
+* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
+  terminal
+
+`More... <https://github.com/search?q=fmtlib&type=Code>`_
+
+If you are aware of other projects using this library, please let me know
+by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
+`issue <https://github.com/fmtlib/fmt/issues>`_.
+
+Motivation
+----------
+
+So why yet another formatting library?
+
+There are plenty of methods for doing this task, from standard ones like
+the printf family of function and iostreams to Boost Format and FastFormat
+libraries. The reason for creating a new library is that every existing
+solution that I found either had serious issues or didn't provide
+all the features I needed.
+
+printf
+~~~~~~
+
+The good thing about ``printf`` is that it is pretty fast and readily available
+being a part of the C standard library. The main drawback is that it
+doesn't support user-defined types. ``printf`` also has safety issues although
+they are somewhat mitigated with `__attribute__ ((format (printf, ...))
+<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
+There is a POSIX extension that adds positional arguments required for
+`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
+to ``printf`` but it is not a part of C99 and may not be available on some
+platforms.
+
+iostreams
+~~~~~~~~~
+
+The main issue with iostreams is best illustrated with an example:
+
+.. code:: c++
+
+    std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
+
+which is a lot of typing compared to printf:
+
+.. code:: c++
+
+    printf("%.2f\n", 1.23456);
+
+Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
+don't support positional arguments by design.
+
+The good part is that iostreams support user-defined types and are safe although
+error handling is awkward.
+
+Boost Format
+~~~~~~~~~~~~
+
+This is a very powerful library which supports both ``printf``-like format
+strings and positional arguments. Its main drawback is performance. According to
+various, benchmarks it is much slower than other methods considered here. Boost
+Format also has excessive build times and severe code bloat issues (see
+`Benchmarks`_).
+
+FastFormat
+~~~~~~~~~~
+
+This is an interesting library which is fast, safe and has positional arguments.
+However, it has significant limitations, citing its author:
+
+    Three features that have no hope of being accommodated within the
+    current design are:
+
+    * Leading zeros (or any other non-space padding)
+    * Octal/hexadecimal encoding
+    * Runtime width/alignment specification
+
+It is also quite big and has a heavy dependency, STLSoft, which might be too
+restrictive for using it in some projects.
+
+Boost Spirit.Karma
+~~~~~~~~~~~~~~~~~~
+
+This is not really a formatting library but I decided to include it here for
+completeness. As iostreams, it suffers from the problem of mixing verbatim text
+with arguments. The library is pretty fast, but slower on integer formatting
+than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
+see `Converting a hundred million integers to strings per second
+<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
+
+License
+-------
+
+{fmt} is distributed under the MIT `license
+<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
+
+Documentation License
+---------------------
+
+The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
+section in the documentation is based on the one from Python `string module
+documentation <https://docs.python.org/3/library/string.html#module-string>`_.
+For this reason the documentation is distributed under the Python Software
+Foundation license available in `doc/python-license.txt
+<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
+It only applies if you distribute the documentation of {fmt}.
+
+Maintainers
+-----------
+
+The {fmt} library is maintained by Victor Zverovich (`vitaut
+<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
+<https://github.com/foonathan>`_) with contributions from many other people.
+See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
+`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
+Let us know if your contribution is not listed or mentioned incorrectly and
+we'll make it right.
diff --git a/libs/fmt/README_SKYWARD.md b/libs/fmt/README_SKYWARD.md
new file mode 100644
index 0000000000000000000000000000000000000000..ed82a56a800cb1d4e37d164029222f6106c148ff
--- /dev/null
+++ b/libs/fmt/README_SKYWARD.md
@@ -0,0 +1,4 @@
+# Compiling
+
+A custom makefile is provided instead of the default cmake build script. Please use the makefile and not cmake.  
+The header `format.h` has been slightly modified as it was not compiling with miosix-g++.
\ No newline at end of file
diff --git a/libs/fmt/doc/CMakeLists.txt b/libs/fmt/doc/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..06848450369c3cc3587ffd1e3b921b58c608651b
--- /dev/null
+++ b/libs/fmt/doc/CMakeLists.txt
@@ -0,0 +1,17 @@
+find_program(DOXYGEN doxygen)
+if (NOT DOXYGEN)
+  message(STATUS "Target 'doc' disabled (requires doxygen)")
+  return ()
+endif ()
+
+find_package(PythonInterp QUIET REQUIRED)
+
+add_custom_target(doc
+  COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
+                               ${FMT_VERSION}
+  SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
+
+include(GNUInstallDirs)
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
+        DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
+        PATTERN ".doctrees" EXCLUDE)
diff --git a/libs/fmt/doc/_static/bootstrap.min.js b/libs/fmt/doc/_static/bootstrap.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8f82e592a181afd01014546f58218e213429f6c
--- /dev/null
+++ b/libs/fmt/doc/_static/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v3.3.4 (http://getbootstrap.com)
+ * Copyright 2011-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.4",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){
+var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
\ No newline at end of file
diff --git a/libs/fmt/doc/_static/breathe.css b/libs/fmt/doc/_static/breathe.css
new file mode 100644
index 0000000000000000000000000000000000000000..2a1534b0aedd5625352b8802fe4c9de761775225
--- /dev/null
+++ b/libs/fmt/doc/_static/breathe.css
@@ -0,0 +1,28 @@
+
+/* -- breathe specific styles ----------------------------------------------- */
+
+/* So enum value descriptions are displayed inline to the item */
+.breatheenumvalues li tt + p {
+  display: inline;
+}
+
+/* So parameter descriptions are displayed inline to the item */
+.breatheparameterlist li tt + p {
+  display: inline;
+}
+
+.container .breathe-sectiondef {
+  width: inherit;
+}
+
+.github-btn {
+  border: 0;
+  overflow: hidden;
+}
+
+.jumbotron {
+  background-size: 100% 4px;
+  background-repeat: repeat-y;
+  color: white;
+  text-align: center;
+}
diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 0000000000000000000000000000000000000000..4a4ca865d67e86f961bc6e2ef00bffa4e34bb9ed
Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.eot differ
diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e3e2dc739dd851f2d7d291be032e30b909e3e95f
--- /dev/null
+++ b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,229 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
+<font-face units-per-em="1200" ascent="960" descent="-240" />
+<missing-glyph horiz-adv-x="500" />
+<glyph />
+<glyph />
+<glyph unicode="&#xd;" />
+<glyph unicode=" " />
+<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
+<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
+<glyph unicode="&#xa0;" />
+<glyph unicode="&#x2000;" horiz-adv-x="652" />
+<glyph unicode="&#x2001;" horiz-adv-x="1304" />
+<glyph unicode="&#x2002;" horiz-adv-x="652" />
+<glyph unicode="&#x2003;" horiz-adv-x="1304" />
+<glyph unicode="&#x2004;" horiz-adv-x="434" />
+<glyph unicode="&#x2005;" horiz-adv-x="326" />
+<glyph unicode="&#x2006;" horiz-adv-x="217" />
+<glyph unicode="&#x2007;" horiz-adv-x="217" />
+<glyph unicode="&#x2008;" horiz-adv-x="163" />
+<glyph unicode="&#x2009;" horiz-adv-x="260" />
+<glyph unicode="&#x200a;" horiz-adv-x="72" />
+<glyph unicode="&#x202f;" horiz-adv-x="260" />
+<glyph unicode="&#x205f;" horiz-adv-x="326" />
+<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
+<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
+<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
+<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
+<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
+<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
+<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
+<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
+<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
+<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
+<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
+<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
+<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
+<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
+<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
+<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
+<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
+<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
+<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
+<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
+<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
+<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
+<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
+<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
+<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
+<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
+<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
+<glyph unicode="&#xe028;" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
+<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
+<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
+<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
+<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
+<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
+<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
+<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
+<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
+<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
+<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
+<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
+<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
+<glyph unicode="&#xe041;" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
+<glyph unicode="&#xe042;" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
+<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
+<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
+<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
+<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
+<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
+<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
+<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
+<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
+<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
+<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
+<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
+<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
+<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
+<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
+<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
+<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
+<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
+<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
+<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
+<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
+<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
+<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
+<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
+<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
+<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
+<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
+<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
+<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
+<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
+<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
+<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
+<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
+<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
+<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
+<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
+<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
+<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
+<glyph unicode="&#xe087;" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
+<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
+<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
+<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
+<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
+<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
+<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
+<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
+<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
+<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
+<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
+<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
+<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
+<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
+<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
+<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
+<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
+<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
+<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
+<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
+<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
+<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
+<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
+<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
+<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
+<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
+<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
+<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
+<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
+<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
+<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
+<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
+<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
+<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
+<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
+<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
+<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
+<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
+<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
+<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
+<glyph unicode="&#xe130;" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
+<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
+<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
+<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
+<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
+<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
+<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
+<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
+<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
+<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
+<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
+<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
+<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
+<glyph unicode="&#xe143;" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
+<glyph unicode="&#xe144;" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
+<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
+<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
+<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
+<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
+<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
+<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
+<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
+<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
+<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
+<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
+<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
+<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
+<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
+<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
+<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
+<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
+<glyph unicode="&#xe162;" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
+<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
+<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
+<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
+<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
+<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
+<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
+<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
+<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
+<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
+<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
+<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
+<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
+<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
+<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
+<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
+<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
+<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
+<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
+<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
+<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
+<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
+<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
+<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
+<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
+<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
+<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
+<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
+<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
+<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
+<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
+<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
+<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
+<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
+</font>
+</defs></svg> 
\ No newline at end of file
diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..67fa00bf83801d2fa568546b982c80d27f6ef74e
Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.ttf differ
diff --git a/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 0000000000000000000000000000000000000000..8c54182aa5d4d1ab3c9171976b615c1dcb1dc187
Binary files /dev/null and b/libs/fmt/doc/_static/fonts/glyphicons-halflings-regular.woff differ
diff --git a/libs/fmt/doc/_templates/layout.html b/libs/fmt/doc/_templates/layout.html
new file mode 100644
index 0000000000000000000000000000000000000000..333a606c88fa1bc872595842e1e13485c78b639f
--- /dev/null
+++ b/libs/fmt/doc/_templates/layout.html
@@ -0,0 +1,148 @@
+{% extends "!layout.html" %}
+
+{% block extrahead %}
+<meta name="description" content="Small, safe and fast formatting library">
+<meta name="keywords" content="C++, formatting, printf, string, library">
+<meta name="author" content="Victor Zverovich">
+<link rel="stylesheet" href="_static/fmt.css">
+{# Google Analytics #}
+<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20116650-4"></script>
+<script>
+  window.dataLayer = window.dataLayer || [];
+  function gtag(){dataLayer.push(arguments);}
+  gtag('js', new Date());
+
+  gtag('config', 'UA-20116650-4');
+</script>
+{% endblock %}
+
+{%- macro searchform(classes, button) %}
+<form class="{{classes}}" role="search" action="{{ pathto('search') }}"
+      method="get">
+  <div class="form-group">
+    <input type="text" name="q" class="form-control"
+           {{ 'placeholder="Search"' if not button }} >
+  </div>
+  <input type="hidden" name="check_keywords" value="yes" />
+  <input type="hidden" name="area" value="default" />
+  {% if button %}
+    <input type="submit" class="btn btn-default" value="search">
+  {% endif %}
+</form>
+{%- endmacro %}
+
+{% block header %}
+<nav class="navbar navbar-inverse">
+  <div class="tb-container">
+    <div class="row">
+      <div class="navbar-content">
+        {# Brand and toggle get grouped for better mobile display #}
+        <div class="navbar-header">
+          <button type="button" class="navbar-toggle collapsed"
+                  data-toggle="collapse" data-target=".navbar-collapse">
+            <span class="sr-only">Toggle navigation</span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+            <span class="icon-bar"></span>
+          </button>
+          <a class="navbar-brand" href="index.html">{fmt}</a>
+        </div>
+
+        {# Collect the nav links, forms, and other content for toggling #}
+        <div class="collapse navbar-collapse">
+          <ul class="nav navbar-nav">
+            <li class="dropdown">
+              <a href="#" class="dropdown-toggle" data-toggle="dropdown"
+                 role="button" aria-expanded="false">{{ version }}
+                <span class="caret"></span></a>
+              <ul class="dropdown-menu" role="menu">
+                {% for v in versions.split(',') %}
+                <li><a href="https://fmt.dev/{{v}}">{{v}}</a></li>
+                {% endfor %}
+              </ul>
+            </li>
+            {% for name in ['Contents', 'Usage', 'API', 'Syntax'] %}
+              {% if pagename == name.lower() %}
+              <li class="active"><a href="{{name.lower()}}.html">{{name}}
+                <span class="sr-only">(current)</span></a></li>
+              {%else%}
+              <li><a href="{{name.lower()}}.html">{{name}}</a></li>
+              {%endif%}
+            {% endfor %}
+          </ul>
+          {% if pagename != 'search' %}
+            {{ searchform('navbar-form navbar-right', False) }}
+          {%endif%}
+        </div> {# /.navbar-collapse #}
+      </div> {# /.col-md-offset-2 #}
+    </div> {# /.row #}
+  </div> {# /.tb-container #}
+</nav>
+{% if pagename == "index" %}
+{% set download_url = 'https://github.com/fmtlib/fmt/releases/download' %}
+<div class="jumbotron">
+  <div class="tb-container">
+    <h1>{fmt}</h1>
+    <p class="lead">A modern formatting library</p>
+    <div class="btn-group" role="group">
+      {% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
+      <a class="btn btn-success"
+         href="{{download_url}}/{{version}}/{{name}}-{{version}}.zip">
+           <span class="glyphicon glyphicon-download"></span> Download
+      </a>
+      <button type="button" class="btn btn-success dropdown-toggle"
+        data-toggle="dropdown"><span class="caret"></span></button>
+      <ul class="dropdown-menu">
+      {% for v in versions.split(',') %}
+      {% set name = 'fmt' if v.split('.')[0]|int >= 3 else 'cppformat' %}
+        <li><a href="{{download_url}}/{{v}}/{{name}}-{{v}}.zip">Version {{v}}
+          </a></li>
+      {% endfor %}
+      </ul>
+    </div>
+  </div>
+</div>
+{% endif %}
+{% endblock %}
+
+{# Disable relbars. #}
+{% block relbar1 %}
+{% endblock %}
+{% block relbar2 %}
+{% endblock %}
+
+{% block content %}
+<div class="tb-container">
+  <div class="row">
+    {# Sidebar is currently disabled.
+    <div class="bs-sidebar">
+      <div class="sphinxsidebar" role="navigation" aria-label="main navigation">
+        <div class="sphinxsidebarwrapper">
+          {%- block sidebarlogo %}
+          {%- if logo %}
+            <p class="logo"><a href="{{ pathto(master_doc) }}">
+              <img class="logo" src="{{ pathto('_static/' + logo, 1) }}"
+                   alt="Logo"/>
+            </a></p>
+          {%- endif %}
+          {%- endblock %}
+          {%- for sidebartemplate in sidebars %}
+          {%- include sidebartemplate %}
+          {%- endfor %}
+        </div>
+      </div>
+    </div>
+    #}
+
+    <div class="content">
+      {% block body %} {% endblock %}
+    </div>
+  </div>
+</div>
+{% endblock %}
+
+{% block footer %}
+{{ super() }}
+{# Placed at the end of the document so the pages load faster. #}
+<script src="_static/bootstrap.min.js"></script>
+{% endblock %}
diff --git a/libs/fmt/doc/_templates/search.html b/libs/fmt/doc/_templates/search.html
new file mode 100644
index 0000000000000000000000000000000000000000..488e0b5d255af5a803b7339bf40beba4eb6d0aa5
--- /dev/null
+++ b/libs/fmt/doc/_templates/search.html
@@ -0,0 +1,55 @@
+{#
+    basic/search.html
+    ~~~~~~~~~~~~~~~~~
+
+    Template for the search page.
+
+    :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- extends "layout.html" %}
+{% set title = _('Search') %}
+{% set script_files = script_files + ['_static/searchtools.js'] %}
+{% block extrahead %}
+  <script type="text/javascript">
+    jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
+  </script>
+  {# this is used when loading the search index using $.ajax fails,
+     such as on Chrome for documents on localhost #}
+  <script type="text/javascript" id="searchindexloader"></script>
+  {{ super() }}
+{% endblock %}
+{% block body %}
+  <h1 id="search-documentation">{{ _('Search') }}</h1>
+  <div id="fallback" class="admonition warning">
+  <script type="text/javascript">$('#fallback').hide();</script>
+  <p>
+    {% trans %}Please activate JavaScript to enable the search
+    functionality.{% endtrans %}
+  </p>
+  </div>
+  <p>
+    {% trans %}From here you can search these documents. Enter your search
+    words into the box below and click "search". Note that the search
+    function will automatically search for all of the words. Pages
+    containing fewer words won't appear in the result list.{% endtrans %}
+  </p>
+  {{ searchform('form-inline', True) }}
+  {% if search_performed %}
+    <h2>{{ _('Search Results') }}</h2>
+    {% if not search_results %}
+      <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
+    {% endif %}
+  {% endif %}
+  <div id="search-results">
+  {% if search_results %}
+    <ul>
+    {% for href, caption, context in search_results %}
+      <li><a href="{{ pathto(item.href) }}">{{ caption }}</a>
+        <div class="context">{{ context|e }}</div>
+      </li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+  </div>
+{% endblock %}
diff --git a/libs/fmt/doc/api.rst b/libs/fmt/doc/api.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2e1bce3fc89c2bca0662c4a65a4c8ec29d3c0cc9
--- /dev/null
+++ b/libs/fmt/doc/api.rst
@@ -0,0 +1,544 @@
+.. _string-formatting-api:
+
+*************
+API Reference
+*************
+
+The {fmt} library API consists of the following parts:
+
+* :ref:`fmt/core.h <core-api>`: the core API providing argument handling
+  facilities and a lightweight subset of formatting functions
+* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
+  format string checks, wide string, output iterator and user-defined type
+  support
+* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
+  and tuples
+* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
+* :ref:`fmt/compile.h <compile-api>`: format string compilation
+* :ref:`fmt/color.h <color-api>`: terminal color and text style
+* :ref:`fmt/os.h <os-api>`: system APIs
+* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
+* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
+
+All functions and types provided by the library reside in namespace ``fmt`` and
+macros have prefix ``FMT_``.
+
+.. _core-api:
+
+Core API
+========
+
+``fmt/core.h`` defines the core API which provides argument handling facilities
+and a lightweight subset of formatting functions. It is only beneficial when
+using {fmt} as a library and not in the header-only mode.
+
+The following functions use :ref:`format string syntax <syntax>`
+similar to that of Python's `str.format
+<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
+They take *format_str* and *args* as arguments.
+
+*format_str* is a format string that contains literal text and replacement
+fields surrounded by braces ``{}``. The fields are replaced with formatted
+arguments in the resulting string. A function taking *format_str* doesn't
+participate in an overload resolution if the latter is not a string.
+
+*args* is an argument list representing objects to be formatted.
+
+.. _format:
+
+.. doxygenfunction:: format(const S &format_str, Args&&... args)
+.. doxygenfunction:: vformat(const S &format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args)
+
+.. doxygenfunction:: format_to(OutputIt out, const S&, const Args&... args)
+.. doxygenfunction:: format_to_n(OutputIt out, size_t n, const S&, const Args&... args)
+.. doxygenfunction:: formatted_size(string_view format_str, Args&&... args)
+
+.. doxygenstruct:: fmt::format_to_n_result
+   :members:
+
+.. _print:
+
+.. doxygenfunction:: fmt::print(const S &format_str, Args&&... args)
+.. doxygenfunction:: vprint(string_view, format_args)
+
+.. doxygenfunction:: print(std::FILE *f, const S &format_str, Args&&... args)
+.. doxygenfunction:: vprint(std::FILE*, string_view, format_args)
+
+Named Arguments
+---------------
+
+.. doxygenfunction:: fmt::arg(const S&, const T&)
+
+Named arguments are not supported in compile-time checks at the moment.
+
+Argument Lists
+--------------
+
+You can create your own formatting function with compile-time checks and small
+binary footprint, for example (https://godbolt.org/z/oba4Mc):
+
+.. code:: c++
+
+    #include <fmt/format.h>
+
+    void vlog(const char* file, int line, fmt::string_view format,
+              fmt::format_args args) {
+      fmt::print("{}: {}: ", file, line);
+      fmt::vprint(format, args);
+    }
+
+    template <typename S, typename... Args>
+    void log(const char* file, int line, const S& format, Args&&... args) {
+      vlog(file, line, format,
+          fmt::make_args_checked<Args...>(format, args...));
+    }
+
+    #define MY_LOG(format, ...) \
+      log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
+
+    MY_LOG("invalid squishiness: {}", 42);
+
+Note that ``vlog`` is not parameterized on argument types which improves compile
+times and reduces binary code size compared to a fully parameterized version.
+
+.. doxygenfunction:: fmt::make_args_checked(const S&, const remove_reference_t<Args>&...)
+
+.. doxygenfunction:: fmt::make_format_args(const Args&...)
+
+.. doxygenclass:: fmt::format_arg_store
+   :members:
+
+.. doxygenclass:: fmt::dynamic_format_arg_store
+   :members:
+
+.. doxygenclass:: fmt::basic_format_args
+   :members:
+
+.. doxygenstruct:: fmt::format_args
+
+.. doxygenclass:: fmt::basic_format_arg
+   :members:
+
+.. doxygenclass:: fmt::basic_format_context
+   :members:
+
+.. doxygentypedef:: fmt::format_context
+.. doxygentypedef:: fmt::wformat_context
+
+Compatibility
+-------------
+
+.. doxygenclass:: fmt::basic_string_view
+   :members:
+
+.. doxygentypedef:: fmt::string_view
+.. doxygentypedef:: fmt::wstring_view
+
+Locale
+------
+
+All formatting is locale-independent by default. Use the ``'L'`` format
+specifier to insert the appropriate number separator characters from the
+locale::
+
+  #include <fmt/core.h>
+  #include <locale>
+
+  std::locale::global(std::locale("en_US.UTF-8"));
+  auto s = fmt::format("{:L}", 1000000);  // s == "1,000,000"
+
+.. _format-api:
+
+Format API
+==========
+
+``fmt/format.h`` defines the full format API providing compile-time format
+string checks, wide string, output iterator and user-defined type support.
+
+Compile-time Format String Checks
+---------------------------------
+
+Compile-time checks are enabled when using ``FMT_STRING``. They support built-in
+and string types as well as user-defined types with ``constexpr`` ``parse``
+functions in their ``formatter`` specializations.
+
+.. doxygendefine:: FMT_STRING
+
+To force the use of compile-time checks, define the preprocessor variable
+``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
+will fail to compile with regular strings. Runtime-checked
+formatting is still possible using ``fmt::vformat``, ``fmt::vprint``, etc.
+
+.. _udt:
+
+Formatting User-defined Types
+-----------------------------
+
+To make a user-defined type formattable, specialize the ``formatter<T>`` struct
+template and implement ``parse`` and ``format`` methods::
+
+  #include <fmt/format.h>
+
+  struct point { double x, y; };
+
+  template <>
+  struct fmt::formatter<point> {
+    // Presentation format: 'f' - fixed, 'e' - exponential.
+    char presentation = 'f';
+
+    // Parses format specifications of the form ['f' | 'e'].
+    constexpr auto parse(format_parse_context& ctx) {
+    // auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
+      // [ctx.begin(), ctx.end()) is a character range that contains a part of
+      // the format string starting from the format specifications to be parsed,
+      // e.g. in
+      //
+      //   fmt::format("{:f} - point of interest", point{1, 2});
+      //
+      // the range will contain "f} - point of interest". The formatter should
+      // parse specifiers until '}' or the end of the range. In this example
+      // the formatter should parse the 'f' specifier and return an iterator
+      // pointing to '}'.
+
+      // Parse the presentation format and store it in the formatter:
+      auto it = ctx.begin(), end = ctx.end();
+      if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
+
+      // Check if reached the end of the range:
+      if (it != end && *it != '}')
+        throw format_error("invalid format");
+
+      // Return an iterator past the end of the parsed range:
+      return it;
+    }
+
+    // Formats the point p using the parsed format specification (presentation)
+    // stored in this formatter.
+    template <typename FormatContext>
+    auto format(const point& p, FormatContext& ctx) {
+    // auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
+      // ctx.out() is an output iterator to write to.
+      return format_to(
+          ctx.out(),
+          presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
+          p.x, p.y);
+    }
+  };
+
+Then you can pass objects of type ``point`` to any formatting function::
+
+  point p = {1, 2};
+  std::string s = fmt::format("{:f}", p);
+  // s == "(1.0, 2.0)"
+
+You can also reuse existing formatters via inheritance or composition, for
+example::
+
+  enum class color {red, green, blue};
+
+  template <> struct fmt::formatter<color>: formatter<string_view> {
+    // parse is inherited from formatter<string_view>.
+    template <typename FormatContext>
+    auto format(color c, FormatContext& ctx) {
+      string_view name = "unknown";
+      switch (c) {
+      case color::red:   name = "red"; break;
+      case color::green: name = "green"; break;
+      case color::blue:  name = "blue"; break;
+      }
+      return formatter<string_view>::format(name, ctx);
+    }
+  };
+
+Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
+all string format specifications, for example
+
+.. code-block:: c++
+
+   fmt::format("{:>10}", color::blue)
+
+will return ``"      blue"``.
+
+You can also write a formatter for a hierarchy of classes::
+
+  #include <type_traits>
+  #include <fmt/format.h>
+
+  struct A {
+    virtual ~A() {}
+    virtual std::string name() const { return "A"; }
+  };
+
+  struct B : A {
+    virtual std::string name() const { return "B"; }
+  };
+
+  template <typename T>
+  struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
+      fmt::formatter<std::string> {
+    template <typename FormatCtx>
+    auto format(const A& a, FormatCtx& ctx) {
+      return fmt::formatter<std::string>::format(a.name(), ctx);
+    }
+  };
+
+  int main() {
+    B b;
+    A& a = b;
+    fmt::print("{}", a); // prints "B"
+  }
+
+If a type provides both a ``formatter`` specialization and an implicit
+conversion to a formattable type, the specialization takes precedence over the
+conversion.
+
+.. doxygenclass:: fmt::basic_format_parse_context
+   :members:
+
+Literal-based API
+-----------------
+
+The following user-defined literals are defined in ``fmt/format.h``.
+
+.. doxygenfunction:: operator""_format(const char *s, size_t n)
+
+.. doxygenfunction:: operator""_a(const char *s, size_t)
+
+Utilities
+---------
+
+.. doxygenstruct:: fmt::is_char
+
+.. doxygentypedef:: fmt::char_t
+
+.. doxygenfunction:: fmt::ptr(const T *p)
+.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p)
+.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p)
+.. doxygenfunction:: fmt::ptr(T (*fn)(Args...))
+
+.. doxygenfunction:: fmt::to_string(const T &value)
+
+.. doxygenfunction:: fmt::to_wstring(const T &value)
+
+.. doxygenfunction:: fmt::to_string_view(const Char *s)
+
+.. doxygenfunction:: fmt::join(Range &&range, string_view sep)
+
+.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep)
+
+.. doxygenclass:: fmt::detail::buffer
+   :members:
+
+.. doxygenclass:: fmt::basic_memory_buffer
+   :protected-members:
+   :members:
+
+System Errors
+-------------
+
+fmt does not use ``errno`` to communicate errors to the user, but it may call
+system functions which set ``errno``. Users should not make any assumptions about
+the value of ``errno`` being preserved by library functions.
+
+.. doxygenclass:: fmt::system_error
+   :members:
+
+.. doxygenfunction:: fmt::format_system_error
+
+.. doxygenclass:: fmt::windows_error
+   :members:
+
+Custom Allocators
+-----------------
+
+The {fmt} library supports custom dynamic memory allocators.
+A custom allocator class can be specified as a template argument to
+:class:`fmt::basic_memory_buffer`::
+
+    using custom_memory_buffer = 
+      fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>;
+
+It is also possible to write a formatting function that uses a custom
+allocator::
+
+    using custom_string =
+      std::basic_string<char, std::char_traits<char>, custom_allocator>;
+
+    custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
+                          fmt::format_args args) {
+      custom_memory_buffer buf(alloc);
+      fmt::vformat_to(buf, format_str, args);
+      return custom_string(buf.data(), buf.size(), alloc);
+    }
+
+    template <typename ...Args>
+    inline custom_string format(custom_allocator alloc,
+                                fmt::string_view format_str,
+                                const Args& ... args) {
+      return vformat(alloc, format_str, fmt::make_format_args(args...));
+    }
+
+The allocator will be used for the output container only. Formatting functions
+normally don't do any allocations for built-in and string types except for
+non-default floating-point formatting that occasionally falls back on
+``sprintf``.
+
+.. _ranges-api:
+
+Ranges and Tuple Formatting
+===========================
+
+The library also supports convenient formatting of ranges and tuples::
+
+  #include <fmt/ranges.h>
+
+  std::tuple<char, int, float> t{'a', 1, 2.0f};
+  // Prints "('a', 1, 2.0)"
+  fmt::print("{}", t);
+
+
+NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
+``format.h`` header, but expect this to change in the future.
+
+Using ``fmt::join``, you can separate tuple elements with a custom separator::
+
+  #include <fmt/ranges.h>
+
+  std::tuple<int, char> t = {1, 'a'};
+  // Prints "1, a"
+  fmt::print("{}", fmt::join(t, ", "));
+
+.. _chrono-api:
+
+Date and Time Formatting
+========================
+
+``fmt/chrono.h`` provides formatters for
+
+* `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
+* `std::chrono::time_point
+  <https://en.cppreference.com/w/cpp/chrono/time_point>`_
+* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_
+
+The format syntax is described in :ref:`chrono-specs`.
+
+**Example**::
+
+  #include <fmt/chrono.h>
+
+  int main() {
+    std::time_t t = std::time(nullptr);
+
+    // Prints "The date is 2020-11-07." (with the current date):
+    fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
+
+    using namespace std::literals::chrono_literals;
+
+    // Prints "Default format: 42s 100ms":
+    fmt::print("Default format: {} {}\n", 42s, 100ms);
+
+    // Prints "strftime-like format: 03:15:30":
+    fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
+  }
+
+.. doxygenfunction:: localtime(std::time_t time)
+
+.. doxygenfunction:: gmtime(std::time_t time)
+
+.. _compile-api:
+
+Format string compilation
+=========================
+
+``fmt/compile.h`` provides format string compilation support when using
+``FMT_COMPILE``. Format strings are parsed, checked and converted into efficient
+formatting code at compile-time. This supports arguments of built-in and string
+types as well as user-defined types with ``constexpr`` ``parse`` functions in
+their ``formatter`` specializations. Format string compilation can generate more
+binary code compared to the default API and is only recommended in places where
+formatting is a performance bottleneck.
+
+.. doxygendefine:: FMT_COMPILE
+
+.. _color-api:
+
+Terminal color and text style
+=============================
+
+``fmt/color.h`` provides support for terminal color and text style output.
+
+.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args)
+
+.. doxygenfunction:: fg(detail::color_type)
+
+.. doxygenfunction:: bg(detail::color_type)
+
+.. _os-api:
+
+System APIs
+===========
+
+.. doxygenclass:: fmt::ostream
+   :members:
+
+.. _ostream-api:
+
+``std::ostream`` Support
+========================
+
+``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
+user-defined types that have an overloaded insertion operator (``operator<<``)::
+
+  #include <fmt/ostream.h>
+
+  class date {
+    int year_, month_, day_;
+  public:
+    date(int year, int month, int day): year_(year), month_(month), day_(day) {}
+
+    friend std::ostream& operator<<(std::ostream& os, const date& d) {
+      return os << d.year_ << '-' << d.month_ << '-' << d.day_;
+    }
+  };
+
+  std::string s = fmt::format("The date is {}", date(2012, 12, 9));
+  // s == "The date is 2012-12-9"
+
+{fmt} only supports insertion operators that are defined in the same namespaces
+as the types they format and can be found with the argument-dependent lookup.
+
+.. doxygenfunction:: print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args)
+
+.. _printf-api:
+
+``printf`` Formatting
+=====================
+
+The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
+The following functions use `printf format string syntax
+<https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
+the POSIX extension for positional arguments. Unlike their standard
+counterparts, the ``fmt`` functions are type-safe and throw an exception if an
+argument type doesn't match its format specification.
+
+.. doxygenfunction:: printf(const S &format_str, const Args&... args)
+
+.. doxygenfunction:: fprintf(std::FILE *f, const S &format, const Args&... args)
+
+.. doxygenfunction:: fprintf(std::basic_ostream<Char> &os, const S &format_str, const Args&... args)
+
+.. doxygenfunction:: sprintf(const S&, const Args&...)
+
+Compatibility with C++20 ``std::format``
+========================================
+
+{fmt} implements nearly all of the `C++20 formatting library
+<https://en.cppreference.com/w/cpp/utility/format>`_ with the following
+differences:
+
+* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
+  collisions with standard library implementations.
+* Width calculation doesn't use grapheme clusterization. The latter has been
+  implemented in a separate branch but hasn't been integrated yet.
+* Chrono formatting doesn't support C++20 date types since they are not provided
+  by standard library implementations.
diff --git a/libs/fmt/doc/basic-bootstrap/README b/libs/fmt/doc/basic-bootstrap/README
new file mode 100644
index 0000000000000000000000000000000000000000..ba67700dfd0229e1883f5a10ba3dce838269c4a5
--- /dev/null
+++ b/libs/fmt/doc/basic-bootstrap/README
@@ -0,0 +1,2 @@
+Sphinx basic theme with Bootstrap support. Modifications are kept to
+a minimum to simplify integration in case of changes to Sphinx theming.
diff --git a/libs/fmt/doc/basic-bootstrap/layout.html b/libs/fmt/doc/basic-bootstrap/layout.html
new file mode 100644
index 0000000000000000000000000000000000000000..a257b03d3e60349917e3a3bbe2cc452c074bb947
--- /dev/null
+++ b/libs/fmt/doc/basic-bootstrap/layout.html
@@ -0,0 +1,206 @@
+{#
+    basic/layout.html
+    ~~~~~~~~~~~~~~~~~
+
+    Master layout template for Sphinx themes.
+
+    :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block doctype -%}
+<!DOCTYPE html>
+{%- endblock %}
+{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
+{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
+{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
+                         (sidebars != []) %}
+{%- set url_root = pathto('', 1) %}
+{# XXX necessary? #}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+{%- macro relbar() %}
+    <div class="related" role="navigation" aria-label="related navigation">
+      <h3>{{ _('Navigation') }}</h3>
+      <ul>
+        {%- for rellink in rellinks %}
+        <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
+          <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+             {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
+          {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
+        {%- endfor %}
+        {%- block rootrellink %}
+        <li class="nav-item nav-item-0"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
+        {%- endblock %}
+        {%- for parent in parents %}
+          <li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
+        {%- endfor %}
+        {%- block relbaritems %} {% endblock %}
+      </ul>
+    </div>
+{%- endmacro %}
+
+{%- macro sidebar() %}
+      {%- if render_sidebar %}
+      <div class="sphinxsidebar" role="navigation" aria-label="main navigation">
+        <div class="sphinxsidebarwrapper">
+          {%- block sidebarlogo %}
+          {%- if logo %}
+            <p class="logo"><a href="{{ pathto(master_doc) }}">
+              <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+            </a></p>
+          {%- endif %}
+          {%- endblock %}
+          {%- if sidebars != None %}
+            {#- new style sidebar: explicitly include/exclude templates #}
+            {%- for sidebartemplate in sidebars %}
+            {%- include sidebartemplate %}
+            {%- endfor %}
+          {%- else %}
+            {#- old style sidebars: using blocks -- should be deprecated #}
+            {%- block sidebartoc %}
+            {%- include "localtoc.html" %}
+            {%- endblock %}
+            {%- block sidebarrel %}
+            {%- include "relations.html" %}
+            {%- endblock %}
+            {%- block sidebarsourcelink %}
+            {%- include "sourcelink.html" %}
+            {%- endblock %}
+            {%- if customsidebar %}
+            {%- include customsidebar %}
+            {%- endif %}
+            {%- block sidebarsearch %}
+            {%- include "searchbox.html" %}
+            {%- endblock %}
+          {%- endif %}
+        </div>
+      </div>
+      {%- endif %}
+{%- endmacro %}
+
+{%- macro script() %}
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '{{ url_root }}',
+        VERSION:     '{{ release|e }}',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
+        HAS_SOURCE:  {{ has_source|lower }},
+        SOURCELINK_SUFFIX:  '{{ sourcelink_suffix }}'
+      };
+    </script>
+    {%- for scriptfile in script_files %}
+    <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+{%- endmacro %}
+
+{%- macro css() %}
+    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+    <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
+    {%- for cssfile in css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+    {%- endfor %}
+{%- endmacro %}
+
+<html lang="en">
+  <head>
+    <meta charset="{{ encoding }}">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    {# The above 3 meta tags *must* come first in the head; any other head content
+       must come *after* these tags. #}
+    {{ metatags }}
+    {%- block htmltitle %}
+    <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+    {%- endblock %}
+    {{ css() }}
+    {%- if not embedded %}
+    {{ script() }}
+    {%- if use_opensearch %}
+    <link rel="search" type="application/opensearchdescription+xml"
+          title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
+          href="{{ pathto('_static/opensearch.xml', 1) }}"/>
+    {%- endif %}
+    {%- if favicon %}
+    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+    {%- endif %}
+    {%- endif %}
+{%- block linktags %}
+    {%- if hasdoc('about') %}
+    <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+    <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
+    {%- endif %}
+    {%- if hasdoc('search') %}
+    <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+    <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
+    {%- endif %}
+    {%- if parents %}
+    <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
+    {%- endif %}
+    {%- if next %}
+    <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
+    {%- endif %}
+    {%- if prev %}
+    <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
+    {%- endif %}
+{%- endblock %}
+{%- block extrahead %} {% endblock %}
+  </head>
+  <body role="document">
+{%- block header %}{% endblock %}
+
+{%- block relbar1 %}{{ relbar() }}{% endblock %}
+
+{%- block content %}
+  {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
+
+    <div class="document">
+  {%- block document %}
+      <div class="documentwrapper">
+      {%- if render_sidebar %}
+        <div class="bodywrapper">
+      {%- endif %}
+          <div class="body" role="main">
+            {% block body %} {% endblock %}
+          </div>
+      {%- if render_sidebar %}
+        </div>
+      {%- endif %}
+      </div>
+  {%- endblock %}
+
+  {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
+      <div class="clearer"></div>
+    </div>
+{%- endblock %}
+
+{%- block relbar2 %}{{ relbar() }}{% endblock %}
+
+{%- block footer %}
+    <div class="footer" role="contentinfo">
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    {%- if show_sphinx %}
+      {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
+    {%- endif %}
+    </div>
+{%- endblock %}
+  </body>
+</html>
diff --git a/libs/fmt/doc/basic-bootstrap/theme.conf b/libs/fmt/doc/basic-bootstrap/theme.conf
new file mode 100644
index 0000000000000000000000000000000000000000..89e03bbdac5e2ff5c08eb4bc4918067516932c5c
--- /dev/null
+++ b/libs/fmt/doc/basic-bootstrap/theme.conf
@@ -0,0 +1,2 @@
+[theme]
+inherit = basic
diff --git a/libs/fmt/doc/bootstrap/alerts.less b/libs/fmt/doc/bootstrap/alerts.less
new file mode 100644
index 0000000000000000000000000000000000000000..c4199db927e795b6d7d35a39d475ef3087e2dfec
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/alerts.less
@@ -0,0 +1,73 @@
+//
+// Alerts
+// --------------------------------------------------
+
+
+// Base styles
+// -------------------------
+
+.alert {
+  padding: @alert-padding;
+  margin-bottom: @line-height-computed;
+  border: 1px solid transparent;
+  border-radius: @alert-border-radius;
+
+  // Headings for larger alerts
+  h4 {
+    margin-top: 0;
+    // Specified for the h4 to prevent conflicts of changing @headings-color
+    color: inherit;
+  }
+
+  // Provide class for links that match alerts
+  .alert-link {
+    font-weight: @alert-link-font-weight;
+  }
+
+  // Improve alignment and spacing of inner content
+  > p,
+  > ul {
+    margin-bottom: 0;
+  }
+
+  > p + p {
+    margin-top: 5px;
+  }
+}
+
+// Dismissible alerts
+//
+// Expand the right padding and account for the close button's positioning.
+
+.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.
+.alert-dismissible {
+  padding-right: (@alert-padding + 20);
+
+  // Adjust close link position
+  .close {
+    position: relative;
+    top: -2px;
+    right: -21px;
+    color: inherit;
+  }
+}
+
+// Alternate styles
+//
+// Generate contextual modifier classes for colorizing the alert.
+
+.alert-success {
+  .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);
+}
+
+.alert-info {
+  .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);
+}
+
+.alert-warning {
+  .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);
+}
+
+.alert-danger {
+  .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);
+}
diff --git a/libs/fmt/doc/bootstrap/badges.less b/libs/fmt/doc/bootstrap/badges.less
new file mode 100644
index 0000000000000000000000000000000000000000..c70bb939ebe253a17a39419d595d0038c7a683bc
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/badges.less
@@ -0,0 +1,66 @@
+//
+// Badges
+// --------------------------------------------------
+
+
+// Base class
+.badge {
+  display: inline-block;
+  min-width: 10px;
+  padding: 3px 7px;
+  font-size: @font-size-small;
+  font-weight: @badge-font-weight;
+  color: @badge-color;
+  line-height: @badge-line-height;
+  vertical-align: baseline;
+  white-space: nowrap;
+  text-align: center;
+  background-color: @badge-bg;
+  border-radius: @badge-border-radius;
+
+  // Empty badges collapse automatically (not available in IE8)
+  &:empty {
+    display: none;
+  }
+
+  // Quick fix for badges in buttons
+  .btn & {
+    position: relative;
+    top: -1px;
+  }
+
+  .btn-xs &,
+  .btn-group-xs > .btn & {
+    top: 0;
+    padding: 1px 5px;
+  }
+
+  // Hover state, but only for links
+  a& {
+    &:hover,
+    &:focus {
+      color: @badge-link-hover-color;
+      text-decoration: none;
+      cursor: pointer;
+    }
+  }
+
+  // Account for badges in navs
+  .list-group-item.active > &,
+  .nav-pills > .active > a > & {
+    color: @badge-active-color;
+    background-color: @badge-active-bg;
+  }
+
+  .list-group-item > & {
+    float: right;
+  }
+
+  .list-group-item > & + & {
+    margin-right: 5px;
+  }
+
+  .nav-pills > li > a > & {
+    margin-left: 3px;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/bootstrap.less b/libs/fmt/doc/bootstrap/bootstrap.less
new file mode 100644
index 0000000000000000000000000000000000000000..61b77474f9ad6ec9f9b5dda2ed67703f7580fde2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/bootstrap.less
@@ -0,0 +1,50 @@
+// Core variables and mixins
+@import "variables.less";
+@import "mixins.less";
+
+// Reset and dependencies
+@import "normalize.less";
+@import "print.less";
+@import "glyphicons.less";
+
+// Core CSS
+@import "scaffolding.less";
+@import "type.less";
+@import "code.less";
+@import "grid.less";
+@import "tables.less";
+@import "forms.less";
+@import "buttons.less";
+
+// Components
+@import "component-animations.less";
+@import "dropdowns.less";
+@import "button-groups.less";
+@import "input-groups.less";
+@import "navs.less";
+@import "navbar.less";
+@import "breadcrumbs.less";
+@import "pagination.less";
+@import "pager.less";
+@import "labels.less";
+@import "badges.less";
+@import "jumbotron.less";
+@import "thumbnails.less";
+@import "alerts.less";
+@import "progress-bars.less";
+@import "media.less";
+@import "list-group.less";
+@import "panels.less";
+@import "responsive-embed.less";
+@import "wells.less";
+@import "close.less";
+
+// Components w/ JavaScript
+@import "modals.less";
+@import "tooltip.less";
+@import "popovers.less";
+@import "carousel.less";
+
+// Utility classes
+@import "utilities.less";
+@import "responsive-utilities.less";
diff --git a/libs/fmt/doc/bootstrap/breadcrumbs.less b/libs/fmt/doc/bootstrap/breadcrumbs.less
new file mode 100644
index 0000000000000000000000000000000000000000..cb01d503fbe5f7615e53c989bbee20c07c86e7ef
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/breadcrumbs.less
@@ -0,0 +1,26 @@
+//
+// Breadcrumbs
+// --------------------------------------------------
+
+
+.breadcrumb {
+  padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;
+  margin-bottom: @line-height-computed;
+  list-style: none;
+  background-color: @breadcrumb-bg;
+  border-radius: @border-radius-base;
+
+  > li {
+    display: inline-block;
+
+    + li:before {
+      content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space
+      padding: 0 5px;
+      color: @breadcrumb-color;
+    }
+  }
+
+  > .active {
+    color: @breadcrumb-active-color;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/button-groups.less b/libs/fmt/doc/bootstrap/button-groups.less
new file mode 100644
index 0000000000000000000000000000000000000000..13c1800deaaacf739cdb3a4eaba95ee986f656c4
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/button-groups.less
@@ -0,0 +1,243 @@
+//
+// Button groups
+// --------------------------------------------------
+
+// Make the div behave like a button
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: inline-block;
+  vertical-align: middle; // match .btn alignment given font-size hack above
+  > .btn {
+    position: relative;
+    float: left;
+    // Bring the "active" button to the front
+    &:hover,
+    &:focus,
+    &:active,
+    &.active {
+      z-index: 2;
+    }
+  }
+}
+
+// Prevent double borders when buttons are next to each other
+.btn-group {
+  .btn + .btn,
+  .btn + .btn-group,
+  .btn-group + .btn,
+  .btn-group + .btn-group {
+    margin-left: -1px;
+  }
+}
+
+// Optional: Group multiple button groups together for a toolbar
+.btn-toolbar {
+  margin-left: -5px; // Offset the first child's margin
+  &:extend(.clearfix all);
+
+  .btn-group,
+  .input-group {
+    float: left;
+  }
+  > .btn,
+  > .btn-group,
+  > .input-group {
+    margin-left: 5px;
+  }
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+  border-radius: 0;
+}
+
+// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
+.btn-group > .btn:first-child {
+  margin-left: 0;
+  &:not(:last-child):not(.dropdown-toggle) {
+    .border-right-radius(0);
+  }
+}
+// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+  .border-left-radius(0);
+}
+
+// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)
+.btn-group > .btn-group {
+  float: left;
+}
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group > .btn-group:first-child:not(:last-child) {
+  > .btn:last-child,
+  > .dropdown-toggle {
+    .border-right-radius(0);
+  }
+}
+.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  .border-left-radius(0);
+}
+
+// On active and open, don't show outline
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+
+
+// Sizing
+//
+// Remix the default button sizing classes into new ones for easier manipulation.
+
+.btn-group-xs > .btn { &:extend(.btn-xs); }
+.btn-group-sm > .btn { &:extend(.btn-sm); }
+.btn-group-lg > .btn { &:extend(.btn-lg); }
+
+
+// Split button dropdowns
+// ----------------------
+
+// Give the line between buttons some depth
+.btn-group > .btn + .dropdown-toggle {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+.btn-group > .btn-lg + .dropdown-toggle {
+  padding-left: 12px;
+  padding-right: 12px;
+}
+
+// The clickable button for toggling the menu
+// Remove the gradient and set the same inset shadow as the :active state
+.btn-group.open .dropdown-toggle {
+  .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+
+  // Show no shadow for `.btn-link` since it has no other button styles.
+  &.btn-link {
+    .box-shadow(none);
+  }
+}
+
+
+// Reposition the caret
+.btn .caret {
+  margin-left: 0;
+}
+// Carets in other button sizes
+.btn-lg .caret {
+  border-width: @caret-width-large @caret-width-large 0;
+  border-bottom-width: 0;
+}
+// Upside down carets for .dropup
+.dropup .btn-lg .caret {
+  border-width: 0 @caret-width-large @caret-width-large;
+}
+
+
+// Vertical button groups
+// ----------------------
+
+.btn-group-vertical {
+  > .btn,
+  > .btn-group,
+  > .btn-group > .btn {
+    display: block;
+    float: none;
+    width: 100%;
+    max-width: 100%;
+  }
+
+  // Clear floats so dropdown menus can be properly placed
+  > .btn-group {
+    &:extend(.clearfix all);
+    > .btn {
+      float: none;
+    }
+  }
+
+  > .btn + .btn,
+  > .btn + .btn-group,
+  > .btn-group + .btn,
+  > .btn-group + .btn-group {
+    margin-top: -1px;
+    margin-left: 0;
+  }
+}
+
+.btn-group-vertical > .btn {
+  &:not(:first-child):not(:last-child) {
+    border-radius: 0;
+  }
+  &:first-child:not(:last-child) {
+    border-top-right-radius: @border-radius-base;
+    .border-bottom-radius(0);
+  }
+  &:last-child:not(:first-child) {
+    border-bottom-left-radius: @border-radius-base;
+    .border-top-radius(0);
+  }
+}
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+  border-radius: 0;
+}
+.btn-group-vertical > .btn-group:first-child:not(:last-child) {
+  > .btn:last-child,
+  > .dropdown-toggle {
+    .border-bottom-radius(0);
+  }
+}
+.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+  .border-top-radius(0);
+}
+
+
+// Justified button groups
+// ----------------------
+
+.btn-group-justified {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+  border-collapse: separate;
+  > .btn,
+  > .btn-group {
+    float: none;
+    display: table-cell;
+    width: 1%;
+  }
+  > .btn-group .btn {
+    width: 100%;
+  }
+
+  > .btn-group .dropdown-menu {
+    left: auto;
+  }
+}
+
+
+// Checkbox and radio options
+//
+// In order to support the browser's form validation feedback, powered by the
+// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
+// `display: none;` or `visibility: hidden;` as that also hides the popover.
+// Simply visually hiding the inputs via `opacity` would leave them clickable in
+// certain cases which is prevented by using `clip` and `pointer-events`.
+// This way, we ensure a DOM element is visible to position the popover from.
+//
+// See https://github.com/twbs/bootstrap/pull/12794 and
+// https://github.com/twbs/bootstrap/pull/14559 for more information.
+
+[data-toggle="buttons"] {
+  > .btn,
+  > .btn-group > .btn {
+    input[type="radio"],
+    input[type="checkbox"] {
+      position: absolute;
+      clip: rect(0,0,0,0);
+      pointer-events: none;
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/buttons.less b/libs/fmt/doc/bootstrap/buttons.less
new file mode 100644
index 0000000000000000000000000000000000000000..5a746049e3cea5c5f912144e11850d56a36cf0d2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/buttons.less
@@ -0,0 +1,160 @@
+//
+// Buttons
+// --------------------------------------------------
+
+
+// Base styles
+// --------------------------------------------------
+
+.btn {
+  display: inline-block;
+  margin-bottom: 0; // For input.btn
+  font-weight: @btn-font-weight;
+  text-align: center;
+  vertical-align: middle;
+  touch-action: manipulation;
+  cursor: pointer;
+  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+  border: 1px solid transparent;
+  white-space: nowrap;
+  .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);
+  .user-select(none);
+
+  &,
+  &:active,
+  &.active {
+    &:focus,
+    &.focus {
+      .tab-focus();
+    }
+  }
+
+  &:hover,
+  &:focus,
+  &.focus {
+    color: @btn-default-color;
+    text-decoration: none;
+  }
+
+  &:active,
+  &.active {
+    outline: 0;
+    background-image: none;
+    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+  }
+
+  &.disabled,
+  &[disabled],
+  fieldset[disabled] & {
+    cursor: @cursor-disabled;
+    pointer-events: none; // Future-proof disabling of clicks
+    .opacity(.65);
+    .box-shadow(none);
+  }
+}
+
+
+// Alternate buttons
+// --------------------------------------------------
+
+.btn-default {
+  .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
+}
+.btn-primary {
+  .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
+}
+// Success appears as green
+.btn-success {
+  .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);
+}
+// Info appears as blue-green
+.btn-info {
+  .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);
+}
+// Warning appears as orange
+.btn-warning {
+  .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);
+}
+// Danger and error appear as red
+.btn-danger {
+  .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);
+}
+
+
+// Link buttons
+// -------------------------
+
+// Make a button look and behave like a link
+.btn-link {
+  color: @link-color;
+  font-weight: normal;
+  border-radius: 0;
+
+  &,
+  &:active,
+  &.active,
+  &[disabled],
+  fieldset[disabled] & {
+    background-color: transparent;
+    .box-shadow(none);
+  }
+  &,
+  &:hover,
+  &:focus,
+  &:active {
+    border-color: transparent;
+  }
+  &:hover,
+  &:focus {
+    color: @link-hover-color;
+    text-decoration: @link-hover-decoration;
+    background-color: transparent;
+  }
+  &[disabled],
+  fieldset[disabled] & {
+    &:hover,
+    &:focus {
+      color: @btn-link-disabled-color;
+      text-decoration: none;
+    }
+  }
+}
+
+
+// Button Sizes
+// --------------------------------------------------
+
+.btn-lg {
+  // line-height: ensure even-numbered height of button next to large input
+  .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
+}
+.btn-sm {
+  // line-height: ensure proper height of button next to small input
+  .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
+}
+.btn-xs {
+  .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);
+}
+
+
+// Block button
+// --------------------------------------------------
+
+.btn-block {
+  display: block;
+  width: 100%;
+}
+
+// Vertically space out multiple block buttons
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+
+// Specificity overrides
+input[type="submit"],
+input[type="reset"],
+input[type="button"] {
+  &.btn-block {
+    width: 100%;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/carousel.less b/libs/fmt/doc/bootstrap/carousel.less
new file mode 100644
index 0000000000000000000000000000000000000000..4bbe946d3aa8f36a49c9dd77306e02888634be87
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/carousel.less
@@ -0,0 +1,269 @@
+//
+// Carousel
+// --------------------------------------------------
+
+
+// Wrapper for the slide container and indicators
+.carousel {
+  position: relative;
+}
+
+.carousel-inner {
+  position: relative;
+  overflow: hidden;
+  width: 100%;
+
+  > .item {
+    display: none;
+    position: relative;
+    .transition(.6s ease-in-out left);
+
+    // Account for jankitude on images
+    > img,
+    > a > img {
+      &:extend(.img-responsive);
+      line-height: 1;
+    }
+
+    // WebKit CSS3 transforms for supported devices
+    @media all and (transform-3d), (-webkit-transform-3d) {
+      .transition-transform(~'0.6s ease-in-out');
+      .backface-visibility(~'hidden');
+      .perspective(1000);
+
+      &.next,
+      &.active.right {
+        .translate3d(100%, 0, 0);
+        left: 0;
+      }
+      &.prev,
+      &.active.left {
+        .translate3d(-100%, 0, 0);
+        left: 0;
+      }
+      &.next.left,
+      &.prev.right,
+      &.active {
+        .translate3d(0, 0, 0);
+        left: 0;
+      }
+    }
+  }
+
+  > .active,
+  > .next,
+  > .prev {
+    display: block;
+  }
+
+  > .active {
+    left: 0;
+  }
+
+  > .next,
+  > .prev {
+    position: absolute;
+    top: 0;
+    width: 100%;
+  }
+
+  > .next {
+    left: 100%;
+  }
+  > .prev {
+    left: -100%;
+  }
+  > .next.left,
+  > .prev.right {
+    left: 0;
+  }
+
+  > .active.left {
+    left: -100%;
+  }
+  > .active.right {
+    left: 100%;
+  }
+
+}
+
+// Left/right controls for nav
+// ---------------------------
+
+.carousel-control {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  width: @carousel-control-width;
+  .opacity(@carousel-control-opacity);
+  font-size: @carousel-control-font-size;
+  color: @carousel-control-color;
+  text-align: center;
+  text-shadow: @carousel-text-shadow;
+  // We can't have this transition here because WebKit cancels the carousel
+  // animation if you trip this while in the middle of another animation.
+
+  // Set gradients for backgrounds
+  &.left {
+    #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));
+  }
+  &.right {
+    left: auto;
+    right: 0;
+    #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));
+  }
+
+  // Hover/focus state
+  &:hover,
+  &:focus {
+    outline: 0;
+    color: @carousel-control-color;
+    text-decoration: none;
+    .opacity(.9);
+  }
+
+  // Toggles
+  .icon-prev,
+  .icon-next,
+  .glyphicon-chevron-left,
+  .glyphicon-chevron-right {
+    position: absolute;
+    top: 50%;
+    z-index: 5;
+    display: inline-block;
+  }
+  .icon-prev,
+  .glyphicon-chevron-left {
+    left: 50%;
+    margin-left: -10px;
+  }
+  .icon-next,
+  .glyphicon-chevron-right {
+    right: 50%;
+    margin-right: -10px;
+  }
+  .icon-prev,
+  .icon-next {
+    width:  20px;
+    height: 20px;
+    margin-top: -10px;
+    line-height: 1;
+    font-family: serif;
+  }
+
+
+  .icon-prev {
+    &:before {
+      content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
+    }
+  }
+  .icon-next {
+    &:before {
+      content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
+    }
+  }
+}
+
+// Optional indicator pips
+//
+// Add an unordered list with the following class and add a list item for each
+// slide your carousel holds.
+
+.carousel-indicators {
+  position: absolute;
+  bottom: 10px;
+  left: 50%;
+  z-index: 15;
+  width: 60%;
+  margin-left: -30%;
+  padding-left: 0;
+  list-style: none;
+  text-align: center;
+
+  li {
+    display: inline-block;
+    width:  10px;
+    height: 10px;
+    margin: 1px;
+    text-indent: -999px;
+    border: 1px solid @carousel-indicator-border-color;
+    border-radius: 10px;
+    cursor: pointer;
+
+    // IE8-9 hack for event handling
+    //
+    // Internet Explorer 8-9 does not support clicks on elements without a set
+    // `background-color`. We cannot use `filter` since that's not viewed as a
+    // background color by the browser. Thus, a hack is needed.
+    // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer
+    //
+    // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we
+    // set alpha transparency for the best results possible.
+    background-color: #000 \9; // IE8
+    background-color: rgba(0,0,0,0); // IE9
+  }
+  .active {
+    margin: 0;
+    width:  12px;
+    height: 12px;
+    background-color: @carousel-indicator-active-bg;
+  }
+}
+
+// Optional captions
+// -----------------------------
+// Hidden by default for smaller viewports
+.carousel-caption {
+  position: absolute;
+  left: 15%;
+  right: 15%;
+  bottom: 20px;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: @carousel-caption-color;
+  text-align: center;
+  text-shadow: @carousel-text-shadow;
+  & .btn {
+    text-shadow: none; // No shadow for button elements in carousel-caption
+  }
+}
+
+
+// Scale up controls for tablets and up
+@media screen and (min-width: @screen-sm-min) {
+
+  // Scale up the controls a smidge
+  .carousel-control {
+    .glyphicon-chevron-left,
+    .glyphicon-chevron-right,
+    .icon-prev,
+    .icon-next {
+      width: 30px;
+      height: 30px;
+      margin-top: -15px;
+      font-size: 30px;
+    }
+    .glyphicon-chevron-left,
+    .icon-prev {
+      margin-left: -15px;
+    }
+    .glyphicon-chevron-right,
+    .icon-next {
+      margin-right: -15px;
+    }
+  }
+
+  // Show and left align the captions
+  .carousel-caption {
+    left: 20%;
+    right: 20%;
+    padding-bottom: 30px;
+  }
+
+  // Move up the indicators
+  .carousel-indicators {
+    bottom: 20px;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/close.less b/libs/fmt/doc/bootstrap/close.less
new file mode 100644
index 0000000000000000000000000000000000000000..6d5bfe087aed3342270b1de9d70d034c474a3779
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/close.less
@@ -0,0 +1,34 @@
+//
+// Close icons
+// --------------------------------------------------
+
+
+.close {
+  float: right;
+  font-size: (@font-size-base * 1.5);
+  font-weight: @close-font-weight;
+  line-height: 1;
+  color: @close-color;
+  text-shadow: @close-text-shadow;
+  .opacity(.2);
+
+  &:hover,
+  &:focus {
+    color: @close-color;
+    text-decoration: none;
+    cursor: pointer;
+    .opacity(.5);
+  }
+
+  // Additional properties for button version
+  // iOS requires the button element instead of an anchor tag.
+  // If you want the anchor version, it requires `href="#"`.
+  // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
+  button& {
+    padding: 0;
+    cursor: pointer;
+    background: transparent;
+    border: 0;
+    -webkit-appearance: none;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/code.less b/libs/fmt/doc/bootstrap/code.less
new file mode 100644
index 0000000000000000000000000000000000000000..a08b4d48c4c8715dac93a5422f0d340ae9cc6bff
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/code.less
@@ -0,0 +1,69 @@
+//
+// Code (inline and block)
+// --------------------------------------------------
+
+
+// Inline and block code styles
+code,
+kbd,
+pre,
+samp {
+  font-family: @font-family-monospace;
+}
+
+// Inline code
+code {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: @code-color;
+  background-color: @code-bg;
+  border-radius: @border-radius-base;
+}
+
+// User input typically entered via keyboard
+kbd {
+  padding: 2px 4px;
+  font-size: 90%;
+  color: @kbd-color;
+  background-color: @kbd-bg;
+  border-radius: @border-radius-small;
+  box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
+
+  kbd {
+    padding: 0;
+    font-size: 100%;
+    font-weight: bold;
+    box-shadow: none;
+  }
+}
+
+// Blocks of code
+pre {
+  display: block;
+  padding: ((@line-height-computed - 1) / 2);
+  margin: 0 0 (@line-height-computed / 2);
+  font-size: (@font-size-base - 1); // 14px to 13px
+  line-height: @line-height-base;
+  word-break: break-all;
+  word-wrap: break-word;
+  color: @pre-color;
+  background-color: @pre-bg;
+  border: 1px solid @pre-border-color;
+  border-radius: @border-radius-base;
+
+  // Account for some code outputs that place code tags in pre tags
+  code {
+    padding: 0;
+    font-size: inherit;
+    color: inherit;
+    white-space: pre-wrap;
+    background-color: transparent;
+    border-radius: 0;
+  }
+}
+
+// Enable scrollable blocks of code
+.pre-scrollable {
+  max-height: @pre-scrollable-max-height;
+  overflow-y: scroll;
+}
diff --git a/libs/fmt/doc/bootstrap/component-animations.less b/libs/fmt/doc/bootstrap/component-animations.less
new file mode 100644
index 0000000000000000000000000000000000000000..0bcee910ac5f2c249cd6280d1e69fd03d178b24e
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/component-animations.less
@@ -0,0 +1,33 @@
+//
+// Component animations
+// --------------------------------------------------
+
+// Heads up!
+//
+// We don't use the `.opacity()` mixin here since it causes a bug with text
+// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.
+
+.fade {
+  opacity: 0;
+  .transition(opacity .15s linear);
+  &.in {
+    opacity: 1;
+  }
+}
+
+.collapse {
+  display: none;
+
+  &.in      { display: block; }
+  tr&.in    { display: table-row; }
+  tbody&.in { display: table-row-group; }
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  .transition-property(~"height, visibility");
+  .transition-duration(.35s);
+  .transition-timing-function(ease);
+}
diff --git a/libs/fmt/doc/bootstrap/dropdowns.less b/libs/fmt/doc/bootstrap/dropdowns.less
new file mode 100644
index 0000000000000000000000000000000000000000..af344607bd967350aab19a91d8f069eee20b4230
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/dropdowns.less
@@ -0,0 +1,214 @@
+//
+// Dropdown menus
+// --------------------------------------------------
+
+
+// Dropdown arrow/caret
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  margin-left: 2px;
+  vertical-align: middle;
+  border-top:   @caret-width-base dashed;
+  border-right: @caret-width-base solid transparent;
+  border-left:  @caret-width-base solid transparent;
+}
+
+// The dropdown wrapper (div)
+.dropup,
+.dropdown {
+  position: relative;
+}
+
+// Prevent the focus on the dropdown toggle when closing dropdowns
+.dropdown-toggle:focus {
+  outline: 0;
+}
+
+// The dropdown menu (ul)
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: @zindex-dropdown;
+  display: none; // none by default, but block on "open" of the menu
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0; // override default ul
+  list-style: none;
+  font-size: @font-size-base;
+  text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)
+  background-color: @dropdown-bg;
+  border: 1px solid @dropdown-fallback-border; // IE8 fallback
+  border: 1px solid @dropdown-border;
+  border-radius: @border-radius-base;
+  .box-shadow(0 6px 12px rgba(0,0,0,.175));
+  background-clip: padding-box;
+
+  // Aligns the dropdown menu to right
+  //
+  // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`
+  &.pull-right {
+    right: 0;
+    left: auto;
+  }
+
+  // Dividers (basically an hr) within the dropdown
+  .divider {
+    .nav-divider(@dropdown-divider-bg);
+  }
+
+  // Links within the dropdown menu
+  > li > a {
+    display: block;
+    padding: 3px 20px;
+    clear: both;
+    font-weight: normal;
+    line-height: @line-height-base;
+    color: @dropdown-link-color;
+    white-space: nowrap; // prevent links from randomly breaking onto new lines
+  }
+}
+
+// Hover/Focus state
+.dropdown-menu > li > a {
+  &:hover,
+  &:focus {
+    text-decoration: none;
+    color: @dropdown-link-hover-color;
+    background-color: @dropdown-link-hover-bg;
+  }
+}
+
+// Active state
+.dropdown-menu > .active > a {
+  &,
+  &:hover,
+  &:focus {
+    color: @dropdown-link-active-color;
+    text-decoration: none;
+    outline: 0;
+    background-color: @dropdown-link-active-bg;
+  }
+}
+
+// Disabled state
+//
+// Gray out text and ensure the hover/focus state remains gray
+
+.dropdown-menu > .disabled > a {
+  &,
+  &:hover,
+  &:focus {
+    color: @dropdown-link-disabled-color;
+  }
+
+  // Nuke hover/focus effects
+  &:hover,
+  &:focus {
+    text-decoration: none;
+    background-color: transparent;
+    background-image: none; // Remove CSS gradient
+    .reset-filter();
+    cursor: @cursor-disabled;
+  }
+}
+
+// Open state for the dropdown
+.open {
+  // Show the menu
+  > .dropdown-menu {
+    display: block;
+  }
+
+  // Remove the outline when :focus is triggered
+  > a {
+    outline: 0;
+  }
+}
+
+// Menu positioning
+//
+// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown
+// menu with the parent.
+.dropdown-menu-right {
+  left: auto; // Reset the default from `.dropdown-menu`
+  right: 0;
+}
+// With v3, we enabled auto-flipping if you have a dropdown within a right
+// aligned nav component. To enable the undoing of that, we provide an override
+// to restore the default dropdown menu alignment.
+//
+// This is only for left-aligning a dropdown menu within a `.navbar-right` or
+// `.pull-right` nav component.
+.dropdown-menu-left {
+  left: 0;
+  right: auto;
+}
+
+// Dropdown section headers
+.dropdown-header {
+  display: block;
+  padding: 3px 20px;
+  font-size: @font-size-small;
+  line-height: @line-height-base;
+  color: @dropdown-header-color;
+  white-space: nowrap; // as with > li > a
+}
+
+// Backdrop to catch body clicks on mobile, etc.
+.dropdown-backdrop {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  top: 0;
+  z-index: (@zindex-dropdown - 10);
+}
+
+// Right aligned dropdowns
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+
+// Allow for dropdowns to go bottom up (aka, dropup-menu)
+//
+// Just add .dropup after the standard .dropdown class and you're set, bro.
+// TODO: abstract this so that the navbar fixed styles are not placed here?
+
+.dropup,
+.navbar-fixed-bottom .dropdown {
+  // Reverse the caret
+  .caret {
+    border-top: 0;
+    border-bottom: @caret-width-base solid;
+    content: "";
+  }
+  // Different positioning for bottom up menu
+  .dropdown-menu {
+    top: auto;
+    bottom: 100%;
+    margin-bottom: 2px;
+  }
+}
+
+
+// Component alignment
+//
+// Reiterate per navbar.less and the modified component alignment there.
+
+@media (min-width: @grid-float-breakpoint) {
+  .navbar-right {
+    .dropdown-menu {
+      .dropdown-menu-right();
+    }
+    // Necessary for overrides of the default right aligned menu.
+    // Will remove come v4 in all likelihood.
+    .dropdown-menu-left {
+      .dropdown-menu-left();
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/forms.less b/libs/fmt/doc/bootstrap/forms.less
new file mode 100644
index 0000000000000000000000000000000000000000..e4b50629e97cd9bbac46c4c2d9d36a55f3f9686d
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/forms.less
@@ -0,0 +1,574 @@
+//
+// Forms
+// --------------------------------------------------
+
+
+// Normalize non-controls
+//
+// Restyle and baseline non-control form elements.
+
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+  // Chrome and Firefox set a `min-width: min-content;` on fieldsets,
+  // so we reset that to ensure it behaves more like a standard block element.
+  // See https://github.com/twbs/bootstrap/issues/12359.
+  min-width: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: @line-height-computed;
+  font-size: (@font-size-base * 1.5);
+  line-height: inherit;
+  color: @legend-color;
+  border: 0;
+  border-bottom: 1px solid @legend-border-color;
+}
+
+label {
+  display: inline-block;
+  max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+
+// Normalize form controls
+//
+// While most of our form styles require extra classes, some basic normalization
+// is required to ensure optimum display with or without those classes to better
+// address browser inconsistencies.
+
+// Override content-box in Normalize (* isn't specific enough)
+input[type="search"] {
+  .box-sizing(border-box);
+}
+
+// Position radios and checkboxes better
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  margin-top: 1px \9; // IE8-9
+  line-height: normal;
+}
+
+// Set the height of file controls to match text inputs
+input[type="file"] {
+  display: block;
+}
+
+// Make range inputs behave like textual form controls
+input[type="range"] {
+  display: block;
+  width: 100%;
+}
+
+// Make multiple select elements height not fixed
+select[multiple],
+select[size] {
+  height: auto;
+}
+
+// Focus for file, radio, and checkbox
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  .tab-focus();
+}
+
+// Adjust output element
+output {
+  display: block;
+  padding-top: (@padding-base-vertical + 1);
+  font-size: @font-size-base;
+  line-height: @line-height-base;
+  color: @input-color;
+}
+
+
+// Common form controls
+//
+// Shared size and type resets for form controls. Apply `.form-control` to any
+// of the following form controls:
+//
+// select
+// textarea
+// input[type="text"]
+// input[type="password"]
+// input[type="datetime"]
+// input[type="datetime-local"]
+// input[type="date"]
+// input[type="month"]
+// input[type="time"]
+// input[type="week"]
+// input[type="number"]
+// input[type="email"]
+// input[type="url"]
+// input[type="search"]
+// input[type="tel"]
+// input[type="color"]
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
+  padding: @padding-base-vertical @padding-base-horizontal;
+  font-size: @font-size-base;
+  line-height: @line-height-base;
+  color: @input-color;
+  background-color: @input-bg;
+  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+  border: 1px solid @input-border;
+  border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.
+  .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
+  .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s");
+
+  // Customize the `:focus` state to imitate native WebKit styles.
+  .form-control-focus();
+
+  // Placeholder
+  .placeholder();
+
+  // Disabled and read-only inputs
+  //
+  // HTML5 says that controls under a fieldset > legend:first-child won't be
+  // disabled if the fieldset is disabled. Due to implementation difficulty, we
+  // don't honor that edge case; we style them as disabled anyway.
+  &[disabled],
+  &[readonly],
+  fieldset[disabled] & {
+    background-color: @input-bg-disabled;
+    opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655
+  }
+
+  &[disabled],
+  fieldset[disabled] & {
+    cursor: @cursor-disabled;
+  }
+
+  // Reset height for `textarea`s
+  textarea& {
+    height: auto;
+  }
+}
+
+
+// Search inputs in iOS
+//
+// This overrides the extra rounded corners on search inputs in iOS so that our
+// `.form-control` class can properly style them. Note that this cannot simply
+// be added to `.form-control` as it's not specific enough. For details, see
+// https://github.com/twbs/bootstrap/issues/11586.
+
+input[type="search"] {
+  -webkit-appearance: none;
+}
+
+
+// Special styles for iOS temporal inputs
+//
+// In Mobile Safari, setting `display: block` on temporal inputs causes the
+// text within the input to become vertically misaligned. As a workaround, we
+// set a pixel line-height that matches the given height of the input, but only
+// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848
+
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+  input[type="date"],
+  input[type="time"],
+  input[type="datetime-local"],
+  input[type="month"] {
+    line-height: @input-height-base;
+
+    &.input-sm,
+    .input-group-sm & {
+      line-height: @input-height-small;
+    }
+
+    &.input-lg,
+    .input-group-lg & {
+      line-height: @input-height-large;
+    }
+  }
+}
+
+
+// Form groups
+//
+// Designed to help with the organization and spacing of vertical forms. For
+// horizontal forms, use the predefined grid classes.
+
+.form-group {
+  margin-bottom: @form-group-margin-bottom;
+}
+
+
+// Checkboxes and radios
+//
+// Indent the labels to position radios/checkboxes as hanging controls.
+
+.radio,
+.checkbox {
+  position: relative;
+  display: block;
+  margin-top: 10px;
+  margin-bottom: 10px;
+
+  label {
+    min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text
+    padding-left: 20px;
+    margin-bottom: 0;
+    font-weight: normal;
+    cursor: pointer;
+  }
+}
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+  position: absolute;
+  margin-left: -20px;
+  margin-top: 4px \9;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+  margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing
+}
+
+// Radios and checkboxes on same line
+.radio-inline,
+.checkbox-inline {
+  position: relative;
+  display: inline-block;
+  padding-left: 20px;
+  margin-bottom: 0;
+  vertical-align: middle;
+  font-weight: normal;
+  cursor: pointer;
+}
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+  margin-top: 0;
+  margin-left: 10px; // space out consecutive inline controls
+}
+
+// Apply same disabled cursor tweak as for inputs
+// Some special care is needed because <label>s don't inherit their parent's `cursor`.
+//
+// Note: Neither radios nor checkboxes can be readonly.
+input[type="radio"],
+input[type="checkbox"] {
+  &[disabled],
+  &.disabled,
+  fieldset[disabled] & {
+    cursor: @cursor-disabled;
+  }
+}
+// These classes are used directly on <label>s
+.radio-inline,
+.checkbox-inline {
+  &.disabled,
+  fieldset[disabled] & {
+    cursor: @cursor-disabled;
+  }
+}
+// These classes are used on elements with <label> descendants
+.radio,
+.checkbox {
+  &.disabled,
+  fieldset[disabled] & {
+    label {
+      cursor: @cursor-disabled;
+    }
+  }
+}
+
+
+// Static form control text
+//
+// Apply class to a `p` element to make any string of text align with labels in
+// a horizontal form layout.
+
+.form-control-static {
+  // Size it appropriately next to real form controls
+  padding-top: (@padding-base-vertical + 1);
+  padding-bottom: (@padding-base-vertical + 1);
+  // Remove default margin from `p`
+  margin-bottom: 0;
+  min-height: (@line-height-computed + @font-size-base);
+
+  &.input-lg,
+  &.input-sm {
+    padding-left: 0;
+    padding-right: 0;
+  }
+}
+
+
+// Form control sizing
+//
+// Build on `.form-control` with modifier classes to decrease or increase the
+// height and font-size of form controls.
+//
+// The `.form-group-* form-control` variations are sadly duplicated to avoid the
+// issue documented in https://github.com/twbs/bootstrap/issues/15074.
+
+.input-sm {
+  .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);
+}
+.form-group-sm {
+  .form-control {
+    .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);
+  }
+  .form-control-static {
+    height: @input-height-small;
+    padding: @padding-small-vertical @padding-small-horizontal;
+    font-size: @font-size-small;
+    line-height: @line-height-small;
+    min-height: (@line-height-computed + @font-size-small);
+  }
+}
+
+.input-lg {
+  .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);
+}
+.form-group-lg {
+  .form-control {
+    .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);
+  }
+  .form-control-static {
+    height: @input-height-large;
+    padding: @padding-large-vertical @padding-large-horizontal;
+    font-size: @font-size-large;
+    line-height: @line-height-large;
+    min-height: (@line-height-computed + @font-size-large);
+  }
+}
+
+
+// Form control feedback states
+//
+// Apply contextual and semantic states to individual form controls.
+
+.has-feedback {
+  // Enable absolute positioning
+  position: relative;
+
+  // Ensure icons don't overlap text
+  .form-control {
+    padding-right: (@input-height-base * 1.25);
+  }
+}
+// Feedback icon (requires .glyphicon classes)
+.form-control-feedback {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2; // Ensure icon is above input groups
+  display: block;
+  width: @input-height-base;
+  height: @input-height-base;
+  line-height: @input-height-base;
+  text-align: center;
+  pointer-events: none;
+}
+.input-lg + .form-control-feedback {
+  width: @input-height-large;
+  height: @input-height-large;
+  line-height: @input-height-large;
+}
+.input-sm + .form-control-feedback {
+  width: @input-height-small;
+  height: @input-height-small;
+  line-height: @input-height-small;
+}
+
+// Feedback states
+.has-success {
+  .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);
+}
+.has-warning {
+  .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);
+}
+.has-error {
+  .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);
+}
+
+// Reposition feedback icon if input has visible label above
+.has-feedback label {
+
+  & ~ .form-control-feedback {
+     top: (@line-height-computed + 5); // Height of the `label` and its margin
+  }
+  &.sr-only ~ .form-control-feedback {
+     top: 0;
+  }
+}
+
+
+// Help text
+//
+// Apply to any element you wish to create light text for placement immediately
+// below a form control. Use for general help, formatting, or instructional text.
+
+.help-block {
+  display: block; // account for any element using help-block
+  margin-top: 5px;
+  margin-bottom: 10px;
+  color: lighten(@text-color, 25%); // lighten the text some for contrast
+}
+
+
+// Inline forms
+//
+// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
+// forms begin stacked on extra small (mobile) devices and then go inline when
+// viewports reach <768px.
+//
+// Requires wrapping inputs and labels with `.form-group` for proper display of
+// default HTML form controls and our custom form controls (e.g., input groups).
+//
+// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.
+
+.form-inline {
+
+  // Kick in the inline
+  @media (min-width: @screen-sm-min) {
+    // Inline-block all the things for "inline"
+    .form-group {
+      display: inline-block;
+      margin-bottom: 0;
+      vertical-align: middle;
+    }
+
+    // In navbar-form, allow folks to *not* use `.form-group`
+    .form-control {
+      display: inline-block;
+      width: auto; // Prevent labels from stacking above inputs in `.form-group`
+      vertical-align: middle;
+    }
+
+    // Make static controls behave like regular ones
+    .form-control-static {
+      display: inline-block;
+    }
+
+    .input-group {
+      display: inline-table;
+      vertical-align: middle;
+
+      .input-group-addon,
+      .input-group-btn,
+      .form-control {
+        width: auto;
+      }
+    }
+
+    // Input groups need that 100% width though
+    .input-group > .form-control {
+      width: 100%;
+    }
+
+    .control-label {
+      margin-bottom: 0;
+      vertical-align: middle;
+    }
+
+    // Remove default margin on radios/checkboxes that were used for stacking, and
+    // then undo the floating of radios and checkboxes to match.
+    .radio,
+    .checkbox {
+      display: inline-block;
+      margin-top: 0;
+      margin-bottom: 0;
+      vertical-align: middle;
+
+      label {
+        padding-left: 0;
+      }
+    }
+    .radio input[type="radio"],
+    .checkbox input[type="checkbox"] {
+      position: relative;
+      margin-left: 0;
+    }
+
+    // Re-override the feedback icon.
+    .has-feedback .form-control-feedback {
+      top: 0;
+    }
+  }
+}
+
+
+// Horizontal forms
+//
+// Horizontal forms are built on grid classes and allow you to create forms with
+// labels on the left and inputs on the right.
+
+.form-horizontal {
+
+  // Consistent vertical alignment of radios and checkboxes
+  //
+  // Labels also get some reset styles, but that is scoped to a media query below.
+  .radio,
+  .checkbox,
+  .radio-inline,
+  .checkbox-inline {
+    margin-top: 0;
+    margin-bottom: 0;
+    padding-top: (@padding-base-vertical + 1); // Default padding plus a border
+  }
+  // Account for padding we're adding to ensure the alignment and of help text
+  // and other content below items
+  .radio,
+  .checkbox {
+    min-height: (@line-height-computed + (@padding-base-vertical + 1));
+  }
+
+  // Make form groups behave like rows
+  .form-group {
+    .make-row();
+  }
+
+  // Reset spacing and right align labels, but scope to media queries so that
+  // labels on narrow viewports stack the same as a default form example.
+  @media (min-width: @screen-sm-min) {
+    .control-label {
+      text-align: right;
+      margin-bottom: 0;
+      padding-top: (@padding-base-vertical + 1); // Default padding plus a border
+    }
+  }
+
+  // Validation states
+  //
+  // Reposition the icon because it's now within a grid column and columns have
+  // `position: relative;` on them. Also accounts for the grid gutter padding.
+  .has-feedback .form-control-feedback {
+    right: (@grid-gutter-width / 2);
+  }
+
+  // Form group sizes
+  //
+  // Quick utility class for applying `.input-lg` and `.input-sm` styles to the
+  // inputs and labels within a `.form-group`.
+  .form-group-lg {
+    @media (min-width: @screen-sm-min) {
+      .control-label {
+        padding-top: ((@padding-large-vertical * @line-height-large) + 1);
+      }
+    }
+  }
+  .form-group-sm {
+    @media (min-width: @screen-sm-min) {
+      .control-label {
+        padding-top: (@padding-small-vertical + 1);
+      }
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/glyphicons.less b/libs/fmt/doc/bootstrap/glyphicons.less
new file mode 100644
index 0000000000000000000000000000000000000000..335d80aa6bfebfc5bd64b60f75a9d49ea79687be
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/glyphicons.less
@@ -0,0 +1,305 @@
+//
+// Glyphicons for Bootstrap
+//
+// Since icons are fonts, they can be placed anywhere text is placed and are
+// thus automatically sized to match the surrounding child. To use, create an
+// inline element with the appropriate classes, like so:
+//
+// <a href="#"><span class="glyphicon glyphicon-star"></span> Star</a>
+
+// Import the fonts
+@font-face {
+  font-family: 'Glyphicons Halflings';
+  src: url('@{icon-font-path}@{icon-font-name}.eot');
+  src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),
+       url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),
+       url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),
+       url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),
+       url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');
+}
+
+// Catchall baseclass
+.glyphicon {
+  position: relative;
+  top: 1px;
+  display: inline-block;
+  font-family: 'Glyphicons Halflings';
+  font-style: normal;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+// Individual icons
+.glyphicon-asterisk               { &:before { content: "\2a"; } }
+.glyphicon-plus                   { &:before { content: "\2b"; } }
+.glyphicon-euro,
+.glyphicon-eur                    { &:before { content: "\20ac"; } }
+.glyphicon-minus                  { &:before { content: "\2212"; } }
+.glyphicon-cloud                  { &:before { content: "\2601"; } }
+.glyphicon-envelope               { &:before { content: "\2709"; } }
+.glyphicon-pencil                 { &:before { content: "\270f"; } }
+.glyphicon-glass                  { &:before { content: "\e001"; } }
+.glyphicon-music                  { &:before { content: "\e002"; } }
+.glyphicon-search                 { &:before { content: "\e003"; } }
+.glyphicon-heart                  { &:before { content: "\e005"; } }
+.glyphicon-star                   { &:before { content: "\e006"; } }
+.glyphicon-star-empty             { &:before { content: "\e007"; } }
+.glyphicon-user                   { &:before { content: "\e008"; } }
+.glyphicon-film                   { &:before { content: "\e009"; } }
+.glyphicon-th-large               { &:before { content: "\e010"; } }
+.glyphicon-th                     { &:before { content: "\e011"; } }
+.glyphicon-th-list                { &:before { content: "\e012"; } }
+.glyphicon-ok                     { &:before { content: "\e013"; } }
+.glyphicon-remove                 { &:before { content: "\e014"; } }
+.glyphicon-zoom-in                { &:before { content: "\e015"; } }
+.glyphicon-zoom-out               { &:before { content: "\e016"; } }
+.glyphicon-off                    { &:before { content: "\e017"; } }
+.glyphicon-signal                 { &:before { content: "\e018"; } }
+.glyphicon-cog                    { &:before { content: "\e019"; } }
+.glyphicon-trash                  { &:before { content: "\e020"; } }
+.glyphicon-home                   { &:before { content: "\e021"; } }
+.glyphicon-file                   { &:before { content: "\e022"; } }
+.glyphicon-time                   { &:before { content: "\e023"; } }
+.glyphicon-road                   { &:before { content: "\e024"; } }
+.glyphicon-download-alt           { &:before { content: "\e025"; } }
+.glyphicon-download               { &:before { content: "\e026"; } }
+.glyphicon-upload                 { &:before { content: "\e027"; } }
+.glyphicon-inbox                  { &:before { content: "\e028"; } }
+.glyphicon-play-circle            { &:before { content: "\e029"; } }
+.glyphicon-repeat                 { &:before { content: "\e030"; } }
+.glyphicon-refresh                { &:before { content: "\e031"; } }
+.glyphicon-list-alt               { &:before { content: "\e032"; } }
+.glyphicon-lock                   { &:before { content: "\e033"; } }
+.glyphicon-flag                   { &:before { content: "\e034"; } }
+.glyphicon-headphones             { &:before { content: "\e035"; } }
+.glyphicon-volume-off             { &:before { content: "\e036"; } }
+.glyphicon-volume-down            { &:before { content: "\e037"; } }
+.glyphicon-volume-up              { &:before { content: "\e038"; } }
+.glyphicon-qrcode                 { &:before { content: "\e039"; } }
+.glyphicon-barcode                { &:before { content: "\e040"; } }
+.glyphicon-tag                    { &:before { content: "\e041"; } }
+.glyphicon-tags                   { &:before { content: "\e042"; } }
+.glyphicon-book                   { &:before { content: "\e043"; } }
+.glyphicon-bookmark               { &:before { content: "\e044"; } }
+.glyphicon-print                  { &:before { content: "\e045"; } }
+.glyphicon-camera                 { &:before { content: "\e046"; } }
+.glyphicon-font                   { &:before { content: "\e047"; } }
+.glyphicon-bold                   { &:before { content: "\e048"; } }
+.glyphicon-italic                 { &:before { content: "\e049"; } }
+.glyphicon-text-height            { &:before { content: "\e050"; } }
+.glyphicon-text-width             { &:before { content: "\e051"; } }
+.glyphicon-align-left             { &:before { content: "\e052"; } }
+.glyphicon-align-center           { &:before { content: "\e053"; } }
+.glyphicon-align-right            { &:before { content: "\e054"; } }
+.glyphicon-align-justify          { &:before { content: "\e055"; } }
+.glyphicon-list                   { &:before { content: "\e056"; } }
+.glyphicon-indent-left            { &:before { content: "\e057"; } }
+.glyphicon-indent-right           { &:before { content: "\e058"; } }
+.glyphicon-facetime-video         { &:before { content: "\e059"; } }
+.glyphicon-picture                { &:before { content: "\e060"; } }
+.glyphicon-map-marker             { &:before { content: "\e062"; } }
+.glyphicon-adjust                 { &:before { content: "\e063"; } }
+.glyphicon-tint                   { &:before { content: "\e064"; } }
+.glyphicon-edit                   { &:before { content: "\e065"; } }
+.glyphicon-share                  { &:before { content: "\e066"; } }
+.glyphicon-check                  { &:before { content: "\e067"; } }
+.glyphicon-move                   { &:before { content: "\e068"; } }
+.glyphicon-step-backward          { &:before { content: "\e069"; } }
+.glyphicon-fast-backward          { &:before { content: "\e070"; } }
+.glyphicon-backward               { &:before { content: "\e071"; } }
+.glyphicon-play                   { &:before { content: "\e072"; } }
+.glyphicon-pause                  { &:before { content: "\e073"; } }
+.glyphicon-stop                   { &:before { content: "\e074"; } }
+.glyphicon-forward                { &:before { content: "\e075"; } }
+.glyphicon-fast-forward           { &:before { content: "\e076"; } }
+.glyphicon-step-forward           { &:before { content: "\e077"; } }
+.glyphicon-eject                  { &:before { content: "\e078"; } }
+.glyphicon-chevron-left           { &:before { content: "\e079"; } }
+.glyphicon-chevron-right          { &:before { content: "\e080"; } }
+.glyphicon-plus-sign              { &:before { content: "\e081"; } }
+.glyphicon-minus-sign             { &:before { content: "\e082"; } }
+.glyphicon-remove-sign            { &:before { content: "\e083"; } }
+.glyphicon-ok-sign                { &:before { content: "\e084"; } }
+.glyphicon-question-sign          { &:before { content: "\e085"; } }
+.glyphicon-info-sign              { &:before { content: "\e086"; } }
+.glyphicon-screenshot             { &:before { content: "\e087"; } }
+.glyphicon-remove-circle          { &:before { content: "\e088"; } }
+.glyphicon-ok-circle              { &:before { content: "\e089"; } }
+.glyphicon-ban-circle             { &:before { content: "\e090"; } }
+.glyphicon-arrow-left             { &:before { content: "\e091"; } }
+.glyphicon-arrow-right            { &:before { content: "\e092"; } }
+.glyphicon-arrow-up               { &:before { content: "\e093"; } }
+.glyphicon-arrow-down             { &:before { content: "\e094"; } }
+.glyphicon-share-alt              { &:before { content: "\e095"; } }
+.glyphicon-resize-full            { &:before { content: "\e096"; } }
+.glyphicon-resize-small           { &:before { content: "\e097"; } }
+.glyphicon-exclamation-sign       { &:before { content: "\e101"; } }
+.glyphicon-gift                   { &:before { content: "\e102"; } }
+.glyphicon-leaf                   { &:before { content: "\e103"; } }
+.glyphicon-fire                   { &:before { content: "\e104"; } }
+.glyphicon-eye-open               { &:before { content: "\e105"; } }
+.glyphicon-eye-close              { &:before { content: "\e106"; } }
+.glyphicon-warning-sign           { &:before { content: "\e107"; } }
+.glyphicon-plane                  { &:before { content: "\e108"; } }
+.glyphicon-calendar               { &:before { content: "\e109"; } }
+.glyphicon-random                 { &:before { content: "\e110"; } }
+.glyphicon-comment                { &:before { content: "\e111"; } }
+.glyphicon-magnet                 { &:before { content: "\e112"; } }
+.glyphicon-chevron-up             { &:before { content: "\e113"; } }
+.glyphicon-chevron-down           { &:before { content: "\e114"; } }
+.glyphicon-retweet                { &:before { content: "\e115"; } }
+.glyphicon-shopping-cart          { &:before { content: "\e116"; } }
+.glyphicon-folder-close           { &:before { content: "\e117"; } }
+.glyphicon-folder-open            { &:before { content: "\e118"; } }
+.glyphicon-resize-vertical        { &:before { content: "\e119"; } }
+.glyphicon-resize-horizontal      { &:before { content: "\e120"; } }
+.glyphicon-hdd                    { &:before { content: "\e121"; } }
+.glyphicon-bullhorn               { &:before { content: "\e122"; } }
+.glyphicon-bell                   { &:before { content: "\e123"; } }
+.glyphicon-certificate            { &:before { content: "\e124"; } }
+.glyphicon-thumbs-up              { &:before { content: "\e125"; } }
+.glyphicon-thumbs-down            { &:before { content: "\e126"; } }
+.glyphicon-hand-right             { &:before { content: "\e127"; } }
+.glyphicon-hand-left              { &:before { content: "\e128"; } }
+.glyphicon-hand-up                { &:before { content: "\e129"; } }
+.glyphicon-hand-down              { &:before { content: "\e130"; } }
+.glyphicon-circle-arrow-right     { &:before { content: "\e131"; } }
+.glyphicon-circle-arrow-left      { &:before { content: "\e132"; } }
+.glyphicon-circle-arrow-up        { &:before { content: "\e133"; } }
+.glyphicon-circle-arrow-down      { &:before { content: "\e134"; } }
+.glyphicon-globe                  { &:before { content: "\e135"; } }
+.glyphicon-wrench                 { &:before { content: "\e136"; } }
+.glyphicon-tasks                  { &:before { content: "\e137"; } }
+.glyphicon-filter                 { &:before { content: "\e138"; } }
+.glyphicon-briefcase              { &:before { content: "\e139"; } }
+.glyphicon-fullscreen             { &:before { content: "\e140"; } }
+.glyphicon-dashboard              { &:before { content: "\e141"; } }
+.glyphicon-paperclip              { &:before { content: "\e142"; } }
+.glyphicon-heart-empty            { &:before { content: "\e143"; } }
+.glyphicon-link                   { &:before { content: "\e144"; } }
+.glyphicon-phone                  { &:before { content: "\e145"; } }
+.glyphicon-pushpin                { &:before { content: "\e146"; } }
+.glyphicon-usd                    { &:before { content: "\e148"; } }
+.glyphicon-gbp                    { &:before { content: "\e149"; } }
+.glyphicon-sort                   { &:before { content: "\e150"; } }
+.glyphicon-sort-by-alphabet       { &:before { content: "\e151"; } }
+.glyphicon-sort-by-alphabet-alt   { &:before { content: "\e152"; } }
+.glyphicon-sort-by-order          { &:before { content: "\e153"; } }
+.glyphicon-sort-by-order-alt      { &:before { content: "\e154"; } }
+.glyphicon-sort-by-attributes     { &:before { content: "\e155"; } }
+.glyphicon-sort-by-attributes-alt { &:before { content: "\e156"; } }
+.glyphicon-unchecked              { &:before { content: "\e157"; } }
+.glyphicon-expand                 { &:before { content: "\e158"; } }
+.glyphicon-collapse-down          { &:before { content: "\e159"; } }
+.glyphicon-collapse-up            { &:before { content: "\e160"; } }
+.glyphicon-log-in                 { &:before { content: "\e161"; } }
+.glyphicon-flash                  { &:before { content: "\e162"; } }
+.glyphicon-log-out                { &:before { content: "\e163"; } }
+.glyphicon-new-window             { &:before { content: "\e164"; } }
+.glyphicon-record                 { &:before { content: "\e165"; } }
+.glyphicon-save                   { &:before { content: "\e166"; } }
+.glyphicon-open                   { &:before { content: "\e167"; } }
+.glyphicon-saved                  { &:before { content: "\e168"; } }
+.glyphicon-import                 { &:before { content: "\e169"; } }
+.glyphicon-export                 { &:before { content: "\e170"; } }
+.glyphicon-send                   { &:before { content: "\e171"; } }
+.glyphicon-floppy-disk            { &:before { content: "\e172"; } }
+.glyphicon-floppy-saved           { &:before { content: "\e173"; } }
+.glyphicon-floppy-remove          { &:before { content: "\e174"; } }
+.glyphicon-floppy-save            { &:before { content: "\e175"; } }
+.glyphicon-floppy-open            { &:before { content: "\e176"; } }
+.glyphicon-credit-card            { &:before { content: "\e177"; } }
+.glyphicon-transfer               { &:before { content: "\e178"; } }
+.glyphicon-cutlery                { &:before { content: "\e179"; } }
+.glyphicon-header                 { &:before { content: "\e180"; } }
+.glyphicon-compressed             { &:before { content: "\e181"; } }
+.glyphicon-earphone               { &:before { content: "\e182"; } }
+.glyphicon-phone-alt              { &:before { content: "\e183"; } }
+.glyphicon-tower                  { &:before { content: "\e184"; } }
+.glyphicon-stats                  { &:before { content: "\e185"; } }
+.glyphicon-sd-video               { &:before { content: "\e186"; } }
+.glyphicon-hd-video               { &:before { content: "\e187"; } }
+.glyphicon-subtitles              { &:before { content: "\e188"; } }
+.glyphicon-sound-stereo           { &:before { content: "\e189"; } }
+.glyphicon-sound-dolby            { &:before { content: "\e190"; } }
+.glyphicon-sound-5-1              { &:before { content: "\e191"; } }
+.glyphicon-sound-6-1              { &:before { content: "\e192"; } }
+.glyphicon-sound-7-1              { &:before { content: "\e193"; } }
+.glyphicon-copyright-mark         { &:before { content: "\e194"; } }
+.glyphicon-registration-mark      { &:before { content: "\e195"; } }
+.glyphicon-cloud-download         { &:before { content: "\e197"; } }
+.glyphicon-cloud-upload           { &:before { content: "\e198"; } }
+.glyphicon-tree-conifer           { &:before { content: "\e199"; } }
+.glyphicon-tree-deciduous         { &:before { content: "\e200"; } }
+.glyphicon-cd                     { &:before { content: "\e201"; } }
+.glyphicon-save-file              { &:before { content: "\e202"; } }
+.glyphicon-open-file              { &:before { content: "\e203"; } }
+.glyphicon-level-up               { &:before { content: "\e204"; } }
+.glyphicon-copy                   { &:before { content: "\e205"; } }
+.glyphicon-paste                  { &:before { content: "\e206"; } }
+// The following 2 Glyphicons are omitted for the time being because
+// they currently use Unicode codepoints that are outside the
+// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle
+// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.
+// Notably, the bug affects some older versions of the Android Browser.
+// More info: https://github.com/twbs/bootstrap/issues/10106
+// .glyphicon-door                   { &:before { content: "\1f6aa"; } }
+// .glyphicon-key                    { &:before { content: "\1f511"; } }
+.glyphicon-alert                  { &:before { content: "\e209"; } }
+.glyphicon-equalizer              { &:before { content: "\e210"; } }
+.glyphicon-king                   { &:before { content: "\e211"; } }
+.glyphicon-queen                  { &:before { content: "\e212"; } }
+.glyphicon-pawn                   { &:before { content: "\e213"; } }
+.glyphicon-bishop                 { &:before { content: "\e214"; } }
+.glyphicon-knight                 { &:before { content: "\e215"; } }
+.glyphicon-baby-formula           { &:before { content: "\e216"; } }
+.glyphicon-tent                   { &:before { content: "\26fa"; } }
+.glyphicon-blackboard             { &:before { content: "\e218"; } }
+.glyphicon-bed                    { &:before { content: "\e219"; } }
+.glyphicon-apple                  { &:before { content: "\f8ff"; } }
+.glyphicon-erase                  { &:before { content: "\e221"; } }
+.glyphicon-hourglass              { &:before { content: "\231b"; } }
+.glyphicon-lamp                   { &:before { content: "\e223"; } }
+.glyphicon-duplicate              { &:before { content: "\e224"; } }
+.glyphicon-piggy-bank             { &:before { content: "\e225"; } }
+.glyphicon-scissors               { &:before { content: "\e226"; } }
+.glyphicon-bitcoin                { &:before { content: "\e227"; } }
+.glyphicon-btc                    { &:before { content: "\e227"; } }
+.glyphicon-xbt                    { &:before { content: "\e227"; } }
+.glyphicon-yen                    { &:before { content: "\00a5"; } }
+.glyphicon-jpy                    { &:before { content: "\00a5"; } }
+.glyphicon-ruble                  { &:before { content: "\20bd"; } }
+.glyphicon-rub                    { &:before { content: "\20bd"; } }
+.glyphicon-scale                  { &:before { content: "\e230"; } }
+.glyphicon-ice-lolly              { &:before { content: "\e231"; } }
+.glyphicon-ice-lolly-tasted       { &:before { content: "\e232"; } }
+.glyphicon-education              { &:before { content: "\e233"; } }
+.glyphicon-option-horizontal      { &:before { content: "\e234"; } }
+.glyphicon-option-vertical        { &:before { content: "\e235"; } }
+.glyphicon-menu-hamburger         { &:before { content: "\e236"; } }
+.glyphicon-modal-window           { &:before { content: "\e237"; } }
+.glyphicon-oil                    { &:before { content: "\e238"; } }
+.glyphicon-grain                  { &:before { content: "\e239"; } }
+.glyphicon-sunglasses             { &:before { content: "\e240"; } }
+.glyphicon-text-size              { &:before { content: "\e241"; } }
+.glyphicon-text-color             { &:before { content: "\e242"; } }
+.glyphicon-text-background        { &:before { content: "\e243"; } }
+.glyphicon-object-align-top       { &:before { content: "\e244"; } }
+.glyphicon-object-align-bottom    { &:before { content: "\e245"; } }
+.glyphicon-object-align-horizontal{ &:before { content: "\e246"; } }
+.glyphicon-object-align-left      { &:before { content: "\e247"; } }
+.glyphicon-object-align-vertical  { &:before { content: "\e248"; } }
+.glyphicon-object-align-right     { &:before { content: "\e249"; } }
+.glyphicon-triangle-right         { &:before { content: "\e250"; } }
+.glyphicon-triangle-left          { &:before { content: "\e251"; } }
+.glyphicon-triangle-bottom        { &:before { content: "\e252"; } }
+.glyphicon-triangle-top           { &:before { content: "\e253"; } }
+.glyphicon-console                { &:before { content: "\e254"; } }
+.glyphicon-superscript            { &:before { content: "\e255"; } }
+.glyphicon-subscript              { &:before { content: "\e256"; } }
+.glyphicon-menu-left              { &:before { content: "\e257"; } }
+.glyphicon-menu-right             { &:before { content: "\e258"; } }
+.glyphicon-menu-down              { &:before { content: "\e259"; } }
+.glyphicon-menu-up                { &:before { content: "\e260"; } }
diff --git a/libs/fmt/doc/bootstrap/grid.less b/libs/fmt/doc/bootstrap/grid.less
new file mode 100644
index 0000000000000000000000000000000000000000..2e61c942611cbfcdb79293a3fe86e6e938db0e66
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/grid.less
@@ -0,0 +1,84 @@
+//
+// Grid system
+// --------------------------------------------------
+
+
+// Container widths
+//
+// Set the container width, and override it for fixed navbars in media queries.
+
+.tb-container {
+  .container-fixed();
+
+  @media (min-width: @screen-sm-min) {
+    width: @container-sm;
+  }
+  @media (min-width: @screen-md-min) {
+    width: @container-md;
+  }
+  @media (min-width: @screen-lg-min) {
+    width: @container-lg;
+  }
+}
+
+
+// Fluid container
+//
+// Utilizes the mixin meant for fixed width containers, but without any defined
+// width for fluid, full width layouts.
+
+.container-fluid {
+  .container-fixed();
+}
+
+
+// Row
+//
+// Rows contain and clear the floats of your columns.
+
+.row {
+  .make-row();
+}
+
+
+// Columns
+//
+// Common styles for small and large grid columns
+
+.make-grid-columns();
+
+
+// Extra small grid
+//
+// Columns, offsets, pushes, and pulls for extra small devices like
+// smartphones.
+
+.make-grid(xs);
+
+
+// Small grid
+//
+// Columns, offsets, pushes, and pulls for the small device range, from phones
+// to tablets.
+
+@media (min-width: @screen-sm-min) {
+  .make-grid(sm);
+}
+
+
+// Medium grid
+//
+// Columns, offsets, pushes, and pulls for the desktop device range.
+
+@media (min-width: @screen-md-min) {
+  .make-grid(md);
+}
+
+
+// Large grid
+//
+// Columns, offsets, pushes, and pulls for the large desktop device range.
+
+@media (min-width: @screen-lg-min) {
+  .make-grid(lg);
+}
diff --git a/libs/fmt/doc/bootstrap/input-groups.less b/libs/fmt/doc/bootstrap/input-groups.less
new file mode 100644
index 0000000000000000000000000000000000000000..a8712f25b935f6d13e3d7ea9471d354a7bccd4df
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/input-groups.less
@@ -0,0 +1,166 @@
+//
+// Input groups
+// --------------------------------------------------
+
+// Base styles
+// -------------------------
+.input-group {
+  position: relative; // For dropdowns
+  display: table;
+  border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table
+
+  // Undo padding and float of grid classes
+  &[class*="col-"] {
+    float: none;
+    padding-left: 0;
+    padding-right: 0;
+  }
+
+  .form-control {
+    // Ensure that the input is always above the *appended* addon button for
+    // proper border colors.
+    position: relative;
+    z-index: 2;
+
+    // IE9 fubars the placeholder attribute in text inputs and the arrows on
+    // select elements in input groups. To fix it, we float the input. Details:
+    // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855
+    float: left;
+
+    width: 100%;
+    margin-bottom: 0;
+  }
+}
+
+// Sizing options
+//
+// Remix the default form control sizing classes into new ones for easier
+// manipulation.
+
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+  .input-lg();
+}
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+  .input-sm();
+}
+
+
+// Display as table-cell
+// -------------------------
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+  display: table-cell;
+
+  &:not(:first-child):not(:last-child) {
+    border-radius: 0;
+  }
+}
+// Addon and addon wrapper for buttons
+.input-group-addon,
+.input-group-btn {
+  width: 1%;
+  white-space: nowrap;
+  vertical-align: middle; // Match the inputs
+}
+
+// Text input groups
+// -------------------------
+.input-group-addon {
+  padding: @padding-base-vertical @padding-base-horizontal;
+  font-size: @font-size-base;
+  font-weight: normal;
+  line-height: 1;
+  color: @input-color;
+  text-align: center;
+  background-color: @input-group-addon-bg;
+  border: 1px solid @input-group-addon-border-color;
+  border-radius: @border-radius-base;
+
+  // Sizing
+  &.input-sm {
+    padding: @padding-small-vertical @padding-small-horizontal;
+    font-size: @font-size-small;
+    border-radius: @border-radius-small;
+  }
+  &.input-lg {
+    padding: @padding-large-vertical @padding-large-horizontal;
+    font-size: @font-size-large;
+    border-radius: @border-radius-large;
+  }
+
+  // Nuke default margins from checkboxes and radios to vertically center within.
+  input[type="radio"],
+  input[type="checkbox"] {
+    margin-top: 0;
+  }
+}
+
+// Reset rounded corners
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .btn-group > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
+  .border-right-radius(0);
+}
+.input-group-addon:first-child {
+  border-right: 0;
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .btn-group > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child),
+.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
+  .border-left-radius(0);
+}
+.input-group-addon:last-child {
+  border-left: 0;
+}
+
+// Button input groups
+// -------------------------
+.input-group-btn {
+  position: relative;
+  // Jankily prevent input button groups from wrapping with `white-space` and
+  // `font-size` in combination with `inline-block` on buttons.
+  font-size: 0;
+  white-space: nowrap;
+
+  // Negative margin for spacing, position for bringing hovered/focused/actived
+  // element above the siblings.
+  > .btn {
+    position: relative;
+    + .btn {
+      margin-left: -1px;
+    }
+    // Bring the "active" button to the front
+    &:hover,
+    &:focus,
+    &:active {
+      z-index: 2;
+    }
+  }
+
+  // Negative margin to only have a 1px border between the two
+  &:first-child {
+    > .btn,
+    > .btn-group {
+      margin-right: -1px;
+    }
+  }
+  &:last-child {
+    > .btn,
+    > .btn-group {
+      margin-left: -1px;
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/jumbotron.less b/libs/fmt/doc/bootstrap/jumbotron.less
new file mode 100644
index 0000000000000000000000000000000000000000..909580f9810298dfd9922f1c24f1422a0f51f0b2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/jumbotron.less
@@ -0,0 +1,50 @@
+//
+// Jumbotron
+// --------------------------------------------------
+
+
+.jumbotron {
+  padding: @jumbotron-padding (@jumbotron-padding / 2);
+  margin-bottom: @jumbotron-padding;
+  color: @jumbotron-color;
+  background-color: @jumbotron-bg;
+
+  h1,
+  .h1 {
+    color: @jumbotron-heading-color;
+  }
+
+  p {
+    margin-bottom: (@jumbotron-padding / 2);
+    font-size: @jumbotron-font-size;
+    font-weight: 200;
+  }
+
+  > hr {
+    border-top-color: darken(@jumbotron-bg, 10%);
+  }
+
+  .tb-container &,
+  .container-fluid & {
+    border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container
+  }
+
+  .tb-container {
+    max-width: 100%;
+  }
+
+  @media screen and (min-width: @screen-sm-min) {
+    padding: (@jumbotron-padding * 1.6) 0;
+
+    .tb-container &,
+    .container-fluid & {
+      padding-left:  (@jumbotron-padding * 2);
+      padding-right: (@jumbotron-padding * 2);
+    }
+
+    h1,
+    .h1 {
+      font-size: (@font-size-base * 4.5);
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/labels.less b/libs/fmt/doc/bootstrap/labels.less
new file mode 100644
index 0000000000000000000000000000000000000000..9a5a27006a51f4ac0d350cf11060aa548cd6ef6b
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/labels.less
@@ -0,0 +1,64 @@
+//
+// Labels
+// --------------------------------------------------
+
+.label {
+  display: inline;
+  padding: .2em .6em .3em;
+  font-size: 75%;
+  font-weight: bold;
+  line-height: 1;
+  color: @label-color;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: .25em;
+
+  // Add hover effects, but only for links
+  a& {
+    &:hover,
+    &:focus {
+      color: @label-link-hover-color;
+      text-decoration: none;
+      cursor: pointer;
+    }
+  }
+
+  // Empty labels collapse automatically (not available in IE8)
+  &:empty {
+    display: none;
+  }
+
+  // Quick fix for labels in buttons
+  .btn & {
+    position: relative;
+    top: -1px;
+  }
+}
+
+// Colors
+// Contextual variations (linked labels get darker on :hover)
+
+.label-default {
+  .label-variant(@label-default-bg);
+}
+
+.label-primary {
+  .label-variant(@label-primary-bg);
+}
+
+.label-success {
+  .label-variant(@label-success-bg);
+}
+
+.label-info {
+  .label-variant(@label-info-bg);
+}
+
+.label-warning {
+  .label-variant(@label-warning-bg);
+}
+
+.label-danger {
+  .label-variant(@label-danger-bg);
+}
diff --git a/libs/fmt/doc/bootstrap/list-group.less b/libs/fmt/doc/bootstrap/list-group.less
new file mode 100644
index 0000000000000000000000000000000000000000..1462ce16b325082f98f78d8e28535fe96e86f1da
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/list-group.less
@@ -0,0 +1,124 @@
+//
+// List groups
+// --------------------------------------------------
+
+
+// Base class
+//
+// Easily usable on <ul>, <ol>, or <div>.
+
+.list-group {
+  // No need to set list-style: none; since .list-group-item is block level
+  margin-bottom: 20px;
+  padding-left: 0; // reset padding because ul and ol
+}
+
+
+// Individual list items
+//
+// Use on `li`s or `div`s within the `.list-group` parent.
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 10px 15px;
+  // Place the border on the list items and negative margin up for better styling
+  margin-bottom: -1px;
+  background-color: @list-group-bg;
+  border: 1px solid @list-group-border;
+
+  // Round the first and last items
+  &:first-child {
+    .border-top-radius(@list-group-border-radius);
+  }
+  &:last-child {
+    margin-bottom: 0;
+    .border-bottom-radius(@list-group-border-radius);
+  }
+}
+
+
+// Linked list items
+//
+// Use anchor elements instead of `li`s or `div`s to create linked list items.
+// Includes an extra `.active` modifier class for showing selected items.
+
+a.list-group-item {
+  color: @list-group-link-color;
+
+  .list-group-item-heading {
+    color: @list-group-link-heading-color;
+  }
+
+  // Hover state
+  &:hover,
+  &:focus {
+    text-decoration: none;
+    color: @list-group-link-hover-color;
+    background-color: @list-group-hover-bg;
+  }
+}
+
+.list-group-item {
+  // Disabled state
+  &.disabled,
+  &.disabled:hover,
+  &.disabled:focus {
+    background-color: @list-group-disabled-bg;
+    color: @list-group-disabled-color;
+    cursor: @cursor-disabled;
+
+    // Force color to inherit for custom content
+    .list-group-item-heading {
+      color: inherit;
+    }
+    .list-group-item-text {
+      color: @list-group-disabled-text-color;
+    }
+  }
+
+  // Active class on item itself, not parent
+  &.active,
+  &.active:hover,
+  &.active:focus {
+    z-index: 2; // Place active items above their siblings for proper border styling
+    color: @list-group-active-color;
+    background-color: @list-group-active-bg;
+    border-color: @list-group-active-border;
+
+    // Force color to inherit for custom content
+    .list-group-item-heading,
+    .list-group-item-heading > small,
+    .list-group-item-heading > .small {
+      color: inherit;
+    }
+    .list-group-item-text {
+      color: @list-group-active-text-color;
+    }
+  }
+}
+
+
+// Contextual variants
+//
+// Add modifier classes to change text and background color on individual items.
+// Organizationally, this must come after the `:hover` states.
+
+.list-group-item-variant(success; @state-success-bg; @state-success-text);
+.list-group-item-variant(info; @state-info-bg; @state-info-text);
+.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);
+.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);
+
+
+// Custom content options
+//
+// Extra classes for creating well-formatted content within `.list-group-item`s.
+
+.list-group-item-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+.list-group-item-text {
+  margin-bottom: 0;
+  line-height: 1.3;
+}
diff --git a/libs/fmt/doc/bootstrap/media.less b/libs/fmt/doc/bootstrap/media.less
new file mode 100644
index 0000000000000000000000000000000000000000..a6bf78192469c48166b7ddcd494ba2cbec376233
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/media.less
@@ -0,0 +1,61 @@
+.media {
+  // Proper spacing between instances of .media
+  margin-top: 15px;
+
+  &:first-child {
+    margin-top: 0;
+  }
+}
+
+.media,
+.media-body {
+  zoom: 1;
+  overflow: hidden;
+}
+
+.media-body {
+  width: 10000px;
+}
+
+.media-object {
+  display: block;
+}
+
+.media-right,
+.media > .pull-right {
+  padding-left: 10px;
+}
+
+.media-left,
+.media > .pull-left {
+  padding-right: 10px;
+}
+
+.media-left,
+.media-right,
+.media-body {
+  display: table-cell;
+  vertical-align: top;
+}
+
+.media-middle {
+  vertical-align: middle;
+}
+
+.media-bottom {
+  vertical-align: bottom;
+}
+
+// Reset margins on headings for tighter default spacing
+.media-heading {
+  margin-top: 0;
+  margin-bottom: 5px;
+}
+
+// Media list variation
+//
+// Undo default ul/ol styles
+.media-list {
+  padding-left: 0;
+  list-style: none;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins.less b/libs/fmt/doc/bootstrap/mixins.less
new file mode 100644
index 0000000000000000000000000000000000000000..af4408fc2d6846b5656d9d0d12f2f4cbf4404cf6
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins.less
@@ -0,0 +1,39 @@
+// Mixins
+// --------------------------------------------------
+
+// Utilities
+@import "mixins/hide-text.less";
+@import "mixins/opacity.less";
+@import "mixins/image.less";
+@import "mixins/labels.less";
+@import "mixins/reset-filter.less";
+@import "mixins/resize.less";
+@import "mixins/responsive-visibility.less";
+@import "mixins/size.less";
+@import "mixins/tab-focus.less";
+@import "mixins/text-emphasis.less";
+@import "mixins/text-overflow.less";
+@import "mixins/vendor-prefixes.less";
+
+// Components
+@import "mixins/alerts.less";
+@import "mixins/buttons.less";
+@import "mixins/panels.less";
+@import "mixins/pagination.less";
+@import "mixins/list-group.less";
+@import "mixins/nav-divider.less";
+@import "mixins/forms.less";
+@import "mixins/progress-bar.less";
+@import "mixins/table-row.less";
+
+// Skins
+@import "mixins/background-variant.less";
+@import "mixins/border-radius.less";
+@import "mixins/gradients.less";
+
+// Layout
+@import "mixins/clearfix.less";
+@import "mixins/center-block.less";
+@import "mixins/nav-vertical-align.less";
+@import "mixins/grid-framework.less";
+@import "mixins/grid.less";
diff --git a/libs/fmt/doc/bootstrap/mixins/alerts.less b/libs/fmt/doc/bootstrap/mixins/alerts.less
new file mode 100644
index 0000000000000000000000000000000000000000..396196f438fd31e7757a0874ece4a99fe0cf9fd9
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/alerts.less
@@ -0,0 +1,14 @@
+// Alerts
+
+.alert-variant(@background; @border; @text-color) {
+  background-color: @background;
+  border-color: @border;
+  color: @text-color;
+
+  hr {
+    border-top-color: darken(@border, 5%);
+  }
+  .alert-link {
+    color: darken(@text-color, 10%);
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/background-variant.less b/libs/fmt/doc/bootstrap/mixins/background-variant.less
new file mode 100644
index 0000000000000000000000000000000000000000..556e490d45d004c4e4504153133c5c1050207b65
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/background-variant.less
@@ -0,0 +1,8 @@
+// Contextual backgrounds
+
+.bg-variant(@color) {
+  background-color: @color;
+  a&:hover {
+    background-color: darken(@color, 10%);
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/border-radius.less b/libs/fmt/doc/bootstrap/mixins/border-radius.less
new file mode 100644
index 0000000000000000000000000000000000000000..ca05dbf457000d122a3b8afcc3847e44cffea421
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/border-radius.less
@@ -0,0 +1,18 @@
+// Single side border-radius
+
+.border-top-radius(@radius) {
+  border-top-right-radius: @radius;
+   border-top-left-radius: @radius;
+}
+.border-right-radius(@radius) {
+  border-bottom-right-radius: @radius;
+     border-top-right-radius: @radius;
+}
+.border-bottom-radius(@radius) {
+  border-bottom-right-radius: @radius;
+   border-bottom-left-radius: @radius;
+}
+.border-left-radius(@radius) {
+  border-bottom-left-radius: @radius;
+     border-top-left-radius: @radius;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/buttons.less b/libs/fmt/doc/bootstrap/mixins/buttons.less
new file mode 100644
index 0000000000000000000000000000000000000000..92d8a056cd3b73c82937b0c9d478a86baa6d142a
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/buttons.less
@@ -0,0 +1,52 @@
+// Button variants
+//
+// Easily pump out default styles, as well as :hover, :focus, :active,
+// and disabled options for all buttons
+
+.button-variant(@color; @background; @border) {
+  color: @color;
+  background-color: @background;
+  border-color: @border;
+
+  &:hover,
+  &:focus,
+  &.focus,
+  &:active,
+  &.active,
+  .open > .dropdown-toggle& {
+    color: @color;
+    background-color: darken(@background, 10%);
+        border-color: darken(@border, 12%);
+  }
+  &:active,
+  &.active,
+  .open > .dropdown-toggle& {
+    background-image: none;
+  }
+  &.disabled,
+  &[disabled],
+  fieldset[disabled] & {
+    &,
+    &:hover,
+    &:focus,
+    &.focus,
+    &:active,
+    &.active {
+      background-color: @background;
+          border-color: @border;
+    }
+  }
+
+  .badge {
+    color: @background;
+    background-color: @color;
+  }
+}
+
+// Button sizes
+.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
+  padding: @padding-vertical @padding-horizontal;
+  font-size: @font-size;
+  line-height: @line-height;
+  border-radius: @border-radius;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/center-block.less b/libs/fmt/doc/bootstrap/mixins/center-block.less
new file mode 100644
index 0000000000000000000000000000000000000000..d18d6de9ed6a7af49415277cb72e57b7dbd42d0f
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/center-block.less
@@ -0,0 +1,7 @@
+// Center-align a block level element
+
+.center-block() {
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/clearfix.less b/libs/fmt/doc/bootstrap/mixins/clearfix.less
new file mode 100644
index 0000000000000000000000000000000000000000..3f7a3820c1cf7756322917982646352e77119f46
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/clearfix.less
@@ -0,0 +1,22 @@
+// Clearfix
+//
+// For modern browsers
+// 1. The space content is one way to avoid an Opera bug when the
+//    contenteditable attribute is included anywhere else in the document.
+//    Otherwise it causes space to appear at the top and bottom of elements
+//    that are clearfixed.
+// 2. The use of `table` rather than `block` is only necessary if using
+//    `:before` to contain the top-margins of child elements.
+//
+// Source: http://nicolasgallagher.com/micro-clearfix-hack/
+
+.clearfix() {
+  &:before,
+  &:after {
+    content: " "; // 1
+    display: table; // 2
+  }
+  &:after {
+    clear: both;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/forms.less b/libs/fmt/doc/bootstrap/mixins/forms.less
new file mode 100644
index 0000000000000000000000000000000000000000..6f55ed967082caf4ef515f88c9d591f95036a470
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/forms.less
@@ -0,0 +1,85 @@
+// Form validation states
+//
+// Used in forms.less to generate the form validation CSS for warnings, errors,
+// and successes.
+
+.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
+  // Color the label and help text
+  .help-block,
+  .control-label,
+  .radio,
+  .checkbox,
+  .radio-inline,
+  .checkbox-inline,
+  &.radio label,
+  &.checkbox label,
+  &.radio-inline label,
+  &.checkbox-inline label  {
+    color: @text-color;
+  }
+  // Set the border and box shadow on specific inputs to match
+  .form-control {
+    border-color: @border-color;
+    .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
+    &:focus {
+      border-color: darken(@border-color, 10%);
+      @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);
+      .box-shadow(@shadow);
+    }
+  }
+  // Set validation states also for addons
+  .input-group-addon {
+    color: @text-color;
+    border-color: @border-color;
+    background-color: @background-color;
+  }
+  // Optional feedback icon
+  .form-control-feedback {
+    color: @text-color;
+  }
+}
+
+
+// Form control focus state
+//
+// Generate a customized focus state and for any input with the specified color,
+// which defaults to the `@input-border-focus` variable.
+//
+// We highly encourage you to not customize the default value, but instead use
+// this to tweak colors on an as-needed basis. This aesthetic change is based on
+// WebKit's default styles, but applicable to a wider range of browsers. Its
+// usability and accessibility should be taken into account with any change.
+//
+// Example usage: change the default blue border and shadow to white for better
+// contrast against a dark gray background.
+.form-control-focus(@color: @input-border-focus) {
+  @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
+  &:focus {
+    border-color: @color;
+    outline: 0;
+    .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
+  }
+}
+
+// Form control sizing
+//
+// Relative text size, padding, and border-radii changes for form controls. For
+// horizontal sizing, wrap controls in the predefined grid classes. `<select>`
+// element gets special love because it's special, and that's a fact!
+.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
+  height: @input-height;
+  padding: @padding-vertical @padding-horizontal;
+  font-size: @font-size;
+  line-height: @line-height;
+  border-radius: @border-radius;
+
+  select& {
+    height: @input-height;
+    line-height: @input-height;
+  }
+
+  textarea&,
+  select[multiple]& {
+    height: auto;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/gradients.less b/libs/fmt/doc/bootstrap/mixins/gradients.less
new file mode 100644
index 0000000000000000000000000000000000000000..0b88a89cc56a63effe45558845122b077de03979
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/gradients.less
@@ -0,0 +1,59 @@
+// Gradients
+
+#gradient {
+
+  // Horizontal gradient, from left to right
+  //
+  // Creates two color stops, start and end, by specifying a color and position for each color stop.
+  // Color stops are not available in IE9 and below.
+  .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
+    background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
+    background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12
+    background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
+    background-repeat: repeat-x;
+    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down
+  }
+
+  // Vertical gradient, from top to bottom
+  //
+  // Creates two color stops, start and end, by specifying a color and position for each color stop.
+  // Color stops are not available in IE9 and below.
+  .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
+    background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent);  // Safari 5.1-6, Chrome 10+
+    background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent);  // Opera 12
+    background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
+    background-repeat: repeat-x;
+    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down
+  }
+
+  .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {
+    background-repeat: repeat-x;
+    background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+
+    background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12
+    background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
+  }
+  .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
+    background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
+    background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
+    background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);
+    background-repeat: no-repeat;
+    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
+  }
+  .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
+    background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
+    background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
+    background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);
+    background-repeat: no-repeat;
+    filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
+  }
+  .radial(@inner-color: #555; @outer-color: #333) {
+    background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);
+    background-image: radial-gradient(circle, @inner-color, @outer-color);
+    background-repeat: no-repeat;
+  }
+  .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {
+    background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
+    background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
+    background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/grid-framework.less b/libs/fmt/doc/bootstrap/mixins/grid-framework.less
new file mode 100644
index 0000000000000000000000000000000000000000..f3b3929d6d0e55b9fffe1c2dbde5747b21eb0014
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/grid-framework.less
@@ -0,0 +1,91 @@
+// Framework grid generation
+//
+// Used only by Bootstrap to generate the correct number of grid classes given
+// any value of `@grid-columns`.
+
+.make-grid-columns() {
+  // Common styles for all sizes of grid columns, widths 1-12
+  .col(@index) { // initial
+    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
+    .col((@index + 1), @item);
+  }
+  .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
+    @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
+    .col((@index + 1), ~"@{list}, @{item}");
+  }
+  .col(@index, @list) when (@index > @grid-columns) { // terminal
+    @{list} {
+      position: relative;
+      // Prevent columns from collapsing when empty
+      min-height: 1px;
+      // Inner gutter via padding
+      padding-left:  (@grid-gutter-width / 2);
+      padding-right: (@grid-gutter-width / 2);
+    }
+  }
+  .col(1); // kickstart it
+}
+
+.float-grid-columns(@class) {
+  .col(@index) { // initial
+    @item: ~".col-@{class}-@{index}";
+    .col((@index + 1), @item);
+  }
+  .col(@index, @list) when (@index =< @grid-columns) { // general
+    @item: ~".col-@{class}-@{index}";
+    .col((@index + 1), ~"@{list}, @{item}");
+  }
+  .col(@index, @list) when (@index > @grid-columns) { // terminal
+    @{list} {
+      float: left;
+    }
+  }
+  .col(1); // kickstart it
+}
+
+.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
+  .col-@{class}-@{index} {
+    width: percentage((@index / @grid-columns));
+  }
+}
+.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {
+  .col-@{class}-push-@{index} {
+    left: percentage((@index / @grid-columns));
+  }
+}
+.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {
+  .col-@{class}-push-0 {
+    left: auto;
+  }
+}
+.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {
+  .col-@{class}-pull-@{index} {
+    right: percentage((@index / @grid-columns));
+  }
+}
+.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {
+  .col-@{class}-pull-0 {
+    right: auto;
+  }
+}
+.calc-grid-column(@index, @class, @type) when (@type = offset) {
+  .col-@{class}-offset-@{index} {
+    margin-left: percentage((@index / @grid-columns));
+  }
+}
+
+// Basic looping in LESS
+.loop-grid-columns(@index, @class, @type) when (@index >= 0) {
+  .calc-grid-column(@index, @class, @type);
+  // next iteration
+  .loop-grid-columns((@index - 1), @class, @type);
+}
+
+// Create grid for specific class
+.make-grid(@class) {
+  .float-grid-columns(@class);
+  .loop-grid-columns(@grid-columns, @class, width);
+  .loop-grid-columns(@grid-columns, @class, pull);
+  .loop-grid-columns(@grid-columns, @class, push);
+  .loop-grid-columns(@grid-columns, @class, offset);
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/grid.less b/libs/fmt/doc/bootstrap/mixins/grid.less
new file mode 100644
index 0000000000000000000000000000000000000000..cae5eaff924fed1e4d5978952d9c8490faf8500c
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/grid.less
@@ -0,0 +1,122 @@
+// Grid system
+//
+// Generate semantic grid columns with these mixins.
+
+// Centered container element
+.container-fixed(@gutter: @grid-gutter-width) {
+  margin-right: auto;
+  margin-left: auto;
+  padding-left:  (@gutter / 2);
+  padding-right: (@gutter / 2);
+  &:extend(.clearfix all);
+}
+
+// Creates a wrapper for a series of columns
+.make-row(@gutter: @grid-gutter-width) {
+  margin-left:  (@gutter / -2);
+  margin-right: (@gutter / -2);
+  &:extend(.clearfix all);
+}
+
+// Generate the extra small columns
+.make-xs-column(@columns; @gutter: @grid-gutter-width) {
+  position: relative;
+  float: left;
+  width: percentage((@columns / @grid-columns));
+  min-height: 1px;
+  padding-left:  (@gutter / 2);
+  padding-right: (@gutter / 2);
+}
+.make-xs-column-offset(@columns) {
+  margin-left: percentage((@columns / @grid-columns));
+}
+.make-xs-column-push(@columns) {
+  left: percentage((@columns / @grid-columns));
+}
+.make-xs-column-pull(@columns) {
+  right: percentage((@columns / @grid-columns));
+}
+
+// Generate the small columns
+.make-sm-column(@columns; @gutter: @grid-gutter-width) {
+  position: relative;
+  min-height: 1px;
+  padding-left:  (@gutter / 2);
+  padding-right: (@gutter / 2);
+
+  @media (min-width: @screen-sm-min) {
+    float: left;
+    width: percentage((@columns / @grid-columns));
+  }
+}
+.make-sm-column-offset(@columns) {
+  @media (min-width: @screen-sm-min) {
+    margin-left: percentage((@columns / @grid-columns));
+  }
+}
+.make-sm-column-push(@columns) {
+  @media (min-width: @screen-sm-min) {
+    left: percentage((@columns / @grid-columns));
+  }
+}
+.make-sm-column-pull(@columns) {
+  @media (min-width: @screen-sm-min) {
+    right: percentage((@columns / @grid-columns));
+  }
+}
+
+// Generate the medium columns
+.make-md-column(@columns; @gutter: @grid-gutter-width) {
+  position: relative;
+  min-height: 1px;
+  padding-left:  (@gutter / 2);
+  padding-right: (@gutter / 2);
+
+  @media (min-width: @screen-md-min) {
+    float: left;
+    width: percentage((@columns / @grid-columns));
+  }
+}
+.make-md-column-offset(@columns) {
+  @media (min-width: @screen-md-min) {
+    margin-left: percentage((@columns / @grid-columns));
+  }
+}
+.make-md-column-push(@columns) {
+  @media (min-width: @screen-md-min) {
+    left: percentage((@columns / @grid-columns));
+  }
+}
+.make-md-column-pull(@columns) {
+  @media (min-width: @screen-md-min) {
+    right: percentage((@columns / @grid-columns));
+  }
+}
+
+// Generate the large columns
+.make-lg-column(@columns; @gutter: @grid-gutter-width) {
+  position: relative;
+  min-height: 1px;
+  padding-left:  (@gutter / 2);
+  padding-right: (@gutter / 2);
+
+  @media (min-width: @screen-lg-min) {
+    float: left;
+    width: percentage((@columns / @grid-columns));
+  }
+}
+.make-lg-column-offset(@columns) {
+  @media (min-width: @screen-lg-min) {
+    margin-left: percentage((@columns / @grid-columns));
+  }
+}
+.make-lg-column-push(@columns) {
+  @media (min-width: @screen-lg-min) {
+    left: percentage((@columns / @grid-columns));
+  }
+}
+.make-lg-column-pull(@columns) {
+  @media (min-width: @screen-lg-min) {
+    right: percentage((@columns / @grid-columns));
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/hide-text.less b/libs/fmt/doc/bootstrap/mixins/hide-text.less
new file mode 100644
index 0000000000000000000000000000000000000000..c2315e572f83385f10407cae46253526e0bb3dd7
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/hide-text.less
@@ -0,0 +1,21 @@
+// CSS image replacement
+//
+// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for
+// mixins being reused as classes with the same name, this doesn't hold up. As
+// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
+//
+// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
+
+// Deprecated as of v3.0.1 (will be removed in v4)
+.hide-text() {
+  font: ~"0/0" a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+// New mixin to use as of v3.0.1
+.text-hide() {
+  .hide-text();
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/image.less b/libs/fmt/doc/bootstrap/mixins/image.less
new file mode 100644
index 0000000000000000000000000000000000000000..f233cb3e199f21ac194212e10ebb1e12feb0682b
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/image.less
@@ -0,0 +1,33 @@
+// Image Mixins
+// - Responsive image
+// - Retina image
+
+
+// Responsive image
+//
+// Keep images from scaling beyond the width of their parents.
+.img-responsive(@display: block) {
+  display: @display;
+  max-width: 100%; // Part 1: Set a maximum relative to the parent
+  height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
+}
+
+
+// Retina image
+//
+// Short retina mixin for setting background-image and -size. Note that the
+// spelling of `min--moz-device-pixel-ratio` is intentional.
+.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
+  background-image: url("@{file-1x}");
+
+  @media
+  only screen and (-webkit-min-device-pixel-ratio: 2),
+  only screen and (   min--moz-device-pixel-ratio: 2),
+  only screen and (     -o-min-device-pixel-ratio: 2/1),
+  only screen and (        min-device-pixel-ratio: 2),
+  only screen and (                min-resolution: 192dpi),
+  only screen and (                min-resolution: 2dppx) {
+    background-image: url("@{file-2x}");
+    background-size: @width-1x @height-1x;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/labels.less b/libs/fmt/doc/bootstrap/mixins/labels.less
new file mode 100644
index 0000000000000000000000000000000000000000..9f7a67ee3d08a03ad10277d703805d315cd43d54
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/labels.less
@@ -0,0 +1,12 @@
+// Labels
+
+.label-variant(@color) {
+  background-color: @color;
+
+  &[href] {
+    &:hover,
+    &:focus {
+      background-color: darken(@color, 10%);
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/list-group.less b/libs/fmt/doc/bootstrap/mixins/list-group.less
new file mode 100644
index 0000000000000000000000000000000000000000..8b5b065cb84cec3364d028e046b3fd24878f20b1
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/list-group.less
@@ -0,0 +1,29 @@
+// List Groups
+
+.list-group-item-variant(@state; @background; @color) {
+  .list-group-item-@{state} {
+    color: @color;
+    background-color: @background;
+
+    a& {
+      color: @color;
+
+      .list-group-item-heading {
+        color: inherit;
+      }
+
+      &:hover,
+      &:focus {
+        color: @color;
+        background-color: darken(@background, 5%);
+      }
+      &.active,
+      &.active:hover,
+      &.active:focus {
+        color: #fff;
+        background-color: @color;
+        border-color: @color;
+      }
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/nav-divider.less b/libs/fmt/doc/bootstrap/mixins/nav-divider.less
new file mode 100644
index 0000000000000000000000000000000000000000..feb1e9ed0dad8d0db00ebb93b175591a88abf707
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/nav-divider.less
@@ -0,0 +1,10 @@
+// Horizontal dividers
+//
+// Dividers (basically an hr) within dropdowns and nav lists
+
+.nav-divider(@color: #e5e5e5) {
+  height: 1px;
+  margin: ((@line-height-computed / 2) - 1) 0;
+  overflow: hidden;
+  background-color: @color;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less b/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less
new file mode 100644
index 0000000000000000000000000000000000000000..d458c78613e6dc082141783ef45d56a5a7d601c3
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/nav-vertical-align.less
@@ -0,0 +1,9 @@
+// Navbar vertical align
+//
+// Vertically center elements in the navbar.
+// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
+
+.navbar-vertical-align(@element-height) {
+  margin-top: ((@navbar-height - @element-height) / 2);
+  margin-bottom: ((@navbar-height - @element-height) / 2);
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/opacity.less b/libs/fmt/doc/bootstrap/mixins/opacity.less
new file mode 100644
index 0000000000000000000000000000000000000000..33ed25ce6763de7f8ae195628c1eb168379d8435
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/opacity.less
@@ -0,0 +1,8 @@
+// Opacity
+
+.opacity(@opacity) {
+  opacity: @opacity;
+  // IE8 filter
+  @opacity-ie: (@opacity * 100);
+  filter: ~"alpha(opacity=@{opacity-ie})";
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/pagination.less b/libs/fmt/doc/bootstrap/mixins/pagination.less
new file mode 100644
index 0000000000000000000000000000000000000000..7deb505d25f09cfc939beff303c0f6d52e83a9a2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/pagination.less
@@ -0,0 +1,23 @@
+// Pagination
+
+.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {
+  > li {
+    > a,
+    > span {
+      padding: @padding-vertical @padding-horizontal;
+      font-size: @font-size;
+    }
+    &:first-child {
+      > a,
+      > span {
+        .border-left-radius(@border-radius);
+      }
+    }
+    &:last-child {
+      > a,
+      > span {
+        .border-right-radius(@border-radius);
+      }
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/panels.less b/libs/fmt/doc/bootstrap/mixins/panels.less
new file mode 100644
index 0000000000000000000000000000000000000000..49ee10d4ad39de45e0983d01cbbc50949a1dac2e
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/panels.less
@@ -0,0 +1,24 @@
+// Panels
+
+.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {
+  border-color: @border;
+
+  & > .panel-heading {
+    color: @heading-text-color;
+    background-color: @heading-bg-color;
+    border-color: @heading-border;
+
+    + .panel-collapse > .panel-body {
+      border-top-color: @border;
+    }
+    .badge {
+      color: @heading-bg-color;
+      background-color: @heading-text-color;
+    }
+  }
+  & > .panel-footer {
+    + .panel-collapse > .panel-body {
+      border-bottom-color: @border;
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/progress-bar.less b/libs/fmt/doc/bootstrap/mixins/progress-bar.less
new file mode 100644
index 0000000000000000000000000000000000000000..f07996a34dbc19a1acc1c024a2410eb2b1b25271
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/progress-bar.less
@@ -0,0 +1,10 @@
+// Progress bars
+
+.progress-bar-variant(@color) {
+  background-color: @color;
+
+  // Deprecated parent class requirement as of v3.2.0
+  .progress-striped & {
+    #gradient > .striped();
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/reset-filter.less b/libs/fmt/doc/bootstrap/mixins/reset-filter.less
new file mode 100644
index 0000000000000000000000000000000000000000..68cdb5e18602af3ab8e3ddba57a06045ef1c65bb
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/reset-filter.less
@@ -0,0 +1,8 @@
+// Reset filters for IE
+//
+// When you need to remove a gradient background, do not forget to use this to reset
+// the IE filter for IE9 and below.
+
+.reset-filter() {
+  filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/resize.less b/libs/fmt/doc/bootstrap/mixins/resize.less
new file mode 100644
index 0000000000000000000000000000000000000000..3acd3afdbacbb7f642e04f7ff434c18107faad69
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/resize.less
@@ -0,0 +1,6 @@
+// Resize anything
+
+.resizable(@direction) {
+  resize: @direction; // Options: horizontal, vertical, both
+  overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less b/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less
new file mode 100644
index 0000000000000000000000000000000000000000..f7951c3d75cbe6bb72ea5f7493920611a2800797
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/responsive-visibility.less
@@ -0,0 +1,15 @@
+// Responsive utilities
+
+//
+// More easily include all the states for responsive-utilities.less.
+.responsive-visibility() {
+  display: block !important;
+  table&  { display: table; }
+  tr&     { display: table-row !important; }
+  th&,
+  td&     { display: table-cell !important; }
+}
+
+.responsive-invisibility() {
+  display: none !important;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/size.less b/libs/fmt/doc/bootstrap/mixins/size.less
new file mode 100644
index 0000000000000000000000000000000000000000..a8be65089606863ad878173e951b62fb387daf52
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/size.less
@@ -0,0 +1,10 @@
+// Sizing shortcuts
+
+.size(@width; @height) {
+  width: @width;
+  height: @height;
+}
+
+.square(@size) {
+  .size(@size; @size);
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/tab-focus.less b/libs/fmt/doc/bootstrap/mixins/tab-focus.less
new file mode 100644
index 0000000000000000000000000000000000000000..1f1f05ab054412684539a94423c097d6cdadd8ba
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/tab-focus.less
@@ -0,0 +1,9 @@
+// WebKit-style focus
+
+.tab-focus() {
+  // Default
+  outline: thin dotted;
+  // WebKit
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/table-row.less b/libs/fmt/doc/bootstrap/mixins/table-row.less
new file mode 100644
index 0000000000000000000000000000000000000000..0f287f1a8bdf51607e91d0c8aa6a5be51d6df36e
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/table-row.less
@@ -0,0 +1,28 @@
+// Tables
+
+.table-row-variant(@state; @background) {
+  // Exact selectors below required to override `.table-striped` and prevent
+  // inheritance to nested tables.
+  .table > thead > tr,
+  .table > tbody > tr,
+  .table > tfoot > tr {
+    > td.@{state},
+    > th.@{state},
+    &.@{state} > td,
+    &.@{state} > th {
+      background-color: @background;
+    }
+  }
+
+  // Hover states for `.table-hover`
+  // Note: this is not available for cells or rows within `thead` or `tfoot`.
+  .table-hover > tbody > tr {
+    > td.@{state}:hover,
+    > th.@{state}:hover,
+    &.@{state}:hover > td,
+    &:hover > .@{state},
+    &.@{state}:hover > th {
+      background-color: darken(@background, 5%);
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/text-emphasis.less b/libs/fmt/doc/bootstrap/mixins/text-emphasis.less
new file mode 100644
index 0000000000000000000000000000000000000000..0868ef9f2caaf05c2fdbe9f3f3da1616fe221080
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/text-emphasis.less
@@ -0,0 +1,8 @@
+// Typography
+
+.text-emphasis-variant(@color) {
+  color: @color;
+  a&:hover {
+    color: darken(@color, 10%);
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/text-overflow.less b/libs/fmt/doc/bootstrap/mixins/text-overflow.less
new file mode 100644
index 0000000000000000000000000000000000000000..c11ad2fb7471dd29ae781f1aefdfb8bb5e4a7d6c
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/text-overflow.less
@@ -0,0 +1,8 @@
+// Text overflow
+// Requires inline-block or block for proper styling
+
+.text-overflow() {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
diff --git a/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less b/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less
new file mode 100644
index 0000000000000000000000000000000000000000..afd3331c31da856f226b354916e21ebad9d3311a
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/mixins/vendor-prefixes.less
@@ -0,0 +1,227 @@
+// Vendor Prefixes
+//
+// All vendor mixins are deprecated as of v3.2.0 due to the introduction of
+// Autoprefixer in our Gruntfile. They will be removed in v4.
+
+// - Animations
+// - Backface visibility
+// - Box shadow
+// - Box sizing
+// - Content columns
+// - Hyphens
+// - Placeholder text
+// - Transformations
+// - Transitions
+// - User Select
+
+
+// Animations
+.animation(@animation) {
+  -webkit-animation: @animation;
+       -o-animation: @animation;
+          animation: @animation;
+}
+.animation-name(@name) {
+  -webkit-animation-name: @name;
+          animation-name: @name;
+}
+.animation-duration(@duration) {
+  -webkit-animation-duration: @duration;
+          animation-duration: @duration;
+}
+.animation-timing-function(@timing-function) {
+  -webkit-animation-timing-function: @timing-function;
+          animation-timing-function: @timing-function;
+}
+.animation-delay(@delay) {
+  -webkit-animation-delay: @delay;
+          animation-delay: @delay;
+}
+.animation-iteration-count(@iteration-count) {
+  -webkit-animation-iteration-count: @iteration-count;
+          animation-iteration-count: @iteration-count;
+}
+.animation-direction(@direction) {
+  -webkit-animation-direction: @direction;
+          animation-direction: @direction;
+}
+.animation-fill-mode(@fill-mode) {
+  -webkit-animation-fill-mode: @fill-mode;
+          animation-fill-mode: @fill-mode;
+}
+
+// Backface visibility
+// Prevent browsers from flickering when using CSS 3D transforms.
+// Default value is `visible`, but can be changed to `hidden`
+
+.backface-visibility(@visibility){
+  -webkit-backface-visibility: @visibility;
+     -moz-backface-visibility: @visibility;
+          backface-visibility: @visibility;
+}
+
+// Drop shadows
+//
+// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
+// supported browsers that have box shadow capabilities now support it.
+
+.box-shadow(@shadow) {
+  -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
+          box-shadow: @shadow;
+}
+
+// Box sizing
+.box-sizing(@boxmodel) {
+  -webkit-box-sizing: @boxmodel;
+     -moz-box-sizing: @boxmodel;
+          box-sizing: @boxmodel;
+}
+
+// CSS3 Content Columns
+.content-columns(@column-count; @column-gap: @grid-gutter-width) {
+  -webkit-column-count: @column-count;
+     -moz-column-count: @column-count;
+          column-count: @column-count;
+  -webkit-column-gap: @column-gap;
+     -moz-column-gap: @column-gap;
+          column-gap: @column-gap;
+}
+
+// Optional hyphenation
+.hyphens(@mode: auto) {
+  word-wrap: break-word;
+  -webkit-hyphens: @mode;
+     -moz-hyphens: @mode;
+      -ms-hyphens: @mode; // IE10+
+       -o-hyphens: @mode;
+          hyphens: @mode;
+}
+
+// Placeholder text
+.placeholder(@color: @input-color-placeholder) {
+  // Firefox
+  &::-moz-placeholder {
+    color: @color;
+    opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
+  }
+  &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+
+  &::-webkit-input-placeholder  { color: @color; } // Safari and Chrome
+}
+
+// Transformations
+.scale(@ratio) {
+  -webkit-transform: scale(@ratio);
+      -ms-transform: scale(@ratio); // IE9 only
+       -o-transform: scale(@ratio);
+          transform: scale(@ratio);
+}
+.scale(@ratioX; @ratioY) {
+  -webkit-transform: scale(@ratioX, @ratioY);
+      -ms-transform: scale(@ratioX, @ratioY); // IE9 only
+       -o-transform: scale(@ratioX, @ratioY);
+          transform: scale(@ratioX, @ratioY);
+}
+.scaleX(@ratio) {
+  -webkit-transform: scaleX(@ratio);
+      -ms-transform: scaleX(@ratio); // IE9 only
+       -o-transform: scaleX(@ratio);
+          transform: scaleX(@ratio);
+}
+.scaleY(@ratio) {
+  -webkit-transform: scaleY(@ratio);
+      -ms-transform: scaleY(@ratio); // IE9 only
+       -o-transform: scaleY(@ratio);
+          transform: scaleY(@ratio);
+}
+.skew(@x; @y) {
+  -webkit-transform: skewX(@x) skewY(@y);
+      -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
+       -o-transform: skewX(@x) skewY(@y);
+          transform: skewX(@x) skewY(@y);
+}
+.translate(@x; @y) {
+  -webkit-transform: translate(@x, @y);
+      -ms-transform: translate(@x, @y); // IE9 only
+       -o-transform: translate(@x, @y);
+          transform: translate(@x, @y);
+}
+.translate3d(@x; @y; @z) {
+  -webkit-transform: translate3d(@x, @y, @z);
+          transform: translate3d(@x, @y, @z);
+}
+.rotate(@degrees) {
+  -webkit-transform: rotate(@degrees);
+      -ms-transform: rotate(@degrees); // IE9 only
+       -o-transform: rotate(@degrees);
+          transform: rotate(@degrees);
+}
+.rotateX(@degrees) {
+  -webkit-transform: rotateX(@degrees);
+      -ms-transform: rotateX(@degrees); // IE9 only
+       -o-transform: rotateX(@degrees);
+          transform: rotateX(@degrees);
+}
+.rotateY(@degrees) {
+  -webkit-transform: rotateY(@degrees);
+      -ms-transform: rotateY(@degrees); // IE9 only
+       -o-transform: rotateY(@degrees);
+          transform: rotateY(@degrees);
+}
+.perspective(@perspective) {
+  -webkit-perspective: @perspective;
+     -moz-perspective: @perspective;
+          perspective: @perspective;
+}
+.perspective-origin(@perspective) {
+  -webkit-perspective-origin: @perspective;
+     -moz-perspective-origin: @perspective;
+          perspective-origin: @perspective;
+}
+.transform-origin(@origin) {
+  -webkit-transform-origin: @origin;
+     -moz-transform-origin: @origin;
+      -ms-transform-origin: @origin; // IE9 only
+          transform-origin: @origin;
+}
+
+
+// Transitions
+
+.transition(@transition) {
+  -webkit-transition: @transition;
+       -o-transition: @transition;
+          transition: @transition;
+}
+.transition-property(@transition-property) {
+  -webkit-transition-property: @transition-property;
+          transition-property: @transition-property;
+}
+.transition-delay(@transition-delay) {
+  -webkit-transition-delay: @transition-delay;
+          transition-delay: @transition-delay;
+}
+.transition-duration(@transition-duration) {
+  -webkit-transition-duration: @transition-duration;
+          transition-duration: @transition-duration;
+}
+.transition-timing-function(@timing-function) {
+  -webkit-transition-timing-function: @timing-function;
+          transition-timing-function: @timing-function;
+}
+.transition-transform(@transition) {
+  -webkit-transition: -webkit-transform @transition;
+     -moz-transition: -moz-transform @transition;
+       -o-transition: -o-transform @transition;
+          transition: transform @transition;
+}
+
+
+// User select
+// For selecting text on the page
+
+.user-select(@select) {
+  -webkit-user-select: @select;
+     -moz-user-select: @select;
+      -ms-user-select: @select; // IE10+
+          user-select: @select;
+}
diff --git a/libs/fmt/doc/bootstrap/modals.less b/libs/fmt/doc/bootstrap/modals.less
new file mode 100644
index 0000000000000000000000000000000000000000..1de622050e5ae83742ed1cfa93751c8a39377de2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/modals.less
@@ -0,0 +1,150 @@
+//
+// Modals
+// --------------------------------------------------
+
+// .modal-open      - body class for killing the scroll
+// .modal           - container to scroll within
+// .modal-dialog    - positioning shell for the actual modal
+// .modal-content   - actual modal w/ bg and corners and shit
+
+// Kill the scroll on the body
+.modal-open {
+  overflow: hidden;
+}
+
+// Container that the modal scrolls within
+.modal {
+  display: none;
+  overflow: hidden;
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: @zindex-modal;
+  -webkit-overflow-scrolling: touch;
+
+  // Prevent Chrome on Windows from adding a focus outline. For details, see
+  // https://github.com/twbs/bootstrap/pull/10951.
+  outline: 0;
+
+  // When fading in the modal, animate it to slide down
+  &.fade .modal-dialog {
+    .translate(0, -25%);
+    .transition-transform(~"0.3s ease-out");
+  }
+  &.in .modal-dialog { .translate(0, 0) }
+}
+.modal-open .modal {
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+// Shell div to position the modal with bottom padding
+.modal-dialog {
+  position: relative;
+  width: auto;
+  margin: 10px;
+}
+
+// Actual modal
+.modal-content {
+  position: relative;
+  background-color: @modal-content-bg;
+  border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
+  border: 1px solid @modal-content-border-color;
+  border-radius: @border-radius-large;
+  .box-shadow(0 3px 9px rgba(0,0,0,.5));
+  background-clip: padding-box;
+  // Remove focus outline from opened modal
+  outline: 0;
+}
+
+// Modal background
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: @zindex-modal-background;
+  background-color: @modal-backdrop-bg;
+  // Fade for backdrop
+  &.fade { .opacity(0); }
+  &.in { .opacity(@modal-backdrop-opacity); }
+}
+
+// Modal header
+// Top section of the modal w/ title and dismiss
+.modal-header {
+  padding: @modal-title-padding;
+  border-bottom: 1px solid @modal-header-border-color;
+  min-height: (@modal-title-padding + @modal-title-line-height);
+}
+// Close icon
+.modal-header .close {
+  margin-top: -2px;
+}
+
+// Title text within header
+.modal-title {
+  margin: 0;
+  line-height: @modal-title-line-height;
+}
+
+// Modal body
+// Where all modal content resides (sibling of .modal-header and .modal-footer)
+.modal-body {
+  position: relative;
+  padding: @modal-inner-padding;
+}
+
+// Footer (for actions)
+.modal-footer {
+  padding: @modal-inner-padding;
+  text-align: right; // right align buttons
+  border-top: 1px solid @modal-footer-border-color;
+  &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons
+
+  // Properly space out buttons
+  .btn + .btn {
+    margin-left: 5px;
+    margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
+  }
+  // but override that for button groups
+  .btn-group .btn + .btn {
+    margin-left: -1px;
+  }
+  // and override it for block buttons as well
+  .btn-block + .btn-block {
+    margin-left: 0;
+  }
+}
+
+// Measure scrollbar width for padding body during modal show/hide
+.modal-scrollbar-measure {
+  position: absolute;
+  top: -9999px;
+  width: 50px;
+  height: 50px;
+  overflow: scroll;
+}
+
+// Scale up the modal
+@media (min-width: @screen-sm-min) {
+  // Automatically set modal's width for larger viewports
+  .modal-dialog {
+    width: @modal-md;
+    margin: 30px auto;
+  }
+  .modal-content {
+    .box-shadow(0 5px 15px rgba(0,0,0,.5));
+  }
+
+  // Modal sizes
+  .modal-sm { width: @modal-sm; }
+}
+
+@media (min-width: @screen-md-min) {
+  .modal-lg { width: @modal-lg; }
+}
diff --git a/libs/fmt/doc/bootstrap/navbar.less b/libs/fmt/doc/bootstrap/navbar.less
new file mode 100644
index 0000000000000000000000000000000000000000..03f4e2ac7a14bb0cce7e2a01d104ebcb2b0ff13b
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/navbar.less
@@ -0,0 +1,660 @@
+//
+// Navbars
+// --------------------------------------------------
+
+
+// Wrapper and base class
+//
+// Provide a static navbar from which we expand to create full-width, fixed, and
+// other navbar variations.
+
+.navbar {
+  position: relative;
+  min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)
+  margin-bottom: @navbar-margin-bottom;
+  border: 1px solid transparent;
+
+  // Prevent floats from breaking the navbar
+  &:extend(.clearfix all);
+
+  @media (min-width: @grid-float-breakpoint) {
+    border-radius: @navbar-border-radius;
+  }
+}
+
+
+// Navbar heading
+//
+// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy
+// styling of responsive aspects.
+
+.navbar-header {
+  &:extend(.clearfix all);
+
+  @media (min-width: @grid-float-breakpoint) {
+    float: left;
+  }
+}
+
+
+// Navbar collapse (body)
+//
+// Group your navbar content into this for easy collapsing and expanding across
+// various device sizes. By default, this content is collapsed when <768px, but
+// will expand past that for a horizontal display.
+//
+// To start (on mobile devices) the navbar links, forms, and buttons are stacked
+// vertically and include a `max-height` to overflow in case you have too much
+// content for the user's viewport.
+
+.navbar-collapse {
+  overflow-x: visible;
+  padding-right: @navbar-padding-horizontal;
+  padding-left:  @navbar-padding-horizontal;
+  border-top: 1px solid transparent;
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
+  &:extend(.clearfix all);
+  -webkit-overflow-scrolling: touch;
+
+  &.in {
+    overflow-y: auto;
+  }
+
+  @media (min-width: @grid-float-breakpoint) {
+    width: auto;
+    border-top: 0;
+    box-shadow: none;
+
+    &.collapse {
+      display: block !important;
+      height: auto !important;
+      padding-bottom: 0; // Override default setting
+      overflow: visible !important;
+    }
+
+    &.in {
+      overflow-y: visible;
+    }
+
+    // Undo the collapse side padding for navbars with containers to ensure
+    // alignment of right-aligned contents.
+    .navbar-fixed-top &,
+    .navbar-static-top &,
+    .navbar-fixed-bottom & {
+      padding-left: 0;
+      padding-right: 0;
+    }
+  }
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  .navbar-collapse {
+    max-height: @navbar-collapse-max-height;
+
+    @media (max-device-width: @screen-xs-min) and (orientation: landscape) {
+      max-height: 200px;
+    }
+  }
+}
+
+
+// Both navbar header and collapse
+//
+// When a container is present, change the behavior of the header and collapse.
+
+.tb-container,
+.container-fluid {
+  > .navbar-header,
+  > .navbar-collapse {
+    margin-right: -@navbar-padding-horizontal;
+    margin-left:  -@navbar-padding-horizontal;
+
+    @media (min-width: @grid-float-breakpoint) {
+      margin-right: 0;
+      margin-left:  0;
+    }
+  }
+}
+
+
+//
+// Navbar alignment options
+//
+// Display the navbar across the entirety of the page or fixed it to the top or
+// bottom of the page.
+
+// Static top (unfixed, but 100% wide) navbar
+.navbar-static-top {
+  z-index: @zindex-navbar;
+  border-width: 0 0 1px;
+
+  @media (min-width: @grid-float-breakpoint) {
+    border-radius: 0;
+  }
+}
+
+// Fix the top/bottom navbars when screen real estate supports it
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: @zindex-navbar-fixed;
+
+  // Undo the rounded corners
+  @media (min-width: @grid-float-breakpoint) {
+    border-radius: 0;
+  }
+}
+.navbar-fixed-top {
+  top: 0;
+  border-width: 0 0 1px;
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+  margin-bottom: 0; // override .navbar defaults
+  border-width: 1px 0 0;
+}
+
+
+// Brand/project name
+
+.navbar-brand {
+  float: left;
+  padding: @navbar-padding-vertical @navbar-padding-horizontal;
+  font-size: @font-size-large;
+  line-height: @line-height-computed;
+  height: @navbar-height;
+
+  &:hover,
+  &:focus {
+    text-decoration: none;
+  }
+
+  > img {
+    display: block;
+  }
+
+  @media (min-width: @grid-float-breakpoint) {
+    .navbar > .tb-container &,
+    .navbar > .container-fluid & {
+      margin-left: -@navbar-padding-horizontal;
+    }
+  }
+}
+
+
+// Navbar toggle
+//
+// Custom button for toggling the `.navbar-collapse`, powered by the collapse
+// JavaScript plugin.
+
+.navbar-toggle {
+  position: relative;
+  float: right;
+  margin-right: @navbar-padding-horizontal;
+  padding: 9px 10px;
+  .navbar-vertical-align(34px);
+  background-color: transparent;
+  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
+  border: 1px solid transparent;
+  border-radius: @border-radius-base;
+
+  // We remove the `outline` here, but later compensate by attaching `:hover`
+  // styles to `:focus`.
+  &:focus {
+    outline: 0;
+  }
+
+  // Bars
+  .icon-bar {
+    display: block;
+    width: 22px;
+    height: 2px;
+    border-radius: 1px;
+  }
+  .icon-bar + .icon-bar {
+    margin-top: 4px;
+  }
+
+  @media (min-width: @grid-float-breakpoint) {
+    display: none;
+  }
+}
+
+
+// Navbar nav links
+//
+// Builds on top of the `.nav` components with its own modifier class to make
+// the nav the full height of the horizontal nav (above 768px).
+
+.navbar-nav {
+  margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;
+
+  > li > a {
+    padding-top:    10px;
+    padding-bottom: 10px;
+    line-height: @line-height-computed;
+  }
+
+  @media (max-width: @grid-float-breakpoint-max) {
+    // Dropdowns get custom display when collapsed
+    .open .dropdown-menu {
+      position: static;
+      float: none;
+      width: auto;
+      margin-top: 0;
+      background-color: transparent;
+      border: 0;
+      box-shadow: none;
+      > li > a,
+      .dropdown-header {
+        padding: 5px 15px 5px 25px;
+      }
+      > li > a {
+        line-height: @line-height-computed;
+        &:hover,
+        &:focus {
+          background-image: none;
+        }
+      }
+    }
+  }
+
+  // Uncollapse the nav
+  @media (min-width: @grid-float-breakpoint) {
+    float: left;
+    margin: 0;
+
+    > li {
+      float: left;
+      > a {
+        padding-top:    @navbar-padding-vertical;
+        padding-bottom: @navbar-padding-vertical;
+      }
+    }
+  }
+}
+
+
+// Navbar form
+//
+// Extension of the `.form-inline` with some extra flavor for optimum display in
+// our navbars.
+
+.navbar-form {
+  margin-left: -@navbar-padding-horizontal;
+  margin-right: -@navbar-padding-horizontal;
+  padding: 10px @navbar-padding-horizontal;
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);
+  .box-shadow(@shadow);
+
+  // Mixin behavior for optimum display
+  .form-inline();
+
+  .form-group {
+    @media (max-width: @grid-float-breakpoint-max) {
+      margin-bottom: 5px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+    }
+  }
+
+  // Vertically center in expanded, horizontal navbar
+  .navbar-vertical-align(@input-height-base);
+
+  // Undo 100% width for pull classes
+  @media (min-width: @grid-float-breakpoint) {
+    width: auto;
+    border: 0;
+    margin-left: 0;
+    margin-right: 0;
+    padding-top: 0;
+    padding-bottom: 0;
+    .box-shadow(none);
+  }
+}
+
+
+// Dropdown menus
+
+// Menu position and menu carets
+.navbar-nav > li > .dropdown-menu {
+  margin-top: 0;
+  .border-top-radius(0);
+}
+// Menu position and menu caret support for dropups via extra dropup class
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+  margin-bottom: 0;
+  .border-top-radius(@navbar-border-radius);
+  .border-bottom-radius(0);
+}
+
+
+// Buttons in navbars
+//
+// Vertically center a button within a navbar (when *not* in a form).
+
+.navbar-btn {
+  .navbar-vertical-align(@input-height-base);
+
+  &.btn-sm {
+    .navbar-vertical-align(@input-height-small);
+  }
+  &.btn-xs {
+    .navbar-vertical-align(22);
+  }
+}
+
+
+// Text in navbars
+//
+// Add a class to make any element properly align itself vertically within the navbars.
+
+.navbar-text {
+  .navbar-vertical-align(@line-height-computed);
+
+  @media (min-width: @grid-float-breakpoint) {
+    float: left;
+    margin-left: @navbar-padding-horizontal;
+    margin-right: @navbar-padding-horizontal;
+  }
+}
+
+
+// Component alignment
+//
+// Repurpose the pull utilities as their own navbar utilities to avoid specificity
+// issues with parents and chaining. Only do this when the navbar is uncollapsed
+// though so that navbar contents properly stack and align in mobile.
+//
+// Declared after the navbar components to ensure more specificity on the margins.
+
+@media (min-width: @grid-float-breakpoint) {
+  .navbar-left  { .pull-left(); }
+  .navbar-right {
+    .pull-right();
+    margin-right: -@navbar-padding-horizontal;
+
+    ~ .navbar-right {
+      margin-right: 0;
+    }
+  }
+}
+
+
+// Alternate navbars
+// --------------------------------------------------
+
+// Default navbar
+.navbar-default {
+  background-color: @navbar-default-bg;
+  border-color: @navbar-default-border;
+
+  .navbar-brand {
+    color: @navbar-default-brand-color;
+    &:hover,
+    &:focus {
+      color: @navbar-default-brand-hover-color;
+      background-color: @navbar-default-brand-hover-bg;
+    }
+  }
+
+  .navbar-text {
+    color: @navbar-default-color;
+  }
+
+  .navbar-nav {
+    > li > a {
+      color: @navbar-default-link-color;
+
+      &:hover,
+      &:focus {
+        color: @navbar-default-link-hover-color;
+        background-color: @navbar-default-link-hover-bg;
+      }
+    }
+    > .active > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @navbar-default-link-active-color;
+        background-color: @navbar-default-link-active-bg;
+      }
+    }
+    > .disabled > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @navbar-default-link-disabled-color;
+        background-color: @navbar-default-link-disabled-bg;
+      }
+    }
+  }
+
+  .navbar-toggle {
+    border-color: @navbar-default-toggle-border-color;
+    &:hover,
+    &:focus {
+      background-color: @navbar-default-toggle-hover-bg;
+    }
+    .icon-bar {
+      background-color: @navbar-default-toggle-icon-bar-bg;
+    }
+  }
+
+  .navbar-collapse,
+  .navbar-form {
+    border-color: @navbar-default-border;
+  }
+
+  // Dropdown menu items
+  .navbar-nav {
+    // Remove background color from open dropdown
+    > .open > a {
+      &,
+      &:hover,
+      &:focus {
+        background-color: @navbar-default-link-active-bg;
+        color: @navbar-default-link-active-color;
+      }
+    }
+
+    @media (max-width: @grid-float-breakpoint-max) {
+      // Dropdowns get custom display when collapsed
+      .open .dropdown-menu {
+        > li > a {
+          color: @navbar-default-link-color;
+          &:hover,
+          &:focus {
+            color: @navbar-default-link-hover-color;
+            background-color: @navbar-default-link-hover-bg;
+          }
+        }
+        > .active > a {
+          &,
+          &:hover,
+          &:focus {
+            color: @navbar-default-link-active-color;
+            background-color: @navbar-default-link-active-bg;
+          }
+        }
+        > .disabled > a {
+          &,
+          &:hover,
+          &:focus {
+            color: @navbar-default-link-disabled-color;
+            background-color: @navbar-default-link-disabled-bg;
+          }
+        }
+      }
+    }
+  }
+
+
+  // Links in navbars
+  //
+  // Add a class to ensure links outside the navbar nav are colored correctly.
+
+  .navbar-link {
+    color: @navbar-default-link-color;
+    &:hover {
+      color: @navbar-default-link-hover-color;
+    }
+  }
+
+  .btn-link {
+    color: @navbar-default-link-color;
+    &:hover,
+    &:focus {
+      color: @navbar-default-link-hover-color;
+    }
+    &[disabled],
+    fieldset[disabled] & {
+      &:hover,
+      &:focus {
+        color: @navbar-default-link-disabled-color;
+      }
+    }
+  }
+}
+
+// Inverse navbar
+
+.navbar-inverse {
+  background-color: @navbar-inverse-bg;
+  border-color: @navbar-inverse-border;
+
+  .navbar-brand {
+    color: @navbar-inverse-brand-color;
+    &:hover,
+    &:focus {
+      color: @navbar-inverse-brand-hover-color;
+      background-color: @navbar-inverse-brand-hover-bg;
+    }
+  }
+
+  .navbar-text {
+    color: @navbar-inverse-color;
+  }
+
+  .navbar-nav {
+    > li > a {
+      color: @navbar-inverse-link-color;
+
+      &:hover,
+      &:focus {
+        color: @navbar-inverse-link-hover-color;
+        background-color: @navbar-inverse-link-hover-bg;
+      }
+    }
+    > .active > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @navbar-inverse-link-active-color;
+        background-color: @navbar-inverse-link-active-bg;
+      }
+    }
+    > .disabled > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @navbar-inverse-link-disabled-color;
+        background-color: @navbar-inverse-link-disabled-bg;
+      }
+    }
+  }
+
+  // Darken the responsive nav toggle
+  .navbar-toggle {
+    border-color: @navbar-inverse-toggle-border-color;
+    &:hover,
+    &:focus {
+      background-color: @navbar-inverse-toggle-hover-bg;
+    }
+    .icon-bar {
+      background-color: @navbar-inverse-toggle-icon-bar-bg;
+    }
+  }
+
+  .navbar-collapse,
+  .navbar-form {
+    border-color: darken(@navbar-inverse-bg, 7%);
+  }
+
+  // Dropdowns
+  .navbar-nav {
+    > .open > a {
+      &,
+      &:hover,
+      &:focus {
+        background-color: @navbar-inverse-link-active-bg;
+        color: @navbar-inverse-link-active-color;
+      }
+    }
+
+    @media (max-width: @grid-float-breakpoint-max) {
+      // Dropdowns get custom display
+      .open .dropdown-menu {
+        > .dropdown-header {
+          border-color: @navbar-inverse-border;
+        }
+        .divider {
+          background-color: @navbar-inverse-border;
+        }
+        > li > a {
+          color: @navbar-inverse-link-color;
+          &:hover,
+          &:focus {
+            color: @navbar-inverse-link-hover-color;
+            background-color: @navbar-inverse-link-hover-bg;
+          }
+        }
+        > .active > a {
+          &,
+          &:hover,
+          &:focus {
+            color: @navbar-inverse-link-active-color;
+            background-color: @navbar-inverse-link-active-bg;
+          }
+        }
+        > .disabled > a {
+          &,
+          &:hover,
+          &:focus {
+            color: @navbar-inverse-link-disabled-color;
+            background-color: @navbar-inverse-link-disabled-bg;
+          }
+        }
+      }
+    }
+  }
+
+  .navbar-link {
+    color: @navbar-inverse-link-color;
+    &:hover {
+      color: @navbar-inverse-link-hover-color;
+    }
+  }
+
+  .btn-link {
+    color: @navbar-inverse-link-color;
+    &:hover,
+    &:focus {
+      color: @navbar-inverse-link-hover-color;
+    }
+    &[disabled],
+    fieldset[disabled] & {
+      &:hover,
+      &:focus {
+        color: @navbar-inverse-link-disabled-color;
+      }
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/navs.less b/libs/fmt/doc/bootstrap/navs.less
new file mode 100644
index 0000000000000000000000000000000000000000..a3d11b136211f3ee4a4d3beeb19c5ffba203b15b
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/navs.less
@@ -0,0 +1,242 @@
+//
+// Navs
+// --------------------------------------------------
+
+
+// Base class
+// --------------------------------------------------
+
+.nav {
+  margin-bottom: 0;
+  padding-left: 0; // Override default ul/ol
+  list-style: none;
+  &:extend(.clearfix all);
+
+  > li {
+    position: relative;
+    display: block;
+
+    > a {
+      position: relative;
+      display: block;
+      padding: @nav-link-padding;
+      &:hover,
+      &:focus {
+        text-decoration: none;
+        background-color: @nav-link-hover-bg;
+      }
+    }
+
+    // Disabled state sets text to gray and nukes hover/tab effects
+    &.disabled > a {
+      color: @nav-disabled-link-color;
+
+      &:hover,
+      &:focus {
+        color: @nav-disabled-link-hover-color;
+        text-decoration: none;
+        background-color: transparent;
+        cursor: @cursor-disabled;
+      }
+    }
+  }
+
+  // Open dropdowns
+  .open > a {
+    &,
+    &:hover,
+    &:focus {
+      background-color: @nav-link-hover-bg;
+      border-color: @link-color;
+    }
+  }
+
+  // Nav dividers (deprecated with v3.0.1)
+  //
+  // This should have been removed in v3 with the dropping of `.nav-list`, but
+  // we missed it. We don't currently support this anywhere, but in the interest
+  // of maintaining backward compatibility in case you use it, it's deprecated.
+  .nav-divider {
+    .nav-divider();
+  }
+
+  // Prevent IE8 from misplacing imgs
+  //
+  // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989
+  > li > a > img {
+    max-width: none;
+  }
+}
+
+
+// Tabs
+// -------------------------
+
+// Give the tabs something to sit on
+.nav-tabs {
+  border-bottom: 1px solid @nav-tabs-border-color;
+  > li {
+    float: left;
+    // Make the list-items overlay the bottom border
+    margin-bottom: -1px;
+
+    // Actual tabs (as links)
+    > a {
+      margin-right: 2px;
+      line-height: @line-height-base;
+      border: 1px solid transparent;
+      border-radius: @border-radius-base @border-radius-base 0 0;
+      &:hover {
+        border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;
+      }
+    }
+
+    // Active state, and its :hover to override normal :hover
+    &.active > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @nav-tabs-active-link-hover-color;
+        background-color: @nav-tabs-active-link-hover-bg;
+        border: 1px solid @nav-tabs-active-link-hover-border-color;
+        border-bottom-color: transparent;
+        cursor: default;
+      }
+    }
+  }
+  // pulling this in mainly for less shorthand
+  &.nav-justified {
+    .nav-justified();
+    .nav-tabs-justified();
+  }
+}
+
+
+// Pills
+// -------------------------
+.nav-pills {
+  > li {
+    float: left;
+
+    // Links rendered as pills
+    > a {
+      border-radius: @nav-pills-border-radius;
+    }
+    + li {
+      margin-left: 2px;
+    }
+
+    // Active state
+    &.active > a {
+      &,
+      &:hover,
+      &:focus {
+        color: @nav-pills-active-link-hover-color;
+        background-color: @nav-pills-active-link-hover-bg;
+      }
+    }
+  }
+}
+
+
+// Stacked pills
+.nav-stacked {
+  > li {
+    float: none;
+    + li {
+      margin-top: 2px;
+      margin-left: 0; // no need for this gap between nav items
+    }
+  }
+}
+
+
+// Nav variations
+// --------------------------------------------------
+
+// Justified nav links
+// -------------------------
+
+.nav-justified {
+  width: 100%;
+
+  > li {
+    float: none;
+    > a {
+      text-align: center;
+      margin-bottom: 5px;
+    }
+  }
+
+  > .dropdown .dropdown-menu {
+    top: auto;
+    left: auto;
+  }
+
+  @media (min-width: @screen-sm-min) {
+    > li {
+      display: table-cell;
+      width: 1%;
+      > a {
+        margin-bottom: 0;
+      }
+    }
+  }
+}
+
+// Move borders to anchors instead of bottom of list
+//
+// Mixin for adding on top the shared `.nav-justified` styles for our tabs
+.nav-tabs-justified {
+  border-bottom: 0;
+
+  > li > a {
+    // Override margin from .nav-tabs
+    margin-right: 0;
+    border-radius: @border-radius-base;
+  }
+
+  > .active > a,
+  > .active > a:hover,
+  > .active > a:focus {
+    border: 1px solid @nav-tabs-justified-link-border-color;
+  }
+
+  @media (min-width: @screen-sm-min) {
+    > li > a {
+      border-bottom: 1px solid @nav-tabs-justified-link-border-color;
+      border-radius: @border-radius-base @border-radius-base 0 0;
+    }
+    > .active > a,
+    > .active > a:hover,
+    > .active > a:focus {
+      border-bottom-color: @nav-tabs-justified-active-link-border-color;
+    }
+  }
+}
+
+
+// Tabbable tabs
+// -------------------------
+
+// Hide tabbable panes to start, show them when `.active`
+.tab-content {
+  > .tab-pane {
+    display: none;
+  }
+  > .active {
+    display: block;
+  }
+}
+
+
+// Dropdowns
+// -------------------------
+
+// Specific dropdowns
+.nav-tabs .dropdown-menu {
+  // make dropdown border overlap tab border
+  margin-top: -1px;
+  // Remove the top rounded corners here since there is a hard edge above the menu
+  .border-top-radius(0);
+}
diff --git a/libs/fmt/doc/bootstrap/normalize.less b/libs/fmt/doc/bootstrap/normalize.less
new file mode 100644
index 0000000000000000000000000000000000000000..62a085a4841a536094f22943c9557240e2d8c19d
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/normalize.less
@@ -0,0 +1,427 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+//
+// 1. Set default font family to sans-serif.
+// 2. Prevent iOS text size adjust after orientation change, without disabling
+//    user zoom.
+//
+
+html {
+  font-family: sans-serif; // 1
+  -ms-text-size-adjust: 100%; // 2
+  -webkit-text-size-adjust: 100%; // 2
+}
+
+//
+// Remove default margin.
+//
+
+body {
+  margin: 0;
+}
+
+// HTML5 display definitions
+// ==========================================================================
+
+//
+// Correct `block` display not defined for any HTML5 element in IE 8/9.
+// Correct `block` display not defined for `details` or `summary` in IE 10/11
+// and Firefox.
+// Correct `block` display not defined for `main` in IE 11.
+//
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+
+//
+// 1. Correct `inline-block` display not defined in IE 8/9.
+// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+//
+
+audio,
+canvas,
+progress,
+video {
+  display: inline-block; // 1
+  vertical-align: baseline; // 2
+}
+
+//
+// Prevent modern browsers from displaying `audio` without controls.
+// Remove excess height in iOS 5 devices.
+//
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+//
+// Address `[hidden]` styling not present in IE 8/9/10.
+// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+//
+
+[hidden],
+template {
+  display: none;
+}
+
+// Links
+// ==========================================================================
+
+//
+// Remove the gray background color from active links in IE 10.
+//
+
+a {
+  background-color: transparent;
+}
+
+//
+// Improve readability when focused and also mouse hovered in all browsers.
+//
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+// Text-level semantics
+// ==========================================================================
+
+//
+// Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+//
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+//
+// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+//
+
+b,
+strong {
+  font-weight: bold;
+}
+
+//
+// Address styling not present in Safari and Chrome.
+//
+
+dfn {
+  font-style: italic;
+}
+
+//
+// Address variable `h1` font-size and margin within `section` and `article`
+// contexts in Firefox 4+, Safari, and Chrome.
+//
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+//
+// Address styling not present in IE 8/9.
+//
+
+mark {
+  background: #ff0;
+  color: #000;
+}
+
+//
+// Address inconsistent and variable font size in all browsers.
+//
+
+small {
+  font-size: 80%;
+}
+
+//
+// Prevent `sub` and `sup` affecting `line-height` in all browsers.
+//
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+// Embedded content
+// ==========================================================================
+
+//
+// Remove border when inside `a` element in IE 8/9/10.
+//
+
+img {
+  border: 0;
+}
+
+//
+// Correct overflow not hidden in IE 9/10/11.
+//
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+// Grouping content
+// ==========================================================================
+
+//
+// Address margin not present in IE 8/9 and Safari.
+//
+
+figure {
+  margin: 1em 40px;
+}
+
+//
+// Address differences between Firefox and other browsers.
+//
+
+hr {
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  height: 0;
+}
+
+//
+// Contain overflow in all browsers.
+//
+
+pre {
+  overflow: auto;
+}
+
+//
+// Address odd `em`-unit font size rendering in all browsers.
+//
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+// Forms
+// ==========================================================================
+
+//
+// Known limitation: by default, Chrome and Safari on OS X allow very limited
+// styling of `select`, unless a `border` property is set.
+//
+
+//
+// 1. Correct color not being inherited.
+//    Known issue: affects color of disabled elements.
+// 2. Correct font properties not being inherited.
+// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+//
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit; // 1
+  font: inherit; // 2
+  margin: 0; // 3
+}
+
+//
+// Address `overflow` set to `hidden` in IE 8/9/10/11.
+//
+
+button {
+  overflow: visible;
+}
+
+//
+// Address inconsistent `text-transform` inheritance for `button` and `select`.
+// All other form control elements do not inherit `text-transform` values.
+// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+// Correct `select` style inheritance in Firefox.
+//
+
+button,
+select {
+  text-transform: none;
+}
+
+//
+// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+//    and `video` controls.
+// 2. Correct inability to style clickable `input` types in iOS.
+// 3. Improve usability and consistency of cursor style between image-type
+//    `input` and others.
+//
+
+button,
+html input[type="button"], // 1
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button; // 2
+  cursor: pointer; // 3
+}
+
+//
+// Re-set default cursor for disabled elements.
+//
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+//
+// Remove inner padding and border in Firefox 4+.
+//
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+//
+// Address Firefox 4+ setting `line-height` on `input` using `!important` in
+// the UA stylesheet.
+//
+
+input {
+  line-height: normal;
+}
+
+//
+// It's recommended that you don't attempt to style these elements.
+// Firefox's implementation doesn't respect box-sizing, padding, or width.
+//
+// 1. Address box sizing set to `content-box` in IE 8/9/10.
+// 2. Remove excess padding in IE 8/9/10.
+//
+
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box; // 1
+  padding: 0; // 2
+}
+
+//
+// Fix the cursor style for Chrome's increment/decrement buttons. For certain
+// `font-size` values of the `input`, it causes the cursor style of the
+// decrement button to change from `default` to `text`.
+//
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+//
+// 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+// 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+//    (include `-moz` to future-proof).
+//
+
+input[type="search"] {
+  -webkit-appearance: textfield; // 1
+  -moz-box-sizing: content-box;
+  -webkit-box-sizing: content-box; // 2
+  box-sizing: content-box;
+}
+
+//
+// Remove inner padding and search cancel button in Safari and Chrome on OS X.
+// Safari (but not Chrome) clips the cancel button when the search input has
+// padding (and `textfield` appearance).
+//
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+//
+// Define consistent border, margin, and padding.
+//
+
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+
+//
+// 1. Correct `color` not being inherited in IE 8/9/10/11.
+// 2. Remove padding so people aren't caught out if they zero out fieldsets.
+//
+
+legend {
+  border: 0; // 1
+  padding: 0; // 2
+}
+
+//
+// Remove default vertical scrollbar in IE 8/9/10/11.
+//
+
+textarea {
+  overflow: auto;
+}
+
+//
+// Don't inherit the `font-weight` (applied by a rule above).
+// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+//
+
+optgroup {
+  font-weight: bold;
+}
+
+// Tables
+// ==========================================================================
+
+//
+// Remove most spacing between table cells.
+//
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td,
+th {
+  padding: 0;
+}
diff --git a/libs/fmt/doc/bootstrap/pager.less b/libs/fmt/doc/bootstrap/pager.less
new file mode 100644
index 0000000000000000000000000000000000000000..41abaaadc5dc605c75de869dab943599ef31fbbd
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/pager.less
@@ -0,0 +1,54 @@
+//
+// Pager pagination
+// --------------------------------------------------
+
+
+.pager {
+  padding-left: 0;
+  margin: @line-height-computed 0;
+  list-style: none;
+  text-align: center;
+  &:extend(.clearfix all);
+  li {
+    display: inline;
+    > a,
+    > span {
+      display: inline-block;
+      padding: 5px 14px;
+      background-color: @pager-bg;
+      border: 1px solid @pager-border;
+      border-radius: @pager-border-radius;
+    }
+
+    > a:hover,
+    > a:focus {
+      text-decoration: none;
+      background-color: @pager-hover-bg;
+    }
+  }
+
+  .next {
+    > a,
+    > span {
+      float: right;
+    }
+  }
+
+  .previous {
+    > a,
+    > span {
+      float: left;
+    }
+  }
+
+  .disabled {
+    > a,
+    > a:hover,
+    > a:focus,
+    > span {
+      color: @pager-disabled-color;
+      background-color: @pager-bg;
+      cursor: @cursor-disabled;
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/pagination.less b/libs/fmt/doc/bootstrap/pagination.less
new file mode 100644
index 0000000000000000000000000000000000000000..38c4c3d346f974278503a4596a6b6e96b845c0af
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/pagination.less
@@ -0,0 +1,88 @@
+//
+// Pagination (multiple pages)
+// --------------------------------------------------
+.pagination {
+  display: inline-block;
+  padding-left: 0;
+  margin: @line-height-computed 0;
+  border-radius: @border-radius-base;
+
+  > li {
+    display: inline; // Remove list-style and block-level defaults
+    > a,
+    > span {
+      position: relative;
+      float: left; // Collapse white-space
+      padding: @padding-base-vertical @padding-base-horizontal;
+      line-height: @line-height-base;
+      text-decoration: none;
+      color: @pagination-color;
+      background-color: @pagination-bg;
+      border: 1px solid @pagination-border;
+      margin-left: -1px;
+    }
+    &:first-child {
+      > a,
+      > span {
+        margin-left: 0;
+        .border-left-radius(@border-radius-base);
+      }
+    }
+    &:last-child {
+      > a,
+      > span {
+        .border-right-radius(@border-radius-base);
+      }
+    }
+  }
+
+  > li > a,
+  > li > span {
+    &:hover,
+    &:focus {
+      color: @pagination-hover-color;
+      background-color: @pagination-hover-bg;
+      border-color: @pagination-hover-border;
+    }
+  }
+
+  > .active > a,
+  > .active > span {
+    &,
+    &:hover,
+    &:focus {
+      z-index: 2;
+      color: @pagination-active-color;
+      background-color: @pagination-active-bg;
+      border-color: @pagination-active-border;
+      cursor: default;
+    }
+  }
+
+  > .disabled {
+    > span,
+    > span:hover,
+    > span:focus,
+    > a,
+    > a:hover,
+    > a:focus {
+      color: @pagination-disabled-color;
+      background-color: @pagination-disabled-bg;
+      border-color: @pagination-disabled-border;
+      cursor: @cursor-disabled;
+    }
+  }
+}
+
+// Sizing
+// --------------------------------------------------
+
+// Large
+.pagination-lg {
+  .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);
+}
+
+// Small
+.pagination-sm {
+  .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);
+}
diff --git a/libs/fmt/doc/bootstrap/panels.less b/libs/fmt/doc/bootstrap/panels.less
new file mode 100644
index 0000000000000000000000000000000000000000..04eb2b39ca18d69945ed1e60e725fb30544bde3f
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/panels.less
@@ -0,0 +1,265 @@
+//
+// Panels
+// --------------------------------------------------
+
+
+// Base class
+.panel {
+  margin-bottom: @line-height-computed;
+  background-color: @panel-bg;
+  border: 1px solid transparent;
+  border-radius: @panel-border-radius;
+  .box-shadow(0 1px 1px rgba(0,0,0,.05));
+}
+
+// Panel contents
+.panel-body {
+  padding: @panel-body-padding;
+  &:extend(.clearfix all);
+}
+
+// Optional heading
+.panel-heading {
+  padding: @panel-heading-padding;
+  border-bottom: 1px solid transparent;
+  .border-top-radius((@panel-border-radius - 1));
+
+  > .dropdown .dropdown-toggle {
+    color: inherit;
+  }
+}
+
+// Within heading, strip any `h*` tag of its default margins for spacing.
+.panel-title {
+  margin-top: 0;
+  margin-bottom: 0;
+  font-size: ceil((@font-size-base * 1.125));
+  color: inherit;
+
+  > a,
+  > small,
+  > .small,
+  > small > a,
+  > .small > a {
+    color: inherit;
+  }
+}
+
+// Optional footer (stays gray in every modifier class)
+.panel-footer {
+  padding: @panel-footer-padding;
+  background-color: @panel-footer-bg;
+  border-top: 1px solid @panel-inner-border;
+  .border-bottom-radius((@panel-border-radius - 1));
+}
+
+
+// List groups in panels
+//
+// By default, space out list group content from panel headings to account for
+// any kind of custom content between the two.
+
+.panel {
+  > .list-group,
+  > .panel-collapse > .list-group {
+    margin-bottom: 0;
+
+    .list-group-item {
+      border-width: 1px 0;
+      border-radius: 0;
+    }
+
+    // Add border top radius for first one
+    &:first-child {
+      .list-group-item:first-child {
+        border-top: 0;
+        .border-top-radius((@panel-border-radius - 1));
+      }
+    }
+    // Add border bottom radius for last one
+    &:last-child {
+      .list-group-item:last-child {
+        border-bottom: 0;
+        .border-bottom-radius((@panel-border-radius - 1));
+      }
+    }
+  }
+}
+// Collapse space between when there's no additional content.
+.panel-heading + .list-group {
+  .list-group-item:first-child {
+    border-top-width: 0;
+  }
+}
+.list-group + .panel-footer {
+  border-top-width: 0;
+}
+
+// Tables in panels
+//
+// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and
+// watch it go full width.
+
+.panel {
+  > .table,
+  > .table-responsive > .table,
+  > .panel-collapse > .table {
+    margin-bottom: 0;
+
+    caption {
+      padding-left: @panel-body-padding;
+      padding-right: @panel-body-padding;
+    }
+  }
+  // Add border top radius for first one
+  > .table:first-child,
+  > .table-responsive:first-child > .table:first-child {
+    .border-top-radius((@panel-border-radius - 1));
+
+    > thead:first-child,
+    > tbody:first-child {
+      > tr:first-child {
+        border-top-left-radius: (@panel-border-radius - 1);
+        border-top-right-radius: (@panel-border-radius - 1);
+
+        td:first-child,
+        th:first-child {
+          border-top-left-radius: (@panel-border-radius - 1);
+        }
+        td:last-child,
+        th:last-child {
+          border-top-right-radius: (@panel-border-radius - 1);
+        }
+      }
+    }
+  }
+  // Add border bottom radius for last one
+  > .table:last-child,
+  > .table-responsive:last-child > .table:last-child {
+    .border-bottom-radius((@panel-border-radius - 1));
+
+    > tbody:last-child,
+    > tfoot:last-child {
+      > tr:last-child {
+        border-bottom-left-radius: (@panel-border-radius - 1);
+        border-bottom-right-radius: (@panel-border-radius - 1);
+
+        td:first-child,
+        th:first-child {
+          border-bottom-left-radius: (@panel-border-radius - 1);
+        }
+        td:last-child,
+        th:last-child {
+          border-bottom-right-radius: (@panel-border-radius - 1);
+        }
+      }
+    }
+  }
+  > .panel-body + .table,
+  > .panel-body + .table-responsive,
+  > .table + .panel-body,
+  > .table-responsive + .panel-body {
+    border-top: 1px solid @table-border-color;
+  }
+  > .table > tbody:first-child > tr:first-child th,
+  > .table > tbody:first-child > tr:first-child td {
+    border-top: 0;
+  }
+  > .table-bordered,
+  > .table-responsive > .table-bordered {
+    border: 0;
+    > thead,
+    > tbody,
+    > tfoot {
+      > tr {
+        > th:first-child,
+        > td:first-child {
+          border-left: 0;
+        }
+        > th:last-child,
+        > td:last-child {
+          border-right: 0;
+        }
+      }
+    }
+    > thead,
+    > tbody {
+      > tr:first-child {
+        > td,
+        > th {
+          border-bottom: 0;
+        }
+      }
+    }
+    > tbody,
+    > tfoot {
+      > tr:last-child {
+        > td,
+        > th {
+          border-bottom: 0;
+        }
+      }
+    }
+  }
+  > .table-responsive {
+    border: 0;
+    margin-bottom: 0;
+  }
+}
+
+
+// Collapsable panels (aka, accordion)
+//
+// Wrap a series of panels in `.panel-group` to turn them into an accordion with
+// the help of our collapse JavaScript plugin.
+
+.panel-group {
+  margin-bottom: @line-height-computed;
+
+  // Tighten up margin so it's only between panels
+  .panel {
+    margin-bottom: 0;
+    border-radius: @panel-border-radius;
+
+    + .panel {
+      margin-top: 5px;
+    }
+  }
+
+  .panel-heading {
+    border-bottom: 0;
+
+    + .panel-collapse > .panel-body,
+    + .panel-collapse > .list-group {
+      border-top: 1px solid @panel-inner-border;
+    }
+  }
+
+  .panel-footer {
+    border-top: 0;
+    + .panel-collapse .panel-body {
+      border-bottom: 1px solid @panel-inner-border;
+    }
+  }
+}
+
+
+// Contextual variations
+.panel-default {
+  .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);
+}
+.panel-primary {
+  .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);
+}
+.panel-success {
+  .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);
+}
+.panel-info {
+  .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);
+}
+.panel-warning {
+  .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);
+}
+.panel-danger {
+  .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);
+}
diff --git a/libs/fmt/doc/bootstrap/popovers.less b/libs/fmt/doc/bootstrap/popovers.less
new file mode 100644
index 0000000000000000000000000000000000000000..2748eead13fa69dc8e7d32a1f0f64e6edb71e6e5
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/popovers.less
@@ -0,0 +1,135 @@
+//
+// Popovers
+// --------------------------------------------------
+
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: @zindex-popover;
+  display: none;
+  max-width: @popover-max-width;
+  padding: 1px;
+  // Reset font and text properties given new insertion method
+  font-family: @font-family-base;
+  font-size: @font-size-base;
+  font-weight: normal;
+  line-height: @line-height-base;
+  text-align: left;
+  background-color: @popover-bg;
+  background-clip: padding-box;
+  border: 1px solid @popover-fallback-border-color;
+  border: 1px solid @popover-border-color;
+  border-radius: @border-radius-large;
+  .box-shadow(0 5px 10px rgba(0,0,0,.2));
+
+  // Overrides for proper insertion
+  white-space: normal;
+
+  // Offset the popover to account for the popover arrow
+  &.top     { margin-top: -@popover-arrow-width; }
+  &.right   { margin-left: @popover-arrow-width; }
+  &.bottom  { margin-top: @popover-arrow-width; }
+  &.left    { margin-left: -@popover-arrow-width; }
+}
+
+.popover-title {
+  margin: 0; // reset heading margin
+  padding: 8px 14px;
+  font-size: @font-size-base;
+  background-color: @popover-title-bg;
+  border-bottom: 1px solid darken(@popover-title-bg, 5%);
+  border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;
+}
+
+.popover-content {
+  padding: 9px 14px;
+}
+
+// Arrows
+//
+// .arrow is outer, .arrow:after is inner
+
+.popover > .arrow {
+  &,
+  &:after {
+    position: absolute;
+    display: block;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+  }
+}
+.popover > .arrow {
+  border-width: @popover-arrow-outer-width;
+}
+.popover > .arrow:after {
+  border-width: @popover-arrow-width;
+  content: "";
+}
+
+.popover {
+  &.top > .arrow {
+    left: 50%;
+    margin-left: -@popover-arrow-outer-width;
+    border-bottom-width: 0;
+    border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback
+    border-top-color: @popover-arrow-outer-color;
+    bottom: -@popover-arrow-outer-width;
+    &:after {
+      content: " ";
+      bottom: 1px;
+      margin-left: -@popover-arrow-width;
+      border-bottom-width: 0;
+      border-top-color: @popover-arrow-color;
+    }
+  }
+  &.right > .arrow {
+    top: 50%;
+    left: -@popover-arrow-outer-width;
+    margin-top: -@popover-arrow-outer-width;
+    border-left-width: 0;
+    border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback
+    border-right-color: @popover-arrow-outer-color;
+    &:after {
+      content: " ";
+      left: 1px;
+      bottom: -@popover-arrow-width;
+      border-left-width: 0;
+      border-right-color: @popover-arrow-color;
+    }
+  }
+  &.bottom > .arrow {
+    left: 50%;
+    margin-left: -@popover-arrow-outer-width;
+    border-top-width: 0;
+    border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback
+    border-bottom-color: @popover-arrow-outer-color;
+    top: -@popover-arrow-outer-width;
+    &:after {
+      content: " ";
+      top: 1px;
+      margin-left: -@popover-arrow-width;
+      border-top-width: 0;
+      border-bottom-color: @popover-arrow-color;
+    }
+  }
+
+  &.left > .arrow {
+    top: 50%;
+    right: -@popover-arrow-outer-width;
+    margin-top: -@popover-arrow-outer-width;
+    border-right-width: 0;
+    border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback
+    border-left-color: @popover-arrow-outer-color;
+    &:after {
+      content: " ";
+      right: 1px;
+      border-right-width: 0;
+      border-left-color: @popover-arrow-color;
+      bottom: -@popover-arrow-width;
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/print.less b/libs/fmt/doc/bootstrap/print.less
new file mode 100644
index 0000000000000000000000000000000000000000..94ca58f12a93ea0b3fb378b73eec63d8c9ee7372
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/print.less
@@ -0,0 +1,107 @@
+/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
+
+// ==========================================================================
+// Print styles.
+// Inlined to avoid the additional HTTP request: h5bp.com/r
+// ==========================================================================
+
+@media print {
+    *,
+    *:before,
+    *:after {
+        background: transparent !important;
+        color: #000 !important; // Black prints faster: h5bp.com/s
+        box-shadow: none !important;
+        text-shadow: none !important;
+    }
+
+    a,
+    a:visited {
+        text-decoration: underline;
+    }
+
+    a[href]:after {
+        content: " (" attr(href) ")";
+    }
+
+    abbr[title]:after {
+        content: " (" attr(title) ")";
+    }
+
+    // Don't show links that are fragment identifiers,
+    // or use the `javascript:` pseudo protocol
+    a[href^="#"]:after,
+    a[href^="javascript:"]:after {
+        content: "";
+    }
+
+    pre,
+    blockquote {
+        border: 1px solid #999;
+        page-break-inside: avoid;
+    }
+
+    thead {
+        display: table-header-group; // h5bp.com/t
+    }
+
+    tr,
+    img {
+        page-break-inside: avoid;
+    }
+
+    img {
+        max-width: 100% !important;
+    }
+
+    p,
+    h2,
+    h3 {
+        orphans: 3;
+        widows: 3;
+    }
+
+    h2,
+    h3 {
+        page-break-after: avoid;
+    }
+
+    // Bootstrap specific changes start
+    //
+    // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245
+    // Once fixed, we can just straight up remove this.
+    select {
+        background: #fff !important;
+    }
+
+    // Bootstrap components
+    .navbar {
+        display: none;
+    }
+    .btn,
+    .dropup > .btn {
+        > .caret {
+            border-top-color: #000 !important;
+        }
+    }
+    .label {
+        border: 1px solid #000;
+    }
+
+    .table {
+        border-collapse: collapse !important;
+
+        td,
+        th {
+            background-color: #fff !important;
+        }
+    }
+    .table-bordered {
+        th,
+        td {
+            border: 1px solid #ddd !important;
+        }
+    }
+
+    // Bootstrap specific changes end
+}
diff --git a/libs/fmt/doc/bootstrap/progress-bars.less b/libs/fmt/doc/bootstrap/progress-bars.less
new file mode 100644
index 0000000000000000000000000000000000000000..8868a1feef0f3a7a4b0fabe7548435bfeb481df2
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/progress-bars.less
@@ -0,0 +1,87 @@
+//
+// Progress bars
+// --------------------------------------------------
+
+
+// Bar animations
+// -------------------------
+
+// WebKit
+@-webkit-keyframes progress-bar-stripes {
+  from  { background-position: 40px 0; }
+  to    { background-position: 0 0; }
+}
+
+// Spec and IE10+
+@keyframes progress-bar-stripes {
+  from  { background-position: 40px 0; }
+  to    { background-position: 0 0; }
+}
+
+
+// Bar itself
+// -------------------------
+
+// Outer container
+.progress {
+  overflow: hidden;
+  height: @line-height-computed;
+  margin-bottom: @line-height-computed;
+  background-color: @progress-bg;
+  border-radius: @progress-border-radius;
+  .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
+}
+
+// Bar of progress
+.progress-bar {
+  float: left;
+  width: 0%;
+  height: 100%;
+  font-size: @font-size-small;
+  line-height: @line-height-computed;
+  color: @progress-bar-color;
+  text-align: center;
+  background-color: @progress-bar-bg;
+  .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
+  .transition(width .6s ease);
+}
+
+// Striped bars
+//
+// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the
+// `.progress-bar-striped` class, which you just add to an existing
+// `.progress-bar`.
+.progress-striped .progress-bar,
+.progress-bar-striped {
+  #gradient > .striped();
+  background-size: 40px 40px;
+}
+
+// Call animation for the active one
+//
+// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the
+// `.progress-bar.active` approach.
+.progress.active .progress-bar,
+.progress-bar.active {
+  .animation(progress-bar-stripes 2s linear infinite);
+}
+
+
+// Variations
+// -------------------------
+
+.progress-bar-success {
+  .progress-bar-variant(@progress-bar-success-bg);
+}
+
+.progress-bar-info {
+  .progress-bar-variant(@progress-bar-info-bg);
+}
+
+.progress-bar-warning {
+  .progress-bar-variant(@progress-bar-warning-bg);
+}
+
+.progress-bar-danger {
+  .progress-bar-variant(@progress-bar-danger-bg);
+}
diff --git a/libs/fmt/doc/bootstrap/responsive-embed.less b/libs/fmt/doc/bootstrap/responsive-embed.less
new file mode 100644
index 0000000000000000000000000000000000000000..080a5118fe9ab2af331e6b1444fff4be840ecc03
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/responsive-embed.less
@@ -0,0 +1,35 @@
+// Embeds responsive
+//
+// Credit: Nicolas Gallagher and SUIT CSS.
+
+.embed-responsive {
+  position: relative;
+  display: block;
+  height: 0;
+  padding: 0;
+  overflow: hidden;
+
+  .embed-responsive-item,
+  iframe,
+  embed,
+  object,
+  video {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    height: 100%;
+    width: 100%;
+    border: 0;
+  }
+}
+
+// Modifier class for 16:9 aspect ratio
+.embed-responsive-16by9 {
+  padding-bottom: 56.25%;
+}
+
+// Modifier class for 4:3 aspect ratio
+.embed-responsive-4by3 {
+  padding-bottom: 75%;
+}
diff --git a/libs/fmt/doc/bootstrap/responsive-utilities.less b/libs/fmt/doc/bootstrap/responsive-utilities.less
new file mode 100644
index 0000000000000000000000000000000000000000..b1db31d7bfc19a757d02fd773db765c4273549a1
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/responsive-utilities.less
@@ -0,0 +1,194 @@
+//
+// Responsive: Utility classes
+// --------------------------------------------------
+
+
+// IE10 in Windows (Phone) 8
+//
+// Support for responsive views via media queries is kind of borked in IE10, for
+// Surface/desktop in split view and for Windows Phone 8. This particular fix
+// must be accompanied by a snippet of JavaScript to sniff the user agent and
+// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at
+// our Getting Started page for more information on this bug.
+//
+// For more information, see the following:
+//
+// Issue: https://github.com/twbs/bootstrap/issues/10497
+// Docs: http://getbootstrap.com/getting-started/#support-ie10-width
+// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/
+// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/
+
+@-ms-viewport {
+  width: device-width;
+}
+
+
+// Visibility utilities
+// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0
+.visible-xs,
+.visible-sm,
+.visible-md,
+.visible-lg {
+  .responsive-invisibility();
+}
+
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block {
+  display: none !important;
+}
+
+.visible-xs {
+  @media (max-width: @screen-xs-max) {
+    .responsive-visibility();
+  }
+}
+.visible-xs-block {
+  @media (max-width: @screen-xs-max) {
+    display: block !important;
+  }
+}
+.visible-xs-inline {
+  @media (max-width: @screen-xs-max) {
+    display: inline !important;
+  }
+}
+.visible-xs-inline-block {
+  @media (max-width: @screen-xs-max) {
+    display: inline-block !important;
+  }
+}
+
+.visible-sm {
+  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
+    .responsive-visibility();
+  }
+}
+.visible-sm-block {
+  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
+    display: block !important;
+  }
+}
+.visible-sm-inline {
+  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
+    display: inline !important;
+  }
+}
+.visible-sm-inline-block {
+  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
+    display: inline-block !important;
+  }
+}
+
+.visible-md {
+  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+    .responsive-visibility();
+  }
+}
+.visible-md-block {
+  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+    display: block !important;
+  }
+}
+.visible-md-inline {
+  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+    display: inline !important;
+  }
+}
+.visible-md-inline-block {
+  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+    display: inline-block !important;
+  }
+}
+
+.visible-lg {
+  @media (min-width: @screen-lg-min) {
+    .responsive-visibility();
+  }
+}
+.visible-lg-block {
+  @media (min-width: @screen-lg-min) {
+    display: block !important;
+  }
+}
+.visible-lg-inline {
+  @media (min-width: @screen-lg-min) {
+    display: inline !important;
+  }
+}
+.visible-lg-inline-block {
+  @media (min-width: @screen-lg-min) {
+    display: inline-block !important;
+  }
+}
+
+.hidden-xs {
+  @media (max-width: @screen-xs-max) {
+    .responsive-invisibility();
+  }
+}
+.hidden-sm {
+  @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
+    .responsive-invisibility();
+  }
+}
+.hidden-md {
+  @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
+    .responsive-invisibility();
+  }
+}
+.hidden-lg {
+  @media (min-width: @screen-lg-min) {
+    .responsive-invisibility();
+  }
+}
+
+
+// Print utilities
+//
+// Media queries are placed on the inside to be mixin-friendly.
+
+// Note: Deprecated .visible-print as of v3.2.0
+.visible-print {
+  .responsive-invisibility();
+
+  @media print {
+    .responsive-visibility();
+  }
+}
+.visible-print-block {
+  display: none !important;
+
+  @media print {
+    display: block !important;
+  }
+}
+.visible-print-inline {
+  display: none !important;
+
+  @media print {
+    display: inline !important;
+  }
+}
+.visible-print-inline-block {
+  display: none !important;
+
+  @media print {
+    display: inline-block !important;
+  }
+}
+
+.hidden-print {
+  @media print {
+    .responsive-invisibility();
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/scaffolding.less b/libs/fmt/doc/bootstrap/scaffolding.less
new file mode 100644
index 0000000000000000000000000000000000000000..47b1d5a1aab384e993b689aaaec4212a85260e88
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/scaffolding.less
@@ -0,0 +1,162 @@
+//
+// Scaffolding
+// --------------------------------------------------
+
+
+// Reset the box-sizing
+//
+// Heads up! This reset may cause conflicts with some third-party widgets.
+// For recommendations on resolving such conflicts, see
+// http://getbootstrap.com/getting-started/#third-box-sizing
+* {
+  .box-sizing(border-box);
+}
+*:before,
+*:after {
+  .box-sizing(border-box);
+}
+
+
+// Body reset
+
+html {
+  font-size: 10px;
+  -webkit-tap-highlight-color: rgba(0,0,0,0);
+}
+
+body {
+  font-family: @font-family-base;
+  font-size: @font-size-base;
+  line-height: @line-height-base;
+  color: @text-color;
+  background-color: @body-bg;
+}
+
+// Reset fonts for relevant elements
+input,
+button,
+select,
+textarea {
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+
+// Links
+
+a {
+  color: @link-color;
+  text-decoration: none;
+
+  &:hover,
+  &:focus {
+    color: @link-hover-color;
+    text-decoration: @link-hover-decoration;
+  }
+
+  &:focus {
+    .tab-focus();
+  }
+}
+
+
+// Figures
+//
+// We reset this here because previously Normalize had no `figure` margins. This
+// ensures we don't break anyone's use of the element.
+
+figure {
+  margin: 0;
+}
+
+
+// Images
+
+img {
+  vertical-align: middle;
+}
+
+// Responsive images (ensure images don't scale beyond their parents)
+.img-responsive {
+  .img-responsive();
+}
+
+// Rounded corners
+.img-rounded {
+  border-radius: @border-radius-large;
+}
+
+// Image thumbnails
+//
+// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.
+.img-thumbnail {
+  padding: @thumbnail-padding;
+  line-height: @line-height-base;
+  background-color: @thumbnail-bg;
+  border: 1px solid @thumbnail-border;
+  border-radius: @thumbnail-border-radius;
+  .transition(all .2s ease-in-out);
+
+  // Keep them at most 100% wide
+  .img-responsive(inline-block);
+}
+
+// Perfect circle
+.img-circle {
+  border-radius: 50%; // set radius in percents
+}
+
+
+// Horizontal rules
+
+hr {
+  margin-top:    @line-height-computed;
+  margin-bottom: @line-height-computed;
+  border: 0;
+  border-top: 1px solid @hr-border;
+}
+
+
+// Only display content to screen readers
+//
+// See: http://a11yproject.com/posts/how-to-hide-content/
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  margin: -1px;
+  padding: 0;
+  overflow: hidden;
+  clip: rect(0,0,0,0);
+  border: 0;
+}
+
+// Use in conjunction with .sr-only to only display content when it's focused.
+// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
+// Credit: HTML5 Boilerplate
+
+.sr-only-focusable {
+  &:active,
+  &:focus {
+    position: static;
+    width: auto;
+    height: auto;
+    margin: 0;
+    overflow: visible;
+    clip: auto;
+  }
+}
+
+
+// iOS "clickable elements" fix for role="button"
+//
+// Fixes "clickability" issue (and more generally, the firing of events such as focus as well)
+// for traditionally non-focusable elements with role="button"
+// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
+// Upstream patch for normalize.css submitted: https://github.com/necolas/normalize.css/pull/379 - remove this fix once that is merged
+
+[role="button"] {
+  cursor: pointer;
+}
\ No newline at end of file
diff --git a/libs/fmt/doc/bootstrap/tables.less b/libs/fmt/doc/bootstrap/tables.less
new file mode 100644
index 0000000000000000000000000000000000000000..2242c036868d7358ad684313ccab2e4d4704ee94
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/tables.less
@@ -0,0 +1,234 @@
+//
+// Tables
+// --------------------------------------------------
+
+
+table {
+  background-color: @table-bg;
+}
+caption {
+  padding-top: @table-cell-padding;
+  padding-bottom: @table-cell-padding;
+  color: @text-muted;
+  text-align: left;
+}
+th {
+  text-align: left;
+}
+
+
+// Baseline styles
+
+.table {
+  width: 100%;
+  max-width: 100%;
+  margin-bottom: @line-height-computed;
+  // Cells
+  > thead,
+  > tbody,
+  > tfoot {
+    > tr {
+      > th,
+      > td {
+        padding: @table-cell-padding;
+        line-height: @line-height-base;
+        vertical-align: top;
+        border-top: 1px solid @table-border-color;
+      }
+    }
+  }
+  // Bottom align for column headings
+  > thead > tr > th {
+    vertical-align: bottom;
+    border-bottom: 2px solid @table-border-color;
+  }
+  // Remove top border from thead by default
+  > caption + thead,
+  > colgroup + thead,
+  > thead:first-child {
+    > tr:first-child {
+      > th,
+      > td {
+        border-top: 0;
+      }
+    }
+  }
+  // Account for multiple tbody instances
+  > tbody + tbody {
+    border-top: 2px solid @table-border-color;
+  }
+
+  // Nesting
+  .table {
+    background-color: @body-bg;
+  }
+}
+
+
+// Condensed table w/ half padding
+
+.table-condensed {
+  > thead,
+  > tbody,
+  > tfoot {
+    > tr {
+      > th,
+      > td {
+        padding: @table-condensed-cell-padding;
+      }
+    }
+  }
+}
+
+
+// Bordered version
+//
+// Add borders all around the table and between all the columns.
+
+.table-bordered {
+  border: 1px solid @table-border-color;
+  > thead,
+  > tbody,
+  > tfoot {
+    > tr {
+      > th,
+      > td {
+        border: 1px solid @table-border-color;
+      }
+    }
+  }
+  > thead > tr {
+    > th,
+    > td {
+      border-bottom-width: 2px;
+    }
+  }
+}
+
+
+// Zebra-striping
+//
+// Default zebra-stripe styles (alternating gray and transparent backgrounds)
+
+.table-striped {
+  > tbody > tr:nth-of-type(odd) {
+    background-color: @table-bg-accent;
+  }
+}
+
+
+// Hover effect
+//
+// Placed here since it has to come after the potential zebra striping
+
+.table-hover {
+  > tbody > tr:hover {
+    background-color: @table-bg-hover;
+  }
+}
+
+
+// Table cell sizing
+//
+// Reset default table behavior
+
+table col[class*="col-"] {
+  position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)
+  float: none;
+  display: table-column;
+}
+table {
+  td,
+  th {
+    &[class*="col-"] {
+      position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)
+      float: none;
+      display: table-cell;
+    }
+  }
+}
+
+
+// Table backgrounds
+//
+// Exact selectors below required to override `.table-striped` and prevent
+// inheritance to nested tables.
+
+// Generate the contextual variants
+.table-row-variant(active; @table-bg-active);
+.table-row-variant(success; @state-success-bg);
+.table-row-variant(info; @state-info-bg);
+.table-row-variant(warning; @state-warning-bg);
+.table-row-variant(danger; @state-danger-bg);
+
+
+// Responsive tables
+//
+// Wrap your tables in `.table-responsive` and we'll make them mobile friendly
+// by enabling horizontal scrolling. Only applies <768px. Everything above that
+// will display normally.
+
+.table-responsive {
+  overflow-x: auto;
+  min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)
+
+  @media screen and (max-width: @screen-xs-max) {
+    width: 100%;
+    margin-bottom: (@line-height-computed * 0.75);
+    overflow-y: hidden;
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+    border: 1px solid @table-border-color;
+
+    // Tighten up spacing
+    > .table {
+      margin-bottom: 0;
+
+      // Ensure the content doesn't wrap
+      > thead,
+      > tbody,
+      > tfoot {
+        > tr {
+          > th,
+          > td {
+            white-space: nowrap;
+          }
+        }
+      }
+    }
+
+    // Special overrides for the bordered tables
+    > .table-bordered {
+      border: 0;
+
+      // Nuke the appropriate borders so that the parent can handle them
+      > thead,
+      > tbody,
+      > tfoot {
+        > tr {
+          > th:first-child,
+          > td:first-child {
+            border-left: 0;
+          }
+          > th:last-child,
+          > td:last-child {
+            border-right: 0;
+          }
+        }
+      }
+
+      // Only nuke the last row's bottom-border in `tbody` and `tfoot` since
+      // chances are there will be only one `tr` in a `thead` and that would
+      // remove the border altogether.
+      > tbody,
+      > tfoot {
+        > tr:last-child {
+          > th,
+          > td {
+            border-bottom: 0;
+          }
+        }
+      }
+
+    }
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/theme.less b/libs/fmt/doc/bootstrap/theme.less
new file mode 100644
index 0000000000000000000000000000000000000000..6f2eb62126f12b00cde15580964870c7c3e4eb4c
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/theme.less
@@ -0,0 +1,273 @@
+
+//
+// Load core variables and mixins
+// --------------------------------------------------
+
+@import "variables.less";
+@import "mixins.less";
+
+
+//
+// Buttons
+// --------------------------------------------------
+
+// Common styles
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+  text-shadow: 0 -1px 0 rgba(0,0,0,.2);
+  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);
+  .box-shadow(@shadow);
+
+  // Reset the shadow
+  &:active,
+  &.active {
+    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
+  }
+
+  .badge {
+    text-shadow: none;
+  }
+}
+
+// Mixin for generating new styles
+.btn-styles(@btn-color: #555) {
+  #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));
+  .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620
+  background-repeat: repeat-x;
+  border-color: darken(@btn-color, 14%);
+
+  &:hover,
+  &:focus  {
+    background-color: darken(@btn-color, 12%);
+    background-position: 0 -15px;
+  }
+
+  &:active,
+  &.active {
+    background-color: darken(@btn-color, 12%);
+    border-color: darken(@btn-color, 14%);
+  }
+
+  &.disabled,
+  &:disabled,
+  &[disabled] {
+    background-color: darken(@btn-color, 12%);
+    background-image: none;
+  }
+}
+
+// Common styles
+.btn {
+  // Remove the gradient for the pressed/active state
+  &:active,
+  &.active {
+    background-image: none;
+  }
+}
+
+// Apply the mixin to the buttons
+.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }
+.btn-primary { .btn-styles(@btn-primary-bg); }
+.btn-success { .btn-styles(@btn-success-bg); }
+.btn-info    { .btn-styles(@btn-info-bg); }
+.btn-warning { .btn-styles(@btn-warning-bg); }
+.btn-danger  { .btn-styles(@btn-danger-bg); }
+
+
+//
+// Images
+// --------------------------------------------------
+
+.thumbnail,
+.img-thumbnail {
+  .box-shadow(0 1px 2px rgba(0,0,0,.075));
+}
+
+
+//
+// Dropdowns
+// --------------------------------------------------
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));
+  background-color: darken(@dropdown-link-hover-bg, 5%);
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));
+  background-color: darken(@dropdown-link-active-bg, 5%);
+}
+
+
+//
+// Navbar
+// --------------------------------------------------
+
+// Default navbar
+.navbar-default {
+  #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);
+  .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered
+  border-radius: @navbar-border-radius;
+  @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);
+  .box-shadow(@shadow);
+
+  .navbar-nav > .open > a,
+  .navbar-nav > .active > a {
+    #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));
+    .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));
+  }
+}
+.navbar-brand,
+.navbar-nav > li > a {
+  text-shadow: 0 1px 0 rgba(255,255,255,.25);
+}
+
+// Inverted navbar
+.navbar-inverse {
+  #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);
+  .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257
+
+  .navbar-nav > .open > a,
+  .navbar-nav > .active > a {
+    #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));
+    .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));
+  }
+
+  .navbar-brand,
+  .navbar-nav > li > a {
+    text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+  }
+}
+
+// Undo rounded corners in static and fixed navbars
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  border-radius: 0;
+}
+
+// Fix active state of dropdown items in collapsed mode
+@media (max-width: @grid-float-breakpoint-max) {
+  .navbar .navbar-nav .open .dropdown-menu > .active > a {
+    &,
+    &:hover,
+    &:focus {
+      color: #fff;
+      #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));
+    }
+  }
+}
+
+
+//
+// Alerts
+// --------------------------------------------------
+
+// Common styles
+.alert {
+  text-shadow: 0 1px 0 rgba(255,255,255,.2);
+  @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);
+  .box-shadow(@shadow);
+}
+
+// Mixin for generating new styles
+.alert-styles(@color) {
+  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));
+  border-color: darken(@color, 15%);
+}
+
+// Apply the mixin to the alerts
+.alert-success    { .alert-styles(@alert-success-bg); }
+.alert-info       { .alert-styles(@alert-info-bg); }
+.alert-warning    { .alert-styles(@alert-warning-bg); }
+.alert-danger     { .alert-styles(@alert-danger-bg); }
+
+
+//
+// Progress bars
+// --------------------------------------------------
+
+// Give the progress background some depth
+.progress {
+  #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)
+}
+
+// Mixin for generating new styles
+.progress-bar-styles(@color) {
+  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));
+}
+
+// Apply the mixin to the progress bars
+.progress-bar            { .progress-bar-styles(@progress-bar-bg); }
+.progress-bar-success    { .progress-bar-styles(@progress-bar-success-bg); }
+.progress-bar-info       { .progress-bar-styles(@progress-bar-info-bg); }
+.progress-bar-warning    { .progress-bar-styles(@progress-bar-warning-bg); }
+.progress-bar-danger     { .progress-bar-styles(@progress-bar-danger-bg); }
+
+// Reset the striped class because our mixins don't do multiple gradients and
+// the above custom styles override the new `.progress-bar-striped` in v3.2.0.
+.progress-bar-striped {
+  #gradient > .striped();
+}
+
+
+//
+// List groups
+// --------------------------------------------------
+
+.list-group {
+  border-radius: @border-radius-base;
+  .box-shadow(0 1px 2px rgba(0,0,0,.075));
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+  text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);
+  #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));
+  border-color: darken(@list-group-active-border, 7.5%);
+
+  .badge {
+    text-shadow: none;
+  }
+}
+
+
+//
+// Panels
+// --------------------------------------------------
+
+// Common styles
+.panel {
+  .box-shadow(0 1px 2px rgba(0,0,0,.05));
+}
+
+// Mixin for generating new styles
+.panel-heading-styles(@color) {
+  #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));
+}
+
+// Apply the mixin to the panel headings only
+.panel-default > .panel-heading   { .panel-heading-styles(@panel-default-heading-bg); }
+.panel-primary > .panel-heading   { .panel-heading-styles(@panel-primary-heading-bg); }
+.panel-success > .panel-heading   { .panel-heading-styles(@panel-success-heading-bg); }
+.panel-info > .panel-heading      { .panel-heading-styles(@panel-info-heading-bg); }
+.panel-warning > .panel-heading   { .panel-heading-styles(@panel-warning-heading-bg); }
+.panel-danger > .panel-heading    { .panel-heading-styles(@panel-danger-heading-bg); }
+
+
+//
+// Wells
+// --------------------------------------------------
+
+.well {
+  #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);
+  border-color: darken(@well-bg, 10%);
+  @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);
+  .box-shadow(@shadow);
+}
diff --git a/libs/fmt/doc/bootstrap/thumbnails.less b/libs/fmt/doc/bootstrap/thumbnails.less
new file mode 100644
index 0000000000000000000000000000000000000000..0713e67d0068e9553c5e21bb6024122029cce520
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/thumbnails.less
@@ -0,0 +1,36 @@
+//
+// Thumbnails
+// --------------------------------------------------
+
+
+// Mixin and adjust the regular image class
+.thumbnail {
+  display: block;
+  padding: @thumbnail-padding;
+  margin-bottom: @line-height-computed;
+  line-height: @line-height-base;
+  background-color: @thumbnail-bg;
+  border: 1px solid @thumbnail-border;
+  border-radius: @thumbnail-border-radius;
+  .transition(border .2s ease-in-out);
+
+  > img,
+  a > img {
+    &:extend(.img-responsive);
+    margin-left: auto;
+    margin-right: auto;
+  }
+
+  // Add a hover state for linked versions only
+  a&:hover,
+  a&:focus,
+  a&.active {
+    border-color: @link-color;
+  }
+
+  // Image captions
+  .caption {
+    padding: @thumbnail-caption-padding;
+    color: @thumbnail-caption-color;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/tooltip.less b/libs/fmt/doc/bootstrap/tooltip.less
new file mode 100644
index 0000000000000000000000000000000000000000..9d5684743309e74514cebc5e5a2e49d876f38c93
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/tooltip.less
@@ -0,0 +1,102 @@
+//
+// Tooltips
+// --------------------------------------------------
+
+
+// Base class
+.tooltip {
+  position: absolute;
+  z-index: @zindex-tooltip;
+  display: block;
+  // Reset font and text properties given new insertion method
+  font-family: @font-family-base;
+  font-size: @font-size-small;
+  font-weight: normal;
+  line-height: 1.4;
+  .opacity(0);
+
+  &.in     { .opacity(@tooltip-opacity); }
+  &.top    { margin-top:  -3px; padding: @tooltip-arrow-width 0; }
+  &.right  { margin-left:  3px; padding: 0 @tooltip-arrow-width; }
+  &.bottom { margin-top:   3px; padding: @tooltip-arrow-width 0; }
+  &.left   { margin-left: -3px; padding: 0 @tooltip-arrow-width; }
+}
+
+// Wrapper for the tooltip content
+.tooltip-inner {
+  max-width: @tooltip-max-width;
+  padding: 3px 8px;
+  color: @tooltip-color;
+  text-align: center;
+  text-decoration: none;
+  background-color: @tooltip-bg;
+  border-radius: @border-radius-base;
+}
+
+// Arrows
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1
+.tooltip {
+  &.top .tooltip-arrow {
+    bottom: 0;
+    left: 50%;
+    margin-left: -@tooltip-arrow-width;
+    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
+    border-top-color: @tooltip-arrow-color;
+  }
+  &.top-left .tooltip-arrow {
+    bottom: 0;
+    right: @tooltip-arrow-width;
+    margin-bottom: -@tooltip-arrow-width;
+    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
+    border-top-color: @tooltip-arrow-color;
+  }
+  &.top-right .tooltip-arrow {
+    bottom: 0;
+    left: @tooltip-arrow-width;
+    margin-bottom: -@tooltip-arrow-width;
+    border-width: @tooltip-arrow-width @tooltip-arrow-width 0;
+    border-top-color: @tooltip-arrow-color;
+  }
+  &.right .tooltip-arrow {
+    top: 50%;
+    left: 0;
+    margin-top: -@tooltip-arrow-width;
+    border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;
+    border-right-color: @tooltip-arrow-color;
+  }
+  &.left .tooltip-arrow {
+    top: 50%;
+    right: 0;
+    margin-top: -@tooltip-arrow-width;
+    border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;
+    border-left-color: @tooltip-arrow-color;
+  }
+  &.bottom .tooltip-arrow {
+    top: 0;
+    left: 50%;
+    margin-left: -@tooltip-arrow-width;
+    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
+    border-bottom-color: @tooltip-arrow-color;
+  }
+  &.bottom-left .tooltip-arrow {
+    top: 0;
+    right: @tooltip-arrow-width;
+    margin-top: -@tooltip-arrow-width;
+    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
+    border-bottom-color: @tooltip-arrow-color;
+  }
+  &.bottom-right .tooltip-arrow {
+    top: 0;
+    left: @tooltip-arrow-width;
+    margin-top: -@tooltip-arrow-width;
+    border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;
+    border-bottom-color: @tooltip-arrow-color;
+  }
+}
diff --git a/libs/fmt/doc/bootstrap/type.less b/libs/fmt/doc/bootstrap/type.less
new file mode 100644
index 0000000000000000000000000000000000000000..68ba6017bf184d805a39530f0acd5cbb1495e522
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/type.less
@@ -0,0 +1,302 @@
+//
+// Typography
+// --------------------------------------------------
+
+
+// Headings
+// -------------------------
+
+h1, h2, h3, h4, h5, h6,
+.h1, .h2, .h3, .h4, .h5, .h6 {
+  font-family: @headings-font-family;
+  font-weight: @headings-font-weight;
+  line-height: @headings-line-height;
+  color: @headings-color;
+
+  small,
+  .small {
+    font-weight: normal;
+    line-height: 1;
+    color: @headings-small-color;
+  }
+}
+
+h1, .h1,
+h2, .h2,
+h3, .h3 {
+  margin-top: @line-height-computed;
+  margin-bottom: (@line-height-computed / 2);
+
+  small,
+  .small {
+    font-size: 65%;
+  }
+}
+h4, .h4,
+h5, .h5,
+h6, .h6 {
+  margin-top: (@line-height-computed / 2);
+  margin-bottom: (@line-height-computed / 2);
+
+  small,
+  .small {
+    font-size: 75%;
+  }
+}
+
+h1, .h1 { font-size: @font-size-h1; }
+h2, .h2 { font-size: @font-size-h2; }
+h3, .h3 { font-size: @font-size-h3; }
+h4, .h4 { font-size: @font-size-h4; }
+h5, .h5 { font-size: @font-size-h5; }
+h6, .h6 { font-size: @font-size-h6; }
+
+
+// Body text
+// -------------------------
+
+p {
+  margin: 0 0 (@line-height-computed / 2);
+}
+
+.lead {
+  margin-bottom: @line-height-computed;
+  font-size: floor((@font-size-base * 1.15));
+  font-weight: 300;
+  line-height: 1.4;
+
+  @media (min-width: @screen-sm-min) {
+    font-size: (@font-size-base * 1.5);
+  }
+}
+
+
+// Emphasis & misc
+// -------------------------
+
+// Ex: (12px small font / 14px base font) * 100% = about 85%
+small,
+.small {
+  font-size: floor((100% * @font-size-small / @font-size-base));
+}
+
+mark,
+.mark {
+  background-color: @state-warning-bg;
+  padding: .2em;
+}
+
+// Alignment
+.text-left           { text-align: left; }
+.text-right          { text-align: right; }
+.text-center         { text-align: center; }
+.text-justify        { text-align: justify; }
+.text-nowrap         { white-space: nowrap; }
+
+// Transformation
+.text-lowercase      { text-transform: lowercase; }
+.text-uppercase      { text-transform: uppercase; }
+.text-capitalize     { text-transform: capitalize; }
+
+// Contextual colors
+.text-muted {
+  color: @text-muted;
+}
+.text-primary {
+  .text-emphasis-variant(@brand-primary);
+}
+.text-success {
+  .text-emphasis-variant(@state-success-text);
+}
+.text-info {
+  .text-emphasis-variant(@state-info-text);
+}
+.text-warning {
+  .text-emphasis-variant(@state-warning-text);
+}
+.text-danger {
+  .text-emphasis-variant(@state-danger-text);
+}
+
+// Contextual backgrounds
+// For now we'll leave these alongside the text classes until v4 when we can
+// safely shift things around (per SemVer rules).
+.bg-primary {
+  // Given the contrast here, this is the only class to have its color inverted
+  // automatically.
+  color: #fff;
+  .bg-variant(@brand-primary);
+}
+.bg-success {
+  .bg-variant(@state-success-bg);
+}
+.bg-info {
+  .bg-variant(@state-info-bg);
+}
+.bg-warning {
+  .bg-variant(@state-warning-bg);
+}
+.bg-danger {
+  .bg-variant(@state-danger-bg);
+}
+
+
+// Page header
+// -------------------------
+
+.page-header {
+  padding-bottom: ((@line-height-computed / 2) - 1);
+  margin: (@line-height-computed * 2) 0 @line-height-computed;
+  border-bottom: 1px solid @page-header-border-color;
+}
+
+
+// Lists
+// -------------------------
+
+// Unordered and Ordered lists
+ul,
+ol {
+  margin-top: 0;
+  margin-bottom: (@line-height-computed / 2);
+  ul,
+  ol {
+    margin-bottom: 0;
+  }
+}
+
+// List options
+
+// Unstyled keeps list items block level, just removes default browser padding and list-style
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+// Inline turns list items into inline-block
+.list-inline {
+  .list-unstyled();
+  margin-left: -5px;
+
+  > li {
+    display: inline-block;
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+}
+
+// Description Lists
+dl {
+  margin-top: 0; // Remove browser default
+  margin-bottom: @line-height-computed;
+}
+dt,
+dd {
+  line-height: @line-height-base;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 0; // Undo browser default
+}
+
+// Horizontal description lists
+//
+// Defaults to being stacked without any of the below styles applied, until the
+// grid breakpoint is reached (default of ~768px).
+
+.dl-horizontal {
+  dd {
+    &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
+  }
+
+  @media (min-width: @grid-float-breakpoint) {
+    dt {
+      float: left;
+      width: (@dl-horizontal-offset - 20);
+      clear: left;
+      text-align: right;
+      .text-overflow();
+    }
+    dd {
+      margin-left: @dl-horizontal-offset;
+    }
+  }
+}
+
+
+// Misc
+// -------------------------
+
+// Abbreviations and acronyms
+abbr[title],
+// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted @abbr-border-color;
+}
+.initialism {
+  font-size: 90%;
+  .text-uppercase();
+}
+
+// Blockquotes
+blockquote {
+  padding: (@line-height-computed / 2) @line-height-computed;
+  margin: 0 0 @line-height-computed;
+  font-size: @blockquote-font-size;
+  border-left: 5px solid @blockquote-border-color;
+
+  p,
+  ul,
+  ol {
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  // Note: Deprecated small and .small as of v3.1.0
+  // Context: https://github.com/twbs/bootstrap/issues/11660
+  footer,
+  small,
+  .small {
+    display: block;
+    font-size: 80%; // back to default font-size
+    line-height: @line-height-base;
+    color: @blockquote-small-color;
+
+    &:before {
+      content: '\2014 \00A0'; // em dash, nbsp
+    }
+  }
+}
+
+// Opposite alignment of blockquote
+//
+// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.
+.blockquote-reverse,
+blockquote.pull-right {
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid @blockquote-border-color;
+  border-left: 0;
+  text-align: right;
+
+  // Account for citation
+  footer,
+  small,
+  .small {
+    &:before { content: ''; }
+    &:after {
+      content: '\00A0 \2014'; // nbsp, em dash
+    }
+  }
+}
+
+// Addresses
+address {
+  margin-bottom: @line-height-computed;
+  font-style: normal;
+  line-height: @line-height-base;
+}
diff --git a/libs/fmt/doc/bootstrap/utilities.less b/libs/fmt/doc/bootstrap/utilities.less
new file mode 100644
index 0000000000000000000000000000000000000000..7a8ca27a8ff54db1c439812d4b295d9a1c4f6f76
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/utilities.less
@@ -0,0 +1,55 @@
+//
+// Utility classes
+// --------------------------------------------------
+
+
+// Floats
+// -------------------------
+
+.clearfix {
+  .clearfix();
+}
+.center-block {
+  .center-block();
+}
+.pull-right {
+  float: right !important;
+}
+.pull-left {
+  float: left !important;
+}
+
+
+// Toggling content
+// -------------------------
+
+// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1
+.hide {
+  display: none !important;
+}
+.show {
+  display: block !important;
+}
+.invisible {
+  visibility: hidden;
+}
+.text-hide {
+  .text-hide();
+}
+
+
+// Hide from screenreaders and browsers
+//
+// Credit: HTML5 Boilerplate
+
+.hidden {
+  display: none !important;
+}
+
+
+// For Affix plugin
+// -------------------------
+
+.affix {
+  position: fixed;
+}
diff --git a/libs/fmt/doc/bootstrap/variables.less b/libs/fmt/doc/bootstrap/variables.less
new file mode 100644
index 0000000000000000000000000000000000000000..b5fb9d02cf5197e50517a60dae2e3447a032cfe5
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/variables.less
@@ -0,0 +1,861 @@
+//
+// Variables
+// --------------------------------------------------
+
+
+//== Colors
+//
+//## Gray and brand colors for use across Bootstrap.
+
+@gray-base:              #000;
+@gray-darker:            lighten(@gray-base, 13.5%); // #222
+@gray-dark:              lighten(@gray-base, 20%);   // #333
+@gray:                   lighten(@gray-base, 33.5%); // #555
+@gray-light:             lighten(@gray-base, 46.7%); // #777
+@gray-lighter:           lighten(@gray-base, 93.5%); // #eee
+
+@brand-primary:         darken(#428bca, 6.5%); // #337ab7
+@brand-success:         #5cb85c;
+@brand-info:            #5bc0de;
+@brand-warning:         #f0ad4e;
+@brand-danger:          #d9534f;
+
+
+//== Scaffolding
+//
+//## Settings for some of the most global styles.
+
+//** Background color for `<body>`.
+@body-bg:               #fff;
+//** Global text color on `<body>`.
+@text-color:            @gray-dark;
+
+//** Global textual link color.
+@link-color:            @brand-primary;
+//** Link hover color set via `darken()` function.
+@link-hover-color:      darken(@link-color, 15%);
+//** Link hover decoration.
+@link-hover-decoration: underline;
+
+
+//== Typography
+//
+//## Font, line-height, and color for body text, headings, and more.
+
+@font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-serif;
+@font-family-serif:       Georgia, "Times New Roman", Times, serif;
+//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
+@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-base:        @font-family-sans-serif;
+
+@font-size-base:          14px;
+@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
+@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
+
+@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
+@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
+@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
+@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
+@font-size-h5:            @font-size-base;
+@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+@line-height-base:        1.428571429; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
+
+//** By default, this inherits from the `<body>`.
+@headings-font-family:    inherit;
+@headings-font-weight:    500;
+@headings-line-height:    1.1;
+@headings-color:          inherit;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+@icon-font-path:          "../fonts/";
+//** File name for all font files.
+@icon-font-name:          "glyphicons-halflings-regular";
+//** Element ID within SVG icon file.
+@icon-font-svg-id:        "glyphicons_halflingsregular";
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+@padding-base-vertical:     6px;
+@padding-base-horizontal:   12px;
+
+@padding-large-vertical:    10px;
+@padding-large-horizontal:  16px;
+
+@padding-small-vertical:    5px;
+@padding-small-horizontal:  10px;
+
+@padding-xs-vertical:       1px;
+@padding-xs-horizontal:     5px;
+
+@line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
+@line-height-small:         1.5;
+
+@border-radius-base:        4px;
+@border-radius-large:       6px;
+@border-radius-small:       3px;
+
+//** Global color for active items (e.g., navs or dropdowns).
+@component-active-color:    #fff;
+//** Global background color for active items (e.g., navs or dropdowns).
+@component-active-bg:       @brand-primary;
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+@caret-width-base:          4px;
+//** Carets increase slightly in size for larger components.
+@caret-width-large:         5px;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for `<th>`s and `<td>`s.
+@table-cell-padding:            8px;
+//** Padding for cells in `.table-condensed`.
+@table-condensed-cell-padding:  5px;
+
+//** Default background color used for all tables.
+@table-bg:                      transparent;
+//** Background color used for `.table-striped`.
+@table-bg-accent:               #f9f9f9;
+//** Background color used for `.table-hover`.
+@table-bg-hover:                #f5f5f5;
+@table-bg-active:               @table-bg-hover;
+
+//** Border color for table and cell borders.
+@table-border-color:            #ddd;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+@btn-font-weight:                normal;
+
+@btn-default-color:              #333;
+@btn-default-bg:                 #fff;
+@btn-default-border:             #ccc;
+
+@btn-primary-color:              #fff;
+@btn-primary-bg:                 @brand-primary;
+@btn-primary-border:             darken(@btn-primary-bg, 5%);
+
+@btn-success-color:              #fff;
+@btn-success-bg:                 @brand-success;
+@btn-success-border:             darken(@btn-success-bg, 5%);
+
+@btn-info-color:                 #fff;
+@btn-info-bg:                    @brand-info;
+@btn-info-border:                darken(@btn-info-bg, 5%);
+
+@btn-warning-color:              #fff;
+@btn-warning-bg:                 @brand-warning;
+@btn-warning-border:             darken(@btn-warning-bg, 5%);
+
+@btn-danger-color:               #fff;
+@btn-danger-bg:                  @brand-danger;
+@btn-danger-border:              darken(@btn-danger-bg, 5%);
+
+@btn-link-disabled-color:        @gray-light;
+
+
+//== Forms
+//
+//##
+
+//** `<input>` background color
+@input-bg:                       #fff;
+//** `<input disabled>` background color
+@input-bg-disabled:              @gray-lighter;
+
+//** Text color for `<input>`s
+@input-color:                    @gray;
+//** `<input>` border color
+@input-border:                   #ccc;
+
+// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS.
+@input-border-radius:            @border-radius-base;
+//** Large `.form-control` border radius
+@input-border-radius-large:      @border-radius-large;
+//** Small `.form-control` border radius
+@input-border-radius-small:      @border-radius-small;
+
+//** Border color for inputs on focus
+@input-border-focus:             #66afe9;
+
+//** Placeholder text color
+@input-color-placeholder:        #999;
+
+//** Default `.form-control` height
+@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
+//** Large `.form-control` height
+@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
+//** Small `.form-control` height
+@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
+
+//** `.form-group` margin
+@form-group-margin-bottom:       15px;
+
+@legend-color:                   @gray-dark;
+@legend-border-color:            #e5e5e5;
+
+//** Background color for textual input addons
+@input-group-addon-bg:           @gray-lighter;
+//** Border color for textual input addons
+@input-group-addon-border-color: @input-border;
+
+//** Disabled cursor for form controls and buttons.
+@cursor-disabled:                not-allowed;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+@dropdown-bg:                    #fff;
+//** Dropdown menu `border-color`.
+@dropdown-border:                rgba(0,0,0,.15);
+//** Dropdown menu `border-color` **for IE8**.
+@dropdown-fallback-border:       #ccc;
+//** Divider color for between dropdown items.
+@dropdown-divider-bg:            #e5e5e5;
+
+//** Dropdown link text color.
+@dropdown-link-color:            @gray-dark;
+//** Hover color for dropdown links.
+@dropdown-link-hover-color:      darken(@gray-dark, 5%);
+//** Hover background for dropdown links.
+@dropdown-link-hover-bg:         #f5f5f5;
+
+//** Active dropdown menu item text color.
+@dropdown-link-active-color:     @component-active-color;
+//** Active dropdown menu item background color.
+@dropdown-link-active-bg:        @component-active-bg;
+
+//** Disabled dropdown menu item background color.
+@dropdown-link-disabled-color:   @gray-light;
+
+//** Text color for headers within dropdown menus.
+@dropdown-header-color:          @gray-light;
+
+//** Deprecated `@dropdown-caret-color` as of v3.1.0
+@dropdown-caret-color:           #000;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+@zindex-navbar:            1000;
+@zindex-dropdown:          1000;
+@zindex-popover:           1060;
+@zindex-tooltip:           1070;
+@zindex-navbar-fixed:      1030;
+@zindex-modal-background:  1040;
+@zindex-modal:             1050;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `@screen-xs` as of v3.0.1
+@screen-xs:                  480px;
+//** Deprecated `@screen-xs-min` as of v3.2.0
+@screen-xs-min:              @screen-xs;
+//** Deprecated `@screen-phone` as of v3.0.1
+@screen-phone:               @screen-xs-min;
+
+// Small screen / tablet
+//** Deprecated `@screen-sm` as of v3.0.1
+@screen-sm:                  768px;
+@screen-sm-min:              @screen-sm;
+//** Deprecated `@screen-tablet` as of v3.0.1
+@screen-tablet:              @screen-sm-min;
+
+// Medium screen / desktop
+//** Deprecated `@screen-md` as of v3.0.1
+@screen-md:                  992px;
+@screen-md-min:              @screen-md;
+//** Deprecated `@screen-desktop` as of v3.0.1
+@screen-desktop:             @screen-md-min;
+
+// Large screen / wide desktop
+//** Deprecated `@screen-lg` as of v3.0.1
+@screen-lg:                  1200px;
+@screen-lg-min:              @screen-lg;
+//** Deprecated `@screen-lg-desktop` as of v3.0.1
+@screen-lg-desktop:          @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max:              (@screen-sm-min - 1);
+@screen-sm-max:              (@screen-md-min - 1);
+@screen-md-max:              (@screen-lg-min - 1);
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+@grid-columns:              12;
+//** Padding between columns. Gets divided in half for the left and right.
+@grid-gutter-width:         30px;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+@grid-float-breakpoint:     @screen-sm-min;
+//** Point at which the navbar begins collapsing.
+@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+@container-tablet:             (720px + @grid-gutter-width);
+//** For `@screen-sm-min` and up.
+@container-sm:                 @container-tablet;
+
+// Medium screen / desktop
+@container-desktop:            (940px + @grid-gutter-width);
+//** For `@screen-md-min` and up.
+@container-md:                 @container-desktop;
+
+// Large screen / wide desktop
+@container-large-desktop:      (1140px + @grid-gutter-width);
+//** For `@screen-lg-min` and up.
+@container-lg:                 @container-large-desktop;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+@navbar-height:                    50px;
+@navbar-margin-bottom:             @line-height-computed;
+@navbar-border-radius:             @border-radius-base;
+@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
+@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
+@navbar-collapse-max-height:       340px;
+
+@navbar-default-color:             #777;
+@navbar-default-bg:                #f8f8f8;
+@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
+
+// Navbar links
+@navbar-default-link-color:                #777;
+@navbar-default-link-hover-color:          #333;
+@navbar-default-link-hover-bg:             transparent;
+@navbar-default-link-active-color:         #555;
+@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
+@navbar-default-link-disabled-color:       #ccc;
+@navbar-default-link-disabled-bg:          transparent;
+
+// Navbar brand label
+@navbar-default-brand-color:               @navbar-default-link-color;
+@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
+@navbar-default-brand-hover-bg:            transparent;
+
+// Navbar toggle
+@navbar-default-toggle-hover-bg:           #ddd;
+@navbar-default-toggle-icon-bar-bg:        #888;
+@navbar-default-toggle-border-color:       #ddd;
+
+
+// Inverted navbar
+// Reset inverted navbar basics
+@navbar-inverse-color:                      lighten(@gray-light, 15%);
+@navbar-inverse-bg:                         #222;
+@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
+
+// Inverted navbar links
+@navbar-inverse-link-color:                 lighten(@gray-light, 15%);
+@navbar-inverse-link-hover-color:           #fff;
+@navbar-inverse-link-hover-bg:              transparent;
+@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
+@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
+@navbar-inverse-link-disabled-color:        #444;
+@navbar-inverse-link-disabled-bg:           transparent;
+
+// Inverted navbar brand label
+@navbar-inverse-brand-color:                @navbar-inverse-link-color;
+@navbar-inverse-brand-hover-color:          #fff;
+@navbar-inverse-brand-hover-bg:             transparent;
+
+// Inverted navbar toggle
+@navbar-inverse-toggle-hover-bg:            #333;
+@navbar-inverse-toggle-icon-bar-bg:         #fff;
+@navbar-inverse-toggle-border-color:        #333;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+@nav-link-padding:                          10px 15px;
+@nav-link-hover-bg:                         @gray-lighter;
+
+@nav-disabled-link-color:                   @gray-light;
+@nav-disabled-link-hover-color:             @gray-light;
+
+//== Tabs
+@nav-tabs-border-color:                     #ddd;
+
+@nav-tabs-link-hover-border-color:          @gray-lighter;
+
+@nav-tabs-active-link-hover-bg:             @body-bg;
+@nav-tabs-active-link-hover-color:          @gray;
+@nav-tabs-active-link-hover-border-color:   #ddd;
+
+@nav-tabs-justified-link-border-color:            #ddd;
+@nav-tabs-justified-active-link-border-color:     @body-bg;
+
+//== Pills
+@nav-pills-border-radius:                   @border-radius-base;
+@nav-pills-active-link-hover-bg:            @component-active-bg;
+@nav-pills-active-link-hover-color:         @component-active-color;
+
+
+//== Pagination
+//
+//##
+
+@pagination-color:                     @link-color;
+@pagination-bg:                        #fff;
+@pagination-border:                    #ddd;
+
+@pagination-hover-color:               @link-hover-color;
+@pagination-hover-bg:                  @gray-lighter;
+@pagination-hover-border:              #ddd;
+
+@pagination-active-color:              #fff;
+@pagination-active-bg:                 @brand-primary;
+@pagination-active-border:             @brand-primary;
+
+@pagination-disabled-color:            @gray-light;
+@pagination-disabled-bg:               #fff;
+@pagination-disabled-border:           #ddd;
+
+
+//== Pager
+//
+//##
+
+@pager-bg:                             @pagination-bg;
+@pager-border:                         @pagination-border;
+@pager-border-radius:                  15px;
+
+@pager-hover-bg:                       @pagination-hover-bg;
+
+@pager-active-bg:                      @pagination-active-bg;
+@pager-active-color:                   @pagination-active-color;
+
+@pager-disabled-color:                 @pagination-disabled-color;
+
+
+//== Jumbotron
+//
+//##
+
+@jumbotron-padding:              30px;
+@jumbotron-color:                inherit;
+@jumbotron-bg:                   @gray-lighter;
+@jumbotron-heading-color:        inherit;
+@jumbotron-font-size:            ceil((@font-size-base * 1.5));
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+@state-success-text:             #3c763d;
+@state-success-bg:               #dff0d8;
+@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
+
+@state-info-text:                #31708f;
+@state-info-bg:                  #d9edf7;
+@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
+
+@state-warning-text:             #8a6d3b;
+@state-warning-bg:               #fcf8e3;
+@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
+
+@state-danger-text:              #a94442;
+@state-danger-bg:                #f2dede;
+@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+@tooltip-max-width:           200px;
+//** Tooltip text color
+@tooltip-color:               #fff;
+//** Tooltip background color
+@tooltip-bg:                  #000;
+@tooltip-opacity:             .9;
+
+//** Tooltip arrow width
+@tooltip-arrow-width:         5px;
+//** Tooltip arrow color
+@tooltip-arrow-color:         @tooltip-bg;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+@popover-bg:                          #fff;
+//** Popover maximum width
+@popover-max-width:                   276px;
+//** Popover border color
+@popover-border-color:                rgba(0,0,0,.2);
+//** Popover fallback border color
+@popover-fallback-border-color:       #ccc;
+
+//** Popover title background color
+@popover-title-bg:                    darken(@popover-bg, 3%);
+
+//** Popover arrow width
+@popover-arrow-width:                 10px;
+//** Popover arrow color
+@popover-arrow-color:                 @popover-bg;
+
+//** Popover outer arrow width
+@popover-arrow-outer-width:           (@popover-arrow-width + 1);
+//** Popover outer arrow color
+@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
+//** Popover outer arrow fallback color
+@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+@label-default-bg:            @gray-light;
+//** Primary label background color
+@label-primary-bg:            @brand-primary;
+//** Success label background color
+@label-success-bg:            @brand-success;
+//** Info label background color
+@label-info-bg:               @brand-info;
+//** Warning label background color
+@label-warning-bg:            @brand-warning;
+//** Danger label background color
+@label-danger-bg:             @brand-danger;
+
+//** Default label text color
+@label-color:                 #fff;
+//** Default text color of a linked label
+@label-link-hover-color:      #fff;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+@modal-inner-padding:         15px;
+
+//** Padding applied to the modal title
+@modal-title-padding:         15px;
+//** Modal title line-height
+@modal-title-line-height:     @line-height-base;
+
+//** Background color of modal content area
+@modal-content-bg:                             #fff;
+//** Modal content border color
+@modal-content-border-color:                   rgba(0,0,0,.2);
+//** Modal content border color **for IE8**
+@modal-content-fallback-border-color:          #999;
+
+//** Modal backdrop background color
+@modal-backdrop-bg:           #000;
+//** Modal backdrop opacity
+@modal-backdrop-opacity:      .5;
+//** Modal header border color
+@modal-header-border-color:   #e5e5e5;
+//** Modal footer border color
+@modal-footer-border-color:   @modal-header-border-color;
+
+@modal-lg:                    900px;
+@modal-md:                    600px;
+@modal-sm:                    300px;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+@alert-padding:               15px;
+@alert-border-radius:         @border-radius-base;
+@alert-link-font-weight:      bold;
+
+@alert-success-bg:            @state-success-bg;
+@alert-success-text:          @state-success-text;
+@alert-success-border:        @state-success-border;
+
+@alert-info-bg:               @state-info-bg;
+@alert-info-text:             @state-info-text;
+@alert-info-border:           @state-info-border;
+
+@alert-warning-bg:            @state-warning-bg;
+@alert-warning-text:          @state-warning-text;
+@alert-warning-border:        @state-warning-border;
+
+@alert-danger-bg:             @state-danger-bg;
+@alert-danger-text:           @state-danger-text;
+@alert-danger-border:         @state-danger-border;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+@progress-bg:                 #f5f5f5;
+//** Progress bar text color
+@progress-bar-color:          #fff;
+//** Variable for setting rounded corners on progress bar.
+@progress-border-radius:      @border-radius-base;
+
+//** Default progress bar color
+@progress-bar-bg:             @brand-primary;
+//** Success progress bar color
+@progress-bar-success-bg:     @brand-success;
+//** Warning progress bar color
+@progress-bar-warning-bg:     @brand-warning;
+//** Danger progress bar color
+@progress-bar-danger-bg:      @brand-danger;
+//** Info progress bar color
+@progress-bar-info-bg:        @brand-info;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+@list-group-bg:                 #fff;
+//** `.list-group-item` border color
+@list-group-border:             #ddd;
+//** List group border radius
+@list-group-border-radius:      @border-radius-base;
+
+//** Background color of single list items on hover
+@list-group-hover-bg:           #f5f5f5;
+//** Text color of active list items
+@list-group-active-color:       @component-active-color;
+//** Background color of active list items
+@list-group-active-bg:          @component-active-bg;
+//** Border color of active list elements
+@list-group-active-border:      @list-group-active-bg;
+//** Text color for content within active list items
+@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
+
+//** Text color of disabled list items
+@list-group-disabled-color:      @gray-light;
+//** Background color of disabled list items
+@list-group-disabled-bg:         @gray-lighter;
+//** Text color for content within disabled list items
+@list-group-disabled-text-color: @list-group-disabled-color;
+
+@list-group-link-color:         #555;
+@list-group-link-hover-color:   @list-group-link-color;
+@list-group-link-heading-color: #333;
+
+
+//== Panels
+//
+//##
+
+@panel-bg:                    #fff;
+@panel-body-padding:          15px;
+@panel-heading-padding:       10px 15px;
+@panel-footer-padding:        @panel-heading-padding;
+@panel-border-radius:         @border-radius-base;
+
+//** Border color for elements within panels
+@panel-inner-border:          #ddd;
+@panel-footer-bg:             #f5f5f5;
+
+@panel-default-text:          @gray-dark;
+@panel-default-border:        #ddd;
+@panel-default-heading-bg:    #f5f5f5;
+
+@panel-primary-text:          #fff;
+@panel-primary-border:        @brand-primary;
+@panel-primary-heading-bg:    @brand-primary;
+
+@panel-success-text:          @state-success-text;
+@panel-success-border:        @state-success-border;
+@panel-success-heading-bg:    @state-success-bg;
+
+@panel-info-text:             @state-info-text;
+@panel-info-border:           @state-info-border;
+@panel-info-heading-bg:       @state-info-bg;
+
+@panel-warning-text:          @state-warning-text;
+@panel-warning-border:        @state-warning-border;
+@panel-warning-heading-bg:    @state-warning-bg;
+
+@panel-danger-text:           @state-danger-text;
+@panel-danger-border:         @state-danger-border;
+@panel-danger-heading-bg:     @state-danger-bg;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+@thumbnail-padding:           4px;
+//** Thumbnail background color
+@thumbnail-bg:                @body-bg;
+//** Thumbnail border color
+@thumbnail-border:            #ddd;
+//** Thumbnail border radius
+@thumbnail-border-radius:     @border-radius-base;
+
+//** Custom text color for thumbnail captions
+@thumbnail-caption-color:     @text-color;
+//** Padding around the thumbnail caption
+@thumbnail-caption-padding:   9px;
+
+
+//== Wells
+//
+//##
+
+@well-bg:                     #f5f5f5;
+@well-border:                 darken(@well-bg, 7%);
+
+
+//== Badges
+//
+//##
+
+@badge-color:                 #fff;
+//** Linked badge text color on hover
+@badge-link-hover-color:      #fff;
+@badge-bg:                    @gray-light;
+
+//** Badge text color in active nav link
+@badge-active-color:          @link-color;
+//** Badge background color in active nav link
+@badge-active-bg:             #fff;
+
+@badge-font-weight:           bold;
+@badge-line-height:           1;
+@badge-border-radius:         10px;
+
+
+//== Breadcrumbs
+//
+//##
+
+@breadcrumb-padding-vertical:   8px;
+@breadcrumb-padding-horizontal: 15px;
+//** Breadcrumb background color
+@breadcrumb-bg:                 #f5f5f5;
+//** Breadcrumb text color
+@breadcrumb-color:              #ccc;
+//** Text color of current page in the breadcrumb
+@breadcrumb-active-color:       @gray-light;
+//** Textual separator for between breadcrumb elements
+@breadcrumb-separator:          "/";
+
+
+//== Carousel
+//
+//##
+
+@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
+
+@carousel-control-color:                      #fff;
+@carousel-control-width:                      15%;
+@carousel-control-opacity:                    .5;
+@carousel-control-font-size:                  20px;
+
+@carousel-indicator-active-bg:                #fff;
+@carousel-indicator-border-color:             #fff;
+
+@carousel-caption-color:                      #fff;
+
+
+//== Close
+//
+//##
+
+@close-font-weight:           bold;
+@close-color:                 #000;
+@close-text-shadow:           0 1px 0 #fff;
+
+
+//== Code
+//
+//##
+
+@code-color:                  #c7254e;
+@code-bg:                     #f9f2f4;
+
+@kbd-color:                   #fff;
+@kbd-bg:                      #333;
+
+@pre-bg:                      #f5f5f5;
+@pre-color:                   @gray-dark;
+@pre-border-color:            #ccc;
+@pre-scrollable-max-height:   340px;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+@component-offset-horizontal: 180px;
+//** Text muted color
+@text-muted:                  @gray-light;
+//** Abbreviations and acronyms border color
+@abbr-border-color:           @gray-light;
+//** Headings small color
+@headings-small-color:        @gray-light;
+//** Blockquote small color
+@blockquote-small-color:      @gray-light;
+//** Blockquote font size
+@blockquote-font-size:        (@font-size-base * 1.25);
+//** Blockquote border color
+@blockquote-border-color:     @gray-lighter;
+//** Page header border color
+@page-header-border-color:    @gray-lighter;
+//** Width of horizontal description list titles
+@dl-horizontal-offset:        @component-offset-horizontal;
+//** Horizontal line color.
+@hr-border:                   @gray-lighter;
diff --git a/libs/fmt/doc/bootstrap/wells.less b/libs/fmt/doc/bootstrap/wells.less
new file mode 100644
index 0000000000000000000000000000000000000000..15d072b0cd0e31d6bd40fad5aa1cb8fb1fbdefd9
--- /dev/null
+++ b/libs/fmt/doc/bootstrap/wells.less
@@ -0,0 +1,29 @@
+//
+// Wells
+// --------------------------------------------------
+
+
+// Base class
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: @well-bg;
+  border: 1px solid @well-border;
+  border-radius: @border-radius-base;
+  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
+  blockquote {
+    border-color: #ddd;
+    border-color: rgba(0,0,0,.15);
+  }
+}
+
+// Sizes
+.well-lg {
+  padding: 24px;
+  border-radius: @border-radius-large;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: @border-radius-small;
+}
diff --git a/libs/fmt/doc/build.py b/libs/fmt/doc/build.py
new file mode 100644
index 0000000000000000000000000000000000000000..109d8f9dcc182d7f30e0aa12f36eb746e74ee751
--- /dev/null
+++ b/libs/fmt/doc/build.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+# Build the documentation.
+
+import errno, os, re, sys
+from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
+
+versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0', '6.2.1', '7.0.0', '7.0.1', '7.0.2', '7.0.3', '7.1.0', '7.1.1', '7.1.2', '7.1.3']
+
+class Pip:
+  def __init__(self, venv_dir):
+    self.path = os.path.join(venv_dir, 'bin', 'pip')
+
+  def install(self, package, commit=None):
+    "Install package using pip."
+    if commit:
+      package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
+    print('Installing {0}'.format(package))
+    check_call([self.path, 'install', package])
+
+def create_build_env(venv_dir='virtualenv'):
+  # Create virtualenv.
+  if not os.path.exists(venv_dir):
+    check_call(['python3', '-m', 'venv', venv_dir])
+  # Install Sphinx and Breathe. Require the exact version of Sphinx which is
+  # compatible with Breathe.
+  pip = Pip(venv_dir)
+  pip.install('six')
+  pip.install('sphinx-doc/sphinx', 'v3.3.0')
+  pip.install('michaeljones/breathe', 'v4.23.0')
+
+def build_docs(version='dev', **kwargs):
+  doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
+  work_dir = kwargs.get('work_dir', '.')
+  include_dir = kwargs.get(
+      'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
+  # Build docs.
+  cmd = ['doxygen', '-']
+  p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+  doxyxml_dir = os.path.join(work_dir, 'doxyxml')
+  out, _ = p.communicate(input=r'''
+      PROJECT_NAME      = fmt
+      GENERATE_LATEX    = NO
+      GENERATE_MAN      = NO
+      GENERATE_RTF      = NO
+      CASE_SENSE_NAMES  = NO
+      INPUT             = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
+                          {0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h
+      QUIET             = YES
+      JAVADOC_AUTOBRIEF = YES
+      AUTOLINK_SUPPORT  = NO
+      GENERATE_HTML     = NO
+      GENERATE_XML      = YES
+      XML_OUTPUT        = {1}
+      ALIASES           = "rst=\verbatim embed:rst"
+      ALIASES          += "endrst=\endverbatim"
+      MACRO_EXPANSION   = YES
+      PREDEFINED        = _WIN32=1 \
+                          __linux__=1 \
+                          FMT_USE_VARIADIC_TEMPLATES=1 \
+                          FMT_USE_RVALUE_REFERENCES=1 \
+                          FMT_USE_USER_DEFINED_LITERALS=1 \
+                          FMT_USE_ALIAS_TEMPLATES=1 \
+                          FMT_API= \
+                          "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
+                          "FMT_END_NAMESPACE=}}" \
+                          "FMT_STRING_ALIAS=1" \
+                          "FMT_DOC=1"
+      EXCLUDE_SYMBOLS   = fmt::formatter fmt::printf_formatter fmt::arg_join \
+                          fmt::basic_format_arg::handle
+    '''.format(include_dir, doxyxml_dir).encode('UTF-8'))
+  internal_symbols = [
+    'fmt::detail::.*',
+    'basic_data<>',
+    'fmt::type_identity',
+    'fmt::dynamic_formatter'
+  ]
+  noisy_warnings = [
+    'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
+      ') is not documented.',
+    'warning: Internal inconsistency: .* does not belong to any container!'
+  ]
+  for w in noisy_warnings:
+      out = re.sub('.*' + w + '\n', '', out)
+  print(out)
+  if p.returncode != 0:
+    raise CalledProcessError(p.returncode, cmd)
+
+  html_dir = os.path.join(work_dir, 'html')
+  main_versions = reversed(versions[-3:])
+  check_call([os.path.join('virtualenv', 'bin', 'sphinx-build'),
+              '-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
+              '-Dversion=' + version, '-Drelease=' + version,
+              '-Aversion=' + version, '-Aversions=' + ','.join(main_versions),
+              '-b', 'html', doc_dir, html_dir])
+  try:
+    check_call(['lessc', '--verbose', '--clean-css',
+                '--include-path=' + os.path.join(doc_dir, 'bootstrap'),
+                os.path.join(doc_dir, 'fmt.less'),
+                os.path.join(html_dir, '_static', 'fmt.css')])
+  except OSError as e:
+    if e.errno != errno.ENOENT:
+      raise
+    print('lessc not found; make sure that Less (http://lesscss.org/) ' +
+          'is installed')
+    sys.exit(1)
+  return html_dir
+
+if __name__ == '__main__':
+  create_build_env()
+  build_docs(sys.argv[1])
diff --git a/libs/fmt/doc/conf.py b/libs/fmt/doc/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..0756192b6065f1b58f37b7aff42395f52278d83a
--- /dev/null
+++ b/libs/fmt/doc/conf.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+#
+# format documentation build configuration file, created by
+# sphinx-quickstart on Tue Dec 18 06:46:16 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os, re, subprocess
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+needs_sphinx = '1.2'
+
+if os.environ.get('READTHEDOCS', None) == 'True':
+  subprocess.call('doxygen')
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.ifconfig', 'breathe']
+
+breathe_default_project = "format"
+breathe_domain_by_extension = {"h" : "cpp"}
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+#master_doc = 'contents'
+
+# General information about the project.
+project = u'fmt'
+copyright = u'2012-present, Victor Zverovich'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+
+# Version and release are passed from CMake.
+#version = None
+
+# The full version, including alpha/beta/rc tags.
+#release = version
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['virtualenv']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = 'cpp:any'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+highlight_language = 'c++'
+
+primary_domain = 'cpp'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'basic-bootstrap'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['.']
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+html_sidebars = {
+  '**': ['localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html']
+}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'formatdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'format.tex', u'fmt documentation',
+   u'Victor Zverovich', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'fmt', u'fmt documentation', [u'Victor Zverovich'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'fmt', u'fmt documentation',
+   u'Victor Zverovich', 'fmt', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
diff --git a/libs/fmt/doc/contents.rst b/libs/fmt/doc/contents.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d38691e41b102cdab52a5c3d09909ed639859bda
--- /dev/null
+++ b/libs/fmt/doc/contents.rst
@@ -0,0 +1,10 @@
+########
+Contents
+########
+
+.. toctree::
+   :maxdepth: 2
+
+   usage
+   api
+   syntax
diff --git a/libs/fmt/doc/fmt.less b/libs/fmt/doc/fmt.less
new file mode 100644
index 0000000000000000000000000000000000000000..55c716b816b1df863d5e760a327b2592478d1bdb
--- /dev/null
+++ b/libs/fmt/doc/fmt.less
@@ -0,0 +1,66 @@
+@import 'bootstrap.less';
+
+@header-bg: #094d75;
+@icon-font-path: "fonts/";
+
+html {
+  overflow-y: scroll;
+}
+
+.navbar {
+  border-radius: 0;
+  margin-bottom: 0;
+  background-color: darken(@header-bg, 10%);
+}
+
+.jumbotron {
+  #gradient > .vertical(@header-bg; darken(@header-bg, 2%); 50%; 50%);
+  background-size: 100% 4px;
+  background-color: @header-bg;
+  background-repeat: repeat-y;
+  color: white;
+  text-align: center;
+}
+
+div.sphinxsidebar {
+  margin-left: 0;
+}
+
+// Keep content not too wide for better readability.
+.navbar-content, .content {
+  .make-md-column-offset(1);
+  .make-md-column(10);
+  .make-lg-column-offset(2);
+  .make-lg-column(8);
+}
+
+.footer {
+  padding-top: 20px;
+  padding-bottom: 20px;
+  border-top: 1px solid @gray-lighter;
+  text-align: center;
+}
+
+// Indent descriptions of classes, functions and macros.
+.class dd, .function dd, .macro dd {
+  margin-left: 20px;
+}
+
+// Remove Bootstrap padding for Sphinx containers.
+.breathe-sectiondef.container {
+  padding: 0;
+}
+
+// Remove Bootstrap padding for Sphinx code elements in API signatures.
+.descclassname, .descname {
+  padding: 0;
+}
+
+p.rubric {
+  margin-top: 10px;
+}
+
+.github-btn {
+  border: 0;
+  overflow: hidden;
+}
diff --git a/libs/fmt/doc/index.rst b/libs/fmt/doc/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..a2f4bd95c059875ba239a5900a75eb57f8ac4077
--- /dev/null
+++ b/libs/fmt/doc/index.rst
@@ -0,0 +1,202 @@
+Overview
+========
+
+**{fmt}** is an open-source formatting library providing a fast and safe
+alternative to C stdio and C++ iostreams.
+
+.. raw:: html
+
+   <div class="panel panel-default">
+     <div class="panel-heading">What users say:</div>
+     <div class="panel-body">
+       Thanks for creating this library. It’s been a hole in C++ for
+       a long time. I’ve used both <code>boost::format</code> and
+       <code>loki::SPrintf</code>, and neither felt like the right answer.
+       This does.
+     </div>
+   </div>
+
+.. _format-api-intro:
+
+Format API
+----------
+
+The format API is similar in spirit to the C ``printf`` family of function but
+is safer, simpler and several times `faster
+<https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
+than common standard library implementations.
+The `format string syntax <syntax.html>`_ is similar to the one used by
+`str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ in
+Python:
+
+.. code:: c++
+
+  fmt::format("The answer is {}.", 42);
+  
+The ``fmt::format`` function returns a string "The answer is 42.". You can use
+``fmt::memory_buffer`` to avoid constructing ``std::string``:
+
+.. code:: c++
+
+  fmt::memory_buffer out;
+  format_to(out, "For a moment, {} happened.", "nothing");
+  out.data(); // returns a pointer to the formatted data
+
+The ``fmt::print`` function performs formatting and writes the result to a stream:
+
+.. code:: c++
+
+  fmt::print(stderr, "System error code = {}\n", errno);
+
+If you omit the file argument the function will print to ``stdout``:
+
+.. code:: c++
+
+  fmt::print("Don't {}\n", "panic");
+
+The format API also supports positional arguments useful for localization:
+
+.. code:: c++
+
+  fmt::print("I'd rather be {1} than {0}.", "right", "happy");
+
+You can pass named arguments with ``fmt::arg``:
+
+.. code:: c++
+
+  fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
+             fmt::arg("name", "World"), fmt::arg("number", 42));
+
+If your compiler supports C++11 user-defined literals, the suffix ``_a`` offers 
+an alternative, slightly terser syntax for named arguments:
+
+.. code:: c++
+
+  using namespace fmt::literals;
+  fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
+             "name"_a="World", "number"_a=42);
+
+.. _safety:
+
+Safety
+------
+
+The library is fully type safe, automatic memory management prevents buffer
+overflow, errors in format strings are reported using exceptions or at compile
+time. For example, the code
+
+.. code:: c++
+
+  fmt::format("The answer is {:d}", "forty-two");
+
+throws the ``format_error`` exception because the argument ``"forty-two"`` is a
+string while the format code ``d`` only applies to integers.
+
+The code
+
+.. code:: c++
+
+  format(FMT_STRING("The answer is {:d}"), "forty-two");
+
+reports a compile-time error on compilers that support relaxed ``constexpr``.
+See `here <api.html#c.fmt>`_ for details.
+
+The following code
+
+.. code:: c++
+
+  fmt::format("Cyrillic letter {}", L'\x42e');
+  
+produces a compile-time error because wide character ``L'\x42e'`` cannot be
+formatted into a narrow string. You can use a wide format string instead:
+
+.. code:: c++
+
+  fmt::format(L"Cyrillic letter {}", L'\x42e');
+
+For comparison, writing a wide character to ``std::ostream`` results in
+its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
+which is represented by ``L'\x42e'`` if we use Unicode) which is rarely
+desirable.
+
+Compact Binary Code
+-------------------
+
+The library produces compact per-call compiled code. For example
+(`godbolt <https://godbolt.org/g/TZU4KF>`_),
+
+.. code:: c++
+
+   #include <fmt/core.h>
+
+   int main() {
+     fmt::print("The answer is {}.", 42);
+   }
+
+compiles to just
+
+.. code:: asm
+
+   main: # @main
+     sub rsp, 24
+     mov qword ptr [rsp], 42
+     mov rcx, rsp
+     mov edi, offset .L.str
+     mov esi, 17
+     mov edx, 1
+     call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args)
+     xor eax, eax
+     add rsp, 24
+     ret
+   .L.str:
+     .asciz "The answer is {}."
+
+.. _portability:
+
+Portability
+-----------
+
+The library is highly portable and relies only on a small set of C++11 features:
+
+* variadic templates
+* type traits
+* rvalue references
+* decltype
+* trailing return types
+* deleted functions
+* alias templates
+
+These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent
+compiler version. For older compilers use {fmt} `version 4.x
+<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and
+only requires C++98.
+
+The output of all formatting functions is consistent across platforms.
+For example,
+
+.. code::
+
+  fmt::print("{}", std::numeric_limits<double>::infinity());
+
+always prints ``inf`` while the output of ``printf`` is platform-dependent.
+
+.. _ease-of-use:
+
+Ease of Use
+-----------
+
+{fmt} has a small self-contained code base with the core library consisting of
+just three header files and no external dependencies.
+A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
+using the library both in open-source and commercial projects.
+
+`Learn more... <contents.html>`_
+
+.. raw:: html
+
+  <a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a>
+
+  <div class="section footer">
+    <iframe src="https://ghbtns.com/github-btn.html?user=fmtlib&amp;repo=fmt&amp;type=watch&amp;count=true"
+            class="github-btn" width="100" height="20"></iframe>
+  </div>
diff --git a/libs/fmt/doc/python-license.txt b/libs/fmt/doc/python-license.txt
new file mode 100644
index 0000000000000000000000000000000000000000..88eed1f9cb05d3e5b248eee7915fd860885107a3
--- /dev/null
+++ b/libs/fmt/doc/python-license.txt
@@ -0,0 +1,290 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC.  Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
+year, the PythonLabs team moved to Digital Creations (now Zope
+Corporation, see http://www.zope.com).  In 2001, the Python Software
+Foundation (PSF, see http://www.python.org/psf/) was formed, a
+non-profit organization created specifically to own Python-related
+Intellectual Property.  Zope Corporation is a sponsoring member of
+the PSF.
+
+All Python releases are Open Source (see http://www.opensource.org for
+the Open Source Definition).  Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+    Release         Derived     Year        Owner       GPL-
+                    from                                compatible? (1)
+
+    0.9.0 thru 1.2              1991-1995   CWI         yes
+    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
+    1.6             1.5.2       2000        CNRI        no
+    2.0             1.6         2000        BeOpen.com  no
+    1.6.1           1.6         2001        CNRI        yes (2)
+    2.1             2.0+1.6.1   2001        PSF         no
+    2.0.1           2.0+1.6.1   2001        PSF         yes
+    2.1.1           2.1+2.0.1   2001        PSF         yes
+    2.2             2.1.1       2001        PSF         yes
+    2.1.2           2.1.1       2002        PSF         yes
+    2.1.3           2.1.2       2002        PSF         yes
+    2.2.1           2.2         2002        PSF         yes
+    2.2.2           2.2.1       2002        PSF         yes
+    2.2.3           2.2.2       2003        PSF         yes
+    2.3             2.2.2       2002-2003   PSF         yes
+    2.3.1           2.3         2002-2003   PSF         yes
+    2.3.2           2.3.1       2002-2003   PSF         yes
+    2.3.3           2.3.2       2002-2003   PSF         yes
+    2.3.4           2.3.3       2004        PSF         yes
+    2.3.5           2.3.4       2005        PSF         yes
+    2.4             2.3         2004        PSF         yes
+    2.4.1           2.4         2005        PSF         yes
+    2.4.2           2.4.1       2005        PSF         yes
+    2.4.3           2.4.2       2006        PSF         yes
+    2.4.4           2.4.3       2006        PSF         yes
+    2.5             2.4         2006        PSF         yes
+    2.5.1           2.5         2007        PSF         yes
+    2.5.2           2.5.1       2008        PSF         yes
+    2.5.3           2.5.2       2008        PSF         yes
+    2.6             2.5         2008        PSF         yes
+    2.6.1           2.6         2008        PSF         yes
+    2.6.2           2.6.1       2009        PSF         yes
+    2.6.3           2.6.2       2009        PSF         yes
+    2.6.4           2.6.3       2009        PSF         yes
+    2.6.5           2.6.4       2010        PSF         yes
+    3.0             2.6         2008        PSF         yes
+    3.0.1           3.0         2009        PSF         yes
+    3.1             3.0.1       2009        PSF         yes
+    3.1.1           3.1         2009        PSF         yes
+    3.1.2           3.1.1       2010        PSF         yes
+    3.1.3           3.1.2       2010        PSF         yes
+    3.1.4           3.1.3       2011        PSF         yes
+    3.2             3.1         2011        PSF         yes
+    3.2.1           3.2         2011        PSF         yes
+    3.2.2           3.2.1       2011        PSF         yes
+    3.2.3           3.2.2       2012        PSF         yes
+    3.3.0           3.2         2012        PSF         yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+    the GPL.  All Python licenses, unlike the GPL, let you distribute
+    a modified version without making your changes open source.  The
+    GPL-compatible licenses make it possible to combine Python with
+    other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+    because its license has a choice of law clause.  According to
+    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+    is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python
+alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions.  Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee.  This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party.  As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee.  Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement.  This Agreement together with
+Python 1.6.1 may be located on the Internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013.  This
+Agreement may also be obtained from a proxy server on the Internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee.  This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+        ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands.  All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/libs/fmt/doc/syntax.rst b/libs/fmt/doc/syntax.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d68408611e180daadc11b8cbc5ae2c47154e2289
--- /dev/null
+++ b/libs/fmt/doc/syntax.rst
@@ -0,0 +1,486 @@
+.. _syntax:
+
+********************
+Format String Syntax
+********************
+
+Formatting functions such as :ref:`fmt::format() <format>` and
+:ref:`fmt::print() <print>` use the same format string syntax described in this
+section.
+
+Format strings contain "replacement fields" surrounded by curly braces ``{}``.
+Anything that is not contained in braces is considered literal text, which is
+copied unchanged to the output.  If you need to include a brace character in the
+literal text, it can be escaped by doubling: ``{{`` and ``}}``.
+
+The grammar for a replacement field is as follows:
+
+.. productionlist:: sf
+   replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}"
+   arg_id: `integer` | `identifier`
+   integer: `digit`+
+   digit: "0"..."9"
+   identifier: `id_start` `id_continue`*
+   id_start: "a"..."z" | "A"..."Z" | "_"
+   id_continue: `id_start` | `digit`
+
+In less formal terms, the replacement field can start with an *arg_id*
+that specifies the argument whose value is to be formatted and inserted into
+the output instead of the replacement field.
+The *arg_id* is optionally followed by a *format_spec*, which is preceded by a
+colon ``':'``.  These specify a non-default format for the replacement value.
+
+See also the :ref:`formatspec` section.
+
+If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence,
+they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be
+automatically inserted in that order.
+
+Named arguments can be referred to by their names or indices.
+
+Some simple format string examples::
+
+   "First, thou shalt count to {0}" // References the first argument
+   "Bring me a {}"                  // Implicitly references the first argument
+   "From {} to {}"                  // Same as "From {0} to {1}"
+
+The *format_spec* field contains a specification of how the value should be
+presented, including such details as field width, alignment, padding, decimal
+precision and so on.  Each value type can define its own "formatting
+mini-language" or interpretation of the *format_spec*.
+
+Most built-in types support a common formatting mini-language, which is
+described in the next section.
+
+A *format_spec* field can also include nested replacement fields in certain
+positions within it. These nested replacement fields can contain only an
+argument id; format specifications are not allowed. This allows the formatting
+of a value to be dynamically specified.
+
+See the :ref:`formatexamples` section for some examples.
+
+.. _formatspec:
+
+Format Specification Mini-Language
+==================================
+
+"Format specifications" are used within replacement fields contained within a
+format string to define how individual values are presented (see
+:ref:`syntax`).  Each formattable type may define how the format
+specification is to be interpreted.
+
+Most built-in types implement the following options for format specifications,
+although some of the formatting options are only supported by the numeric types.
+
+The general form of a *standard format specifier* is:
+
+.. productionlist:: sf
+   format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`]
+   fill: <a character other than '{' or '}'>
+   align: "<" | ">" | "^"
+   sign: "+" | "-" | " "
+   width: `integer` | "{" [`arg_id`] "}"
+   precision: `integer` | "{" [`arg_id`] "}"
+   type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" |
+       : "o" | "p" | "s" | "x" | "X"
+
+The *fill* character can be any Unicode code point other than ``'{'`` or
+``'}'``. The presence of a fill character is signaled by the character following
+it, which must be one of the alignment options. If the second character of
+*format_spec* is not a valid alignment option, then it is assumed that both the
+fill character and the alignment option are absent.
+
+The meaning of the various alignment options is as follows:
+
++---------+----------------------------------------------------------+
+| Option  | Meaning                                                  |
++=========+==========================================================+
+| ``'<'`` | Forces the field to be left-aligned within the available |
+|         | space (this is the default for most objects).            |
++---------+----------------------------------------------------------+
+| ``'>'`` | Forces the field to be right-aligned within the          |
+|         | available space (this is the default for numbers).       |
++---------+----------------------------------------------------------+
+| ``'^'`` | Forces the field to be centered within the available     |
+|         | space.                                                   |
++---------+----------------------------------------------------------+
+
+Note that unless a minimum field width is defined, the field width will always
+be the same size as the data to fill it, so that the alignment option has no
+meaning in this case.
+
+The *sign* option is only valid for number types, and can be one of the
+following:
+
++---------+----------------------------------------------------------+
+| Option  | Meaning                                                  |
++=========+==========================================================+
+| ``'+'`` | indicates that a sign should be used for both            |
+|         | positive as well as negative numbers.                    |
++---------+----------------------------------------------------------+
+| ``'-'`` | indicates that a sign should be used only for negative   |
+|         | numbers (this is the default behavior).                  |
++---------+----------------------------------------------------------+
+| space   | indicates that a leading space should be used on         |
+|         | positive numbers, and a minus sign on negative numbers.  |
++---------+----------------------------------------------------------+
+
+The ``'#'`` option causes the "alternate form" to be used for the
+conversion.  The alternate form is defined differently for different
+types.  This option is only valid for integer and floating-point types.
+For integers, when binary, octal, or hexadecimal output is used, this
+option adds the prefix respective ``"0b"`` (``"0B"``), ``"0"``, or
+``"0x"`` (``"0X"``) to the output value.  Whether the prefix is
+lower-case or upper-case is determined by the case of the type
+specifier, for example, the prefix ``"0x"`` is used for the type ``'x'``
+and ``"0X"`` is used for ``'X'``.  For floating-point numbers the
+alternate form causes the result of the conversion to always contain a
+decimal-point character, even if no digits follow it. Normally, a
+decimal-point character appears in the result of these conversions
+only if a digit follows it. In addition, for ``'g'`` and ``'G'``
+conversions, trailing zeros are not removed from the result.
+
+.. ifconfig:: False
+
+   The ``','`` option signals the use of a comma for a thousands separator.
+   For a locale aware separator, use the ``'L'`` integer presentation type
+   instead.
+
+*width* is a decimal integer defining the minimum field width.  If not
+specified, then the field width will be determined by the content.
+
+Preceding the *width* field by a zero (``'0'``) character enables sign-aware
+zero-padding for numeric types. It forces the padding to be placed after the
+sign or base (if any) but before the digits. This is used for printing fields in
+the form '+000000120'. This option is only valid for numeric types and it has no
+effect on formatting of infinity and NaN.
+
+The *precision* is a decimal number indicating how many digits should be
+displayed after the decimal point for a floating-point value formatted with
+``'f'`` and ``'F'``, or before and after the decimal point for a floating-point
+value formatted with ``'g'`` or ``'G'``.  For non-number types the field
+indicates the maximum field size - in other words, how many characters will be
+used from the field content. The *precision* is not allowed for integer,
+character, Boolean, and pointer values.
+
+The ``'L'`` option uses the current locale setting to insert the appropriate
+number separator characters. This option is only valid for numeric types.
+
+Finally, the *type* determines how the data should be presented.
+
+The available string presentation types are:
+
++---------+----------------------------------------------------------+
+| Type    | Meaning                                                  |
++=========+==========================================================+
+| ``'s'`` | String format. This is the default type for strings and  |
+|         | may be omitted.                                          |
++---------+----------------------------------------------------------+
+| none    | The same as ``'s'``.                                     |
++---------+----------------------------------------------------------+
+
+The available character presentation types are:
+
++---------+----------------------------------------------------------+
+| Type    | Meaning                                                  |
++=========+==========================================================+
+| ``'c'`` | Character format. This is the default type for           |
+|         | characters and may be omitted.                           |
++---------+----------------------------------------------------------+
+| none    | The same as ``'c'``.                                     |
++---------+----------------------------------------------------------+
+
+The available integer presentation types are:
+
++---------+----------------------------------------------------------+
+| Type    | Meaning                                                  |
++=========+==========================================================+
+| ``'b'`` | Binary format. Outputs the number in base 2. Using the   |
+|         | ``'#'`` option with this type adds the prefix ``"0b"``   |
+|         | to the output value.                                     |
++---------+----------------------------------------------------------+
+| ``'B'`` | Binary format. Outputs the number in base 2. Using the   |
+|         | ``'#'`` option with this type adds the prefix ``"0B"``   |
+|         | to the output value.                                     |
++---------+----------------------------------------------------------+
+| ``'d'`` | Decimal integer. Outputs the number in base 10.          |
++---------+----------------------------------------------------------+
+| ``'o'`` | Octal format. Outputs the number in base 8.              |
++---------+----------------------------------------------------------+
+| ``'x'`` | Hex format. Outputs the number in base 16, using         |
+|         | lower-case letters for the digits above 9. Using the     |
+|         | ``'#'`` option with this type adds the prefix ``"0x"``   |
+|         | to the output value.                                     |
++---------+----------------------------------------------------------+
+| ``'X'`` | Hex format. Outputs the number in base 16, using         |
+|         | upper-case letters for the digits above 9. Using the     |
+|         | ``'#'`` option with this type adds the prefix ``"0X"``   |
+|         | to the output value.                                     |
++---------+----------------------------------------------------------+
+| none    | The same as ``'d'``.                                     |
++---------+----------------------------------------------------------+
+
+Integer presentation types can also be used with character and Boolean values.
+Boolean values are formatted using textual representation, either ``true`` or
+``false``, if the presentation type is not specified.
+
+The available presentation types for floating-point values are:
+
++---------+----------------------------------------------------------+
+| Type    | Meaning                                                  |
++=========+==========================================================+
+| ``'a'`` | Hexadecimal floating point format. Prints the number in  |
+|         | base 16 with prefix ``"0x"`` and lower-case letters for  |
+|         | digits above 9. Uses ``'p'`` to indicate the exponent.   |
++---------+----------------------------------------------------------+
+| ``'A'`` | Same as ``'a'`` except it uses upper-case letters for    |
+|         | the prefix, digits above 9 and to indicate the exponent. |
++---------+----------------------------------------------------------+
+| ``'e'`` | Exponent notation. Prints the number in scientific       |
+|         | notation using the letter 'e' to indicate the exponent.  |
++---------+----------------------------------------------------------+
+| ``'E'`` | Exponent notation. Same as ``'e'`` except it uses an     |
+|         | upper-case ``'E'`` as the separator character.           |
++---------+----------------------------------------------------------+
+| ``'f'`` | Fixed point. Displays the number as a fixed-point        |
+|         | number.                                                  |
++---------+----------------------------------------------------------+
+| ``'F'`` | Fixed point. Same as ``'f'``, but converts ``nan`` to    |
+|         | ``NAN`` and ``inf`` to ``INF``.                          |
++---------+----------------------------------------------------------+
+| ``'g'`` | General format.  For a given precision ``p >= 1``,       |
+|         | this rounds the number to ``p`` significant digits and   |
+|         | then formats the result in either fixed-point format     |
+|         | or in scientific notation, depending on its magnitude.   |
+|         |                                                          |
+|         | A precision of ``0`` is treated as equivalent to a       |
+|         | precision of ``1``.                                      |
++---------+----------------------------------------------------------+
+| ``'G'`` | General format. Same as ``'g'`` except switches to       |
+|         | ``'E'`` if the number gets too large. The                |
+|         | representations of infinity and NaN are uppercased, too. |
++---------+----------------------------------------------------------+
+| none    | Similar to ``'g'``, except that fixed-point notation,    |
+|         | when used, has at least one digit past the decimal       |
+|         | point. The default precision is as high as needed to     |
+|         | represent the particular value.                          |
++---------+----------------------------------------------------------+
+
+.. ifconfig:: False
+
+   +---------+----------------------------------------------------------+
+   |         | The precise rules are as follows: suppose that the       |
+   |         | result formatted with presentation type ``'e'`` and      |
+   |         | precision ``p-1`` would have exponent ``exp``.  Then     |
+   |         | if ``-4 <= exp < p``, the number is formatted            |
+   |         | with presentation type ``'f'`` and precision             |
+   |         | ``p-1-exp``.  Otherwise, the number is formatted         |
+   |         | with presentation type ``'e'`` and precision ``p-1``.    |
+   |         | In both cases insignificant trailing zeros are removed   |
+   |         | from the significand, and the decimal point is also      |
+   |         | removed if there are no remaining digits following it.   |
+   |         |                                                          |
+   |         | Positive and negative infinity, positive and negative    |
+   |         | zero, and nans, are formatted as ``inf``, ``-inf``,      |
+   |         | ``0``, ``-0`` and ``nan`` respectively, regardless of    |
+   |         | the precision.                                           |
+   |         |                                                          |
+   +---------+----------------------------------------------------------+
+
+The available presentation types for pointers are:
+
++---------+----------------------------------------------------------+
+| Type    | Meaning                                                  |
++=========+==========================================================+
+| ``'p'`` | Pointer format. This is the default type for             |
+|         | pointers and may be omitted.                             |
++---------+----------------------------------------------------------+
+| none    | The same as ``'p'``.                                     |
++---------+----------------------------------------------------------+
+
+.. _chrono-specs:
+
+Chrono Format Specifications
+============================
+
+Format specifications for chrono types have the following syntax:
+
+.. productionlist:: sf
+   chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
+   chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char`
+   conversion_spec: "%" [`modifier`] `chrono_type`
+   literal_char: <a character other than '{', '}' or '%'>
+   modifier: "E" | "O"
+   chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
+              : "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
+              : "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
+              : "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%"
+
+Literal chars are copied unchanged to the output. Precision is valid only for
+``std::chrono::duration`` types with a floating-point representation type.
+
+The available presentation types (*chrono_type*) for chrono durations and time
+points are:
+
++---------+--------------------------------------------------------------------+
+| Type    | Meaning                                                            |
++=========+====================================================================+
+| ``'H'`` | The hour (24-hour clock) as a decimal number. If the result is a   |
+|         | single digit, it is prefixed with 0. The modified command ``%OH``  |
+|         | produces the locale's alternative representation.                  |
++---------+--------------------------------------------------------------------+
+| ``'M'`` | The minute as a decimal number. If the result is a single digit,   |
+|         | it is prefixed with 0. The modified command ``%OM`` produces the   |
+|         | locale's alternative representation.                               |
++---------+--------------------------------------------------------------------+
+| ``'S'`` | Seconds as a decimal number. If the number of seconds is less than |
+|         | 10, the result is prefixed with 0. If the precision of the input   |
+|         | cannot be exactly represented with seconds, then the format is a   |
+|         | decimal floating-point number with a fixed format and a precision  |
+|         | matching that of the precision of the input (or to a microseconds  |
+|         | precision if the conversion to floating-point decimal seconds      |
+|         | cannot be made within 18 fractional digits). The character for the |
+|         | decimal point is localized according to the locale. The modified   |
+|         | command ``%OS`` produces the locale's alternative representation.  |
++---------+--------------------------------------------------------------------+
+
+Specifiers that have a calendaric component such as `'d'` (the day of month)
+are valid only for ``std::tm`` and not durations or time points.
+
+``std::tm`` uses the system's `strftime
+<https://en.cppreference.com/w/cpp/chrono/c/strftime>`_ so refer to its
+documentation for details on supported conversion specifiers. 
+
+.. _formatexamples:
+
+Format Examples
+===============
+
+This section contains examples of the format syntax and comparison with
+the printf formatting.
+
+In most of the cases the syntax is similar to the printf formatting, with the
+addition of the ``{}`` and with ``:`` used instead of ``%``.
+For example, ``"%03.2f"`` can be translated to ``"{:03.2f}"``.
+
+The new format syntax also supports new and different options, shown in the
+following examples.
+
+Accessing arguments by position::
+
+   fmt::format("{0}, {1}, {2}", 'a', 'b', 'c');
+   // Result: "a, b, c"
+   fmt::format("{}, {}, {}", 'a', 'b', 'c');
+   // Result: "a, b, c"
+   fmt::format("{2}, {1}, {0}", 'a', 'b', 'c');
+   // Result: "c, b, a"
+   fmt::format("{0}{1}{0}", "abra", "cad");  // arguments' indices can be repeated
+   // Result: "abracadabra"
+
+Aligning the text and specifying a width::
+
+   fmt::format("{:<30}", "left aligned");
+   // Result: "left aligned                  "
+   fmt::format("{:>30}", "right aligned");
+   // Result: "                 right aligned"
+   fmt::format("{:^30}", "centered");
+   // Result: "           centered           "
+   fmt::format("{:*^30}", "centered");  // use '*' as a fill char
+   // Result: "***********centered***********"
+
+Dynamic width::
+
+   fmt::format("{:<{}}", "left aligned", 30);
+   // Result: "left aligned                  "
+
+Dynamic precision::
+
+   fmt::format("{:.{}f}", 3.14, 1);
+   // Result: "3.1"
+
+Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign::
+
+   fmt::format("{:+f}; {:+f}", 3.14, -3.14);  // show it always
+   // Result: "+3.140000; -3.140000"
+   fmt::format("{: f}; {: f}", 3.14, -3.14);  // show a space for positive numbers
+   // Result: " 3.140000; -3.140000"
+   fmt::format("{:-f}; {:-f}", 3.14, -3.14);  // show only the minus -- same as '{:f}; {:f}'
+   // Result: "3.140000; -3.140000"
+
+Replacing ``%x`` and ``%o`` and converting the value to different bases::
+
+   fmt::format("int: {0:d};  hex: {0:x};  oct: {0:o}; bin: {0:b}", 42);
+   // Result: "int: 42;  hex: 2a;  oct: 52; bin: 101010"
+   // with 0x or 0 or 0b as prefix:
+   fmt::format("int: {0:d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}", 42);
+   // Result: "int: 42;  hex: 0x2a;  oct: 052;  bin: 0b101010"
+
+Padded hex byte with prefix and always prints both hex characters::
+
+   fmt::format("{:#04x}", 0);
+   // Result: "0x00"
+
+Box drawing using Unicode fill::
+
+   fmt::print(
+     "┌{0:─^{2}}┐\n"
+     "│{1: ^{2}}│\n"
+     "└{0:─^{2}}┘\n", "", "Hello, world!", 20);
+
+prints::
+
+   ┌────────────────────┐
+   │   Hello, world!    │
+   └────────────────────┘
+
+Using type-specific formatting::
+
+   #include <fmt/chrono.h>
+
+   auto t = tm();
+   t.tm_year = 2010 - 1900;
+   t.tm_mon = 7;
+   t.tm_mday = 4;
+   t.tm_hour = 12;
+   t.tm_min = 15;
+   t.tm_sec = 58;
+   fmt::print("{:%Y-%m-%d %H:%M:%S}", t);
+   // Prints: 2010-08-04 12:15:58
+
+Using the comma as a thousands separator::
+
+   #include <fmt/locale.h>
+
+   auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
+   // s == "1,234,567,890"
+
+.. ifconfig:: False
+
+   Nesting arguments and more complex examples::
+
+      >>> for align, text in zip('<^>', ['left', 'center', 'right']):
+      ...     '{0:{fill}{align}16}") << text, fill=align, align=align)
+      ...
+      'left<<<<<<<<<<<<'
+      '^^^^^center^^^^^'
+      '>>>>>>>>>>>right'
+      >>>
+      >>> octets = [192, 168, 0, 1]
+      Format("{:02X}{:02X}{:02X}{:02X}") << *octets)
+      'C0A80001'
+      >>> int(_, 16)
+      3232235521
+      >>>
+      >>> width = 5
+      >>> for num in range(5,12):
+      ...     for base in 'dXob':
+      ...         print('{0:{width}{base}}") << num, base=base, width=width), end=' ')
+      ...     print()
+      ...
+          5     5     5   101
+          6     6     6   110
+          7     7     7   111
+          8     8    10  1000
+          9     9    11  1001
+         10     A    12  1010
+         11     B    13  1011
diff --git a/libs/fmt/doc/usage.rst b/libs/fmt/doc/usage.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f9f818584a9a0d7418c81dadcbd3b58164fd6bfd
--- /dev/null
+++ b/libs/fmt/doc/usage.rst
@@ -0,0 +1,212 @@
+*****
+Usage
+*****
+
+To use the {fmt} library, add :file:`fmt/core.h`, :file:`fmt/format.h`,
+:file:`fmt/format-inl.h`, :file:`src/format.cc` and optionally other headers
+from a `release archive <https://github.com/fmtlib/fmt/releases/latest>`_ or
+the `Git repository <https://github.com/fmtlib/fmt>`_ to your project.
+Alternatively, you can :ref:`build the library with CMake <building>`.
+
+.. _building:
+
+Building the Library
+====================
+
+The included `CMake build script`__ can be used to build the fmt
+library on a wide range of platforms. CMake is freely available for
+download from https://www.cmake.org/download/.
+
+__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
+
+CMake works by generating native makefiles or project files that can
+be used in the compiler environment of your choice. The typical
+workflow starts with::
+
+  mkdir build          # Create a directory to hold the build output.
+  cd build
+  cmake ..  # Generate native build scripts.
+
+where :file:`{<path/to/fmt>}` is a path to the ``fmt`` repository.
+
+If you are on a \*nix system, you should now see a Makefile in the
+current directory. Now you can build the library by running :command:`make`.
+
+Once the library has been built you can invoke :command:`make test` to run
+the tests.
+
+You can control generation of the make ``test`` target with the ``FMT_TEST``
+CMake option. This can be useful if you include fmt as a subdirectory in
+your project but don't want to add fmt's tests to your ``test`` target.
+
+If you use Windows and have Visual Studio installed, a :file:`FMT.sln`
+file and several :file:`.vcproj` files will be created. You can then build them
+using Visual Studio or msbuild.
+
+On Mac OS X with Xcode installed, an :file:`.xcodeproj` file will be generated.
+
+To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
+``TRUE``::
+
+  cmake -DBUILD_SHARED_LIBS=TRUE ...
+
+__ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
+
+
+To build a `static library` with position independent code (required if the main
+consumer of the fmt library is a shared library i.e. a Python extension) set the
+``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``::
+
+  cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ...
+
+
+Installing the Library
+======================
+
+After building the library you can install it on a Unix-like system by running
+:command:`sudo make install`.
+
+Usage with CMake
+================
+
+You can add the ``fmt`` library directory into your project and include it in
+your ``CMakeLists.txt`` file::
+
+   add_subdirectory(fmt)
+
+or
+
+::
+
+   add_subdirectory(fmt EXCLUDE_FROM_ALL)
+
+to exclude it from ``make``, ``make all``, or ``cmake --build .``.
+
+You can detect and use an installed version of {fmt} as follows::
+
+   find_package(fmt)
+   target_link_libraries(<your-target> fmt::fmt)
+
+Setting up your target to use a header-only version of ``fmt`` is equally easy::
+
+   target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
+
+Usage with build2
+=================
+
+You can use `build2 <https://build2.org>`_, a dependency manager and a
+build-system combined, to use ``fmt``.
+
+Currently this package is available in these package repositories:
+
+- **https://cppget.org/fmt/** for released and published versions.
+- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_
+  for unreleased or custom revisions of ``fmt``.
+
+**Usage:**
+
+- ``build2`` package name: ``fmt``
+- Library target name : ``lib{fmt}``
+
+For example, to make your ``build2`` project depend on ``fmt``:
+
+- Add one of the repositories to your configurations, or in your
+  ``repositories.manifest``, if not already there::
+
+    :
+    role: prerequisite
+    location: https://pkg.cppget.org/1/stable
+
+- Add this package as a dependency to your ``./manifest`` file
+  (example for ``v7.0.x``)::
+
+    depends: fmt ~7.0.0
+
+- Import the target and use it as a prerequisite to your own target
+  using `fmt` in the appropriate ``buildfile``::
+
+    import fmt = fmt%lib{fmt}
+    lib{mylib} : cxx{**} ... $fmt
+
+Then build your project as usual with `b` or `bdep update`.
+
+For ``build2`` newcomers or to get more details and use cases, you can read the
+``build2``
+`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_.
+
+Building the Documentation
+==========================
+
+To build the documentation you need the following software installed on your
+system:
+
+* `Python <https://www.python.org/>`_ with pip and virtualenv
+* `Doxygen <http://www.stack.nl/~dimitri/doxygen/>`_
+* `Less <http://lesscss.org/>`_ with ``less-plugin-clean-css``.
+  Ubuntu doesn't package the ``clean-css`` plugin so you should use ``npm``
+  instead of ``apt`` to install both ``less`` and the plugin::
+
+    sudo npm install -g less less-plugin-clean-css.
+
+First generate makefiles or project files using CMake as described in
+the previous section. Then compile the ``doc`` target/project, for example::
+
+  make doc
+
+This will generate the HTML documentation in ``doc/html``.
+
+Conda
+=====
+
+fmt can be installed on Linux, macOS and Windows with
+`Conda <https://docs.conda.io/en/latest/>`__, using its
+`conda-forge <https://conda-forge.org>`__
+`package <https://github.com/conda-forge/fmt-feedstock>`__, as follows::
+
+  conda install -c conda-forge fmt
+
+Vcpkg
+=====
+
+You can download and install fmt using the `vcpkg
+<https://github.com/Microsoft/vcpkg>`__ dependency manager::
+
+  git clone https://github.com/Microsoft/vcpkg.git
+  cd vcpkg
+  ./bootstrap-vcpkg.sh
+  ./vcpkg integrate install
+  ./vcpkg install fmt
+
+The fmt port in vcpkg is kept up to date by Microsoft team members and community
+contributors. If the version is out of date, please `create an issue or pull
+request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
+
+LHelper
+=======
+
+You can download and install fmt using
+`lhelper <https://github.com/franko/lhelper>`__ dependency manager::
+
+  lhelper activate <some-environment>
+  lhelper install fmt
+
+All the recipes for lhelper are kept in the
+`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository.
+
+Android NDK
+===========
+
+fmt provides `Android.mk file`__ that can be used to build the library
+with `Android NDK <https://developer.android.com/tools/sdk/ndk/index.html>`_.
+For an example of using fmt with Android NDK, see the
+`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
+repository.
+
+__ https://github.com/fmtlib/fmt/blob/master/support/Android.mk
+
+Homebrew
+========
+
+fmt can be installed on OS X using `Homebrew <https://brew.sh/>`_::
+
+  brew install fmt
diff --git a/libs/fmt/include/fmt/args.h b/libs/fmt/include/fmt/args.h
new file mode 100644
index 0000000000000000000000000000000000000000..562e8ab1118514d8670464980a87863c021f330e
--- /dev/null
+++ b/libs/fmt/include/fmt/args.h
@@ -0,0 +1,232 @@
+// Formatting library for C++ - dynamic format arguments
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_ARGS_H_
+#define FMT_ARGS_H_
+
+#include <functional>  // std::reference_wrapper
+#include <memory>      // std::unique_ptr
+#include <vector>
+
+#include "core.h"
+
+FMT_BEGIN_NAMESPACE
+
+namespace detail {
+
+template <typename T> struct is_reference_wrapper : std::false_type {};
+template <typename T>
+struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
+
+template <typename T> const T& unwrap(const T& v) { return v; }
+template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
+  return static_cast<const T&>(v);
+}
+
+class dynamic_arg_list {
+  // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
+  // templates it doesn't complain about inability to deduce single translation
+  // unit for placing vtable. So storage_node_base is made a fake template.
+  template <typename = void> struct node {
+    virtual ~node() = default;
+    std::unique_ptr<node<>> next;
+  };
+
+  template <typename T> struct typed_node : node<> {
+    T value;
+
+    template <typename Arg>
+    FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
+
+    template <typename Char>
+    FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
+        : value(arg.data(), arg.size()) {}
+  };
+
+  std::unique_ptr<node<>> head_;
+
+ public:
+  template <typename T, typename Arg> const T& push(const Arg& arg) {
+    auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
+    auto& value = new_node->value;
+    new_node->next = std::move(head_);
+    head_ = std::move(new_node);
+    return value;
+  }
+};
+}  // namespace detail
+
+/**
+  \rst
+  A dynamic version of `fmt::format_arg_store`.
+  It's equipped with a storage to potentially temporary objects which lifetimes
+  could be shorter than the format arguments object.
+
+  It can be implicitly converted into `~fmt::basic_format_args` for passing
+  into type-erased formatting functions such as `~fmt::vformat`.
+  \endrst
+ */
+template <typename Context>
+class dynamic_format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+    // Workaround a GCC template argument substitution bug.
+    : public basic_format_args<Context>
+#endif
+{
+ private:
+  using char_type = typename Context::char_type;
+
+  template <typename T> struct need_copy {
+    static constexpr detail::type mapped_type =
+        detail::mapped_type_constant<T, Context>::value;
+
+    enum {
+      value = !(detail::is_reference_wrapper<T>::value ||
+                std::is_same<T, basic_string_view<char_type>>::value ||
+                std::is_same<T, detail::std_string_view<char_type>>::value ||
+                (mapped_type != detail::type::cstring_type &&
+                 mapped_type != detail::type::string_type &&
+                 mapped_type != detail::type::custom_type))
+    };
+  };
+
+  template <typename T>
+  using stored_type = conditional_t<detail::is_string<T>::value &&
+                                        !has_formatter<T, Context>::value &&
+                                        !detail::is_reference_wrapper<T>::value,
+                                    std::basic_string<char_type>, T>;
+
+  // Storage of basic_format_arg must be contiguous.
+  std::vector<basic_format_arg<Context>> data_;
+  std::vector<detail::named_arg_info<char_type>> named_info_;
+
+  // Storage of arguments not fitting into basic_format_arg must grow
+  // without relocation because items in data_ refer to it.
+  detail::dynamic_arg_list dynamic_args_;
+
+  friend class basic_format_args<Context>;
+
+  unsigned long long get_types() const {
+    return detail::is_unpacked_bit | data_.size() |
+           (named_info_.empty()
+                ? 0ULL
+                : static_cast<unsigned long long>(detail::has_named_args_bit));
+  }
+
+  const basic_format_arg<Context>* data() const {
+    return named_info_.empty() ? data_.data() : data_.data() + 1;
+  }
+
+  template <typename T> void emplace_arg(const T& arg) {
+    data_.emplace_back(detail::make_arg<Context>(arg));
+  }
+
+  template <typename T>
+  void emplace_arg(const detail::named_arg<char_type, T>& arg) {
+    if (named_info_.empty()) {
+      constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
+      data_.insert(data_.begin(), {zero_ptr, 0});
+    }
+    data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
+    auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
+      data->pop_back();
+    };
+    std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
+        guard{&data_, pop_one};
+    named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
+    data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
+    guard.release();
+  }
+
+ public:
+  /**
+    \rst
+    Adds an argument into the dynamic store for later passing to a formatting
+    function.
+
+    Note that custom types and string types (but not string views) are copied
+    into the store dynamically allocating memory if necessary.
+
+    **Example**::
+
+      fmt::dynamic_format_arg_store<fmt::format_context> store;
+      store.push_back(42);
+      store.push_back("abc");
+      store.push_back(1.5f);
+      std::string result = fmt::vformat("{} and {} and {}", store);
+    \endrst
+  */
+  template <typename T> void push_back(const T& arg) {
+    if (detail::const_check(need_copy<T>::value))
+      emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
+    else
+      emplace_arg(detail::unwrap(arg));
+  }
+
+  /**
+    \rst
+    Adds a reference to the argument into the dynamic store for later passing to
+    a formatting function.
+
+    **Example**::
+
+      fmt::dynamic_format_arg_store<fmt::format_context> store;
+      char band[] = "Rolling Stones";
+      store.push_back(std::cref(band));
+      band[9] = 'c'; // Changing str affects the output.
+      std::string result = fmt::vformat("{}", store);
+      // result == "Rolling Scones"
+    \endrst
+  */
+  template <typename T> void push_back(std::reference_wrapper<T> arg) {
+    static_assert(
+        need_copy<T>::value,
+        "objects of built-in types and string views are always copied");
+    emplace_arg(arg.get());
+  }
+
+  /**
+    Adds named argument into the dynamic store for later passing to a formatting
+    function. ``std::reference_wrapper`` is supported to avoid copying of the
+    argument. The name is always copied into the store.
+  */
+  template <typename T>
+  void push_back(const detail::named_arg<char_type, T>& arg) {
+    const char_type* arg_name =
+        dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
+    if (detail::const_check(need_copy<T>::value)) {
+      emplace_arg(
+          fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
+    } else {
+      emplace_arg(fmt::arg(arg_name, arg.value));
+    }
+  }
+
+  /** Erase all elements from the store */
+  void clear() {
+    data_.clear();
+    named_info_.clear();
+    dynamic_args_ = detail::dynamic_arg_list();
+  }
+
+  /**
+    \rst
+    Reserves space to store at least *new_cap* arguments including
+    *new_cap_named* named arguments.
+    \endrst
+  */
+  void reserve(size_t new_cap, size_t new_cap_named) {
+    FMT_ASSERT(new_cap >= new_cap_named,
+               "Set of arguments includes set of named arguments");
+    data_.reserve(new_cap);
+    named_info_.reserve(new_cap_named);
+  }
+};
+
+FMT_END_NAMESPACE
+
+#endif  // FMT_ARGS_H_
diff --git a/libs/fmt/include/fmt/chrono.h b/libs/fmt/include/fmt/chrono.h
new file mode 100644
index 0000000000000000000000000000000000000000..c4903c364181d04879b4dbad80c9493907618647
--- /dev/null
+++ b/libs/fmt/include/fmt/chrono.h
@@ -0,0 +1,1195 @@
+// Formatting library for C++ - chrono support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CHRONO_H_
+#define FMT_CHRONO_H_
+
+#include <algorithm>
+#include <chrono>
+#include <ctime>
+#include <locale>
+#include <sstream>
+
+#include "format.h"
+#include "locale.h"
+
+FMT_BEGIN_NAMESPACE
+
+// Enable safe chrono durations, unless explicitly disabled.
+#ifndef FMT_SAFE_DURATION_CAST
+#  define FMT_SAFE_DURATION_CAST 1
+#endif
+#if FMT_SAFE_DURATION_CAST
+
+// For conversion between std::chrono::durations without undefined
+// behaviour or erroneous results.
+// This is a stripped down version of duration_cast, for inclusion in fmt.
+// See https://github.com/pauldreik/safe_duration_cast
+//
+// Copyright Paul Dreik 2019
+namespace safe_duration_cast {
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed ==
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  // A and B are both signed, or both unsigned.
+  if (F::digits <= T::digits) {
+    // From fits in To without any problem.
+  } else {
+    // From does not always fit in To, resort to a dynamic check.
+    if (from < (T::min)() || from > (T::max)()) {
+      // outside range.
+      ec = 1;
+      return {};
+    }
+  }
+  return static_cast<To>(from);
+}
+
+/**
+ * converts From to To, without loss. If the dynamic value of from
+ * can't be converted to To without loss, ec is set.
+ */
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed !=
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  if (detail::const_check(F::is_signed && !T::is_signed)) {
+    // From may be negative, not allowed!
+    if (fmt::detail::is_negative(from)) {
+      ec = 1;
+      return {};
+    }
+    // From is positive. Can it always fit in To?
+    if (F::digits > T::digits &&
+        from > static_cast<From>(detail::max_value<To>())) {
+      ec = 1;
+      return {};
+    }
+  }
+
+  if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
+      from > static_cast<From>(detail::max_value<To>())) {
+    ec = 1;
+    return {};
+  }
+  return static_cast<To>(from);  // Lossless conversion.
+}
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  return from;
+}  // function
+
+// clang-format off
+/**
+ * converts From to To if possible, otherwise ec is set.
+ *
+ * input                            |    output
+ * ---------------------------------|---------------
+ * NaN                              | NaN
+ * Inf                              | Inf
+ * normal, fits in output           | converted (possibly lossy)
+ * normal, does not fit in output   | ec is set
+ * subnormal                        | best effort
+ * -Inf                             | -Inf
+ */
+// clang-format on
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  using T = std::numeric_limits<To>;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  static_assert(std::is_floating_point<To>::value, "To must be floating");
+
+  // catch the only happy case
+  if (std::isfinite(from)) {
+    if (from >= T::lowest() && from <= (T::max)()) {
+      return static_cast<To>(from);
+    }
+    // not within range.
+    ec = 1;
+    return {};
+  }
+
+  // nan and inf will be preserved
+  return static_cast<To>(from);
+}  // function
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  return from;
+}
+
+/**
+ * safe duration cast between integral durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_integral<FromRep>::value),
+          FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // safe conversion to IntermediateRep
+  IntermediateRep count =
+      lossless_integral_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) return {};
+  // multiply with Factor::num without overflow or underflow
+  if (detail::const_check(Factor::num != 1)) {
+    const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    const auto min1 =
+        (std::numeric_limits<IntermediateRep>::min)() / Factor::num;
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= Factor::num;
+  }
+
+  if (detail::const_check(Factor::den != 1)) count /= Factor::den;
+  auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
+  return ec ? To() : To(tocount);
+}
+
+/**
+ * safe duration_cast between floating point durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
+          FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  if (std::isnan(from.count())) {
+    // nan in, gives nan out. easy.
+    return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
+  }
+  // maybe we should also check if from is denormal, and decide what to do about
+  // it.
+
+  // +-inf should be preserved.
+  if (std::isinf(from.count())) {
+    return To{from.count()};
+  }
+
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // force conversion of From::rep -> IntermediateRep to be safe,
+  // even if it will never happen be narrowing in this context.
+  IntermediateRep count =
+      safe_float_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) {
+    return {};
+  }
+
+  // multiply with Factor::num without overflow or underflow
+  if (Factor::num != 1) {
+    constexpr auto max1 = detail::max_value<IntermediateRep>() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= static_cast<IntermediateRep>(Factor::num);
+  }
+
+  // this can't go wrong, right? den>0 is checked earlier.
+  if (Factor::den != 1) {
+    using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
+    count /= static_cast<common_t>(Factor::den);
+  }
+
+  // convert to the to type, safely
+  using ToRep = typename To::rep;
+
+  const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
+  if (ec) {
+    return {};
+  }
+  return To{tocount};
+}
+}  // namespace safe_duration_cast
+#endif
+
+// Prevents expansion of a preceding token as a function-style macro.
+// Usage: f FMT_NOMACRO()
+#define FMT_NOMACRO
+
+namespace detail {
+inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
+inline null<> localtime_s(...) { return null<>(); }
+inline null<> gmtime_r(...) { return null<>(); }
+inline null<> gmtime_s(...) { return null<>(); }
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+  Converts given time since epoch as ``std::time_t`` value into calendar time,
+  expressed in local time. Unlike ``std::localtime``, this function is
+  thread-safe on most platforms.
+ */
+inline std::tm localtime(std::time_t time) {
+  struct dispatcher {
+    std::time_t time_;
+    std::tm tm_;
+
+    dispatcher(std::time_t t) : time_(t) {}
+
+    bool run() {
+      using namespace fmt::detail;
+      return handle(localtime_r(&time_, &tm_));
+    }
+
+    bool handle(std::tm* tm) { return tm != nullptr; }
+
+    bool handle(detail::null<>) {
+      using namespace fmt::detail;
+      return fallback(localtime_s(&tm_, &time_));
+    }
+
+    bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+    bool fallback(detail::null<>) {
+      using namespace fmt::detail;
+      std::tm* tm = std::localtime(&time_);
+      if (tm) tm_ = *tm;
+      return tm != nullptr;
+    }
+#endif
+  };
+  dispatcher lt(time);
+  // Too big time values may be unsupported.
+  if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
+  return lt.tm_;
+}
+
+inline std::tm localtime(
+    std::chrono::time_point<std::chrono::system_clock> time_point) {
+  return localtime(std::chrono::system_clock::to_time_t(time_point));
+}
+
+/**
+  Converts given time since epoch as ``std::time_t`` value into calendar time,
+  expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
+  function is thread-safe on most platforms.
+ */
+inline std::tm gmtime(std::time_t time) {
+  struct dispatcher {
+    std::time_t time_;
+    std::tm tm_;
+
+    dispatcher(std::time_t t) : time_(t) {}
+
+    bool run() {
+      using namespace fmt::detail;
+      return handle(gmtime_r(&time_, &tm_));
+    }
+
+    bool handle(std::tm* tm) { return tm != nullptr; }
+
+    bool handle(detail::null<>) {
+      using namespace fmt::detail;
+      return fallback(gmtime_s(&tm_, &time_));
+    }
+
+    bool fallback(int res) { return res == 0; }
+
+#if !FMT_MSC_VER
+    bool fallback(detail::null<>) {
+      std::tm* tm = std::gmtime(&time_);
+      if (tm) tm_ = *tm;
+      return tm != nullptr;
+    }
+#endif
+  };
+  dispatcher gt(time);
+  // Too big time values may be unsupported.
+  if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
+  return gt.tm_;
+}
+
+inline std::tm gmtime(
+    std::chrono::time_point<std::chrono::system_clock> time_point) {
+  return gmtime(std::chrono::system_clock::to_time_t(time_point));
+}
+
+FMT_MODULE_EXPORT_END
+
+namespace detail {
+inline size_t strftime(char* str, size_t count, const char* format,
+                       const std::tm* time) {
+  // Assign to a pointer to suppress GCCs -Wformat-nonliteral
+  // First assign the nullptr to suppress -Wsuggest-attribute=format
+  std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
+      nullptr;
+  strftime = std::strftime;
+  return strftime(str, count, format, time);
+}
+
+inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
+                       const std::tm* time) {
+  // See above
+  std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
+                          const std::tm*) = nullptr;
+  wcsftime = std::wcsftime;
+  return wcsftime(str, count, format, time);
+}
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename Char, typename Duration>
+struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
+                 Char> : formatter<std::tm, Char> {
+  template <typename FormatContext>
+  auto format(std::chrono::time_point<std::chrono::system_clock> val,
+              FormatContext& ctx) -> decltype(ctx.out()) {
+    std::tm time = localtime(val);
+    return formatter<std::tm, Char>::format(time, ctx);
+  }
+};
+
+template <typename Char> struct formatter<std::tm, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto it = ctx.begin();
+    if (it != ctx.end() && *it == ':') ++it;
+    auto end = it;
+    while (end != ctx.end() && *end != '}') ++end;
+    specs = {it, detail::to_unsigned(end - it)};
+    return end;
+  }
+
+  template <typename FormatContext>
+  auto format(const std::tm& tm, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    basic_memory_buffer<Char> tm_format;
+    tm_format.append(specs.begin(), specs.end());
+    // By appending an extra space we can distinguish an empty result that
+    // indicates insufficient buffer size from a guaranteed non-empty result
+    // https://github.com/fmtlib/fmt/issues/2238
+    tm_format.push_back(' ');
+    tm_format.push_back('\0');
+    basic_memory_buffer<Char> buf;
+    size_t start = buf.size();
+    for (;;) {
+      size_t size = buf.capacity() - start;
+      size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
+      if (count != 0) {
+        buf.resize(start + count);
+        break;
+      }
+      const size_t MIN_GROWTH = 10;
+      buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
+    }
+    // Remove the extra space.
+    return std::copy(buf.begin(), buf.end() - 1, ctx.out());
+  }
+
+  basic_string_view<Char> specs;
+};
+
+namespace detail {
+template <typename Period> FMT_CONSTEXPR const char* get_units() {
+  return nullptr;
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::atto>() {
+  return "as";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::femto>() {
+  return "fs";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::pico>() {
+  return "ps";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::nano>() {
+  return "ns";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::micro>() {
+  return "µs";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::milli>() {
+  return "ms";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::centi>() {
+  return "cs";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::deci>() {
+  return "ds";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<1>>() {
+  return "s";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::deca>() {
+  return "das";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::hecto>() {
+  return "hs";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::kilo>() {
+  return "ks";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::mega>() {
+  return "Ms";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::giga>() {
+  return "Gs";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::tera>() {
+  return "Ts";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::peta>() {
+  return "Ps";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::exa>() {
+  return "Es";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<60>>() {
+  return "m";
+}
+template <> FMT_CONSTEXPR inline const char* get_units<std::ratio<3600>>() {
+  return "h";
+}
+
+enum class numeric_system {
+  standard,
+  // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
+  alternative
+};
+
+// Parses a put_time-like format string and invokes handler actions.
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
+                                              const Char* end,
+                                              Handler&& handler) {
+  auto ptr = begin;
+  while (ptr != end) {
+    auto c = *ptr;
+    if (c == '}') break;
+    if (c != '%') {
+      ++ptr;
+      continue;
+    }
+    if (begin != ptr) handler.on_text(begin, ptr);
+    ++ptr;  // consume '%'
+    if (ptr == end) FMT_THROW(format_error("invalid format"));
+    c = *ptr++;
+    switch (c) {
+    case '%':
+      handler.on_text(ptr - 1, ptr);
+      break;
+    case 'n': {
+      const Char newline[] = {'\n'};
+      handler.on_text(newline, newline + 1);
+      break;
+    }
+    case 't': {
+      const Char tab[] = {'\t'};
+      handler.on_text(tab, tab + 1);
+      break;
+    }
+    // Day of the week:
+    case 'a':
+      handler.on_abbr_weekday();
+      break;
+    case 'A':
+      handler.on_full_weekday();
+      break;
+    case 'w':
+      handler.on_dec0_weekday(numeric_system::standard);
+      break;
+    case 'u':
+      handler.on_dec1_weekday(numeric_system::standard);
+      break;
+    // Month:
+    case 'b':
+      handler.on_abbr_month();
+      break;
+    case 'B':
+      handler.on_full_month();
+      break;
+    // Hour, minute, second:
+    case 'H':
+      handler.on_24_hour(numeric_system::standard);
+      break;
+    case 'I':
+      handler.on_12_hour(numeric_system::standard);
+      break;
+    case 'M':
+      handler.on_minute(numeric_system::standard);
+      break;
+    case 'S':
+      handler.on_second(numeric_system::standard);
+      break;
+    // Other:
+    case 'c':
+      handler.on_datetime(numeric_system::standard);
+      break;
+    case 'x':
+      handler.on_loc_date(numeric_system::standard);
+      break;
+    case 'X':
+      handler.on_loc_time(numeric_system::standard);
+      break;
+    case 'D':
+      handler.on_us_date();
+      break;
+    case 'F':
+      handler.on_iso_date();
+      break;
+    case 'r':
+      handler.on_12_hour_time();
+      break;
+    case 'R':
+      handler.on_24_hour_time();
+      break;
+    case 'T':
+      handler.on_iso_time();
+      break;
+    case 'p':
+      handler.on_am_pm();
+      break;
+    case 'Q':
+      handler.on_duration_value();
+      break;
+    case 'q':
+      handler.on_duration_unit();
+      break;
+    case 'z':
+      handler.on_utc_offset();
+      break;
+    case 'Z':
+      handler.on_tz_name();
+      break;
+    // Alternative representation:
+    case 'E': {
+      if (ptr == end) FMT_THROW(format_error("invalid format"));
+      c = *ptr++;
+      switch (c) {
+      case 'c':
+        handler.on_datetime(numeric_system::alternative);
+        break;
+      case 'x':
+        handler.on_loc_date(numeric_system::alternative);
+        break;
+      case 'X':
+        handler.on_loc_time(numeric_system::alternative);
+        break;
+      default:
+        FMT_THROW(format_error("invalid format"));
+      }
+      break;
+    }
+    case 'O':
+      if (ptr == end) FMT_THROW(format_error("invalid format"));
+      c = *ptr++;
+      switch (c) {
+      case 'w':
+        handler.on_dec0_weekday(numeric_system::alternative);
+        break;
+      case 'u':
+        handler.on_dec1_weekday(numeric_system::alternative);
+        break;
+      case 'H':
+        handler.on_24_hour(numeric_system::alternative);
+        break;
+      case 'I':
+        handler.on_12_hour(numeric_system::alternative);
+        break;
+      case 'M':
+        handler.on_minute(numeric_system::alternative);
+        break;
+      case 'S':
+        handler.on_second(numeric_system::alternative);
+        break;
+      default:
+        FMT_THROW(format_error("invalid format"));
+      }
+      break;
+    default:
+      FMT_THROW(format_error("invalid format"));
+    }
+    begin = ptr;
+  }
+  if (begin != ptr) handler.on_text(begin, ptr);
+  return ptr;
+}
+
+struct chrono_format_checker {
+  FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
+
+  template <typename Char>
+  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+  FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
+  FMT_NORETURN void on_full_weekday() { report_no_date(); }
+  FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
+  FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
+  FMT_NORETURN void on_abbr_month() { report_no_date(); }
+  FMT_NORETURN void on_full_month() { report_no_date(); }
+  FMT_CONSTEXPR void on_24_hour(numeric_system) {}
+  FMT_CONSTEXPR void on_12_hour(numeric_system) {}
+  FMT_CONSTEXPR void on_minute(numeric_system) {}
+  FMT_CONSTEXPR void on_second(numeric_system) {}
+  FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
+  FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
+  FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
+  FMT_NORETURN void on_us_date() { report_no_date(); }
+  FMT_NORETURN void on_iso_date() { report_no_date(); }
+  FMT_CONSTEXPR void on_12_hour_time() {}
+  FMT_CONSTEXPR void on_24_hour_time() {}
+  FMT_CONSTEXPR void on_iso_time() {}
+  FMT_CONSTEXPR void on_am_pm() {}
+  FMT_CONSTEXPR void on_duration_value() {}
+  FMT_CONSTEXPR void on_duration_unit() {}
+  FMT_NORETURN void on_utc_offset() { report_no_date(); }
+  FMT_NORETURN void on_tz_name() { report_no_date(); }
+};
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline bool isnan(T) {
+  return false;
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline bool isnan(T value) {
+  return std::isnan(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline bool isfinite(T) {
+  return true;
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline bool isfinite(T value) {
+  return std::isfinite(value);
+}
+
+// Converts value to int and checks that it's in the range [0, upper).
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline int to_nonnegative_int(T value, int upper) {
+  FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
+             "invalid value");
+  (void)upper;
+  return static_cast<int>(value);
+}
+template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+inline int to_nonnegative_int(T value, int upper) {
+  FMT_ASSERT(
+      std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
+      "invalid value");
+  (void)upper;
+  return static_cast<int>(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline T mod(T x, int y) {
+  return x % static_cast<T>(y);
+}
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+inline T mod(T x, int y) {
+  return std::fmod(x, static_cast<T>(y));
+}
+
+// If T is an integral type, maps T to its unsigned counterpart, otherwise
+// leaves it unchanged (unlike std::make_unsigned).
+template <typename T, bool INTEGRAL = std::is_integral<T>::value>
+struct make_unsigned_or_unchanged {
+  using type = T;
+};
+
+template <typename T> struct make_unsigned_or_unchanged<T, true> {
+  using type = typename std::make_unsigned<T>::type;
+};
+
+#if FMT_SAFE_DURATION_CAST
+// throwing version of safe_duration_cast
+template <typename To, typename FromRep, typename FromPeriod>
+To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
+  int ec;
+  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
+  if (ec) FMT_THROW(format_error("cannot format duration"));
+  return to;
+}
+#endif
+
+template <typename Rep, typename Period,
+          FMT_ENABLE_IF(std::is_integral<Rep>::value)>
+inline std::chrono::duration<Rep, std::milli> get_milliseconds(
+    std::chrono::duration<Rep, Period> d) {
+  // this may overflow and/or the result may not fit in the
+  // target type.
+#if FMT_SAFE_DURATION_CAST
+  using CommonSecondsType =
+      typename std::common_type<decltype(d), std::chrono::seconds>::type;
+  const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
+  const auto d_as_whole_seconds =
+      fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
+  // this conversion should be nonproblematic
+  const auto diff = d_as_common - d_as_whole_seconds;
+  const auto ms =
+      fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
+  return ms;
+#else
+  auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
+  return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
+#endif
+}
+
+template <typename Rep, typename Period,
+          FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
+inline std::chrono::duration<Rep, std::milli> get_milliseconds(
+    std::chrono::duration<Rep, Period> d) {
+  using common_type = typename std::common_type<Rep, std::intmax_t>::type;
+  auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
+                    static_cast<common_type>(Period::den) * 1000,
+                1000);
+  return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
+}
+
+template <typename Char, typename Rep, typename OutputIt,
+          FMT_ENABLE_IF(std::is_integral<Rep>::value)>
+OutputIt format_duration_value(OutputIt out, Rep val, int) {
+  return write<Char>(out, val);
+}
+
+template <typename Char, typename Rep, typename OutputIt,
+          FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
+OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
+  auto specs = basic_format_specs<Char>();
+  specs.precision = precision;
+  specs.type = precision > 0 ? 'f' : 'g';
+  return write<Char>(out, val, specs);
+}
+
+template <typename Char, typename OutputIt>
+OutputIt copy_unit(string_view unit, OutputIt out, Char) {
+  return std::copy(unit.begin(), unit.end(), out);
+}
+
+template <typename OutputIt>
+OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
+  // This works when wchar_t is UTF-32 because units only contain characters
+  // that have the same representation in UTF-16 and UTF-32.
+  utf8_to_utf16 u(unit);
+  return std::copy(u.c_str(), u.c_str() + u.size(), out);
+}
+
+template <typename Char, typename Period, typename OutputIt>
+OutputIt format_duration_unit(OutputIt out) {
+  if (const char* unit = get_units<Period>())
+    return copy_unit(string_view(unit), out, Char());
+  *out++ = '[';
+  out = write<Char>(out, Period::num);
+  if (const_check(Period::den != 1)) {
+    *out++ = '/';
+    out = write<Char>(out, Period::den);
+  }
+  *out++ = ']';
+  *out++ = 's';
+  return out;
+}
+
+template <typename FormatContext, typename OutputIt, typename Rep,
+          typename Period>
+struct chrono_formatter {
+  FormatContext& context;
+  OutputIt out;
+  int precision;
+  // rep is unsigned to avoid overflow.
+  using rep =
+      conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
+                    unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
+  rep val;
+  using seconds = std::chrono::duration<rep>;
+  seconds s;
+  using milliseconds = std::chrono::duration<rep, std::milli>;
+  bool negative;
+
+  using char_type = typename FormatContext::char_type;
+
+  explicit chrono_formatter(FormatContext& ctx, OutputIt o,
+                            std::chrono::duration<Rep, Period> d)
+      : context(ctx),
+        out(o),
+        val(static_cast<rep>(d.count())),
+        negative(false) {
+    if (d.count() < 0) {
+      val = 0 - val;
+      negative = true;
+    }
+
+    // this may overflow and/or the result may not fit in the
+    // target type.
+#if FMT_SAFE_DURATION_CAST
+    // might need checked conversion (rep!=Rep)
+    auto tmpval = std::chrono::duration<rep, Period>(val);
+    s = fmt_safe_duration_cast<seconds>(tmpval);
+#else
+    s = std::chrono::duration_cast<seconds>(
+        std::chrono::duration<rep, Period>(val));
+#endif
+  }
+
+  // returns true if nan or inf, writes to out.
+  bool handle_nan_inf() {
+    if (isfinite(val)) {
+      return false;
+    }
+    if (isnan(val)) {
+      write_nan();
+      return true;
+    }
+    // must be +-inf
+    if (val > 0) {
+      write_pinf();
+    } else {
+      write_ninf();
+    }
+    return true;
+  }
+
+  Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
+
+  Rep hour12() const {
+    Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));
+    return hour <= 0 ? 12 : hour;
+  }
+
+  Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); }
+  Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); }
+
+  std::tm time() const {
+    auto time = std::tm();
+    time.tm_hour = to_nonnegative_int(hour(), 24);
+    time.tm_min = to_nonnegative_int(minute(), 60);
+    time.tm_sec = to_nonnegative_int(second(), 60);
+    return time;
+  }
+
+  void write_sign() {
+    if (negative) {
+      *out++ = '-';
+      negative = false;
+    }
+  }
+
+  void write(Rep value, int width) {
+    write_sign();
+    if (isnan(value)) return write_nan();
+    uint32_or_64_or_128_t<int> n =
+        to_unsigned(to_nonnegative_int(value, max_value<int>()));
+    int num_digits = detail::count_digits(n);
+    if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
+    out = format_decimal<char_type>(out, n, num_digits).end;
+  }
+
+  void write_nan() { std::copy_n("nan", 3, out); }
+  void write_pinf() { std::copy_n("inf", 3, out); }
+  void write_ninf() { std::copy_n("-inf", 4, out); }
+
+  void format_localized(const tm& time, char format, char modifier = 0) {
+    if (isnan(val)) return write_nan();
+    auto locale = context.locale().template get<std::locale>();
+    auto& facet = std::use_facet<std::time_put<char_type>>(locale);
+    std::basic_ostringstream<char_type> os;
+    os.imbue(locale);
+    facet.put(os, os, ' ', &time, format, modifier);
+    auto str = os.str();
+    std::copy(str.begin(), str.end(), out);
+  }
+
+  void on_text(const char_type* begin, const char_type* end) {
+    std::copy(begin, end, out);
+  }
+
+  // These are not implemented because durations don't have date information.
+  void on_abbr_weekday() {}
+  void on_full_weekday() {}
+  void on_dec0_weekday(numeric_system) {}
+  void on_dec1_weekday(numeric_system) {}
+  void on_abbr_month() {}
+  void on_full_month() {}
+  void on_datetime(numeric_system) {}
+  void on_loc_date(numeric_system) {}
+  void on_loc_time(numeric_system) {}
+  void on_us_date() {}
+  void on_iso_date() {}
+  void on_utc_offset() {}
+  void on_tz_name() {}
+
+  void on_24_hour(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(hour(), 2);
+    auto time = tm();
+    time.tm_hour = to_nonnegative_int(hour(), 24);
+    format_localized(time, 'H', 'O');
+  }
+
+  void on_12_hour(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(hour12(), 2);
+    auto time = tm();
+    time.tm_hour = to_nonnegative_int(hour12(), 12);
+    format_localized(time, 'I', 'O');
+  }
+
+  void on_minute(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) return write(minute(), 2);
+    auto time = tm();
+    time.tm_min = to_nonnegative_int(minute(), 60);
+    format_localized(time, 'M', 'O');
+  }
+
+  void on_second(numeric_system ns) {
+    if (handle_nan_inf()) return;
+
+    if (ns == numeric_system::standard) {
+      write(second(), 2);
+#if FMT_SAFE_DURATION_CAST
+      // convert rep->Rep
+      using duration_rep = std::chrono::duration<rep, Period>;
+      using duration_Rep = std::chrono::duration<Rep, Period>;
+      auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
+#else
+      auto tmpval = std::chrono::duration<Rep, Period>(val);
+#endif
+      auto ms = get_milliseconds(tmpval);
+      if (ms != std::chrono::milliseconds(0)) {
+        *out++ = '.';
+        write(ms.count(), 3);
+      }
+      return;
+    }
+    auto time = tm();
+    time.tm_sec = to_nonnegative_int(second(), 60);
+    format_localized(time, 'S', 'O');
+  }
+
+  void on_12_hour_time() {
+    if (handle_nan_inf()) return;
+    format_localized(time(), 'r');
+  }
+
+  void on_24_hour_time() {
+    if (handle_nan_inf()) {
+      *out++ = ':';
+      handle_nan_inf();
+      return;
+    }
+
+    write(hour(), 2);
+    *out++ = ':';
+    write(minute(), 2);
+  }
+
+  void on_iso_time() {
+    on_24_hour_time();
+    *out++ = ':';
+    if (handle_nan_inf()) return;
+    write(second(), 2);
+  }
+
+  void on_am_pm() {
+    if (handle_nan_inf()) return;
+    format_localized(time(), 'p');
+  }
+
+  void on_duration_value() {
+    if (handle_nan_inf()) return;
+    write_sign();
+    out = format_duration_value<char_type>(out, val, precision);
+  }
+
+  void on_duration_unit() {
+    out = format_duration_unit<char_type, Period>(out);
+  }
+};
+}  // namespace detail
+
+template <typename Rep, typename Period, typename Char>
+struct formatter<std::chrono::duration<Rep, Period>, Char> {
+ private:
+  basic_format_specs<Char> specs;
+  int precision = -1;
+  using arg_ref_type = detail::arg_ref<Char>;
+  arg_ref_type width_ref;
+  arg_ref_type precision_ref;
+  basic_string_view<Char> format_str;
+  using duration = std::chrono::duration<Rep, Period>;
+
+  struct spec_handler {
+    formatter& f;
+    basic_format_parse_context<Char>& context;
+    basic_string_view<Char> format_str;
+
+    template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
+      context.check_arg_id(arg_id);
+      return arg_ref_type(arg_id);
+    }
+
+    FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
+      context.check_arg_id(arg_id);
+      return arg_ref_type(arg_id);
+    }
+
+    FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
+      return arg_ref_type(context.next_arg_id());
+    }
+
+    void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
+    FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
+      f.specs.fill = fill;
+    }
+    FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
+    FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
+    FMT_CONSTEXPR void on_precision(int _precision) {
+      f.precision = _precision;
+    }
+    FMT_CONSTEXPR void end_precision() {}
+
+    template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+      f.width_ref = make_arg_ref(arg_id);
+    }
+
+    template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+      f.precision_ref = make_arg_ref(arg_id);
+    }
+  };
+
+  using iterator = typename basic_format_parse_context<Char>::iterator;
+  struct parse_range {
+    iterator begin;
+    iterator end;
+  };
+
+  FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) {
+    auto begin = ctx.begin(), end = ctx.end();
+    if (begin == end || *begin == '}') return {begin, begin};
+    spec_handler handler{*this, ctx, format_str};
+    begin = detail::parse_align(begin, end, handler);
+    if (begin == end) return {begin, begin};
+    begin = detail::parse_width(begin, end, handler);
+    if (begin == end) return {begin, begin};
+    if (*begin == '.') {
+      if (std::is_floating_point<Rep>::value)
+        begin = detail::parse_precision(begin, end, handler);
+      else
+        handler.on_error("precision not allowed for this argument type");
+    }
+    end = parse_chrono_format(begin, end, detail::chrono_format_checker());
+    return {begin, end};
+  }
+
+ public:
+  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
+      -> decltype(ctx.begin()) {
+    auto range = do_parse(ctx);
+    format_str = basic_string_view<Char>(
+        &*range.begin, detail::to_unsigned(range.end - range.begin));
+    return range.end;
+  }
+
+  template <typename FormatContext>
+  auto format(const duration& d, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    auto specs_copy = specs;
+    auto precision_copy = precision;
+    auto begin = format_str.begin(), end = format_str.end();
+    // As a possible future optimization, we could avoid extra copying if width
+    // is not specified.
+    basic_memory_buffer<Char> buf;
+    auto out = std::back_inserter(buf);
+    detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
+                                                       width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
+                                                           precision_ref, ctx);
+    if (begin == end || *begin == '}') {
+      out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
+      detail::format_duration_unit<Char, Period>(out);
+    } else {
+      detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
+          ctx, out, d);
+      f.precision = precision_copy;
+      parse_chrono_format(begin, end, f);
+    }
+    return detail::write(
+        ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
+  }
+};
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_CHRONO_H_
diff --git a/libs/fmt/include/fmt/color.h b/libs/fmt/include/fmt/color.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2e1a798f3103733a06df599f4bb770b644832fc
--- /dev/null
+++ b/libs/fmt/include/fmt/color.h
@@ -0,0 +1,629 @@
+// Formatting library for C++ - color support
+//
+// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_COLOR_H_
+#define FMT_COLOR_H_
+
+#include "format.h"
+
+// __declspec(deprecated) is broken in some MSVC versions.
+#if FMT_MSC_VER
+#  define FMT_DEPRECATED_NONMSVC
+#else
+#  define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
+#endif
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+enum class color : uint32_t {
+  alice_blue = 0xF0F8FF,               // rgb(240,248,255)
+  antique_white = 0xFAEBD7,            // rgb(250,235,215)
+  aqua = 0x00FFFF,                     // rgb(0,255,255)
+  aquamarine = 0x7FFFD4,               // rgb(127,255,212)
+  azure = 0xF0FFFF,                    // rgb(240,255,255)
+  beige = 0xF5F5DC,                    // rgb(245,245,220)
+  bisque = 0xFFE4C4,                   // rgb(255,228,196)
+  black = 0x000000,                    // rgb(0,0,0)
+  blanched_almond = 0xFFEBCD,          // rgb(255,235,205)
+  blue = 0x0000FF,                     // rgb(0,0,255)
+  blue_violet = 0x8A2BE2,              // rgb(138,43,226)
+  brown = 0xA52A2A,                    // rgb(165,42,42)
+  burly_wood = 0xDEB887,               // rgb(222,184,135)
+  cadet_blue = 0x5F9EA0,               // rgb(95,158,160)
+  chartreuse = 0x7FFF00,               // rgb(127,255,0)
+  chocolate = 0xD2691E,                // rgb(210,105,30)
+  coral = 0xFF7F50,                    // rgb(255,127,80)
+  cornflower_blue = 0x6495ED,          // rgb(100,149,237)
+  cornsilk = 0xFFF8DC,                 // rgb(255,248,220)
+  crimson = 0xDC143C,                  // rgb(220,20,60)
+  cyan = 0x00FFFF,                     // rgb(0,255,255)
+  dark_blue = 0x00008B,                // rgb(0,0,139)
+  dark_cyan = 0x008B8B,                // rgb(0,139,139)
+  dark_golden_rod = 0xB8860B,          // rgb(184,134,11)
+  dark_gray = 0xA9A9A9,                // rgb(169,169,169)
+  dark_green = 0x006400,               // rgb(0,100,0)
+  dark_khaki = 0xBDB76B,               // rgb(189,183,107)
+  dark_magenta = 0x8B008B,             // rgb(139,0,139)
+  dark_olive_green = 0x556B2F,         // rgb(85,107,47)
+  dark_orange = 0xFF8C00,              // rgb(255,140,0)
+  dark_orchid = 0x9932CC,              // rgb(153,50,204)
+  dark_red = 0x8B0000,                 // rgb(139,0,0)
+  dark_salmon = 0xE9967A,              // rgb(233,150,122)
+  dark_sea_green = 0x8FBC8F,           // rgb(143,188,143)
+  dark_slate_blue = 0x483D8B,          // rgb(72,61,139)
+  dark_slate_gray = 0x2F4F4F,          // rgb(47,79,79)
+  dark_turquoise = 0x00CED1,           // rgb(0,206,209)
+  dark_violet = 0x9400D3,              // rgb(148,0,211)
+  deep_pink = 0xFF1493,                // rgb(255,20,147)
+  deep_sky_blue = 0x00BFFF,            // rgb(0,191,255)
+  dim_gray = 0x696969,                 // rgb(105,105,105)
+  dodger_blue = 0x1E90FF,              // rgb(30,144,255)
+  fire_brick = 0xB22222,               // rgb(178,34,34)
+  floral_white = 0xFFFAF0,             // rgb(255,250,240)
+  forest_green = 0x228B22,             // rgb(34,139,34)
+  fuchsia = 0xFF00FF,                  // rgb(255,0,255)
+  gainsboro = 0xDCDCDC,                // rgb(220,220,220)
+  ghost_white = 0xF8F8FF,              // rgb(248,248,255)
+  gold = 0xFFD700,                     // rgb(255,215,0)
+  golden_rod = 0xDAA520,               // rgb(218,165,32)
+  gray = 0x808080,                     // rgb(128,128,128)
+  green = 0x008000,                    // rgb(0,128,0)
+  green_yellow = 0xADFF2F,             // rgb(173,255,47)
+  honey_dew = 0xF0FFF0,                // rgb(240,255,240)
+  hot_pink = 0xFF69B4,                 // rgb(255,105,180)
+  indian_red = 0xCD5C5C,               // rgb(205,92,92)
+  indigo = 0x4B0082,                   // rgb(75,0,130)
+  ivory = 0xFFFFF0,                    // rgb(255,255,240)
+  khaki = 0xF0E68C,                    // rgb(240,230,140)
+  lavender = 0xE6E6FA,                 // rgb(230,230,250)
+  lavender_blush = 0xFFF0F5,           // rgb(255,240,245)
+  lawn_green = 0x7CFC00,               // rgb(124,252,0)
+  lemon_chiffon = 0xFFFACD,            // rgb(255,250,205)
+  light_blue = 0xADD8E6,               // rgb(173,216,230)
+  light_coral = 0xF08080,              // rgb(240,128,128)
+  light_cyan = 0xE0FFFF,               // rgb(224,255,255)
+  light_golden_rod_yellow = 0xFAFAD2,  // rgb(250,250,210)
+  light_gray = 0xD3D3D3,               // rgb(211,211,211)
+  light_green = 0x90EE90,              // rgb(144,238,144)
+  light_pink = 0xFFB6C1,               // rgb(255,182,193)
+  light_salmon = 0xFFA07A,             // rgb(255,160,122)
+  light_sea_green = 0x20B2AA,          // rgb(32,178,170)
+  light_sky_blue = 0x87CEFA,           // rgb(135,206,250)
+  light_slate_gray = 0x778899,         // rgb(119,136,153)
+  light_steel_blue = 0xB0C4DE,         // rgb(176,196,222)
+  light_yellow = 0xFFFFE0,             // rgb(255,255,224)
+  lime = 0x00FF00,                     // rgb(0,255,0)
+  lime_green = 0x32CD32,               // rgb(50,205,50)
+  linen = 0xFAF0E6,                    // rgb(250,240,230)
+  magenta = 0xFF00FF,                  // rgb(255,0,255)
+  maroon = 0x800000,                   // rgb(128,0,0)
+  medium_aquamarine = 0x66CDAA,        // rgb(102,205,170)
+  medium_blue = 0x0000CD,              // rgb(0,0,205)
+  medium_orchid = 0xBA55D3,            // rgb(186,85,211)
+  medium_purple = 0x9370DB,            // rgb(147,112,219)
+  medium_sea_green = 0x3CB371,         // rgb(60,179,113)
+  medium_slate_blue = 0x7B68EE,        // rgb(123,104,238)
+  medium_spring_green = 0x00FA9A,      // rgb(0,250,154)
+  medium_turquoise = 0x48D1CC,         // rgb(72,209,204)
+  medium_violet_red = 0xC71585,        // rgb(199,21,133)
+  midnight_blue = 0x191970,            // rgb(25,25,112)
+  mint_cream = 0xF5FFFA,               // rgb(245,255,250)
+  misty_rose = 0xFFE4E1,               // rgb(255,228,225)
+  moccasin = 0xFFE4B5,                 // rgb(255,228,181)
+  navajo_white = 0xFFDEAD,             // rgb(255,222,173)
+  navy = 0x000080,                     // rgb(0,0,128)
+  old_lace = 0xFDF5E6,                 // rgb(253,245,230)
+  olive = 0x808000,                    // rgb(128,128,0)
+  olive_drab = 0x6B8E23,               // rgb(107,142,35)
+  orange = 0xFFA500,                   // rgb(255,165,0)
+  orange_red = 0xFF4500,               // rgb(255,69,0)
+  orchid = 0xDA70D6,                   // rgb(218,112,214)
+  pale_golden_rod = 0xEEE8AA,          // rgb(238,232,170)
+  pale_green = 0x98FB98,               // rgb(152,251,152)
+  pale_turquoise = 0xAFEEEE,           // rgb(175,238,238)
+  pale_violet_red = 0xDB7093,          // rgb(219,112,147)
+  papaya_whip = 0xFFEFD5,              // rgb(255,239,213)
+  peach_puff = 0xFFDAB9,               // rgb(255,218,185)
+  peru = 0xCD853F,                     // rgb(205,133,63)
+  pink = 0xFFC0CB,                     // rgb(255,192,203)
+  plum = 0xDDA0DD,                     // rgb(221,160,221)
+  powder_blue = 0xB0E0E6,              // rgb(176,224,230)
+  purple = 0x800080,                   // rgb(128,0,128)
+  rebecca_purple = 0x663399,           // rgb(102,51,153)
+  red = 0xFF0000,                      // rgb(255,0,0)
+  rosy_brown = 0xBC8F8F,               // rgb(188,143,143)
+  royal_blue = 0x4169E1,               // rgb(65,105,225)
+  saddle_brown = 0x8B4513,             // rgb(139,69,19)
+  salmon = 0xFA8072,                   // rgb(250,128,114)
+  sandy_brown = 0xF4A460,              // rgb(244,164,96)
+  sea_green = 0x2E8B57,                // rgb(46,139,87)
+  sea_shell = 0xFFF5EE,                // rgb(255,245,238)
+  sienna = 0xA0522D,                   // rgb(160,82,45)
+  silver = 0xC0C0C0,                   // rgb(192,192,192)
+  sky_blue = 0x87CEEB,                 // rgb(135,206,235)
+  slate_blue = 0x6A5ACD,               // rgb(106,90,205)
+  slate_gray = 0x708090,               // rgb(112,128,144)
+  snow = 0xFFFAFA,                     // rgb(255,250,250)
+  spring_green = 0x00FF7F,             // rgb(0,255,127)
+  steel_blue = 0x4682B4,               // rgb(70,130,180)
+  tan = 0xD2B48C,                      // rgb(210,180,140)
+  teal = 0x008080,                     // rgb(0,128,128)
+  thistle = 0xD8BFD8,                  // rgb(216,191,216)
+  tomato = 0xFF6347,                   // rgb(255,99,71)
+  turquoise = 0x40E0D0,                // rgb(64,224,208)
+  violet = 0xEE82EE,                   // rgb(238,130,238)
+  wheat = 0xF5DEB3,                    // rgb(245,222,179)
+  white = 0xFFFFFF,                    // rgb(255,255,255)
+  white_smoke = 0xF5F5F5,              // rgb(245,245,245)
+  yellow = 0xFFFF00,                   // rgb(255,255,0)
+  yellow_green = 0x9ACD32              // rgb(154,205,50)
+};                                     // enum class color
+
+enum class terminal_color : uint8_t {
+  black = 30,
+  red,
+  green,
+  yellow,
+  blue,
+  magenta,
+  cyan,
+  white,
+  bright_black = 90,
+  bright_red,
+  bright_green,
+  bright_yellow,
+  bright_blue,
+  bright_magenta,
+  bright_cyan,
+  bright_white
+};
+
+enum class emphasis : uint8_t {
+  bold = 1,
+  italic = 1 << 1,
+  underline = 1 << 2,
+  strikethrough = 1 << 3
+};
+
+// rgb is a struct for red, green and blue colors.
+// Using the name "rgb" makes some editors show the color in a tooltip.
+struct rgb {
+  FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
+  FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
+  FMT_CONSTEXPR rgb(uint32_t hex)
+      : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
+  FMT_CONSTEXPR rgb(color hex)
+      : r((uint32_t(hex) >> 16) & 0xFF),
+        g((uint32_t(hex) >> 8) & 0xFF),
+        b(uint32_t(hex) & 0xFF) {}
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+};
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+// color is a struct of either a rgb color or a terminal color.
+struct color_type {
+  FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
+  FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
+                                                           value{} {
+    value.rgb_color = static_cast<uint32_t>(rgb_color);
+  }
+  FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
+    value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
+                      (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
+  }
+  FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
+                                                                     value{} {
+    value.term_color = static_cast<uint8_t>(term_color);
+  }
+  bool is_rgb;
+  union color_union {
+    uint8_t term_color;
+    uint32_t rgb_color;
+  } value;
+};
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+/** A text style consisting of foreground and background colors and emphasis. */
+class text_style {
+ public:
+  FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
+      : set_foreground_color(),
+        set_background_color(),
+        ems(em) {}
+
+  FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
+    if (!set_foreground_color) {
+      set_foreground_color = rhs.set_foreground_color;
+      foreground_color = rhs.foreground_color;
+    } else if (rhs.set_foreground_color) {
+      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
+        FMT_THROW(format_error("can't OR a terminal color"));
+      foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
+    }
+
+    if (!set_background_color) {
+      set_background_color = rhs.set_background_color;
+      background_color = rhs.background_color;
+    } else if (rhs.set_background_color) {
+      if (!background_color.is_rgb || !rhs.background_color.is_rgb)
+        FMT_THROW(format_error("can't OR a terminal color"));
+      background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
+    }
+
+    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
+                                static_cast<uint8_t>(rhs.ems));
+    return *this;
+  }
+
+  friend FMT_CONSTEXPR text_style operator|(text_style lhs,
+                                            const text_style& rhs) {
+    return lhs |= rhs;
+  }
+
+  FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
+      const text_style& rhs) {
+    return and_assign(rhs);
+  }
+
+  FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
+  operator&(text_style lhs, const text_style& rhs) {
+    return lhs.and_assign(rhs);
+  }
+
+  FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
+    return set_foreground_color;
+  }
+  FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
+    return set_background_color;
+  }
+  FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
+    return static_cast<uint8_t>(ems) != 0;
+  }
+  FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_foreground(), "no foreground specified for this style");
+    return foreground_color;
+  }
+  FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_background(), "no background specified for this style");
+    return background_color;
+  }
+  FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
+    FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
+    return ems;
+  }
+
+ private:
+  FMT_CONSTEXPR text_style(bool is_foreground,
+                           detail::color_type text_color) FMT_NOEXCEPT
+      : set_foreground_color(),
+        set_background_color(),
+        ems() {
+    if (is_foreground) {
+      foreground_color = text_color;
+      set_foreground_color = true;
+    } else {
+      background_color = text_color;
+      set_background_color = true;
+    }
+  }
+
+  // DEPRECATED!
+  FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
+    if (!set_foreground_color) {
+      set_foreground_color = rhs.set_foreground_color;
+      foreground_color = rhs.foreground_color;
+    } else if (rhs.set_foreground_color) {
+      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
+        FMT_THROW(format_error("can't AND a terminal color"));
+      foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
+    }
+
+    if (!set_background_color) {
+      set_background_color = rhs.set_background_color;
+      background_color = rhs.background_color;
+    } else if (rhs.set_background_color) {
+      if (!background_color.is_rgb || !rhs.background_color.is_rgb)
+        FMT_THROW(format_error("can't AND a terminal color"));
+      background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
+    }
+
+    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
+                                static_cast<uint8_t>(rhs.ems));
+    return *this;
+  }
+
+  friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
+      FMT_NOEXCEPT;
+
+  friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
+      FMT_NOEXCEPT;
+
+  detail::color_type foreground_color;
+  detail::color_type background_color;
+  bool set_foreground_color;
+  bool set_background_color;
+  emphasis ems;
+};
+
+/** Creates a text style from the foreground (text) color. */
+FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
+  return text_style(true, foreground);
+}
+
+/** Creates a text style from the background color. */
+FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
+  return text_style(false, background);
+}
+
+FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
+                                          emphasis rhs) FMT_NOEXCEPT {
+  return text_style(lhs) | rhs;
+}
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+template <typename Char> struct ansi_color_escape {
+  FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
+                                  const char* esc) FMT_NOEXCEPT {
+    // If we have a terminal color, we need to output another escape code
+    // sequence.
+    if (!text_color.is_rgb) {
+      bool is_background = esc == string_view("\x1b[48;2;");
+      uint32_t value = text_color.value.term_color;
+      // Background ASCII codes are the same as the foreground ones but with
+      // 10 more.
+      if (is_background) value += 10u;
+
+      size_t index = 0;
+      buffer[index++] = static_cast<Char>('\x1b');
+      buffer[index++] = static_cast<Char>('[');
+
+      if (value >= 100u) {
+        buffer[index++] = static_cast<Char>('1');
+        value %= 100u;
+      }
+      buffer[index++] = static_cast<Char>('0' + value / 10u);
+      buffer[index++] = static_cast<Char>('0' + value % 10u);
+
+      buffer[index++] = static_cast<Char>('m');
+      buffer[index++] = static_cast<Char>('\0');
+      return;
+    }
+
+    for (int i = 0; i < 7; i++) {
+      buffer[i] = static_cast<Char>(esc[i]);
+    }
+    rgb color(text_color.value.rgb_color);
+    to_esc(color.r, buffer + 7, ';');
+    to_esc(color.g, buffer + 11, ';');
+    to_esc(color.b, buffer + 15, 'm');
+    buffer[19] = static_cast<Char>(0);
+  }
+  FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
+    uint8_t em_codes[4] = {};
+    uint8_t em_bits = static_cast<uint8_t>(em);
+    if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
+    if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
+    if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
+    if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
+      em_codes[3] = 9;
+
+    size_t index = 0;
+    for (int i = 0; i < 4; ++i) {
+      if (!em_codes[i]) continue;
+      buffer[index++] = static_cast<Char>('\x1b');
+      buffer[index++] = static_cast<Char>('[');
+      buffer[index++] = static_cast<Char>('0' + em_codes[i]);
+      buffer[index++] = static_cast<Char>('m');
+    }
+    buffer[index++] = static_cast<Char>(0);
+  }
+  FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
+
+  FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
+  FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
+    return buffer + std::char_traits<Char>::length(buffer);
+  }
+
+ private:
+  Char buffer[7u + 3u * 4u + 1u];
+
+  static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
+                                   char delimiter) FMT_NOEXCEPT {
+    out[0] = static_cast<Char>('0' + c / 100);
+    out[1] = static_cast<Char>('0' + c / 10 % 10);
+    out[2] = static_cast<Char>('0' + c % 10);
+    out[3] = static_cast<Char>(delimiter);
+  }
+};
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
+    detail::color_type foreground) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
+}
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
+    detail::color_type background) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(background, "\x1b[48;2;");
+}
+
+template <typename Char>
+FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
+  return ansi_color_escape<Char>(em);
+}
+
+template <typename Char>
+inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
+  std::fputs(chars, stream);
+}
+
+template <>
+inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
+  std::fputws(chars, stream);
+}
+
+template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
+  fputs("\x1b[0m", stream);
+}
+
+template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
+  fputs(L"\x1b[0m", stream);
+}
+
+template <typename Char>
+inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
+  auto reset_color = string_view("\x1b[0m");
+  buffer.append(reset_color.begin(), reset_color.end());
+}
+
+template <typename Char>
+void vformat_to(buffer<Char>& buf, const text_style& ts,
+                basic_string_view<Char> format_str,
+                basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  bool has_style = false;
+  if (ts.has_emphasis()) {
+    has_style = true;
+    auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
+    buf.append(emphasis.begin(), emphasis.end());
+  }
+  if (ts.has_foreground()) {
+    has_style = true;
+    auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
+    buf.append(foreground.begin(), foreground.end());
+  }
+  if (ts.has_background()) {
+    has_style = true;
+    auto background = detail::make_background_color<Char>(ts.get_background());
+    buf.append(background.begin(), background.end());
+  }
+  detail::vformat_to(buf, format_str, args);
+  if (has_style) detail::reset_color<Char>(buf);
+}
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename S, typename Char = char_t<S>>
+void vprint(std::FILE* f, const text_style& ts, const S& format,
+            basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buf;
+  detail::vformat_to(buf, ts, to_string_view(format), args);
+  buf.push_back(Char(0));
+  detail::fputs(buf.data(), f);
+}
+
+/**
+  \rst
+  Formats a string and prints it to the specified file stream using ANSI
+  escape sequences to specify text formatting.
+
+  **Example**::
+
+    fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
+               "Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+void print(std::FILE* f, const text_style& ts, const S& format_str,
+           const Args&... args) {
+  vprint(f, ts, format_str,
+         fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+/**
+  \rst
+  Formats a string and prints it to stdout using ANSI escape sequences to
+  specify text formatting.
+
+  **Example**::
+
+    fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
+               "Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+void print(const text_style& ts, const S& format_str, const Args&... args) {
+  return print(stdout, ts, format_str, args...);
+}
+
+template <typename S, typename Char = char_t<S>>
+inline std::basic_string<Char> vformat(
+    const text_style& ts, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buf;
+  detail::vformat_to(buf, ts, to_string_view(format_str), args);
+  return fmt::to_string(buf);
+}
+
+/**
+  \rst
+  Formats arguments and returns the result as a string using ANSI
+  escape sequences to specify text formatting.
+
+  **Example**::
+
+    #include <fmt/color.h>
+    std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
+                                      "The answer is {}", 42);
+  \endrst
+*/
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
+                                      const Args&... args) {
+  return vformat(ts, to_string_view(format_str),
+                 fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+/**
+  Formats a string with the given text_style and writes the output to ``out``.
+ */
+template <typename OutputIt, typename Char,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
+OutputIt vformat_to(
+    OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
+  detail::vformat_to(buf, ts, format_str, args);
+  return detail::get_iterator(buf);
+}
+
+/**
+  \rst
+  Formats arguments with the given text_style, writes the result to the output
+  iterator ``out`` and returns the iterator past the end of the output range.
+
+  **Example**::
+
+    std::vector<char> out;
+    fmt::format_to(std::back_inserter(out),
+                   fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
+  \endrst
+*/
+template <typename OutputIt, typename S, typename... Args,
+          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
+              detail::is_string<S>::value>
+inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
+                      Args&&... args) ->
+    typename std::enable_if<enable, OutputIt>::type {
+  return vformat_to(out, ts, to_string_view(format_str),
+                    fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_COLOR_H_
diff --git a/libs/fmt/include/fmt/compile.h b/libs/fmt/include/fmt/compile.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ff2fe866077edd77c0b335a77f91b6f8c0226a7
--- /dev/null
+++ b/libs/fmt/include/fmt/compile.h
@@ -0,0 +1,642 @@
+// Formatting library for C++ - experimental format string compilation
+//
+// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_COMPILE_H_
+#define FMT_COMPILE_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+// An output iterator that counts the number of objects written to it and
+// discards them.
+class counting_iterator {
+ private:
+  size_t count_;
+
+ public:
+  using iterator_category = std::output_iterator_tag;
+  using difference_type = std::ptrdiff_t;
+  using pointer = void;
+  using reference = void;
+  using _Unchecked_type = counting_iterator;  // Mark iterator as checked.
+
+  struct value_type {
+    template <typename T> void operator=(const T&) {}
+  };
+
+  counting_iterator() : count_(0) {}
+
+  size_t count() const { return count_; }
+
+  counting_iterator& operator++() {
+    ++count_;
+    return *this;
+  }
+  counting_iterator operator++(int) {
+    auto it = *this;
+    ++*this;
+    return it;
+  }
+
+  friend counting_iterator operator+(counting_iterator it, difference_type n) {
+    it.count_ += static_cast<size_t>(n);
+    return it;
+  }
+
+  value_type operator*() const { return {}; }
+};
+
+template <typename Char, typename InputIt>
+inline counting_iterator copy_str(InputIt begin, InputIt end,
+                                  counting_iterator it) {
+  return it + (end - begin);
+}
+
+template <typename OutputIt> class truncating_iterator_base {
+ protected:
+  OutputIt out_;
+  size_t limit_;
+  size_t count_ = 0;
+
+  truncating_iterator_base() : out_(), limit_(0) {}
+
+  truncating_iterator_base(OutputIt out, size_t limit)
+      : out_(out), limit_(limit) {}
+
+ public:
+  using iterator_category = std::output_iterator_tag;
+  using value_type = typename std::iterator_traits<OutputIt>::value_type;
+  using difference_type = std::ptrdiff_t;
+  using pointer = void;
+  using reference = void;
+  using _Unchecked_type =
+      truncating_iterator_base;  // Mark iterator as checked.
+
+  OutputIt base() const { return out_; }
+  size_t count() const { return count_; }
+};
+
+// An output iterator that truncates the output and counts the number of objects
+// written to it.
+template <typename OutputIt,
+          typename Enable = typename std::is_void<
+              typename std::iterator_traits<OutputIt>::value_type>::type>
+class truncating_iterator;
+
+template <typename OutputIt>
+class truncating_iterator<OutputIt, std::false_type>
+    : public truncating_iterator_base<OutputIt> {
+  mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
+
+ public:
+  using value_type = typename truncating_iterator_base<OutputIt>::value_type;
+
+  truncating_iterator() = default;
+
+  truncating_iterator(OutputIt out, size_t limit)
+      : truncating_iterator_base<OutputIt>(out, limit) {}
+
+  truncating_iterator& operator++() {
+    if (this->count_++ < this->limit_) ++this->out_;
+    return *this;
+  }
+
+  truncating_iterator operator++(int) {
+    auto it = *this;
+    ++*this;
+    return it;
+  }
+
+  value_type& operator*() const {
+    return this->count_ < this->limit_ ? *this->out_ : blackhole_;
+  }
+};
+
+template <typename OutputIt>
+class truncating_iterator<OutputIt, std::true_type>
+    : public truncating_iterator_base<OutputIt> {
+ public:
+  truncating_iterator() = default;
+
+  truncating_iterator(OutputIt out, size_t limit)
+      : truncating_iterator_base<OutputIt>(out, limit) {}
+
+  template <typename T> truncating_iterator& operator=(T val) {
+    if (this->count_++ < this->limit_) *this->out_++ = val;
+    return *this;
+  }
+
+  truncating_iterator& operator++() { return *this; }
+  truncating_iterator& operator++(int) { return *this; }
+  truncating_iterator& operator*() { return *this; }
+};
+
+// A compile-time string which is compiled into fast formatting code.
+class compiled_string {};
+
+template <typename S>
+struct is_compiled_string : std::is_base_of<compiled_string, S> {};
+
+/**
+  \rst
+  Converts a string literal *s* into a format string that will be parsed at
+  compile time and converted into efficient formatting code. Requires C++17
+  ``constexpr if`` compiler support.
+
+  **Example**::
+
+    // Converts 42 into std::string using the most efficient method and no
+    // runtime format string processing.
+    std::string s = fmt::format(FMT_COMPILE("{}"), 42);
+  \endrst
+ */
+#ifdef __cpp_if_constexpr
+#  define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
+#else
+#  define FMT_COMPILE(s) FMT_STRING(s)
+#endif
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename Char, size_t N, fixed_string<Char, N> Str>
+struct udl_compiled_string : compiled_string {
+  using char_type = Char;
+  constexpr operator basic_string_view<char_type>() const {
+    return {Str.data, N - 1};
+  }
+};
+#endif
+
+template <typename T, typename... Tail>
+const T& first(const T& value, const Tail&...) {
+  return value;
+}
+
+#ifdef __cpp_if_constexpr
+template <typename... Args> struct type_list {};
+
+// Returns a reference to the argument at index N from [first, rest...].
+template <int N, typename T, typename... Args>
+constexpr const auto& get([[maybe_unused]] const T& first,
+                          [[maybe_unused]] const Args&... rest) {
+  static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
+  if constexpr (N == 0)
+    return first;
+  else
+    return get<N - 1>(rest...);
+}
+
+constexpr int invalid_arg_index = -1;
+
+template <int N, typename T, typename... Args, typename Char>
+constexpr int get_arg_index_by_name(basic_string_view<Char> name) {
+  if constexpr (detail::is_statically_named_arg<T>()) {
+    if (name == T::name) return N;
+  }
+  if constexpr (sizeof...(Args) == 0) {
+    return invalid_arg_index;
+  } else {
+    return get_arg_index_by_name<N + 1, Args...>(name);
+  }
+}
+
+template <typename Char, typename... Args>
+constexpr int get_arg_index_by_name(basic_string_view<Char> name,
+                                    type_list<Args...>) {
+  return get_arg_index_by_name<0, Args...>(name);
+}
+
+template <int N, typename> struct get_type_impl;
+
+template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
+  using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
+};
+
+template <int N, typename T>
+using get_type = typename get_type_impl<N, T>::type;
+
+template <typename T> struct is_compiled_format : std::false_type {};
+
+template <typename Char> struct text {
+  basic_string_view<Char> data;
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&...) const {
+    return write<Char>(out, data);
+  }
+};
+
+template <typename Char>
+struct is_compiled_format<text<Char>> : std::true_type {};
+
+template <typename Char>
+constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
+                               size_t size) {
+  return {{&s[pos], size}};
+}
+
+template <typename Char> struct code_unit {
+  Char value;
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&...) const {
+    return write<Char>(out, value);
+  }
+};
+
+// This ensures that the argument type is convertible to `const T&`.
+template <typename T, int N, typename... Args>
+constexpr const T& get_arg_checked(const Args&... args) {
+  const auto& arg = get<N>(args...);
+  if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
+    return arg.value;
+  } else {
+    return arg;
+  }
+}
+
+template <typename Char>
+struct is_compiled_format<code_unit<Char>> : std::true_type {};
+
+// A replacement field that refers to argument N.
+template <typename Char, typename T, int N> struct field {
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    return write<Char>(out, get_arg_checked<T, N>(args...));
+  }
+};
+
+template <typename Char, typename T, int N>
+struct is_compiled_format<field<Char, T, N>> : std::true_type {};
+
+// A replacement field that refers to argument with name.
+template <typename Char> struct runtime_named_field {
+  using char_type = Char;
+  basic_string_view<Char> name;
+
+  template <typename OutputIt, typename T>
+  constexpr static bool try_format_argument(
+      OutputIt& out,
+      // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
+      [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
+    if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
+      if (arg_name == arg.name) {
+        out = write<Char>(out, arg.value);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    bool found = (try_format_argument(out, name, args) || ...);
+    if (!found) {
+      throw format_error("argument with specified name is not found");
+    }
+    return out;
+  }
+};
+
+template <typename Char>
+struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
+
+// A replacement field that refers to argument N and has format specifiers.
+template <typename Char, typename T, int N> struct spec_field {
+  using char_type = Char;
+  formatter<T, Char> fmt;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    const auto& vargs =
+        make_format_args<basic_format_context<OutputIt, Char>>(args...);
+    basic_format_context<OutputIt, Char> ctx(out, vargs);
+    return fmt.format(get_arg_checked<T, N>(args...), ctx);
+  }
+};
+
+template <typename Char, typename T, int N>
+struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
+
+template <typename L, typename R> struct concat {
+  L lhs;
+  R rhs;
+  using char_type = typename L::char_type;
+
+  template <typename OutputIt, typename... Args>
+  constexpr OutputIt format(OutputIt out, const Args&... args) const {
+    out = lhs.format(out, args...);
+    return rhs.format(out, args...);
+  }
+};
+
+template <typename L, typename R>
+struct is_compiled_format<concat<L, R>> : std::true_type {};
+
+template <typename L, typename R>
+constexpr concat<L, R> make_concat(L lhs, R rhs) {
+  return {lhs, rhs};
+}
+
+struct unknown_format {};
+
+template <typename Char>
+constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
+  for (size_t size = str.size(); pos != size; ++pos) {
+    if (str[pos] == '{' || str[pos] == '}') break;
+  }
+  return pos;
+}
+
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str);
+
+template <typename Args, size_t POS, int ID, typename T, typename S>
+constexpr auto parse_tail(T head, S format_str) {
+  if constexpr (POS !=
+                basic_string_view<typename S::char_type>(format_str).size()) {
+    constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
+    if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
+                               unknown_format>())
+      return tail;
+    else
+      return make_concat(head, tail);
+  } else {
+    return head;
+  }
+}
+
+template <typename T, typename Char> struct parse_specs_result {
+  formatter<T, Char> fmt;
+  size_t end;
+  int next_arg_id;
+};
+
+constexpr int manual_indexing_id = -1;
+
+template <typename T, typename Char>
+constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
+                                                  size_t pos, int next_arg_id) {
+  str.remove_prefix(pos);
+  auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
+  auto f = formatter<T, Char>();
+  auto end = f.parse(ctx);
+  return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
+          next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
+}
+
+template <typename Char> struct arg_id_handler {
+  constexpr void on_error(const char* message) { throw format_error(message); }
+
+  constexpr int on_arg_id() {
+    FMT_ASSERT(false, "handler cannot be used with automatic indexing");
+    return 0;
+  }
+
+  constexpr int on_arg_id(int id) {
+    arg_id = arg_ref<Char>(id);
+    return 0;
+  }
+
+  constexpr int on_arg_id(basic_string_view<Char> id) {
+    arg_id = arg_ref<Char>(id);
+    return 0;
+  }
+
+  arg_ref<Char> arg_id;
+};
+
+template <typename Char> struct parse_arg_id_result {
+  arg_ref<Char> arg_id;
+  const Char* arg_id_end;
+};
+
+template <int ID, typename Char>
+constexpr auto parse_arg_id(const Char* begin, const Char* end) {
+  auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
+  auto adapter = id_adapter<arg_id_handler<Char>, Char>{handler, 0};
+  auto arg_id_end = parse_arg_id(begin, end, adapter);
+  return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
+}
+
+template <typename T, typename Enable = void> struct field_type {
+  using type = remove_cvref_t<T>;
+};
+
+template <typename T>
+struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
+  using type = remove_cvref_t<decltype(T::value)>;
+};
+
+template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
+          typename S>
+constexpr auto parse_replacement_field_then_tail(S format_str) {
+  using char_type = typename S::char_type;
+  constexpr basic_string_view<char_type> str = format_str;
+  constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
+  if constexpr (c == '}') {
+    return parse_tail<Args, END_POS + 1, NEXT_ID>(
+        field<char_type, typename field_type<T>::type, ARG_INDEX>(),
+        format_str);
+  } else if constexpr (c == ':') {
+    constexpr auto result = parse_specs<typename field_type<T>::type>(
+        str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
+    return parse_tail<Args, result.end, result.next_arg_id>(
+        spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
+            result.fmt},
+        format_str);
+  }
+}
+
+// Compiles a non-empty format string and returns the compiled representation
+// or unknown_format() on unrecognized input.
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str) {
+  using char_type = typename S::char_type;
+  constexpr basic_string_view<char_type> str = format_str;
+  if constexpr (str[POS] == '{') {
+    if constexpr (POS + 1 == str.size())
+      throw format_error("unmatched '{' in format string");
+    if constexpr (str[POS + 1] == '{') {
+      return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+    } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
+      static_assert(ID != manual_indexing_id,
+                    "cannot switch from manual to automatic argument indexing");
+      constexpr auto next_id =
+          ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
+      return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
+                                               POS + 1, ID, next_id>(
+          format_str);
+    } else {
+      constexpr auto arg_id_result =
+          parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
+      constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
+      constexpr char_type c =
+          arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
+      static_assert(c == '}' || c == ':', "missing '}' in format string");
+      if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
+        static_assert(
+            ID == manual_indexing_id || ID == 0,
+            "cannot switch from automatic to manual argument indexing");
+        constexpr auto arg_index = arg_id_result.arg_id.val.index;
+        return parse_replacement_field_then_tail<get_type<arg_index, Args>,
+                                                 Args, arg_id_end_pos,
+                                                 arg_index, manual_indexing_id>(
+            format_str);
+      } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
+        constexpr auto arg_index =
+            get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
+        if constexpr (arg_index != invalid_arg_index) {
+          constexpr auto next_id =
+              ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
+          return parse_replacement_field_then_tail<
+              decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
+              arg_index, next_id>(format_str);
+        } else {
+          if constexpr (c == '}') {
+            return parse_tail<Args, arg_id_end_pos + 1, ID>(
+                runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
+                format_str);
+          } else if constexpr (c == ':') {
+            return unknown_format();  // no type info for specs parsing
+          }
+        }
+      }
+    }
+  } else if constexpr (str[POS] == '}') {
+    if constexpr (POS + 1 == str.size())
+      throw format_error("unmatched '}' in format string");
+    return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+  } else {
+    constexpr auto end = parse_text(str, POS + 1);
+    if constexpr (end - POS > 1) {
+      return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
+                                       format_str);
+    } else {
+      return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
+                                       format_str);
+    }
+  }
+}
+
+template <typename... Args, typename S,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+constexpr auto compile(S format_str) {
+  constexpr basic_string_view<typename S::char_type> str = format_str;
+  if constexpr (str.size() == 0) {
+    return detail::make_text(str, 0, 0);
+  } else {
+    constexpr auto result =
+        detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
+            format_str);
+    return result;
+  }
+}
+#endif  // __cpp_if_constexpr
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+#ifdef __cpp_if_constexpr
+
+template <typename CompiledFormat, typename... Args,
+          typename Char = typename CompiledFormat::char_type,
+          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
+FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
+                                          const Args&... args) {
+  basic_memory_buffer<Char> buffer;
+  cf.format(detail::buffer_appender<Char>(buffer), args...);
+  return to_string(buffer);
+}
+
+template <typename OutputIt, typename CompiledFormat, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
+constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf,
+                             const Args&... args) {
+  return cf.format(out, args...);
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
+                                                           Args&&... args) {
+  if constexpr (std::is_same<typename S::char_type, char>::value) {
+    constexpr basic_string_view<typename S::char_type> str = S();
+    if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
+      const auto& first = detail::first(args...);
+      if constexpr (detail::is_named_arg<
+                        remove_cvref_t<decltype(first)>>::value) {
+        return fmt::to_string(first.value);
+      } else {
+        return fmt::to_string(first);
+      }
+    }
+  }
+  constexpr auto compiled = detail::compile<Args...>(S());
+  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
+                             detail::unknown_format>()) {
+    return format(static_cast<basic_string_view<typename S::char_type>>(S()),
+                  std::forward<Args>(args)...);
+  } else {
+    return format(compiled, std::forward<Args>(args)...);
+  }
+}
+
+template <typename OutputIt, typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
+  constexpr auto compiled = detail::compile<Args...>(S());
+  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
+                             detail::unknown_format>()) {
+    return format_to(out,
+                     static_cast<basic_string_view<typename S::char_type>>(S()),
+                     std::forward<Args>(args)...);
+  } else {
+    return format_to(out, compiled, std::forward<Args>(args)...);
+  }
+}
+#endif
+
+template <typename OutputIt, typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
+                                         const S& format_str, Args&&... args) {
+  auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
+                      std::forward<Args>(args)...);
+  return {it.base(), it.count()};
+}
+
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
+size_t formatted_size(const S& format_str, const Args&... args) {
+  return format_to(detail::counting_iterator(), format_str, args...).count();
+}
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+inline namespace literals {
+template <detail::fixed_string Str>
+constexpr detail::udl_compiled_string<
+    remove_cvref_t<decltype(Str.data[0])>,
+    sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
+operator""_cf() {
+  return {};
+}
+}  // namespace literals
+#endif
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_COMPILE_H_
diff --git a/libs/fmt/include/fmt/core.h b/libs/fmt/include/fmt/core.h
new file mode 100644
index 0000000000000000000000000000000000000000..10ac28438e0e36d6d1e7904605d280498ad25f35
--- /dev/null
+++ b/libs/fmt/include/fmt/core.h
@@ -0,0 +1,2531 @@
+// Formatting library for C++ - the core API
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_CORE_H_
+#define FMT_CORE_H_
+
+#include <climits>  // INT_MAX
+#include <cstdio>   // std::FILE
+#include <cstring>
+#include <iterator>
+#include <string>
+#include <type_traits>
+
+// The fmt library version in the form major * 10000 + minor * 100 + patch.
+#define FMT_VERSION 70104
+
+#ifdef __clang__
+#  define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
+#else
+#  define FMT_CLANG_VERSION 0
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+#  define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+#  define FMT_GCC_PRAGMA(arg) _Pragma(arg)
+#else
+#  define FMT_GCC_VERSION 0
+#  define FMT_GCC_PRAGMA(arg)
+#endif
+
+#if defined(__INTEL_COMPILER)
+#  define FMT_ICC_VERSION __INTEL_COMPILER
+#else
+#  define FMT_ICC_VERSION 0
+#endif
+
+#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
+#  define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION
+#else
+#  define FMT_HAS_GXX_CXX11 0
+#endif
+
+// Check if constexpr std::char_traits<>::compare,length is supported.
+//
+// libstdc++: GCC 7 and newer and __cplusplus >= 201703L
+// MSVC     : VS 2017 15.7 and newer and /std:c++17
+// https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance
+// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
+// libc++   : 4.0 and newer and if __cplusplus >= 201703L
+// https://libcxx.llvm.org/docs/Cxx1zStatus.html
+//
+// NOTE: FMT_GCC_VERSION  - is not libstdc++ version.
+//       _GLIBCXX_RELEASE - is present in GCC 7 libstdc++ and newer.
+#if defined(_MSC_VER)
+#  if _MSVC_LANG >= 201703L && _MSC_VER >= 1914
+#    define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#  endif
+#elif defined(_LIBCPP_VERSION)
+#  if __cplusplus >= 201703L && _LIBCPP_VERSION >= 4000
+#    define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#  endif
+#elif defined(__GLIBCXX__)
+#  if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
+      _GLIBCXX_RELEASE >= 7
+#    define FMT_CONSTEXPR_CHAR_TRAITS constexpr
+#  endif
+#endif
+#ifndef FMT_CONSTEXPR_CHAR_TRAITS
+#  define FMT_CONSTEXPR_CHAR_TRAITS
+#endif
+
+#ifdef __NVCC__
+#  define FMT_NVCC __NVCC__
+#else
+#  define FMT_NVCC 0
+#endif
+
+#ifdef _MSC_VER
+#  define FMT_MSC_VER _MSC_VER
+#  define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__))
+#else
+#  define FMT_MSC_VER 0
+#  define FMT_MSC_WARNING(...)
+#endif
+
+#ifdef __has_feature
+#  define FMT_HAS_FEATURE(x) __has_feature(x)
+#else
+#  define FMT_HAS_FEATURE(x) 0
+#endif
+
+#if defined(__has_include) && !defined(__INTELLISENSE__) && \
+    (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
+#  define FMT_HAS_INCLUDE(x) __has_include(x)
+#else
+#  define FMT_HAS_INCLUDE(x) 0
+#endif
+
+#ifdef __has_cpp_attribute
+#  define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+#  define FMT_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \
+  (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+
+#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \
+  (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+
+// Check if relaxed C++14 constexpr is supported.
+// GCC doesn't allow throw in constexpr until version 6 (bug 67371).
+#ifndef FMT_USE_CONSTEXPR
+#  define FMT_USE_CONSTEXPR                                           \
+    (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \
+     (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) &&           \
+        !FMT_NVCC && !FMT_ICC_VERSION
+#endif
+#if FMT_USE_CONSTEXPR
+#  define FMT_CONSTEXPR constexpr
+#  define FMT_CONSTEXPR_DECL constexpr
+#else
+#  define FMT_CONSTEXPR
+#  define FMT_CONSTEXPR_DECL
+#endif
+
+#ifndef FMT_OVERRIDE
+#  if FMT_HAS_FEATURE(cxx_override_control) || \
+      (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+#    define FMT_OVERRIDE override
+#  else
+#    define FMT_OVERRIDE
+#  endif
+#endif
+
+// Check if exceptions are disabled.
+#ifndef FMT_EXCEPTIONS
+#  if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \
+      FMT_MSC_VER && !_HAS_EXCEPTIONS
+#    define FMT_EXCEPTIONS 0
+#  else
+#    define FMT_EXCEPTIONS 1
+#  endif
+#endif
+
+// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature).
+#ifndef FMT_USE_NOEXCEPT
+#  define FMT_USE_NOEXCEPT 0
+#endif
+
+#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \
+    (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+#  define FMT_DETECTED_NOEXCEPT noexcept
+#  define FMT_HAS_CXX11_NOEXCEPT 1
+#else
+#  define FMT_DETECTED_NOEXCEPT throw()
+#  define FMT_HAS_CXX11_NOEXCEPT 0
+#endif
+
+#ifndef FMT_NOEXCEPT
+#  if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT
+#    define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT
+#  else
+#    define FMT_NOEXCEPT
+#  endif
+#endif
+
+// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code
+// warnings.
+#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \
+    !FMT_NVCC
+#  define FMT_NORETURN [[noreturn]]
+#else
+#  define FMT_NORETURN
+#endif
+
+#ifndef FMT_MAYBE_UNUSED
+#  if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
+#    define FMT_MAYBE_UNUSED [[maybe_unused]]
+#  else
+#    define FMT_MAYBE_UNUSED
+#  endif
+#endif
+
+#ifndef FMT_DEPRECATED
+#  if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
+#    define FMT_DEPRECATED [[deprecated]]
+#  else
+#    if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
+#      define FMT_DEPRECATED __attribute__((deprecated))
+#    elif FMT_MSC_VER
+#      define FMT_DEPRECATED __declspec(deprecated)
+#    else
+#      define FMT_DEPRECATED /* deprecated */
+#    endif
+#  endif
+#endif
+
+// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers.
+#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC
+#  define FMT_DEPRECATED_ALIAS
+#else
+#  define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
+#endif
+
+#ifndef FMT_INLINE
+#  if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#    define FMT_INLINE inline __attribute__((always_inline))
+#  else
+#    define FMT_INLINE inline
+#  endif
+#endif
+
+#ifndef FMT_USE_INLINE_NAMESPACES
+#  if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
+      (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED))
+#    define FMT_USE_INLINE_NAMESPACES 1
+#  else
+#    define FMT_USE_INLINE_NAMESPACES 0
+#  endif
+#endif
+
+#ifndef FMT_BEGIN_NAMESPACE
+#  if FMT_USE_INLINE_NAMESPACES
+#    define FMT_INLINE_NAMESPACE inline namespace
+#    define FMT_END_NAMESPACE \
+      }                       \
+      }
+#  else
+#    define FMT_INLINE_NAMESPACE namespace
+#    define FMT_END_NAMESPACE \
+      }                       \
+      using namespace v7;     \
+      }
+#  endif
+#  define FMT_BEGIN_NAMESPACE \
+    namespace fmt {           \
+    FMT_INLINE_NAMESPACE v7 {
+#endif
+
+#ifndef FMT_MODULE_EXPORT
+#  define FMT_MODULE_EXPORT
+#endif
+#ifndef FMT_MODULE_EXPORT_BEGIN
+#  define FMT_MODULE_EXPORT_BEGIN
+#endif
+#ifndef FMT_MODULE_EXPORT_END
+#  define FMT_MODULE_EXPORT_END
+#endif
+
+#if !defined(FMT_HEADER_ONLY) && defined(_WIN32)
+#  define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275)
+#  ifdef FMT_EXPORT
+#    define FMT_API __declspec(dllexport)
+#  elif defined(FMT_SHARED)
+#    define FMT_API __declspec(dllimport)
+#  endif
+#else
+#  define FMT_CLASS_API
+#endif
+#ifndef FMT_API
+#  define FMT_API
+#endif
+
+#if FMT_GCC_VERSION
+#  define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
+#else
+#  define FMT_GCC_VISIBILITY_HIDDEN
+#endif
+
+// libc++ supports string_view in pre-c++17.
+#if (FMT_HAS_INCLUDE(<string_view>) &&                       \
+     (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \
+    (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
+#  include <string_view>
+#  define FMT_USE_STRING_VIEW
+#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L
+#  include <experimental/string_view>
+#  define FMT_USE_EXPERIMENTAL_STRING_VIEW
+#endif
+
+#ifndef FMT_UNICODE
+#  define FMT_UNICODE !FMT_MSC_VER
+#endif
+
+#ifndef FMT_COMPILE_TIME_CHECKS
+#  define FMT_COMPILE_TIME_CHECKS 0
+#endif
+
+// Enable minimal optimizations for more compact code in debug mode.
+FMT_GCC_PRAGMA("GCC push_options")
+#ifndef __OPTIMIZE__
+FMT_GCC_PRAGMA("GCC optimize(\"Og\")")
+#endif
+
+FMT_BEGIN_NAMESPACE
+FMT_MODULE_EXPORT_BEGIN
+
+// Implementations of enable_if_t and other metafunctions for older systems.
+template <bool B, class T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+template <bool B, class T, class F>
+using conditional_t = typename std::conditional<B, T, F>::type;
+template <bool B> using bool_constant = std::integral_constant<bool, B>;
+template <typename T>
+using remove_reference_t = typename std::remove_reference<T>::type;
+template <typename T>
+using remove_const_t = typename std::remove_const<T>::type;
+template <typename T>
+using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;
+template <typename T> struct type_identity { using type = T; };
+template <typename T> using type_identity_t = typename type_identity<T>::type;
+
+struct monostate {};
+
+FMT_MODULE_EXPORT_END
+
+// An enable_if helper to be used in template parameters which results in much
+// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed
+// to workaround a bug in MSVC 2019 (see #1140 and #1186).
+#ifdef FMT_DOC
+#  define FMT_ENABLE_IF(...)
+#else
+#  define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0
+#endif
+
+namespace detail {
+
+constexpr FMT_INLINE bool is_constant_evaluated() FMT_NOEXCEPT {
+#ifdef __cpp_lib_is_constant_evaluated
+  return std::is_constant_evaluated();
+#else
+  return false;
+#endif
+}
+
+// A helper function to suppress "conditional expression is constant" warnings.
+template <typename T> constexpr T const_check(T value) { return value; }
+
+FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
+                                      const char* message);
+
+#ifndef FMT_ASSERT
+#  ifdef NDEBUG
+// FMT_ASSERT is not empty to avoid -Werror=empty-body.
+#    define FMT_ASSERT(condition, message) ((void)0)
+#  else
+#    define FMT_ASSERT(condition, message)                                    \
+      ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
+           ? (void)0                                                          \
+           : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message)))
+#  endif
+#endif
+
+#if defined(FMT_USE_STRING_VIEW)
+template <typename Char> using std_string_view = std::basic_string_view<Char>;
+#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
+template <typename Char>
+using std_string_view = std::experimental::basic_string_view<Char>;
+#else
+template <typename T> struct std_string_view {};
+#endif
+
+#ifdef FMT_USE_INT128
+// Do nothing.
+#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \
+    !(FMT_CLANG_VERSION && FMT_MSC_VER)
+#  define FMT_USE_INT128 1
+using int128_t = __int128_t;
+using uint128_t = __uint128_t;
+#else
+#  define FMT_USE_INT128 0
+#endif
+#if !FMT_USE_INT128
+struct int128_t {};
+struct uint128_t {};
+#endif
+
+// Casts a nonnegative integer to unsigned.
+template <typename Int>
+FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) {
+  FMT_ASSERT(value >= 0, "negative value");
+  return static_cast<typename std::make_unsigned<Int>::type>(value);
+}
+
+FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5";
+
+template <typename Char> constexpr bool is_unicode() {
+  return FMT_UNICODE || sizeof(Char) != 1 ||
+         (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5);
+}
+}  // namespace detail
+
+/**
+  An implementation of ``std::basic_string_view`` for pre-C++17. It provides a
+  subset of the API. ``fmt::basic_string_view`` is used for format strings even
+  if ``std::string_view`` is available to prevent issues when a library is
+  compiled with a different ``-std`` option than the client code (which is not
+  recommended).
+ */
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename Char> class basic_string_view {
+ private:
+  const Char* data_;
+  size_t size_;
+
+ public:
+  using value_type = Char;
+  using iterator = const Char*;
+
+  constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {}
+
+  /** Constructs a string reference object from a C string and a size. */
+  constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT
+      : data_(s),
+        size_(count) {}
+
+  /**
+    \rst
+    Constructs a string reference object from a C string computing
+    the size with ``std::char_traits<Char>::length``.
+    \endrst
+   */
+  FMT_CONSTEXPR_CHAR_TRAITS
+  FMT_INLINE
+  basic_string_view(const Char* s) : data_(s) {
+    if (detail::const_check(std::is_same<Char, char>::value &&
+                            !detail::is_constant_evaluated()))
+      size_ = std::strlen(reinterpret_cast<const char*>(s));
+    else
+      size_ = std::char_traits<Char>::length(s);
+  }
+
+  /** Constructs a string reference from a ``std::basic_string`` object. */
+  template <typename Traits, typename Alloc>
+  FMT_CONSTEXPR basic_string_view(
+      const std::basic_string<Char, Traits, Alloc>& s) FMT_NOEXCEPT
+      : data_(s.data()),
+        size_(s.size()) {}
+
+  template <typename S, FMT_ENABLE_IF(std::is_same<
+                                      S, detail::std_string_view<Char>>::value)>
+  FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()),
+                                                      size_(s.size()) {}
+
+  /** Returns a pointer to the string data. */
+  constexpr const Char* data() const { return data_; }
+
+  /** Returns the string size. */
+  constexpr size_t size() const { return size_; }
+
+  constexpr iterator begin() const { return data_; }
+  constexpr iterator end() const { return data_ + size_; }
+
+  constexpr const Char& operator[](size_t pos) const { return data_[pos]; }
+
+  FMT_CONSTEXPR void remove_prefix(size_t n) {
+    data_ += n;
+    size_ -= n;
+  }
+
+  // Lexicographically compare this string reference to other.
+  FMT_CONSTEXPR_CHAR_TRAITS int compare(basic_string_view other) const {
+    size_t str_size = size_ < other.size_ ? size_ : other.size_;
+    int result = std::char_traits<Char>::compare(data_, other.data_, str_size);
+    if (result == 0)
+      result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);
+    return result;
+  }
+
+  FMT_CONSTEXPR_CHAR_TRAITS friend bool operator==(basic_string_view lhs,
+                                                   basic_string_view rhs) {
+    return lhs.compare(rhs) == 0;
+  }
+  friend bool operator!=(basic_string_view lhs, basic_string_view rhs) {
+    return lhs.compare(rhs) != 0;
+  }
+  friend bool operator<(basic_string_view lhs, basic_string_view rhs) {
+    return lhs.compare(rhs) < 0;
+  }
+  friend bool operator<=(basic_string_view lhs, basic_string_view rhs) {
+    return lhs.compare(rhs) <= 0;
+  }
+  friend bool operator>(basic_string_view lhs, basic_string_view rhs) {
+    return lhs.compare(rhs) > 0;
+  }
+  friend bool operator>=(basic_string_view lhs, basic_string_view rhs) {
+    return lhs.compare(rhs) >= 0;
+  }
+};
+
+using string_view = basic_string_view<char>;
+using wstring_view = basic_string_view<wchar_t>;
+
+/** Specifies if ``T`` is a character type. Can be specialized by users. */
+template <typename T> struct is_char : std::false_type {};
+template <> struct is_char<char> : std::true_type {};
+template <> struct is_char<wchar_t> : std::true_type {};
+
+/**
+  \rst
+  Returns a string view of `s`. In order to add custom string type support to
+  {fmt} provide an overload of `to_string_view` for it in the same namespace as
+  the type for the argument-dependent lookup to work.
+
+  **Example**::
+
+    namespace my_ns {
+    inline string_view to_string_view(const my_string& s) {
+      return {s.data(), s.length()};
+    }
+    }
+    std::string message = fmt::format(my_string("The answer is {}"), 42);
+  \endrst
+ */
+template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>
+FMT_INLINE basic_string_view<Char> to_string_view(const Char* s) {
+  return s;
+}
+
+template <typename Char, typename Traits, typename Alloc>
+inline basic_string_view<Char> to_string_view(
+    const std::basic_string<Char, Traits, Alloc>& s) {
+  return s;
+}
+
+template <typename Char>
+constexpr basic_string_view<Char> to_string_view(basic_string_view<Char> s) {
+  return s;
+}
+
+template <typename Char,
+          FMT_ENABLE_IF(!std::is_empty<detail::std_string_view<Char>>::value)>
+inline basic_string_view<Char> to_string_view(detail::std_string_view<Char> s) {
+  return s;
+}
+
+// A base class for compile-time strings. It is defined in the fmt namespace to
+// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42).
+struct compile_string {};
+
+template <typename S>
+struct is_compile_string : std::is_base_of<compile_string, S> {};
+
+template <typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
+constexpr basic_string_view<typename S::char_type> to_string_view(const S& s) {
+  return s;
+}
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+void to_string_view(...);
+using fmt::v7::to_string_view;
+
+// Specifies whether S is a string type convertible to fmt::basic_string_view.
+// It should be a constexpr function but MSVC 2017 fails to compile it in
+// enable_if and MSVC 2015 fails to compile it as an alias template.
+template <typename S>
+struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> {
+};
+
+template <typename S, typename = void> struct char_t_impl {};
+template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> {
+  using result = decltype(to_string_view(std::declval<S>()));
+  using type = typename result::value_type;
+};
+
+// Reports a compile-time error if S is not a valid format string.
+template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
+FMT_INLINE void check_format_string(const S&) {
+#ifdef FMT_ENFORCE_COMPILE_STRING
+  static_assert(is_compile_string<S>::value,
+                "FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
+                "FMT_STRING.");
+#endif
+}
+template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
+void check_format_string(S);
+
+struct error_handler {
+  constexpr error_handler() = default;
+  constexpr error_handler(const error_handler&) = default;
+
+  // This function is intentionally not constexpr to give a compile-time error.
+  FMT_NORETURN FMT_API void on_error(const char* message);
+};
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+/** String's character type. */
+template <typename S> using char_t = typename detail::char_t_impl<S>::type;
+
+/**
+  \rst
+  Parsing context consisting of a format string range being parsed and an
+  argument counter for automatic indexing.
+
+  You can use one of the following type aliases for common character types:
+
+  +-----------------------+-------------------------------------+
+  | Type                  | Definition                          |
+  +=======================+=====================================+
+  | format_parse_context  | basic_format_parse_context<char>    |
+  +-----------------------+-------------------------------------+
+  | wformat_parse_context | basic_format_parse_context<wchar_t> |
+  +-----------------------+-------------------------------------+
+  \endrst
+ */
+template <typename Char, typename ErrorHandler = detail::error_handler>
+class basic_format_parse_context : private ErrorHandler {
+ private:
+  basic_string_view<Char> format_str_;
+  int next_arg_id_;
+
+ public:
+  using char_type = Char;
+  using iterator = typename basic_string_view<Char>::iterator;
+
+  explicit constexpr basic_format_parse_context(
+      basic_string_view<Char> format_str, ErrorHandler eh = {},
+      int next_arg_id = 0)
+      : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {}
+
+  /**
+    Returns an iterator to the beginning of the format string range being
+    parsed.
+   */
+  constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); }
+
+  /**
+    Returns an iterator past the end of the format string range being parsed.
+   */
+  constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); }
+
+  /** Advances the begin iterator to ``it``. */
+  FMT_CONSTEXPR void advance_to(iterator it) {
+    format_str_.remove_prefix(detail::to_unsigned(it - begin()));
+  }
+
+  /**
+    Reports an error if using the manual argument indexing; otherwise returns
+    the next argument index and switches to the automatic indexing.
+   */
+  FMT_CONSTEXPR int next_arg_id() {
+    // Don't check if the argument id is valid to avoid overhead and because it
+    // will be checked during formatting anyway.
+    if (next_arg_id_ >= 0) return next_arg_id_++;
+    on_error("cannot switch from manual to automatic argument indexing");
+    return 0;
+  }
+
+  /**
+    Reports an error if using the automatic argument indexing; otherwise
+    switches to the manual indexing.
+   */
+  FMT_CONSTEXPR void check_arg_id(int) {
+    if (next_arg_id_ > 0)
+      on_error("cannot switch from automatic to manual argument indexing");
+    else
+      next_arg_id_ = -1;
+  }
+
+  FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {}
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    ErrorHandler::on_error(message);
+  }
+
+  constexpr ErrorHandler error_handler() const { return *this; }
+};
+
+using format_parse_context = basic_format_parse_context<char>;
+using wformat_parse_context = basic_format_parse_context<wchar_t>;
+
+template <typename Context> class basic_format_arg;
+template <typename Context> class basic_format_args;
+template <typename Context> class dynamic_format_arg_store;
+
+// A formatter for objects of type T.
+template <typename T, typename Char = char, typename Enable = void>
+struct formatter {
+  // A deleted default constructor indicates a disabled formatter.
+  formatter() = delete;
+};
+
+// DEPRECATED!
+// Specifies if T has an enabled formatter specialization. A type can be
+// formattable even if it doesn't have a formatter e.g. via a conversion.
+template <typename T, typename Context>
+using has_formatter =
+    std::is_constructible<typename Context::template formatter_type<T>>;
+
+// Checks whether T is a container with contiguous storage.
+template <typename T> struct is_contiguous : std::false_type {};
+template <typename Char>
+struct is_contiguous<std::basic_string<Char>> : std::true_type {};
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+// Extracts a reference to the container from back_insert_iterator.
+template <typename Container>
+inline Container& get_container(std::back_insert_iterator<Container> it) {
+  using bi_iterator = std::back_insert_iterator<Container>;
+  struct accessor : bi_iterator {
+    accessor(bi_iterator iter) : bi_iterator(iter) {}
+    using bi_iterator::container;
+  };
+  return *accessor(it).container;
+}
+
+/**
+  \rst
+  A contiguous memory buffer with an optional growing ability. It is an internal
+  class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`.
+  \endrst
+ */
+template <typename T> class buffer {
+ private:
+  T* ptr_;
+  size_t size_;
+  size_t capacity_;
+
+ protected:
+  // Don't initialize ptr_ since it is not accessed to save a few cycles.
+  FMT_MSC_WARNING(suppress : 26495)
+  buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {}
+
+  buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT
+      : ptr_(p),
+        size_(sz),
+        capacity_(cap) {}
+
+  ~buffer() = default;
+
+  /** Sets the buffer data and capacity. */
+  void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
+    ptr_ = buf_data;
+    capacity_ = buf_capacity;
+  }
+
+  /** Increases the buffer capacity to hold at least *capacity* elements. */
+  virtual void grow(size_t capacity) = 0;
+
+ public:
+  using value_type = T;
+  using const_reference = const T&;
+
+  buffer(const buffer&) = delete;
+  void operator=(const buffer&) = delete;
+
+  T* begin() FMT_NOEXCEPT { return ptr_; }
+  T* end() FMT_NOEXCEPT { return ptr_ + size_; }
+
+  const T* begin() const FMT_NOEXCEPT { return ptr_; }
+  const T* end() const FMT_NOEXCEPT { return ptr_ + size_; }
+
+  /** Returns the size of this buffer. */
+  size_t size() const FMT_NOEXCEPT { return size_; }
+
+  /** Returns the capacity of this buffer. */
+  size_t capacity() const FMT_NOEXCEPT { return capacity_; }
+
+  /** Returns a pointer to the buffer data. */
+  T* data() FMT_NOEXCEPT { return ptr_; }
+
+  /** Returns a pointer to the buffer data. */
+  const T* data() const FMT_NOEXCEPT { return ptr_; }
+
+  /** Clears this buffer. */
+  void clear() { size_ = 0; }
+
+  // Tries resizing the buffer to contain *count* elements. If T is a POD type
+  // the new elements may not be initialized.
+  void try_resize(size_t count) {
+    try_reserve(count);
+    size_ = count <= capacity_ ? count : capacity_;
+  }
+
+  // Tries increasing the buffer capacity to *new_capacity*. It can increase the
+  // capacity by a smaller amount than requested but guarantees there is space
+  // for at least one additional element either by increasing the capacity or by
+  // flushing the buffer if it is full.
+  void try_reserve(size_t new_capacity) {
+    if (new_capacity > capacity_) grow(new_capacity);
+  }
+
+  void push_back(const T& value) {
+    try_reserve(size_ + 1);
+    ptr_[size_++] = value;
+  }
+
+  /** Appends data to the end of the buffer. */
+  template <typename U> void append(const U* begin, const U* end);
+
+  template <typename I> T& operator[](I index) { return ptr_[index]; }
+  template <typename I> const T& operator[](I index) const {
+    return ptr_[index];
+  }
+};
+
+struct buffer_traits {
+  explicit buffer_traits(size_t) {}
+  size_t count() const { return 0; }
+  size_t limit(size_t size) { return size; }
+};
+
+class fixed_buffer_traits {
+ private:
+  size_t count_ = 0;
+  size_t limit_;
+
+ public:
+  explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
+  size_t count() const { return count_; }
+  size_t limit(size_t size) {
+    size_t n = limit_ > count_ ? limit_ - count_ : 0;
+    count_ += size;
+    return size < n ? size : n;
+  }
+};
+
+// A buffer that writes to an output iterator when flushed.
+template <typename OutputIt, typename T, typename Traits = buffer_traits>
+class iterator_buffer final : public Traits, public buffer<T> {
+ private:
+  OutputIt out_;
+  enum { buffer_size = 256 };
+  T data_[buffer_size];
+
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {
+    if (this->size() == buffer_size) flush();
+  }
+  void flush();
+
+ public:
+  explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
+      : Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {}
+  ~iterator_buffer() { flush(); }
+
+  OutputIt out() {
+    flush();
+    return out_;
+  }
+  size_t count() const { return Traits::count() + this->size(); }
+};
+
+template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {}
+
+ public:
+  explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
+
+  T* out() { return &*this->end(); }
+};
+
+// A buffer that writes to a container with the contiguous storage.
+template <typename Container>
+class iterator_buffer<std::back_insert_iterator<Container>,
+                      enable_if_t<is_contiguous<Container>::value,
+                                  typename Container::value_type>>
+    final : public buffer<typename Container::value_type> {
+ private:
+  Container& container_;
+
+ protected:
+  void grow(size_t capacity) final FMT_OVERRIDE {
+    container_.resize(capacity);
+    this->set(&container_[0], capacity);
+  }
+
+ public:
+  explicit iterator_buffer(Container& c)
+      : buffer<typename Container::value_type>(c.size()), container_(c) {}
+  explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
+      : iterator_buffer(get_container(out)) {}
+  std::back_insert_iterator<Container> out() {
+    return std::back_inserter(container_);
+  }
+};
+
+// A buffer that counts the number of code units written discarding the output.
+template <typename T = char> class counting_buffer final : public buffer<T> {
+ private:
+  enum { buffer_size = 256 };
+  T data_[buffer_size];
+  size_t count_ = 0;
+
+ protected:
+  void grow(size_t) final FMT_OVERRIDE {
+    if (this->size() != buffer_size) return;
+    count_ += this->size();
+    this->clear();
+  }
+
+ public:
+  counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
+
+  size_t count() { return count_ + this->size(); }
+};
+
+// An output iterator that appends to the buffer.
+// It is used to reduce symbol sizes for the common case.
+template <typename T>
+class buffer_appender : public std::back_insert_iterator<buffer<T>> {
+  using base = std::back_insert_iterator<buffer<T>>;
+
+ public:
+  using std::back_insert_iterator<buffer<T>>::back_insert_iterator;
+  buffer_appender(base it) : base(it) {}
+  using _Unchecked_type = buffer_appender;  // Mark iterator as checked.
+
+  buffer_appender& operator++() {
+    base::operator++();
+    return *this;
+  }
+
+  buffer_appender operator++(int) {
+    buffer_appender tmp = *this;
+    ++*this;
+    return tmp;
+  }
+};
+
+// Maps an output iterator into a buffer.
+template <typename T, typename OutputIt>
+iterator_buffer<OutputIt, T> get_buffer(OutputIt);
+template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
+
+template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
+  return out;
+}
+template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
+  return get_container(out);
+}
+
+template <typename Buffer>
+auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
+  return buf.out();
+}
+template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
+  return buffer_appender<T>(buf);
+}
+
+template <typename T, typename Char = char, typename Enable = void>
+struct fallback_formatter {
+  fallback_formatter() = delete;
+};
+
+// Specifies if T has an enabled fallback_formatter specialization.
+template <typename T, typename Char>
+using has_fallback_formatter =
+    std::is_constructible<fallback_formatter<T, Char>>;
+
+struct view {};
+
+template <typename Char, typename T> struct named_arg : view {
+  const Char* name;
+  const T& value;
+  named_arg(const Char* n, const T& v) : name(n), value(v) {}
+};
+
+template <typename Char> struct named_arg_info {
+  const Char* name;
+  int id;
+};
+
+template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
+struct arg_data {
+  // args_[0].named_args points to named_args_ to avoid bloating format_args.
+  // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
+  T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
+  named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
+
+  template <typename... U>
+  arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {}
+  arg_data(const arg_data& other) = delete;
+  const T* args() const { return args_ + 1; }
+  named_arg_info<Char>* named_args() { return named_args_; }
+};
+
+template <typename T, typename Char, size_t NUM_ARGS>
+struct arg_data<T, Char, NUM_ARGS, 0> {
+  // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
+  T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
+
+  template <typename... U>
+  FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {}
+  FMT_CONSTEXPR FMT_INLINE const T* args() const { return args_; }
+  FMT_CONSTEXPR FMT_INLINE std::nullptr_t named_args() { return nullptr; }
+};
+
+template <typename Char>
+inline void init_named_args(named_arg_info<Char>*, int, int) {}
+
+template <typename T> struct is_named_arg : std::false_type {};
+
+template <typename T, typename Char>
+struct is_named_arg<named_arg<Char, T>> : std::true_type {};
+
+template <typename Char, typename T, typename... Tail,
+          FMT_ENABLE_IF(!is_named_arg<T>::value)>
+void init_named_args(named_arg_info<Char>* named_args, int arg_count,
+                     int named_arg_count, const T&, const Tail&... args) {
+  init_named_args(named_args, arg_count + 1, named_arg_count, args...);
+}
+
+template <typename Char, typename T, typename... Tail,
+          FMT_ENABLE_IF(is_named_arg<T>::value)>
+void init_named_args(named_arg_info<Char>* named_args, int arg_count,
+                     int named_arg_count, const T& arg, const Tail&... args) {
+  named_args[named_arg_count++] = {arg.name, arg_count};
+  init_named_args(named_args, arg_count + 1, named_arg_count, args...);
+}
+
+template <typename... Args>
+FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int,
+                                              const Args&...) {}
+
+template <bool B = false> constexpr size_t count() { return B ? 1 : 0; }
+template <bool B1, bool B2, bool... Tail> constexpr size_t count() {
+  return (B1 ? 1 : 0) + count<B2, Tail...>();
+}
+
+template <typename... Args> constexpr size_t count_named_args() {
+  return count<is_named_arg<Args>::value...>();
+}
+
+enum class type {
+  none_type,
+  // Integer types should go first,
+  int_type,
+  uint_type,
+  long_long_type,
+  ulong_long_type,
+  int128_type,
+  uint128_type,
+  bool_type,
+  char_type,
+  last_integer_type = char_type,
+  // followed by floating-point types.
+  float_type,
+  double_type,
+  long_double_type,
+  last_numeric_type = long_double_type,
+  cstring_type,
+  string_type,
+  pointer_type,
+  custom_type
+};
+
+// Maps core type T to the corresponding type enum constant.
+template <typename T, typename Char>
+struct type_constant : std::integral_constant<type, type::custom_type> {};
+
+#define FMT_TYPE_CONSTANT(Type, constant) \
+  template <typename Char>                \
+  struct type_constant<Type, Char>        \
+      : std::integral_constant<type, type::constant> {}
+
+FMT_TYPE_CONSTANT(int, int_type);
+FMT_TYPE_CONSTANT(unsigned, uint_type);
+FMT_TYPE_CONSTANT(long long, long_long_type);
+FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);
+FMT_TYPE_CONSTANT(int128_t, int128_type);
+FMT_TYPE_CONSTANT(uint128_t, uint128_type);
+FMT_TYPE_CONSTANT(bool, bool_type);
+FMT_TYPE_CONSTANT(Char, char_type);
+FMT_TYPE_CONSTANT(float, float_type);
+FMT_TYPE_CONSTANT(double, double_type);
+FMT_TYPE_CONSTANT(long double, long_double_type);
+FMT_TYPE_CONSTANT(const Char*, cstring_type);
+FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);
+FMT_TYPE_CONSTANT(const void*, pointer_type);
+
+constexpr bool is_integral_type(type t) {
+  return t > type::none_type && t <= type::last_integer_type;
+}
+
+constexpr bool is_arithmetic_type(type t) {
+  return t > type::none_type && t <= type::last_numeric_type;
+}
+
+template <typename Char> struct string_value {
+  const Char* data;
+  size_t size;
+};
+
+template <typename Char> struct named_arg_value {
+  const named_arg_info<Char>* data;
+  size_t size;
+};
+
+template <typename Context> struct custom_value {
+  using parse_context = typename Context::parse_context_type;
+  const void* value;
+  void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx);
+};
+
+// A formatting argument value.
+template <typename Context> class value {
+ public:
+  using char_type = typename Context::char_type;
+
+  union {
+    int int_value;
+    unsigned uint_value;
+    long long long_long_value;
+    unsigned long long ulong_long_value;
+    int128_t int128_value;
+    uint128_t uint128_value;
+    bool bool_value;
+    char_type char_value;
+    float float_value;
+    double double_value;
+    long double long_double_value;
+    const void* pointer;
+    string_value<char_type> string;
+    custom_value<Context> custom;
+    named_arg_value<char_type> named_args;
+  };
+
+  constexpr FMT_INLINE value(int val = 0) : int_value(val) {}
+  constexpr FMT_INLINE value(unsigned val) : uint_value(val) {}
+  constexpr FMT_INLINE value(long long val) : long_long_value(val) {}
+  constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
+  FMT_INLINE value(int128_t val) : int128_value(val) {}
+  FMT_INLINE value(uint128_t val) : uint128_value(val) {}
+  FMT_INLINE value(float val) : float_value(val) {}
+  FMT_INLINE value(double val) : double_value(val) {}
+  FMT_INLINE value(long double val) : long_double_value(val) {}
+  constexpr FMT_INLINE value(bool val) : bool_value(val) {}
+  constexpr FMT_INLINE value(char_type val) : char_value(val) {}
+  FMT_CONSTEXPR FMT_INLINE value(const char_type* val) {
+    string.data = val;
+    if (is_constant_evaluated()) string.size = {};
+  }
+  FMT_CONSTEXPR FMT_INLINE value(basic_string_view<char_type> val) {
+    string.data = val.data();
+    string.size = val.size();
+  }
+  FMT_INLINE value(const void* val) : pointer(val) {}
+  FMT_INLINE value(const named_arg_info<char_type>* args, size_t size)
+      : named_args{args, size} {}
+
+  template <typename T> FMT_INLINE value(const T& val) {
+    custom.value = &val;
+    // Get the formatter type through the context to allow different contexts
+    // have different extension points, e.g. `formatter<T>` for `format` and
+    // `printf_formatter<T>` for `printf`.
+    custom.format = format_custom_arg<
+        T, conditional_t<has_formatter<T, Context>::value,
+                         typename Context::template formatter_type<T>,
+                         fallback_formatter<T, char_type>>>;
+  }
+
+ private:
+  // Formats an argument of a custom type, such as a user-defined class.
+  template <typename T, typename Formatter>
+  static void format_custom_arg(const void* arg,
+                                typename Context::parse_context_type& parse_ctx,
+                                Context& ctx) {
+    Formatter f;
+    parse_ctx.advance_to(f.parse(parse_ctx));
+    ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
+  }
+};
+
+template <typename Context, typename T>
+FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value);
+
+// To minimize the number of types we need to deal with, long is translated
+// either to int or to long long depending on its size.
+enum { long_short = sizeof(long) == sizeof(int) };
+using long_type = conditional_t<long_short, int, long long>;
+using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
+
+struct unformattable {};
+
+// Maps formatting arguments to core types.
+template <typename Context> struct arg_mapper {
+  using char_type = typename Context::char_type;
+
+  FMT_CONSTEXPR FMT_INLINE int map(signed char val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned char val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE int map(short val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned short val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE int map(int val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE unsigned map(unsigned val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE long_type map(long val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE ulong_type map(unsigned long val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE long long map(long long val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE unsigned long long map(unsigned long long val) {
+    return val;
+  }
+  FMT_CONSTEXPR FMT_INLINE int128_t map(int128_t val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE uint128_t map(uint128_t val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE bool map(bool val) { return val; }
+
+  template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE char_type map(T val) {
+    static_assert(
+        std::is_same<T, char>::value || std::is_same<T, char_type>::value,
+        "mixing character types is disallowed");
+    return val;
+  }
+
+  FMT_CONSTEXPR FMT_INLINE float map(float val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE double map(double val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE long double map(long double val) { return val; }
+
+  FMT_CONSTEXPR FMT_INLINE const char_type* map(char_type* val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE const char_type* map(const char_type* val) {
+    return val;
+  }
+  template <typename T, FMT_ENABLE_IF(is_string<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) {
+    static_assert(std::is_same<char_type, char_t<T>>::value,
+                  "mixing character types is disallowed");
+    return to_string_view(val);
+  }
+  template <typename T,
+            FMT_ENABLE_IF(
+                std::is_constructible<basic_string_view<char_type>, T>::value &&
+                !is_string<T>::value && !has_formatter<T, Context>::value &&
+                !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) {
+    return basic_string_view<char_type>(val);
+  }
+  template <
+      typename T,
+      FMT_ENABLE_IF(
+          std::is_constructible<std_string_view<char_type>, T>::value &&
+          !std::is_constructible<basic_string_view<char_type>, T>::value &&
+          !is_string<T>::value && !has_formatter<T, Context>::value &&
+          !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE basic_string_view<char_type> map(const T& val) {
+    return std_string_view<char_type>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE const char* map(const signed char* val) {
+    static_assert(std::is_same<char_type, char>::value, "invalid string type");
+    return reinterpret_cast<const char*>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE const char* map(const unsigned char* val) {
+    static_assert(std::is_same<char_type, char>::value, "invalid string type");
+    return reinterpret_cast<const char*>(val);
+  }
+  FMT_CONSTEXPR FMT_INLINE const char* map(signed char* val) {
+    const auto* const_val = val;
+    return map(const_val);
+  }
+  FMT_CONSTEXPR FMT_INLINE const char* map(unsigned char* val) {
+    const auto* const_val = val;
+    return map(const_val);
+  }
+
+  FMT_CONSTEXPR FMT_INLINE const void* map(void* val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE const void* map(const void* val) { return val; }
+  FMT_CONSTEXPR FMT_INLINE const void* map(std::nullptr_t val) { return val; }
+
+  // We use SFINAE instead of a const T* parameter to avoid conflicting with
+  // the C array overload.
+  template <typename T>
+  FMT_CONSTEXPR auto map(T) -> enable_if_t<std::is_pointer<T>::value, int> {
+    // Formatting of arbitrary pointers is disallowed. If you want to output
+    // a pointer cast it to "void *" or "const void *". In particular, this
+    // forbids formatting of "[const] volatile char *" which is printed as bool
+    // by iostreams.
+    static_assert(!sizeof(T), "formatting of non-void pointers is disallowed");
+    return 0;
+  }
+
+  template <typename T, std::size_t N>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] {
+    return values;
+  }
+
+  template <typename T,
+            FMT_ENABLE_IF(std::is_enum<T>::value &&
+                          !has_formatter<T, Context>::value &&
+                          !has_fallback_formatter<T, char_type>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
+      -> decltype(std::declval<arg_mapper>().map(
+          static_cast<typename std::underlying_type<T>::type>(val))) {
+    return map(static_cast<typename std::underlying_type<T>::type>(val));
+  }
+  template <typename T,
+            FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
+                          (has_formatter<T, Context>::value ||
+                           has_fallback_formatter<T, char_type>::value))>
+  FMT_CONSTEXPR FMT_INLINE const T& map(const T& val) {
+    return val;
+  }
+
+  template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
+      -> decltype(std::declval<arg_mapper>().map(named_arg.value)) {
+    return map(named_arg.value);
+  }
+
+  unformattable map(...) { return {}; }
+};
+
+// A type constant after applying arg_mapper<Context>.
+template <typename T, typename Context>
+using mapped_type_constant =
+    type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())),
+                  typename Context::char_type>;
+
+enum { packed_arg_bits = 4 };
+// Maximum number of arguments with packed types.
+enum { max_packed_args = 62 / packed_arg_bits };
+enum : unsigned long long { is_unpacked_bit = 1ULL << 63 };
+enum : unsigned long long { has_named_args_bit = 1ULL << 62 };
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+// A formatting argument. It is a trivially copyable/constructible type to
+// allow storage in basic_memory_buffer.
+template <typename Context> class basic_format_arg {
+ private:
+  detail::value<Context> value_;
+  detail::type type_;
+
+  template <typename ContextType, typename T>
+  friend FMT_CONSTEXPR basic_format_arg<ContextType> detail::make_arg(
+      const T& value);
+
+  template <typename Visitor, typename Ctx>
+  friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis,
+                                             const basic_format_arg<Ctx>& arg)
+      -> decltype(vis(0));
+
+  friend class basic_format_args<Context>;
+  friend class dynamic_format_arg_store<Context>;
+
+  using char_type = typename Context::char_type;
+
+  template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
+  friend struct detail::arg_data;
+
+  basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size)
+      : value_(args, size) {}
+
+ public:
+  class handle {
+   public:
+    explicit handle(detail::custom_value<Context> custom) : custom_(custom) {}
+
+    void format(typename Context::parse_context_type& parse_ctx,
+                Context& ctx) const {
+      custom_.format(custom_.value, parse_ctx, ctx);
+    }
+
+   private:
+    detail::custom_value<Context> custom_;
+  };
+
+  constexpr basic_format_arg() : type_(detail::type::none_type) {}
+
+  constexpr explicit operator bool() const FMT_NOEXCEPT {
+    return type_ != detail::type::none_type;
+  }
+
+  detail::type type() const { return type_; }
+
+  bool is_integral() const { return detail::is_integral_type(type_); }
+  bool is_arithmetic() const { return detail::is_arithmetic_type(type_); }
+};
+
+/**
+  \rst
+  Visits an argument dispatching to the appropriate visit method based on
+  the argument type. For example, if the argument type is ``double`` then
+  ``vis(value)`` will be called with the value of type ``double``.
+  \endrst
+ */
+template <typename Visitor, typename Context>
+FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
+    Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) {
+  using char_type = typename Context::char_type;
+  switch (arg.type_) {
+  case detail::type::none_type:
+    break;
+  case detail::type::int_type:
+    return vis(arg.value_.int_value);
+  case detail::type::uint_type:
+    return vis(arg.value_.uint_value);
+  case detail::type::long_long_type:
+    return vis(arg.value_.long_long_value);
+  case detail::type::ulong_long_type:
+    return vis(arg.value_.ulong_long_value);
+#if FMT_USE_INT128
+  case detail::type::int128_type:
+    return vis(arg.value_.int128_value);
+  case detail::type::uint128_type:
+    return vis(arg.value_.uint128_value);
+#else
+  case detail::type::int128_type:
+  case detail::type::uint128_type:
+    break;
+#endif
+  case detail::type::bool_type:
+    return vis(arg.value_.bool_value);
+  case detail::type::char_type:
+    return vis(arg.value_.char_value);
+  case detail::type::float_type:
+    return vis(arg.value_.float_value);
+  case detail::type::double_type:
+    return vis(arg.value_.double_value);
+  case detail::type::long_double_type:
+    return vis(arg.value_.long_double_value);
+  case detail::type::cstring_type:
+    return vis(arg.value_.string.data);
+  case detail::type::string_type:
+    return vis(basic_string_view<char_type>(arg.value_.string.data,
+                                            arg.value_.string.size));
+  case detail::type::pointer_type:
+    return vis(arg.value_.pointer);
+  case detail::type::custom_type:
+    return vis(typename basic_format_arg<Context>::handle(arg.value_.custom));
+  }
+  return vis(monostate());
+}
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
+// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
+template <typename... Ts> struct void_t_impl { using type = void; };
+template <typename... Ts>
+using void_t = typename detail::void_t_impl<Ts...>::type;
+#else
+template <typename...> using void_t = void;
+#endif
+
+template <typename It, typename T, typename Enable = void>
+struct is_output_iterator : std::false_type {};
+
+template <typename It, typename T>
+struct is_output_iterator<
+    It, T,
+    void_t<typename std::iterator_traits<It>::iterator_category,
+           decltype(*std::declval<It>() = std::declval<T>())>>
+    : std::true_type {};
+
+template <typename OutputIt>
+struct is_back_insert_iterator : std::false_type {};
+template <typename Container>
+struct is_back_insert_iterator<std::back_insert_iterator<Container>>
+    : std::true_type {};
+
+template <typename OutputIt>
+struct is_contiguous_back_insert_iterator : std::false_type {};
+template <typename Container>
+struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
+    : is_contiguous<Container> {};
+template <typename Char>
+struct is_contiguous_back_insert_iterator<buffer_appender<Char>>
+    : std::true_type {};
+
+// A type-erased reference to an std::locale to avoid heavy <locale> include.
+class locale_ref {
+ private:
+  const void* locale_;  // A type-erased pointer to std::locale.
+
+ public:
+  constexpr locale_ref() : locale_(nullptr) {}
+  template <typename Locale> explicit locale_ref(const Locale& loc);
+
+  explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
+
+  template <typename Locale> Locale get() const;
+};
+
+template <typename> constexpr unsigned long long encode_types() { return 0; }
+
+template <typename Context, typename Arg, typename... Args>
+constexpr unsigned long long encode_types() {
+  return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) |
+         (encode_types<Context, Args...>() << packed_arg_bits);
+}
+
+template <typename Context, typename T>
+FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) {
+  basic_format_arg<Context> arg;
+  arg.type_ = mapped_type_constant<T, Context>::value;
+  arg.value_ = arg_mapper<Context>().map(value);
+  return arg;
+}
+
+// The type template parameter is there to avoid an ODR violation when using
+// a fallback formatter in one translation unit and an implicit conversion in
+// another (not recommended).
+template <bool IS_PACKED, typename Context, type, typename T,
+          FMT_ENABLE_IF(IS_PACKED)>
+FMT_CONSTEXPR FMT_INLINE value<Context> make_arg(const T& val) {
+  const auto& arg = arg_mapper<Context>().map(val);
+  static_assert(
+      !std::is_same<decltype(arg), const unformattable&>::value,
+      "Cannot format an argument. To make type T formattable provide a "
+      "formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
+  return arg;
+}
+
+template <bool IS_PACKED, typename Context, type, typename T,
+          FMT_ENABLE_IF(!IS_PACKED)>
+inline basic_format_arg<Context> make_arg(const T& value) {
+  return make_arg<Context>(value);
+}
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+// Formatting context.
+template <typename OutputIt, typename Char> class basic_format_context {
+ public:
+  /** The character type for the output. */
+  using char_type = Char;
+
+ private:
+  OutputIt out_;
+  basic_format_args<basic_format_context> args_;
+  detail::locale_ref loc_;
+
+ public:
+  using iterator = OutputIt;
+  using format_arg = basic_format_arg<basic_format_context>;
+  using parse_context_type = basic_format_parse_context<Char>;
+  template <typename T> using formatter_type = formatter<T, char_type>;
+
+  basic_format_context(basic_format_context&&) = default;
+  basic_format_context(const basic_format_context&) = delete;
+  void operator=(const basic_format_context&) = delete;
+  /**
+   Constructs a ``basic_format_context`` object. References to the arguments are
+   stored in the object so make sure they have appropriate lifetimes.
+   */
+  constexpr basic_format_context(
+      OutputIt out, basic_format_args<basic_format_context> ctx_args,
+      detail::locale_ref loc = detail::locale_ref())
+      : out_(out), args_(ctx_args), loc_(loc) {}
+
+  constexpr format_arg arg(int id) const { return args_.get(id); }
+  FMT_CONSTEXPR format_arg arg(basic_string_view<char_type> name) {
+    return args_.get(name);
+  }
+  int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); }
+  const basic_format_args<basic_format_context>& args() const { return args_; }
+
+  FMT_CONSTEXPR detail::error_handler error_handler() { return {}; }
+  void on_error(const char* message) { error_handler().on_error(message); }
+
+  // Returns an iterator to the beginning of the output range.
+  FMT_CONSTEXPR iterator out() { return out_; }
+
+  // Advances the begin iterator to ``it``.
+  void advance_to(iterator it) {
+    if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
+  }
+
+  FMT_CONSTEXPR detail::locale_ref locale() { return loc_; }
+};
+
+template <typename Char>
+using buffer_context =
+    basic_format_context<detail::buffer_appender<Char>, Char>;
+using format_context = buffer_context<char>;
+using wformat_context = buffer_context<wchar_t>;
+
+FMT_MODULE_EXPORT_END
+
+// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
+#define FMT_BUFFER_CONTEXT(Char) \
+  basic_format_context<detail::buffer_appender<Char>, Char>
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename T, typename Char = char>
+using is_formattable = bool_constant<
+    !std::is_same<decltype(detail::arg_mapper<buffer_context<Char>>().map(
+                      std::declval<T>())),
+                  detail::unformattable>::value &&
+    !detail::has_fallback_formatter<T, Char>::value>;
+
+/**
+  \rst
+  An array of references to arguments. It can be implicitly converted into
+  `~fmt::basic_format_args` for passing into type-erased formatting functions
+  such as `~fmt::vformat`.
+  \endrst
+ */
+template <typename Context, typename... Args>
+class format_arg_store
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+    // Workaround a GCC template argument substitution bug.
+    : public basic_format_args<Context>
+#endif
+{
+ private:
+  static const size_t num_args = sizeof...(Args);
+  static const size_t num_named_args = detail::count_named_args<Args...>();
+  static const bool is_packed = num_args <= detail::max_packed_args;
+
+  using value_type = conditional_t<is_packed, detail::value<Context>,
+                                   basic_format_arg<Context>>;
+
+  detail::arg_data<value_type, typename Context::char_type, num_args,
+                   num_named_args>
+      data_;
+
+  friend class basic_format_args<Context>;
+
+  static constexpr unsigned long long desc =
+      (is_packed ? detail::encode_types<Context, Args...>()
+                 : detail::is_unpacked_bit | num_args) |
+      (num_named_args != 0
+           ? static_cast<unsigned long long>(detail::has_named_args_bit)
+           : 0);
+
+ public:
+  FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args)
+      :
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+        basic_format_args<Context>(*this),
+#endif
+        data_{detail::make_arg<
+            is_packed, Context,
+            detail::mapped_type_constant<Args, Context>::value>(args)...} {
+    detail::init_named_args(data_.named_args(), 0, 0, args...);
+  }
+};
+
+/**
+  \rst
+  Constructs a `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::format_args`. `Context`
+  can be omitted in which case it defaults to `~fmt::context`.
+  See `~fmt::arg` for lifetime considerations.
+  \endrst
+ */
+template <typename Context = format_context, typename... Args>
+constexpr format_arg_store<Context, Args...> make_format_args(
+    const Args&... args) {
+  return {args...};
+}
+
+/**
+  \rst
+  Constructs a `~fmt::format_arg_store` object that contains references
+  to arguments and can be implicitly converted to `~fmt::format_args`.
+  If ``format_str`` is a compile-time string then `make_args_checked` checks
+  its validity at compile time.
+  \endrst
+ */
+template <typename... Args, typename S, typename Char = char_t<S>>
+FMT_INLINE auto make_args_checked(const S& format_str,
+                                  const remove_reference_t<Args>&... args)
+    -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
+  static_assert(
+      detail::count<(
+              std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
+              std::is_reference<Args>::value)...>() == 0,
+      "passing views as lvalues is disallowed");
+  detail::check_format_string<Args...>(format_str);
+  return {args...};
+}
+
+/**
+  \rst
+  Returns a named argument to be used in a formatting function.
+  It should only be used in a call to a formatting function or
+  `dynamic_format_arg_store::push_back`.
+
+  **Example**::
+
+    fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23));
+  \endrst
+ */
+template <typename Char, typename T>
+inline detail::named_arg<Char, T> arg(const Char* name, const T& arg) {
+  static_assert(!detail::is_named_arg<T>(), "nested named arguments");
+  return {name, arg};
+}
+
+/**
+  \rst
+  A view of a collection of formatting arguments. To avoid lifetime issues it
+  should only be used as a parameter type in type-erased functions such as
+  ``vformat``::
+
+    void vlog(string_view format_str, format_args args);  // OK
+    format_args args = make_format_args(42);  // Error: dangling reference
+  \endrst
+ */
+template <typename Context> class basic_format_args {
+ public:
+  using size_type = int;
+  using format_arg = basic_format_arg<Context>;
+
+ private:
+  // A descriptor that contains information about formatting arguments.
+  // If the number of arguments is less or equal to max_packed_args then
+  // argument types are passed in the descriptor. This reduces binary code size
+  // per formatting function call.
+  unsigned long long desc_;
+  union {
+    // If is_packed() returns true then argument values are stored in values_;
+    // otherwise they are stored in args_. This is done to improve cache
+    // locality and reduce compiled code size since storing larger objects
+    // may require more code (at least on x86-64) even if the same amount of
+    // data is actually copied to stack. It saves ~10% on the bloat test.
+    const detail::value<Context>* values_;
+    const format_arg* args_;
+  };
+
+  constexpr bool is_packed() const {
+    return (desc_ & detail::is_unpacked_bit) == 0;
+  }
+  bool has_named_args() const {
+    return (desc_ & detail::has_named_args_bit) != 0;
+  }
+
+  FMT_CONSTEXPR detail::type type(int index) const {
+    int shift = index * detail::packed_arg_bits;
+    unsigned int mask = (1 << detail::packed_arg_bits) - 1;
+    return static_cast<detail::type>((desc_ >> shift) & mask);
+  }
+
+  constexpr FMT_INLINE basic_format_args(unsigned long long desc,
+                                         const detail::value<Context>* values)
+      : desc_(desc), values_(values) {}
+  constexpr basic_format_args(unsigned long long desc, const format_arg* args)
+      : desc_(desc), args_(args) {}
+
+ public:
+  constexpr basic_format_args() : desc_(0), args_(nullptr) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from `~fmt::format_arg_store`.
+   \endrst
+   */
+  template <typename... Args>
+  constexpr FMT_INLINE basic_format_args(
+      const format_arg_store<Context, Args...>& store)
+      : basic_format_args(format_arg_store<Context, Args...>::desc,
+                          store.data_.args()) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from
+   `~fmt::dynamic_format_arg_store`.
+   \endrst
+   */
+  constexpr FMT_INLINE basic_format_args(
+      const dynamic_format_arg_store<Context>& store)
+      : basic_format_args(store.get_types(), store.data()) {}
+
+  /**
+   \rst
+   Constructs a `basic_format_args` object from a dynamic set of arguments.
+   \endrst
+   */
+  constexpr basic_format_args(const format_arg* args, int count)
+      : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count),
+                          args) {}
+
+  /** Returns the argument with the specified id. */
+  FMT_CONSTEXPR format_arg get(int id) const {
+    format_arg arg;
+    if (!is_packed()) {
+      if (id < max_size()) arg = args_[id];
+      return arg;
+    }
+    if (id >= detail::max_packed_args) return arg;
+    arg.type_ = type(id);
+    if (arg.type_ == detail::type::none_type) return arg;
+    arg.value_ = values_[id];
+    return arg;
+  }
+
+  template <typename Char> format_arg get(basic_string_view<Char> name) const {
+    int id = get_id(name);
+    return id >= 0 ? get(id) : format_arg();
+  }
+
+  template <typename Char> int get_id(basic_string_view<Char> name) const {
+    if (!has_named_args()) return -1;
+    const auto& named_args =
+        (is_packed() ? values_[-1] : args_[-1].value_).named_args;
+    for (size_t i = 0; i < named_args.size; ++i) {
+      if (named_args.data[i].name == name) return named_args.data[i].id;
+    }
+    return -1;
+  }
+
+  int max_size() const {
+    unsigned long long max_packed = detail::max_packed_args;
+    return static_cast<int>(is_packed() ? max_packed
+                                        : desc_ & ~detail::is_unpacked_bit);
+  }
+};
+
+/** An alias to ``basic_format_args<format_context>``. */
+// Separate types would result in shorter symbols but break ABI compatibility
+// between clang and gcc on ARM (#1919).
+using format_args = basic_format_args<format_context>;
+using wformat_args = basic_format_args<wformat_context>;
+
+// We cannot use enum classes as bit fields because of a gcc bug
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414.
+namespace align {
+enum type { none, left, right, center, numeric };
+}
+using align_t = align::type;
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+template <typename Char> constexpr bool is_ascii_letter(Char c) {
+  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+// Converts a character to ASCII. Returns a number > 127 on conversion failure.
+template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)>
+constexpr Char to_ascii(Char value) {
+  return value;
+}
+template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)>
+constexpr typename std::underlying_type<Char>::type to_ascii(Char value) {
+  return value;
+}
+
+template <typename Char>
+FMT_CONSTEXPR int code_point_length(const Char* begin) {
+  if (const_check(sizeof(Char) != 1)) return 1;
+  constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                              0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
+  int len = lengths[static_cast<unsigned char>(*begin) >> 3];
+
+  // Compute the pointer to the next character early so that the next
+  // iteration can start working on the next character. Neither Clang
+  // nor GCC figure out this reordering on their own.
+  return len + !len;
+}
+
+// Parses the range [begin, end) as an unsigned integer. This function assumes
+// that the range is non-empty and the first character is a digit.
+template <typename Char, typename ErrorHandler>
+FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end,
+                                        ErrorHandler&& eh) {
+  FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', "");
+  unsigned value = 0;
+  // Convert to unsigned to prevent a warning.
+  const unsigned max_int = to_unsigned(INT_MAX);
+  unsigned big = max_int / 10;
+  do {
+    // Check for overflow.
+    if (value > big) {
+      value = max_int + 1;
+      break;
+    }
+    value = value * 10 + unsigned(*begin - '0');
+    ++begin;
+  } while (begin != end && '0' <= *begin && *begin <= '9');
+  if (value > max_int) eh.on_error("number is too big");
+  return static_cast<int>(value);
+}
+
+// Parses fill and alignment.
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_align(const Char* begin, const Char* end,
+                                      Handler&& handler) {
+  FMT_ASSERT(begin != end, "");
+  auto align = align::none;
+  auto p = begin + code_point_length(begin);
+  if (p >= end) p = begin;
+  for (;;) {
+    switch (to_ascii(*p)) {
+    case '<':
+      align = align::left;
+      break;
+    case '>':
+      align = align::right;
+      break;
+    case '^':
+      align = align::center;
+      break;
+    default:
+      break;
+    }
+    if (align != align::none) {
+      if (p != begin) {
+        auto c = *begin;
+        if (c == '{')
+          return handler.on_error("invalid fill character '{'"), begin;
+        handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin)));
+        begin = p + 1;
+      } else
+        ++begin;
+      handler.on_align(align);
+      break;
+    } else if (p == begin) {
+      break;
+    }
+    p = begin;
+  }
+  return begin;
+}
+
+struct auto_id {};
+
+template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) {
+  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR const Char* do_parse_arg_id(const Char* begin, const Char* end,
+                                          IDHandler&& handler) {
+  FMT_ASSERT(begin != end, "");
+  Char c = *begin;
+  if (c >= '0' && c <= '9') {
+    int index = 0;
+    if (c != '0')
+      index = parse_nonnegative_int(begin, end, handler);
+    else
+      ++begin;
+    if (begin == end || (*begin != '}' && *begin != ':'))
+      handler.on_error("invalid format string");
+    else
+      handler(index);
+    return begin;
+  }
+  if (!is_name_start(c)) {
+    handler.on_error("invalid format string");
+    return begin;
+  }
+  auto it = begin;
+  do {
+    ++it;
+  } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9')));
+  handler(basic_string_view<Char>(begin, to_unsigned(it - begin)));
+  return it;
+}
+
+template <typename Char, typename IDHandler>
+FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_arg_id(const Char* begin,
+                                                       const Char* end,
+                                                       IDHandler&& handler) {
+  Char c = *begin;
+  if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler);
+  handler();
+  return begin;
+}
+
+// Adapts SpecHandler to IDHandler API for dynamic width.
+template <typename SpecHandler, typename Char> struct width_adapter {
+  explicit FMT_CONSTEXPR width_adapter(SpecHandler& h) : handler(h) {}
+
+  FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); }
+  FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); }
+  FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+    handler.on_dynamic_width(id);
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    handler.on_error(message);
+  }
+
+  SpecHandler& handler;
+};
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end,
+                                      Handler&& handler) {
+  FMT_ASSERT(begin != end, "");
+  if ('0' <= *begin && *begin <= '9') {
+    handler.on_width(parse_nonnegative_int(begin, end, handler));
+  } else if (*begin == '{') {
+    ++begin;
+    if (begin != end)
+      begin = parse_arg_id(begin, end, width_adapter<Handler, Char>(handler));
+    if (begin == end || *begin != '}')
+      return handler.on_error("invalid format string"), begin;
+    ++begin;
+  }
+  return begin;
+}
+
+// Adapts SpecHandler to IDHandler API for dynamic precision.
+template <typename SpecHandler, typename Char> struct precision_adapter {
+  explicit FMT_CONSTEXPR precision_adapter(SpecHandler& h) : handler(h) {}
+
+  FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); }
+  FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); }
+  FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+    handler.on_dynamic_precision(id);
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    handler.on_error(message);
+  }
+
+  SpecHandler& handler;
+};
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end,
+                                          Handler&& handler) {
+  ++begin;
+  auto c = begin != end ? *begin : Char();
+  if ('0' <= c && c <= '9') {
+    handler.on_precision(parse_nonnegative_int(begin, end, handler));
+  } else if (c == '{') {
+    ++begin;
+    if (begin != end) {
+      begin =
+          parse_arg_id(begin, end, precision_adapter<Handler, Char>(handler));
+    }
+    if (begin == end || *begin++ != '}')
+      return handler.on_error("invalid format string"), begin;
+  } else {
+    return handler.on_error("missing precision specifier"), begin;
+  }
+  handler.end_precision();
+  return begin;
+}
+
+// Parses standard format specifiers and sends notifications about parsed
+// components to handler.
+template <typename Char, typename SpecHandler>
+FMT_CONSTEXPR_DECL FMT_INLINE const Char* parse_format_specs(
+    const Char* begin, const Char* end, SpecHandler&& handler) {
+  if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) &&
+      *begin != 'L') {
+    handler.on_type(*begin++);
+    return begin;
+  }
+
+  if (begin == end) return begin;
+
+  begin = parse_align(begin, end, handler);
+  if (begin == end) return begin;
+
+  // Parse sign.
+  switch (to_ascii(*begin)) {
+  case '+':
+    handler.on_plus();
+    ++begin;
+    break;
+  case '-':
+    handler.on_minus();
+    ++begin;
+    break;
+  case ' ':
+    handler.on_space();
+    ++begin;
+    break;
+  default:
+    break;
+  }
+  if (begin == end) return begin;
+
+  if (*begin == '#') {
+    handler.on_hash();
+    if (++begin == end) return begin;
+  }
+
+  // Parse zero flag.
+  if (*begin == '0') {
+    handler.on_zero();
+    if (++begin == end) return begin;
+  }
+
+  begin = parse_width(begin, end, handler);
+  if (begin == end) return begin;
+
+  // Parse precision.
+  if (*begin == '.') {
+    begin = parse_precision(begin, end, handler);
+    if (begin == end) return begin;
+  }
+
+  if (*begin == 'L') {
+    handler.on_localized();
+    ++begin;
+  }
+
+  // Parse type.
+  if (begin != end && *begin != '}') handler.on_type(*begin++);
+  return begin;
+}
+
+// Return the result via the out param to workaround gcc bug 77539.
+template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
+FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr& out) {
+  for (out = first; out != last; ++out) {
+    if (*out == value) return true;
+  }
+  return false;
+}
+
+template <>
+inline bool find<false, char>(const char* first, const char* last, char value,
+                              const char*& out) {
+  out = static_cast<const char*>(
+      std::memchr(first, value, to_unsigned(last - first)));
+  return out != nullptr;
+}
+
+template <typename Handler, typename Char> struct id_adapter {
+  Handler& handler;
+  int arg_id;
+
+  FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); }
+  FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); }
+  FMT_CONSTEXPR void operator()(basic_string_view<Char> id) {
+    arg_id = handler.on_arg_id(id);
+  }
+  FMT_CONSTEXPR void on_error(const char* message) {
+    handler.on_error(message);
+  }
+};
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR const Char* parse_replacement_field(const Char* begin,
+                                                  const Char* end,
+                                                  Handler&& handler) {
+  ++begin;
+  if (begin == end) return handler.on_error("invalid format string"), end;
+  if (*begin == '}') {
+    handler.on_replacement_field(handler.on_arg_id(), begin);
+  } else if (*begin == '{') {
+    handler.on_text(begin, begin + 1);
+  } else {
+    auto adapter = id_adapter<Handler, Char>{handler, 0};
+    begin = parse_arg_id(begin, end, adapter);
+    Char c = begin != end ? *begin : Char();
+    if (c == '}') {
+      handler.on_replacement_field(adapter.arg_id, begin);
+    } else if (c == ':') {
+      begin = handler.on_format_specs(adapter.arg_id, begin + 1, end);
+      if (begin == end || *begin != '}')
+        return handler.on_error("unknown format specifier"), end;
+    } else {
+      return handler.on_error("missing '}' in format string"), end;
+    }
+  }
+  return begin + 1;
+}
+
+template <bool IS_CONSTEXPR, typename Char, typename Handler>
+FMT_CONSTEXPR_DECL FMT_INLINE void parse_format_string(
+    basic_string_view<Char> format_str, Handler&& handler) {
+  // this is most likely a name-lookup defect in msvc's modules implementation
+  using detail::find;
+
+  auto begin = format_str.data();
+  auto end = begin + format_str.size();
+  if (end - begin < 32) {
+    // Use a simple loop instead of memchr for small strings.
+    const Char* p = begin;
+    while (p != end) {
+      auto c = *p++;
+      if (c == '{') {
+        handler.on_text(begin, p - 1);
+        begin = p = parse_replacement_field(p - 1, end, handler);
+      } else if (c == '}') {
+        if (p == end || *p != '}')
+          return handler.on_error("unmatched '}' in format string");
+        handler.on_text(begin, p);
+        begin = ++p;
+      }
+    }
+    handler.on_text(begin, end);
+    return;
+  }
+  struct writer {
+    FMT_CONSTEXPR void operator()(const Char* pbegin, const Char* pend) {
+      if (pbegin == pend) return;
+      for (;;) {
+        const Char* p = nullptr;
+        if (!find<IS_CONSTEXPR>(pbegin, pend, '}', p))
+          return handler_.on_text(pbegin, pend);
+        ++p;
+        if (p == pend || *p != '}')
+          return handler_.on_error("unmatched '}' in format string");
+        handler_.on_text(pbegin, p);
+        pbegin = p + 1;
+      }
+    }
+    Handler& handler_;
+  } write{handler};
+  while (begin != end) {
+    // Doing two passes with memchr (one for '{' and another for '}') is up to
+    // 2.5x faster than the naive one-pass implementation on big format strings.
+    const Char* p = begin;
+    if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, '{', p))
+      return write(begin, end);
+    write(begin, p);
+    begin = parse_replacement_field(p, end, handler);
+  }
+}
+
+template <typename T, typename ParseContext>
+FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs(
+    ParseContext& ctx) {
+  using char_type = typename ParseContext::char_type;
+  using context = buffer_context<char_type>;
+  using mapped_type = conditional_t<
+      mapped_type_constant<T, context>::value != type::custom_type,
+      decltype(arg_mapper<context>().map(std::declval<const T&>())), T>;
+  auto f = conditional_t<has_formatter<mapped_type, context>::value,
+                         formatter<mapped_type, char_type>,
+                         fallback_formatter<T, char_type>>();
+  return f.parse(ctx);
+}
+
+// A parse context with extra argument id checks. It is only used at compile
+// time because adding checks at runtime would introduce substantial overhead
+// and would be redundant since argument ids are checked when arguments are
+// retrieved anyway.
+template <typename Char, typename ErrorHandler = error_handler>
+class compile_parse_context
+    : public basic_format_parse_context<Char, ErrorHandler> {
+ private:
+  int num_args_;
+  using base = basic_format_parse_context<Char, ErrorHandler>;
+
+ public:
+  explicit FMT_CONSTEXPR compile_parse_context(
+      basic_string_view<Char> format_str, int num_args = INT_MAX,
+      ErrorHandler eh = {})
+      : base(format_str, eh), num_args_(num_args) {}
+
+  FMT_CONSTEXPR int next_arg_id() {
+    int id = base::next_arg_id();
+    if (id >= num_args_) this->on_error("argument not found");
+    return id;
+  }
+
+  FMT_CONSTEXPR void check_arg_id(int id) {
+    base::check_arg_id(id);
+    if (id >= num_args_) this->on_error("argument not found");
+  }
+  using base::check_arg_id;
+};
+
+template <typename Char, typename ErrorHandler, typename... Args>
+class format_string_checker {
+ public:
+  explicit FMT_CONSTEXPR format_string_checker(
+      basic_string_view<Char> format_str, ErrorHandler eh)
+      : context_(format_str, num_args, eh),
+        parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
+
+  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+
+  FMT_CONSTEXPR int on_arg_id() { return context_.next_arg_id(); }
+  FMT_CONSTEXPR int on_arg_id(int id) { return context_.check_arg_id(id), id; }
+  FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
+    on_error("compile-time checks don't support named arguments");
+    return 0;
+  }
+
+  FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
+
+  FMT_CONSTEXPR const Char* on_format_specs(int id, const Char* begin,
+                                            const Char*) {
+    advance_to(context_, begin);
+    // id >= 0 check is a workaround for gcc 10 bug (#2065).
+    return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin;
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    context_.on_error(message);
+  }
+
+ private:
+  using parse_context_type = compile_parse_context<Char, ErrorHandler>;
+  enum { num_args = sizeof...(Args) };
+
+  // Format specifier parsing function.
+  using parse_func = const Char* (*)(parse_context_type&);
+
+  parse_context_type context_;
+  parse_func parse_funcs_[num_args > 0 ? num_args : 1];
+};
+
+template <typename... Args, typename S,
+          enable_if_t<(is_compile_string<S>::value), int>>
+void check_format_string(S format_str) {
+  FMT_CONSTEXPR_DECL auto s = to_string_view(format_str);
+  using checker = format_string_checker<typename S::char_type, error_handler,
+                                        remove_cvref_t<Args>...>;
+  FMT_CONSTEXPR_DECL bool invalid_format =
+      (parse_format_string<true>(s, checker(s, {})), true);
+  (void)invalid_format;
+}
+
+// Converts string literals to basic_string_view.
+template <typename Char, size_t N>
+FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
+    const Char (&s)[N]) {
+  // Remove trailing null character if needed. Won't be present if this is used
+  // with raw character array (i.e. not defined as a string).
+  return {s,
+          N - ((std::char_traits<Char>::to_int_type(s[N - 1]) == 0) ? 1 : 0)};
+}
+
+// Converts string_view to basic_string_view.
+template <typename Char>
+FMT_CONSTEXPR basic_string_view<Char> compile_string_to_view(
+    const std_string_view<Char>& s) {
+  return {s.data(), s.size()};
+}
+
+#define FMT_STRING_IMPL(s, base)                                           \
+  [] {                                                                     \
+    /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
+    /* Use a macro-like name to avoid shadowing warnings. */               \
+    struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base {           \
+      using char_type = fmt::remove_cvref_t<decltype(s[0])>;               \
+      FMT_MAYBE_UNUSED FMT_CONSTEXPR                                       \
+      operator fmt::basic_string_view<char_type>() const {                 \
+        return fmt::detail::compile_string_to_view<char_type>(s);          \
+      }                                                                    \
+    };                                                                     \
+    return FMT_COMPILE_STRING();                                           \
+  }()
+
+/**
+  \rst
+  Constructs a compile-time format string from a string literal *s*.
+
+  **Example**::
+
+    // A compile-time error because 'd' is an invalid specifier for strings.
+    std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
+  \endrst
+ */
+#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string)
+
+template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+std::basic_string<Char> vformat(
+    basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args);
+
+FMT_API std::string vformat(string_view format_str, format_args args);
+
+template <typename Char>
+void vformat_to(
+    buffer<Char>& buf, basic_string_view<Char> format_str,
+    basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
+    detail::locale_ref loc = {});
+
+template <typename Char, typename Args,
+          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+inline void vprint_mojibake(std::FILE*, basic_string_view<Char>, const Args&) {}
+
+FMT_API void vprint_mojibake(std::FILE*, string_view, format_args);
+#ifndef _WIN32
+inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
+#endif
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+/** Formats a string and writes the output to ``out``. */
+// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
+// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
+template <typename OutputIt, typename S, typename Char = char_t<S>,
+          bool enable = detail::is_output_iterator<OutputIt, Char>::value>
+auto vformat_to(OutputIt out, const S& format_str,
+                basic_format_args<buffer_context<type_identity_t<Char>>> args)
+    -> typename std::enable_if<enable, OutputIt>::type {
+  decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
+  detail::vformat_to(buf, to_string_view(format_str), args);
+  return detail::get_iterator(buf);
+}
+
+/**
+ \rst
+ Formats arguments, writes the result to the output iterator ``out`` and returns
+ the iterator past the end of the output range.
+
+ **Example**::
+
+   std::vector<char> out;
+   fmt::format_to(std::back_inserter(out), "{}", 42);
+ \endrst
+ */
+// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3.
+template <typename OutputIt, typename S, typename... Args,
+          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
+inline auto format_to(OutputIt out, const S& format_str, Args&&... args) ->
+    typename std::enable_if<enable, OutputIt>::type {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat_to(out, to_string_view(format_str), vargs);
+}
+
+template <typename OutputIt> struct format_to_n_result {
+  /** Iterator past the end of the output range. */
+  OutputIt out;
+  /** Total (not truncated) output size. */
+  size_t size;
+};
+
+template <typename OutputIt, typename Char, typename... Args,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
+inline format_to_n_result<OutputIt> vformat_to_n(
+    OutputIt out, size_t n, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
+                                                                           n);
+  detail::vformat_to(buf, format_str, args);
+  return {buf.out(), buf.count()};
+}
+
+/**
+ \rst
+ Formats arguments, writes up to ``n`` characters of the result to the output
+ iterator ``out`` and returns the total output size and the iterator past the
+ end of the output range.
+ \endrst
+ */
+template <typename OutputIt, typename S, typename... Args,
+          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
+inline auto format_to_n(OutputIt out, size_t n, const S& format_str,
+                        const Args&... args) ->
+    typename std::enable_if<enable, format_to_n_result<OutputIt>>::type {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat_to_n(out, n, to_string_view(format_str), vargs);
+}
+
+/**
+  Returns the number of characters in the output of
+  ``format(format_str, args...)``.
+ */
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline size_t formatted_size(const S& format_str, Args&&... args) {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  detail::counting_buffer<> buf;
+  detail::vformat_to(buf, to_string_view(format_str), vargs);
+  return buf.count();
+}
+
+template <typename S, typename Char = char_t<S>>
+FMT_INLINE std::basic_string<Char> vformat(
+    const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  return detail::vformat(to_string_view(format_str), args);
+}
+
+/**
+  \rst
+  Formats arguments and returns the result as a string.
+
+  **Example**::
+
+    #include <fmt/core.h>
+    std::string message = fmt::format("The answer is {}", 42);
+  \endrst
+*/
+// Pass char_t as a default template parameter instead of using
+// std::basic_string<char_t<S>> to reduce the symbol size.
+template <typename S, typename... Args, typename Char = char_t<S>,
+          FMT_ENABLE_IF(!FMT_COMPILE_TIME_CHECKS ||
+                        !std::is_same<Char, char>::value)>
+FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return detail::vformat(to_string_view(format_str), vargs);
+}
+
+FMT_API void vprint(string_view, format_args);
+FMT_API void vprint(std::FILE*, string_view, format_args);
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``format_str`` and writes the
+  output to the file ``f``. Strings are assumed to be Unicode-encoded unless the
+  ``FMT_UNICODE`` macro is set to 0.
+
+  **Example**::
+
+    fmt::print(stderr, "Don't {}!", "panic");
+  \endrst
+ */
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline void print(std::FILE* f, const S& format_str, Args&&... args) {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return detail::is_unicode<Char>()
+             ? vprint(f, to_string_view(format_str), vargs)
+             : detail::vprint_mojibake(f, to_string_view(format_str), vargs);
+}
+
+/**
+  \rst
+  Formats ``args`` according to specifications in ``format_str`` and writes
+  the output to ``stdout``. Strings are assumed to be Unicode-encoded unless
+  the ``FMT_UNICODE`` macro is set to 0.
+
+  **Example**::
+
+    fmt::print("Elapsed time: {0:.2f} seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline void print(const S& format_str, Args&&... args) {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return detail::is_unicode<Char>()
+             ? vprint(to_string_view(format_str), vargs)
+             : detail::vprint_mojibake(stdout, to_string_view(format_str),
+                                       vargs);
+}
+
+FMT_MODULE_EXPORT_END
+FMT_GCC_PRAGMA("GCC pop_options")
+FMT_END_NAMESPACE
+
+#ifdef FMT_HEADER_ONLY
+#  include "format.h"
+#endif
+#endif  // FMT_CORE_H_
diff --git a/libs/fmt/include/fmt/format-inl.h b/libs/fmt/include/fmt/format-inl.h
new file mode 100644
index 0000000000000000000000000000000000000000..d23af721c4c0838de3a9a1cda866bc4f428ce731
--- /dev/null
+++ b/libs/fmt/include/fmt/format-inl.h
@@ -0,0 +1,2723 @@
+// Formatting library for C++ - implementation
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_FORMAT_INL_H_
+#define FMT_FORMAT_INL_H_
+
+#include <algorithm>
+#include <cctype>
+#include <climits>
+#include <cmath>
+#include <cstdarg>
+#include <cstring>  // std::memmove
+#include <cwchar>
+#include <exception>
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+#  include <locale>
+#endif
+
+#ifdef _WIN32
+#  include <io.h>  // _isatty
+#endif
+
+#include "format.h"
+
+// Dummy implementations of strerror_r and strerror_s called if corresponding
+// system functions are not available.
+inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; }
+inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; }
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
+  // Use unchecked std::fprintf to avoid triggering another assertion when
+  // writing to stderr fails
+  std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
+  // Chosen instead of std::abort to satisfy Clang in CUDA mode during device
+  // code pass.
+  std::terminate();
+}
+
+#ifndef _MSC_VER
+#  define FMT_SNPRINTF snprintf
+#else  // _MSC_VER
+inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
+  va_list args;
+  va_start(args, format);
+  int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
+  va_end(args);
+  return result;
+}
+#  define FMT_SNPRINTF fmt_snprintf
+#endif  // _MSC_VER
+
+// A portable thread-safe version of strerror.
+// Sets buffer to point to a string describing the error code.
+// This can be either a pointer to a string stored in buffer,
+// or a pointer to some static immutable string.
+// Returns one of the following values:
+//   0      - success
+//   ERANGE - buffer is not large enough to store the error message
+//   other  - failure
+// Buffer should be at least of size 1.
+inline int safe_strerror(int error_code, char*& buffer, size_t buffer_size) {
+  FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer");
+
+  class dispatcher {
+   private:
+    int error_code_;
+    char*& buffer_;
+    size_t buffer_size_;
+
+    // A noop assignment operator to avoid bogus warnings.
+    void operator=(const dispatcher&) {}
+
+    // Handle the result of XSI-compliant version of strerror_r.
+    int handle(int result) {
+      // glibc versions before 2.13 return result in errno.
+      return result == -1 ? errno : result;
+    }
+
+    // Handle the result of GNU-specific version of strerror_r.
+    FMT_MAYBE_UNUSED
+    int handle(char* message) {
+      // If the buffer is full then the message is probably truncated.
+      if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
+        return ERANGE;
+      buffer_ = message;
+      return 0;
+    }
+
+    // Handle the case when strerror_r is not available.
+    FMT_MAYBE_UNUSED
+    int handle(detail::null<>) {
+      return fallback(strerror_s(buffer_, buffer_size_, error_code_));
+    }
+
+    // Fallback to strerror_s when strerror_r is not available.
+    FMT_MAYBE_UNUSED
+    int fallback(int result) {
+      // If the buffer is full then the message is probably truncated.
+      return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE
+                                                                : result;
+    }
+
+#if !FMT_MSC_VER
+    // Fallback to strerror if strerror_r and strerror_s are not available.
+    int fallback(detail::null<>) {
+      errno = 0;
+      buffer_ = strerror(error_code_);
+      return errno;
+    }
+#endif
+
+   public:
+    dispatcher(int err_code, char*& buf, size_t buf_size)
+        : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
+
+    int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); }
+  };
+  return dispatcher(error_code, buffer, buffer_size).run();
+}
+
+FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
+                                string_view message) FMT_NOEXCEPT {
+  // Report error code making sure that the output fits into
+  // inline_buffer_size to avoid dynamic memory allocation and potential
+  // bad_alloc.
+  out.try_resize(0);
+  static const char SEP[] = ": ";
+  static const char ERROR_STR[] = "error ";
+  // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
+  size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
+  auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
+  if (detail::is_negative(error_code)) {
+    abs_value = 0 - abs_value;
+    ++error_code_size;
+  }
+  error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
+  auto it = buffer_appender<char>(out);
+  if (message.size() <= inline_buffer_size - error_code_size)
+    format_to(it, FMT_STRING("{}{}"), message, SEP);
+  format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
+  FMT_ASSERT(out.size() <= inline_buffer_size, "");
+}
+
+FMT_FUNC void report_error(format_func func, int error_code,
+                           string_view message) FMT_NOEXCEPT {
+  memory_buffer full_message;
+  func(full_message, error_code, message);
+  // Don't use fwrite_fully because the latter may throw.
+  if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
+    std::fputc('\n', stderr);
+}
+
+// A wrapper around fwrite that throws on error.
+inline void fwrite_fully(const void* ptr, size_t size, size_t count,
+                         FILE* stream) {
+  size_t written = std::fwrite(ptr, size, count, stream);
+  if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
+}
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+template <typename Locale>
+locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
+  static_assert(std::is_same<Locale, std::locale>::value, "");
+}
+
+template <typename Locale> Locale locale_ref::get() const {
+  static_assert(std::is_same<Locale, std::locale>::value, "");
+  return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
+}
+
+template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref loc) {
+  return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()).grouping();
+}
+template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref loc) {
+  return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
+      .thousands_sep();
+}
+template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
+  return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
+      .decimal_point();
+}
+#else
+template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref) {
+  return "\03";
+}
+template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref) {
+  return FMT_STATIC_THOUSANDS_SEPARATOR;
+}
+template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
+  return '.';
+}
+#endif
+}  // namespace detail
+
+#if !FMT_MSC_VER
+FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default;
+FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default;
+#endif
+
+FMT_FUNC void system_error::init(int err_code, string_view format_str,
+                                 format_args args) {
+  error_code_ = err_code;
+  memory_buffer buffer;
+  format_system_error(buffer, err_code, vformat(format_str, args));
+  std::runtime_error& base = *this;
+  base = std::runtime_error(to_string(buffer));
+}
+
+namespace detail {
+
+template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
+  // fallback_uintptr is always stored in little endian.
+  int i = static_cast<int>(sizeof(void*)) - 1;
+  while (i > 0 && n.value[i] == 0) --i;
+  auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
+  return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
+}
+
+#if __cplusplus < 201703L
+template <typename T> constexpr const char basic_data<T>::digits[][2];
+template <typename T> constexpr const char basic_data<T>::hex_digits[];
+template <typename T> constexpr const char basic_data<T>::signs[];
+template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
+template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
+template <typename T>
+constexpr const char basic_data<T>::right_padding_shifts[];
+#endif
+
+template <typename T> struct bits {
+  static FMT_CONSTEXPR_DECL const int value =
+      static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
+};
+
+class fp;
+template <int SHIFT = 0> fp normalize(fp value);
+
+// Lower (upper) boundary is a value half way between a floating-point value
+// and its predecessor (successor). Boundaries have the same exponent as the
+// value so only significands are stored.
+struct boundaries {
+  uint64_t lower;
+  uint64_t upper;
+};
+
+// A handmade floating-point number f * pow(2, e).
+class fp {
+ private:
+  using significand_type = uint64_t;
+
+  template <typename Float>
+  using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
+                                           sizeof(Float) == sizeof(uint32_t)>;
+
+ public:
+  significand_type f;
+  int e;
+
+  // All sizes are in bits.
+  // Subtract 1 to account for an implicit most significant bit in the
+  // normalized form.
+  static FMT_CONSTEXPR_DECL const int double_significand_size =
+      std::numeric_limits<double>::digits - 1;
+  static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
+      1ULL << double_significand_size;
+  static FMT_CONSTEXPR_DECL const int significand_size =
+      bits<significand_type>::value;
+
+  fp() : f(0), e(0) {}
+  fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
+
+  // Constructs fp from an IEEE754 double. It is a template to prevent compile
+  // errors on platforms where double is not IEEE754.
+  template <typename Double> explicit fp(Double d) { assign(d); }
+
+  // Assigns d to this and return true iff predecessor is closer than successor.
+  template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
+  bool assign(Float d) {
+    // Assume float is in the format [sign][exponent][significand].
+    using limits = std::numeric_limits<Float>;
+    const int float_significand_size = limits::digits - 1;
+    const int exponent_size =
+        bits<Float>::value - float_significand_size - 1;  // -1 for sign
+    const uint64_t float_implicit_bit = 1ULL << float_significand_size;
+    const uint64_t significand_mask = float_implicit_bit - 1;
+    const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
+    const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
+    constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
+    auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
+    f = u & significand_mask;
+    int biased_e =
+        static_cast<int>((u & exponent_mask) >> float_significand_size);
+    // Predecessor is closer if d is a normalized power of 2 (f == 0) other than
+    // the smallest normalized number (biased_e > 1).
+    bool is_predecessor_closer = f == 0 && biased_e > 1;
+    if (biased_e != 0)
+      f += float_implicit_bit;
+    else
+      biased_e = 1;  // Subnormals use biased exponent 1 (min exponent).
+    e = biased_e - exponent_bias - float_significand_size;
+    return is_predecessor_closer;
+  }
+
+  template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
+  bool assign(Float) {
+    *this = fp();
+    return false;
+  }
+};
+
+// Normalizes the value converted from double and multiplied by (1 << SHIFT).
+template <int SHIFT> fp normalize(fp value) {
+  // Handle subnormals.
+  const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
+  while ((value.f & shifted_implicit_bit) == 0) {
+    value.f <<= 1;
+    --value.e;
+  }
+  // Subtract 1 to account for hidden bit.
+  const auto offset =
+      fp::significand_size - fp::double_significand_size - SHIFT - 1;
+  value.f <<= offset;
+  value.e -= offset;
+  return value;
+}
+
+inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
+
+// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
+inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
+#if FMT_USE_INT128
+  auto product = static_cast<__uint128_t>(lhs) * rhs;
+  auto f = static_cast<uint64_t>(product >> 64);
+  return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
+#else
+  // Multiply 32-bit parts of significands.
+  uint64_t mask = (1ULL << 32) - 1;
+  uint64_t a = lhs >> 32, b = lhs & mask;
+  uint64_t c = rhs >> 32, d = rhs & mask;
+  uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
+  // Compute mid 64-bit of result and round.
+  uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
+  return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
+#endif
+}
+
+inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
+
+// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
+// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
+inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
+  // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
+  // These are generated by support/compute-powers.py.
+  static constexpr const uint64_t pow10_significands[] = {
+      0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
+      0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
+      0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
+      0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
+      0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
+      0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
+      0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
+      0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
+      0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
+      0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
+      0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
+      0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
+      0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
+      0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
+      0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
+      0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
+      0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
+      0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
+      0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
+      0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
+      0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
+      0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
+      0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
+      0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
+      0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
+      0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
+      0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
+      0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
+      0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
+  };
+
+  // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
+  // to significands above.
+  static constexpr const int16_t pow10_exponents[] = {
+      -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
+      -927,  -901,  -874,  -847,  -821,  -794,  -768,  -741,  -715,  -688, -661,
+      -635,  -608,  -582,  -555,  -529,  -502,  -475,  -449,  -422,  -396, -369,
+      -343,  -316,  -289,  -263,  -236,  -210,  -183,  -157,  -130,  -103, -77,
+      -50,   -24,   3,     30,    56,    83,    109,   136,   162,   189,  216,
+      242,   269,   295,   322,   348,   375,   402,   428,   455,   481,  508,
+      534,   561,   588,   614,   641,   667,   694,   720,   747,   774,  800,
+      827,   853,   880,   907,   933,   960,   986,   1013,  1039,  1066};
+
+  const int shift = 32;
+  const auto significand = static_cast<int64_t>(data::log10_2_significand);
+  int index = static_cast<int>(
+      ((min_exponent + fp::significand_size - 1) * (significand >> shift) +
+       ((int64_t(1) << shift) - 1))  // ceil
+      >> 32                          // arithmetic shift
+  );
+  // Decimal exponent of the first (smallest) cached power of 10.
+  const int first_dec_exp = -348;
+  // Difference between 2 consecutive decimal exponents in cached powers of 10.
+  const int dec_exp_step = 8;
+  index = (index - first_dec_exp - 1) / dec_exp_step + 1;
+  pow10_exponent = first_dec_exp + index * dec_exp_step;
+  return {pow10_significands[index], pow10_exponents[index]};
+}
+
+// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
+// is not available.
+struct accumulator {
+  uint64_t lower;
+  uint64_t upper;
+
+  accumulator() : lower(0), upper(0) {}
+  explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
+
+  void operator+=(uint64_t n) {
+    lower += n;
+    if (lower < n) ++upper;
+  }
+  void operator>>=(int shift) {
+    FMT_ASSERT(shift == 32, "");
+    (void)shift;
+    lower = (upper << 32) | (lower >> 32);
+    upper >>= 32;
+  }
+};
+
+class bigint {
+ private:
+  // A bigint is stored as an array of bigits (big digits), with bigit at index
+  // 0 being the least significant one.
+  using bigit = uint32_t;
+  using double_bigit = uint64_t;
+  enum { bigits_capacity = 32 };
+  basic_memory_buffer<bigit, bigits_capacity> bigits_;
+  int exp_;
+
+  bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
+  bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
+
+  static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
+
+  friend struct formatter<bigint>;
+
+  void subtract_bigits(int index, bigit other, bigit& borrow) {
+    auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
+    (*this)[index] = static_cast<bigit>(result);
+    borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
+  }
+
+  void remove_leading_zeros() {
+    int num_bigits = static_cast<int>(bigits_.size()) - 1;
+    while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
+    bigits_.resize(to_unsigned(num_bigits + 1));
+  }
+
+  // Computes *this -= other assuming aligned bigints and *this >= other.
+  void subtract_aligned(const bigint& other) {
+    FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
+    FMT_ASSERT(compare(*this, other) >= 0, "");
+    bigit borrow = 0;
+    int i = other.exp_ - exp_;
+    for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j)
+      subtract_bigits(i, other.bigits_[j], borrow);
+    while (borrow > 0) subtract_bigits(i, 0, borrow);
+    remove_leading_zeros();
+  }
+
+  void multiply(uint32_t value) {
+    const double_bigit wide_value = value;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * wide_value + carry;
+      bigits_[i] = static_cast<bigit>(result);
+      carry = static_cast<bigit>(result >> bigit_bits);
+    }
+    if (carry != 0) bigits_.push_back(carry);
+  }
+
+  void multiply(uint64_t value) {
+    const bigit mask = ~bigit(0);
+    const double_bigit lower = value & mask;
+    const double_bigit upper = value >> bigit_bits;
+    double_bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * lower + (carry & mask);
+      carry =
+          bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits);
+      bigits_[i] = static_cast<bigit>(result);
+    }
+    while (carry != 0) {
+      bigits_.push_back(carry & mask);
+      carry >>= bigit_bits;
+    }
+  }
+
+ public:
+  bigint() : exp_(0) {}
+  explicit bigint(uint64_t n) { assign(n); }
+  ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); }
+
+  bigint(const bigint&) = delete;
+  void operator=(const bigint&) = delete;
+
+  void assign(const bigint& other) {
+    auto size = other.bigits_.size();
+    bigits_.resize(size);
+    auto data = other.bigits_.data();
+    std::copy(data, data + size, make_checked(bigits_.data(), size));
+    exp_ = other.exp_;
+  }
+
+  void assign(uint64_t n) {
+    size_t num_bigits = 0;
+    do {
+      bigits_[num_bigits++] = n & ~bigit(0);
+      n >>= bigit_bits;
+    } while (n != 0);
+    bigits_.resize(num_bigits);
+    exp_ = 0;
+  }
+
+  int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
+
+  FMT_NOINLINE bigint& operator<<=(int shift) {
+    FMT_ASSERT(shift >= 0, "");
+    exp_ += shift / bigit_bits;
+    shift %= bigit_bits;
+    if (shift == 0) return *this;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      bigit c = bigits_[i] >> (bigit_bits - shift);
+      bigits_[i] = (bigits_[i] << shift) + carry;
+      carry = c;
+    }
+    if (carry != 0) bigits_.push_back(carry);
+    return *this;
+  }
+
+  template <typename Int> bigint& operator*=(Int value) {
+    FMT_ASSERT(value > 0, "");
+    multiply(uint32_or_64_or_128_t<Int>(value));
+    return *this;
+  }
+
+  friend int compare(const bigint& lhs, const bigint& rhs) {
+    int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
+    if (num_lhs_bigits != num_rhs_bigits)
+      return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
+    int i = static_cast<int>(lhs.bigits_.size()) - 1;
+    int j = static_cast<int>(rhs.bigits_.size()) - 1;
+    int end = i - j;
+    if (end < 0) end = 0;
+    for (; i >= end; --i, --j) {
+      bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j];
+      if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1;
+    }
+    if (i != j) return i > j ? 1 : -1;
+    return 0;
+  }
+
+  // Returns compare(lhs1 + lhs2, rhs).
+  friend int add_compare(const bigint& lhs1, const bigint& lhs2,
+                         const bigint& rhs) {
+    int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
+    int num_rhs_bigits = rhs.num_bigits();
+    if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
+    if (max_lhs_bigits > num_rhs_bigits) return 1;
+    auto get_bigit = [](const bigint& n, int i) -> bigit {
+      return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0;
+    };
+    double_bigit borrow = 0;
+    int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_);
+    for (int i = num_rhs_bigits - 1; i >= min_exp; --i) {
+      double_bigit sum =
+          static_cast<double_bigit>(get_bigit(lhs1, i)) + get_bigit(lhs2, i);
+      bigit rhs_bigit = get_bigit(rhs, i);
+      if (sum > rhs_bigit + borrow) return 1;
+      borrow = rhs_bigit + borrow - sum;
+      if (borrow > 1) return -1;
+      borrow <<= bigit_bits;
+    }
+    return borrow != 0 ? -1 : 0;
+  }
+
+  // Assigns pow(10, exp) to this bigint.
+  void assign_pow10(int exp) {
+    FMT_ASSERT(exp >= 0, "");
+    if (exp == 0) return assign(1);
+    // Find the top bit.
+    int bitmask = 1;
+    while (exp >= bitmask) bitmask <<= 1;
+    bitmask >>= 1;
+    // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by
+    // repeated squaring and multiplication.
+    assign(5);
+    bitmask >>= 1;
+    while (bitmask != 0) {
+      square();
+      if ((exp & bitmask) != 0) *this *= 5;
+      bitmask >>= 1;
+    }
+    *this <<= exp;  // Multiply by pow(2, exp) by shifting.
+  }
+
+  void square() {
+    basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
+    int num_bigits = static_cast<int>(bigits_.size());
+    int num_result_bigits = 2 * num_bigits;
+    bigits_.resize(to_unsigned(num_result_bigits));
+    using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
+    auto sum = accumulator_t();
+    for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) {
+      // Compute bigit at position bigit_index of the result by adding
+      // cross-product terms n[i] * n[j] such that i + j == bigit_index.
+      for (int i = 0, j = bigit_index; j >= 0; ++i, --j) {
+        // Most terms are multiplied twice which can be optimized in the future.
+        sum += static_cast<double_bigit>(n[i]) * n[j];
+      }
+      (*this)[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;  // Compute the carry.
+    }
+    // Do the same for the top half.
+    for (int bigit_index = num_bigits; bigit_index < num_result_bigits;
+         ++bigit_index) {
+      for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)
+        sum += static_cast<double_bigit>(n[i++]) * n[j--];
+      (*this)[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;
+    }
+    --num_result_bigits;
+    remove_leading_zeros();
+    exp_ *= 2;
+  }
+
+  // If this bigint has a bigger exponent than other, adds trailing zero to make
+  // exponents equal. This simplifies some operations such as subtraction.
+  void align(const bigint& other) {
+    int exp_difference = exp_ - other.exp_;
+    if (exp_difference <= 0) return;
+    int num_bigits = static_cast<int>(bigits_.size());
+    bigits_.resize(to_unsigned(num_bigits + exp_difference));
+    for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
+      bigits_[j] = bigits_[i];
+    std::uninitialized_fill_n(bigits_.data(), exp_difference, 0);
+    exp_ -= exp_difference;
+  }
+
+  // Divides this bignum by divisor, assigning the remainder to this and
+  // returning the quotient.
+  int divmod_assign(const bigint& divisor) {
+    FMT_ASSERT(this != &divisor, "");
+    if (compare(*this, divisor) < 0) return 0;
+    FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
+    align(divisor);
+    int quotient = 0;
+    do {
+      subtract_aligned(divisor);
+      ++quotient;
+    } while (compare(*this, divisor) >= 0);
+    return quotient;
+  }
+};
+
+enum class round_direction { unknown, up, down };
+
+// Given the divisor (normally a power of 10), the remainder = v % divisor for
+// some number v and the error, returns whether v should be rounded up, down, or
+// whether the rounding direction can't be determined due to error.
+// error should be less than divisor / 2.
+inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
+                                           uint64_t error) {
+  FMT_ASSERT(remainder < divisor, "");  // divisor - remainder won't overflow.
+  FMT_ASSERT(error < divisor, "");      // divisor - error won't overflow.
+  FMT_ASSERT(error < divisor - error, "");  // error * 2 won't overflow.
+  // Round down if (remainder + error) * 2 <= divisor.
+  if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2)
+    return round_direction::down;
+  // Round up if (remainder - error) * 2 >= divisor.
+  if (remainder >= error &&
+      remainder - error >= divisor - (remainder - error)) {
+    return round_direction::up;
+  }
+  return round_direction::unknown;
+}
+
+namespace digits {
+enum result {
+  more,  // Generate more digits.
+  done,  // Done generating digits.
+  error  // Digit generation cancelled due to an error.
+};
+}
+
+inline uint64_t power_of_10_64(int exp) {
+  static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1),
+                                            FMT_POWERS_OF_10(1000000000ULL),
+                                            10000000000000000000ULL};
+  return data[exp];
+}
+
+// Generates output using the Grisu digit-gen algorithm.
+// error: the size of the region (lower, upper) outside of which numbers
+// definitely do not round to value (Delta in Grisu3).
+template <typename Handler>
+FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error,
+                                                  int& exp, Handler& handler) {
+  const fp one(1ULL << -value.e, value.e);
+  // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
+  // zero because it contains a product of two 64-bit numbers with MSB set (due
+  // to normalization) - 1, shifted right by at most 60 bits.
+  auto integral = static_cast<uint32_t>(value.f >> -one.e);
+  FMT_ASSERT(integral != 0, "");
+  FMT_ASSERT(integral == value.f >> -one.e, "");
+  // The fractional part of scaled value (p2 in Grisu) c = value % one.
+  uint64_t fractional = value.f & (one.f - 1);
+  exp = count_digits(integral);  // kappa in Grisu.
+  // Divide by 10 to prevent overflow.
+  auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e,
+                                 value.f / 10, error * 10, exp);
+  if (result != digits::more) return result;
+  // Generate digits for the integral part. This can produce up to 10 digits.
+  do {
+    uint32_t digit = 0;
+    auto divmod_integral = [&](uint32_t divisor) {
+      digit = integral / divisor;
+      integral %= divisor;
+    };
+    // This optimization by Milo Yip reduces the number of integer divisions by
+    // one per iteration.
+    switch (exp) {
+    case 10:
+      divmod_integral(1000000000);
+      break;
+    case 9:
+      divmod_integral(100000000);
+      break;
+    case 8:
+      divmod_integral(10000000);
+      break;
+    case 7:
+      divmod_integral(1000000);
+      break;
+    case 6:
+      divmod_integral(100000);
+      break;
+    case 5:
+      divmod_integral(10000);
+      break;
+    case 4:
+      divmod_integral(1000);
+      break;
+    case 3:
+      divmod_integral(100);
+      break;
+    case 2:
+      divmod_integral(10);
+      break;
+    case 1:
+      digit = integral;
+      integral = 0;
+      break;
+    default:
+      FMT_ASSERT(false, "invalid number of digits");
+    }
+    --exp;
+    auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
+    result = handler.on_digit(static_cast<char>('0' + digit),
+                              power_of_10_64(exp) << -one.e, remainder, error,
+                              exp, true);
+    if (result != digits::more) return result;
+  } while (exp > 0);
+  // Generate digits for the fractional part.
+  for (;;) {
+    fractional *= 10;
+    error *= 10;
+    char digit = static_cast<char>('0' + (fractional >> -one.e));
+    fractional &= one.f - 1;
+    --exp;
+    result = handler.on_digit(digit, one.f, fractional, error, exp, false);
+    if (result != digits::more) return result;
+  }
+}
+
+// The fixed precision digit handler.
+struct fixed_handler {
+  char* buf;
+  int size;
+  int precision;
+  int exp10;
+  bool fixed;
+
+  digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
+                          int& exp) {
+    // Non-fixed formats require at least one digit and no precision adjustment.
+    if (!fixed) return digits::more;
+    // Adjust fixed precision by exponent because it is relative to decimal
+    // point.
+    precision += exp + exp10;
+    // Check if precision is satisfied just by leading zeros, e.g.
+    // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
+    if (precision > 0) return digits::more;
+    if (precision < 0) return digits::done;
+    auto dir = get_round_direction(divisor, remainder, error);
+    if (dir == round_direction::unknown) return digits::error;
+    buf[size++] = dir == round_direction::up ? '1' : '0';
+    return digits::done;
+  }
+
+  digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
+                          uint64_t error, int, bool integral) {
+    FMT_ASSERT(remainder < divisor, "");
+    buf[size++] = digit;
+    if (!integral && error >= remainder) return digits::error;
+    if (size < precision) return digits::more;
+    if (!integral) {
+      // Check if error * 2 < divisor with overflow prevention.
+      // The check is not needed for the integral part because error = 1
+      // and divisor > (1 << 32) there.
+      if (error >= divisor || error >= divisor - error) return digits::error;
+    } else {
+      FMT_ASSERT(error == 1 && divisor > 2, "");
+    }
+    auto dir = get_round_direction(divisor, remainder, error);
+    if (dir != round_direction::up)
+      return dir == round_direction::down ? digits::done : digits::error;
+    ++buf[size - 1];
+    for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
+      buf[i] = '0';
+      ++buf[i - 1];
+    }
+    if (buf[0] > '9') {
+      buf[0] = '1';
+      if (fixed)
+        buf[size++] = '0';
+      else
+        ++exp10;
+    }
+    return digits::done;
+  }
+};
+
+// A 128-bit integer type used internally,
+struct uint128_wrapper {
+  uint128_wrapper() = default;
+
+#if FMT_USE_INT128
+  uint128_t internal_;
+
+  constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+      : internal_{static_cast<uint128_t>(low) |
+                  (static_cast<uint128_t>(high) << 64)} {}
+
+  constexpr uint128_wrapper(uint128_t u) : internal_{u} {}
+
+  constexpr uint64_t high() const FMT_NOEXCEPT {
+    return uint64_t(internal_ >> 64);
+  }
+  constexpr uint64_t low() const FMT_NOEXCEPT { return uint64_t(internal_); }
+
+  uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+    internal_ += n;
+    return *this;
+  }
+#else
+  uint64_t high_;
+  uint64_t low_;
+
+  constexpr uint128_wrapper(uint64_t high, uint64_t low) FMT_NOEXCEPT
+      : high_{high},
+        low_{low} {}
+
+  constexpr uint64_t high() const FMT_NOEXCEPT { return high_; }
+  constexpr uint64_t low() const FMT_NOEXCEPT { return low_; }
+
+  uint128_wrapper& operator+=(uint64_t n) FMT_NOEXCEPT {
+#  if defined(_MSC_VER) && defined(_M_X64)
+    unsigned char carry = _addcarry_u64(0, low_, n, &low_);
+    _addcarry_u64(carry, high_, 0, &high_);
+    return *this;
+#  else
+    uint64_t sum = low_ + n;
+    high_ += (sum < low_ ? 1 : 0);
+    low_ = sum;
+    return *this;
+#  endif
+  }
+#endif
+};
+
+// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
+namespace dragonbox {
+// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
+inline uint128_wrapper umul128(uint64_t x, uint64_t y) FMT_NOEXCEPT {
+#if FMT_USE_INT128
+  return static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
+#elif defined(_MSC_VER) && defined(_M_X64)
+  uint128_wrapper result;
+  result.low_ = _umul128(x, y, &result.high_);
+  return result;
+#else
+  const uint64_t mask = (uint64_t(1) << 32) - uint64_t(1);
+
+  uint64_t a = x >> 32;
+  uint64_t b = x & mask;
+  uint64_t c = y >> 32;
+  uint64_t d = y & mask;
+
+  uint64_t ac = a * c;
+  uint64_t bc = b * c;
+  uint64_t ad = a * d;
+  uint64_t bd = b * d;
+
+  uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
+
+  return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
+          (intermediate << 32) + (bd & mask)};
+#endif
+}
+
+// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
+inline uint64_t umul128_upper64(uint64_t x, uint64_t y) FMT_NOEXCEPT {
+#if FMT_USE_INT128
+  auto p = static_cast<uint128_t>(x) * static_cast<uint128_t>(y);
+  return static_cast<uint64_t>(p >> 64);
+#elif defined(_MSC_VER) && defined(_M_X64)
+  return __umulh(x, y);
+#else
+  return umul128(x, y).high();
+#endif
+}
+
+// Computes upper 64 bits of multiplication of a 64-bit unsigned integer and a
+// 128-bit unsigned integer.
+inline uint64_t umul192_upper64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
+  uint128_wrapper g0 = umul128(x, y.high());
+  g0 += umul128_upper64(x, y.low());
+  return g0.high();
+}
+
+// Computes upper 32 bits of multiplication of a 32-bit unsigned integer and a
+// 64-bit unsigned integer.
+inline uint32_t umul96_upper32(uint32_t x, uint64_t y) FMT_NOEXCEPT {
+  return static_cast<uint32_t>(umul128_upper64(x, y));
+}
+
+// Computes middle 64 bits of multiplication of a 64-bit unsigned integer and a
+// 128-bit unsigned integer.
+inline uint64_t umul192_middle64(uint64_t x, uint128_wrapper y) FMT_NOEXCEPT {
+  uint64_t g01 = x * y.high();
+  uint64_t g10 = umul128_upper64(x, y.low());
+  return g01 + g10;
+}
+
+// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a
+// 64-bit unsigned integer.
+inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT {
+  return x * y;
+}
+
+// Computes floor(log10(pow(2, e))) for e in [-1700, 1700] using the method from
+// https://fmt.dev/papers/Grisu-Exact.pdf#page=5, section 3.4.
+inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
+  const int shift = 22;
+  return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >>
+         shift;
+}
+
+// Various fast log computations.
+inline int floor_log2_pow10(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
+  const uint64_t log2_10_integer_part = 3;
+  const uint64_t log2_10_fractional_digits = 0x5269e12f346e2bf9;
+  const int shift_amount = 19;
+  return (e * static_cast<int>(
+                  (log2_10_integer_part << shift_amount) |
+                  (log2_10_fractional_digits >> (64 - shift_amount)))) >>
+         shift_amount;
+}
+inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT {
+  FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
+  const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
+  const int shift_amount = 22;
+  return (e * static_cast<int>(data::log10_2_significand >>
+                               (64 - shift_amount)) -
+          static_cast<int>(log10_4_over_3_fractional_digits >>
+                           (64 - shift_amount))) >>
+         shift_amount;
+}
+
+// Returns true iff x is divisible by pow(2, exp).
+inline bool divisible_by_power_of_2(uint32_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp >= 1, "");
+  FMT_ASSERT(x != 0, "");
+#ifdef FMT_BUILTIN_CTZ
+  return FMT_BUILTIN_CTZ(x) >= exp;
+#else
+  return exp < num_bits<uint32_t>() && x == ((x >> exp) << exp);
+#endif
+}
+inline bool divisible_by_power_of_2(uint64_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp >= 1, "");
+  FMT_ASSERT(x != 0, "");
+#ifdef FMT_BUILTIN_CTZLL
+  return FMT_BUILTIN_CTZLL(x) >= exp;
+#else
+  return exp < num_bits<uint64_t>() && x == ((x >> exp) << exp);
+#endif
+}
+
+// Table entry type for divisibility test.
+template <typename T> struct divtest_table_entry {
+  T mod_inv;
+  T max_quotient;
+};
+
+// Returns true iff x is divisible by pow(5, exp).
+inline bool divisible_by_power_of_5(uint32_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp <= 10, "too large exponent");
+  static constexpr const divtest_table_entry<uint32_t> divtest_table[] = {
+      {0x00000001, 0xffffffff}, {0xcccccccd, 0x33333333},
+      {0xc28f5c29, 0x0a3d70a3}, {0x26e978d5, 0x020c49ba},
+      {0x3afb7e91, 0x0068db8b}, {0x0bcbe61d, 0x0014f8b5},
+      {0x68c26139, 0x000431bd}, {0xae8d46a5, 0x0000d6bf},
+      {0x22e90e21, 0x00002af3}, {0x3a2e9c6d, 0x00000897},
+      {0x3ed61f49, 0x000001b7}};
+  return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
+}
+inline bool divisible_by_power_of_5(uint64_t x, int exp) FMT_NOEXCEPT {
+  FMT_ASSERT(exp <= 23, "too large exponent");
+  static constexpr const divtest_table_entry<uint64_t> divtest_table[] = {
+      {0x0000000000000001, 0xffffffffffffffff},
+      {0xcccccccccccccccd, 0x3333333333333333},
+      {0x8f5c28f5c28f5c29, 0x0a3d70a3d70a3d70},
+      {0x1cac083126e978d5, 0x020c49ba5e353f7c},
+      {0xd288ce703afb7e91, 0x0068db8bac710cb2},
+      {0x5d4e8fb00bcbe61d, 0x0014f8b588e368f0},
+      {0x790fb65668c26139, 0x000431bde82d7b63},
+      {0xe5032477ae8d46a5, 0x0000d6bf94d5e57a},
+      {0xc767074b22e90e21, 0x00002af31dc46118},
+      {0x8e47ce423a2e9c6d, 0x0000089705f4136b},
+      {0x4fa7f60d3ed61f49, 0x000001b7cdfd9d7b},
+      {0x0fee64690c913975, 0x00000057f5ff85e5},
+      {0x3662e0e1cf503eb1, 0x000000119799812d},
+      {0xa47a2cf9f6433fbd, 0x0000000384b84d09},
+      {0x54186f653140a659, 0x00000000b424dc35},
+      {0x7738164770402145, 0x0000000024075f3d},
+      {0xe4a4d1417cd9a041, 0x000000000734aca5},
+      {0xc75429d9e5c5200d, 0x000000000170ef54},
+      {0xc1773b91fac10669, 0x000000000049c977},
+      {0x26b172506559ce15, 0x00000000000ec1e4},
+      {0xd489e3a9addec2d1, 0x000000000002f394},
+      {0x90e860bb892c8d5d, 0x000000000000971d},
+      {0x502e79bf1b6f4f79, 0x0000000000001e39},
+      {0xdcd618596be30fe5, 0x000000000000060b}};
+  return x * divtest_table[exp].mod_inv <= divtest_table[exp].max_quotient;
+}
+
+// Replaces n by floor(n / pow(5, N)) returning true if and only if n is
+// divisible by pow(5, N).
+// Precondition: n <= 2 * pow(5, N + 1).
+template <int N>
+bool check_divisibility_and_divide_by_pow5(uint32_t& n) FMT_NOEXCEPT {
+  static constexpr struct {
+    uint32_t magic_number;
+    int bits_for_comparison;
+    uint32_t threshold;
+    int shift_amount;
+  } infos[] = {{0xcccd, 16, 0x3333, 18}, {0xa429, 8, 0x0a, 20}};
+  constexpr auto info = infos[N - 1];
+  n *= info.magic_number;
+  const uint32_t comparison_mask = (1u << info.bits_for_comparison) - 1;
+  bool result = (n & comparison_mask) <= info.threshold;
+  n >>= info.shift_amount;
+  return result;
+}
+
+// Computes floor(n / pow(10, N)) for small n and N.
+// Precondition: n <= pow(10, N + 1).
+template <int N> uint32_t small_division_by_pow10(uint32_t n) FMT_NOEXCEPT {
+  static constexpr struct {
+    uint32_t magic_number;
+    int shift_amount;
+    uint32_t divisor_times_10;
+  } infos[] = {{0xcccd, 19, 100}, {0xa3d8, 22, 1000}};
+  constexpr auto info = infos[N - 1];
+  FMT_ASSERT(n <= info.divisor_times_10, "n is too large");
+  return n * info.magic_number >> info.shift_amount;
+}
+
+// Computes floor(n / 10^(kappa + 1)) (float)
+inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) FMT_NOEXCEPT {
+  return n / float_info<float>::big_divisor;
+}
+// Computes floor(n / 10^(kappa + 1)) (double)
+inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) FMT_NOEXCEPT {
+  return umul128_upper64(n, 0x83126e978d4fdf3c) >> 9;
+}
+
+// Various subroutines using pow10 cache
+template <class T> struct cache_accessor;
+
+template <> struct cache_accessor<float> {
+  using carrier_uint = float_info<float>::carrier_uint;
+  using cache_entry_type = uint64_t;
+
+  static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
+    FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
+               "k is out of range");
+    constexpr const uint64_t pow10_significands[] = {
+        0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
+        0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
+        0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
+        0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb,
+        0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a,
+        0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810,
+        0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff,
+        0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd,
+        0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424,
+        0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b,
+        0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000,
+        0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000,
+        0xc350000000000000, 0xf424000000000000, 0x9896800000000000,
+        0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000,
+        0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000,
+        0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000,
+        0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000,
+        0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000,
+        0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0,
+        0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984,
+        0xa18f07d736b90be5, 0xc9f2c9cd04674ede, 0xfc6f7c4045812296,
+        0x9dc5ada82b70b59d, 0xc5371912364ce305, 0xf684df56c3e01bc6,
+        0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20,
+        0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd,
+        0x92efd1b8d0cf37be, 0xb7abc627050305ad, 0xe596b7b0c643c719,
+        0x8f7e32ce7bea5c6f, 0xb35dbf821ae4f38b, 0xe0352f62a19e306e};
+    return pow10_significands[k - float_info<float>::min_k];
+  }
+
+  static carrier_uint compute_mul(carrier_uint u,
+                                  const cache_entry_type& cache) FMT_NOEXCEPT {
+    return umul96_upper32(u, cache);
+  }
+
+  static uint32_t compute_delta(const cache_entry_type& cache,
+                                int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<uint32_t>(cache >> (64 - 1 - beta_minus_1));
+  }
+
+  static bool compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta_minus_1) FMT_NOEXCEPT {
+    FMT_ASSERT(beta_minus_1 >= 1, "");
+    FMT_ASSERT(beta_minus_1 < 64, "");
+
+    return ((umul96_lower64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0;
+  }
+
+  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<carrier_uint>(
+        (cache - (cache >> (float_info<float>::significand_bits + 2))) >>
+        (64 - float_info<float>::significand_bits - 1 - beta_minus_1));
+  }
+
+  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<carrier_uint>(
+        (cache + (cache >> (float_info<float>::significand_bits + 1))) >>
+        (64 - float_info<float>::significand_bits - 1 - beta_minus_1));
+  }
+
+  static carrier_uint compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (static_cast<carrier_uint>(
+                cache >>
+                (64 - float_info<float>::significand_bits - 2 - beta_minus_1)) +
+            1) /
+           2;
+  }
+};
+
+template <> struct cache_accessor<double> {
+  using carrier_uint = float_info<double>::carrier_uint;
+  using cache_entry_type = uint128_wrapper;
+
+  static uint128_wrapper get_cached_power(int k) FMT_NOEXCEPT {
+    FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
+               "k is out of range");
+
+    static constexpr const uint128_wrapper pow10_significands[] = {
+#if FMT_USE_FULL_CACHE_DRAGONBOX
+      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+      {0x9faacf3df73609b1, 0x77b191618c54e9ad},
+      {0xc795830d75038c1d, 0xd59df5b9ef6a2418},
+      {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e},
+      {0x9becce62836ac577, 0x4ee367f9430aec33},
+      {0xc2e801fb244576d5, 0x229c41f793cda740},
+      {0xf3a20279ed56d48a, 0x6b43527578c11110},
+      {0x9845418c345644d6, 0x830a13896b78aaaa},
+      {0xbe5691ef416bd60c, 0x23cc986bc656d554},
+      {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9},
+      {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa},
+      {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54},
+      {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69},
+      {0x91376c36d99995be, 0x23100809b9c21fa2},
+      {0xb58547448ffffb2d, 0xabd40a0c2832a78b},
+      {0xe2e69915b3fff9f9, 0x16c90c8f323f516d},
+      {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4},
+      {0xb1442798f49ffb4a, 0x99cd11cfdf41779d},
+      {0xdd95317f31c7fa1d, 0x40405643d711d584},
+      {0x8a7d3eef7f1cfc52, 0x482835ea666b2573},
+      {0xad1c8eab5ee43b66, 0xda3243650005eed0},
+      {0xd863b256369d4a40, 0x90bed43e40076a83},
+      {0x873e4f75e2224e68, 0x5a7744a6e804a292},
+      {0xa90de3535aaae202, 0x711515d0a205cb37},
+      {0xd3515c2831559a83, 0x0d5a5b44ca873e04},
+      {0x8412d9991ed58091, 0xe858790afe9486c3},
+      {0xa5178fff668ae0b6, 0x626e974dbe39a873},
+      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+      {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a},
+      {0xa139029f6a239f72, 0x1c1fffc1ebc44e81},
+      {0xc987434744ac874e, 0xa327ffb266b56221},
+      {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9},
+      {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa},
+      {0xc4ce17b399107c22, 0xcb550fb4384d21d4},
+      {0xf6019da07f549b2b, 0x7e2a53a146606a49},
+      {0x99c102844f94e0fb, 0x2eda7444cbfc426e},
+      {0xc0314325637a1939, 0xfa911155fefb5309},
+      {0xf03d93eebc589f88, 0x793555ab7eba27cb},
+      {0x96267c7535b763b5, 0x4bc1558b2f3458df},
+      {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17},
+      {0xea9c227723ee8bcb, 0x465e15a979c1cadd},
+      {0x92a1958a7675175f, 0x0bfacd89ec191eca},
+      {0xb749faed14125d36, 0xcef980ec671f667c},
+      {0xe51c79a85916f484, 0x82b7e12780e7401b},
+      {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811},
+      {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16},
+      {0xdfbdcece67006ac9, 0x67a791e093e1d49b},
+      {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1},
+      {0xaecc49914078536d, 0x58fae9f773886e19},
+      {0xda7f5bf590966848, 0xaf39a475506a899f},
+      {0x888f99797a5e012d, 0x6d8406c952429604},
+      {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84},
+      {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65},
+      {0x855c3be0a17fcd26, 0x5cf2eea09a550680},
+      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+      {0xd0601d8efc57b08b, 0xf13b94daf124da27},
+      {0x823c12795db6ce57, 0x76c53d08d6b70859},
+      {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f},
+      {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a},
+      {0xfe5d54150b090b02, 0xd3f93b35435d7c4d},
+      {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0},
+      {0xc6b8e9b0709f109a, 0x359ab6419ca1091c},
+      {0xf867241c8cc6d4c0, 0xc30163d203c94b63},
+      {0x9b407691d7fc44f8, 0x79e0de63425dcf1e},
+      {0xc21094364dfb5636, 0x985915fc12f542e5},
+      {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e},
+      {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43},
+      {0xbd8430bd08277231, 0x50c6ff782a838354},
+      {0xece53cec4a314ebd, 0xa4f8bf5635246429},
+      {0x940f4613ae5ed136, 0x871b7795e136be9a},
+      {0xb913179899f68584, 0x28e2557b59846e40},
+      {0xe757dd7ec07426e5, 0x331aeada2fe589d0},
+      {0x9096ea6f3848984f, 0x3ff0d2c85def7622},
+      {0xb4bca50b065abe63, 0x0fed077a756b53aa},
+      {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895},
+      {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d},
+      {0xb080392cc4349dec, 0xbd8d794d96aacfb4},
+      {0xdca04777f541c567, 0xecf0d7a0fc5583a1},
+      {0x89e42caaf9491b60, 0xf41686c49db57245},
+      {0xac5d37d5b79b6239, 0x311c2875c522ced6},
+      {0xd77485cb25823ac7, 0x7d633293366b828c},
+      {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+      {0xa8530886b54dbdeb, 0xd9f57f830283fdfd},
+      {0xd267caa862a12d66, 0xd072df63c324fd7c},
+      {0x8380dea93da4bc60, 0x4247cb9e59f71e6e},
+      {0xa46116538d0deb78, 0x52d9be85f074e609},
+      {0xcd795be870516656, 0x67902e276c921f8c},
+      {0x806bd9714632dff6, 0x00ba1cd8a3db53b7},
+      {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5},
+      {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce},
+      {0xfad2a4b13d1b5d6c, 0x796b805720085f82},
+      {0x9cc3a6eec6311a63, 0xcbe3303674053bb1},
+      {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d},
+      {0xf4f1b4d515acb93b, 0xee92fb5515482d45},
+      {0x991711052d8bf3c5, 0x751bdd152d4d1c4b},
+      {0xbf5cd54678eef0b6, 0xd262d45a78a0635e},
+      {0xef340a98172aace4, 0x86fb897116c87c35},
+      {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1},
+      {0xbae0a846d2195712, 0x8974836059cca10a},
+      {0xe998d258869facd7, 0x2bd1a438703fc94c},
+      {0x91ff83775423cc06, 0x7b6306a34627ddd0},
+      {0xb67f6455292cbf08, 0x1a3bc84c17b1d543},
+      {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94},
+      {0x8e938662882af53e, 0x547eb47b7282ee9d},
+      {0xb23867fb2a35b28d, 0xe99e619a4f23aa44},
+      {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5},
+      {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05},
+      {0xae0b158b4738705e, 0x9624ab50b148d446},
+      {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+      {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7},
+      {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d},
+      {0xd47487cc8470652b, 0x7647c32000696720},
+      {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074},
+      {0xa5fb0a17c777cf09, 0xf468107100525891},
+      {0xcf79cc9db955c2cc, 0x7182148d4066eeb5},
+      {0x81ac1fe293d599bf, 0xc6f14cd848405531},
+      {0xa21727db38cb002f, 0xb8ada00e5a506a7d},
+      {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d},
+      {0xfd442e4688bd304a, 0x908f4a166d1da664},
+      {0x9e4a9cec15763e2e, 0x9a598e4e043287ff},
+      {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe},
+      {0xf7549530e188c128, 0xd12bee59e68ef47d},
+      {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf},
+      {0xc13a148e3032d6e7, 0xe36a52363c1faf02},
+      {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2},
+      {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba},
+      {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8},
+      {0xebdf661791d60f56, 0x111b495b3464ad22},
+      {0x936b9fcebb25c995, 0xcab10dd900beec35},
+      {0xb84687c269ef3bfb, 0x3d5d514f40eea743},
+      {0xe65829b3046b0afa, 0x0cb4a5a3112a5113},
+      {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac},
+      {0xb3f4e093db73a093, 0x59ed216765690f57},
+      {0xe0f218b8d25088b8, 0x306869c13ec3532d},
+      {0x8c974f7383725573, 0x1e414218c73a13fc},
+      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+      {0xdbac6c247d62a583, 0xdf45f746b74abf3a},
+      {0x894bc396ce5da772, 0x6b8bba8c328eb784},
+      {0xab9eb47c81f5114f, 0x066ea92f3f326565},
+      {0xd686619ba27255a2, 0xc80a537b0efefebe},
+      {0x8613fd0145877585, 0xbd06742ce95f5f37},
+      {0xa798fc4196e952e7, 0x2c48113823b73705},
+      {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6},
+      {0x82ef85133de648c4, 0x9a984d73dbe722fc},
+      {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb},
+      {0xcc963fee10b7d1b3, 0x318df905079926a9},
+      {0xffbbcfe994e5c61f, 0xfdf17746497f7053},
+      {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634},
+      {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1},
+      {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1},
+      {0x9c1661a651213e2d, 0x06bea10ca65c084f},
+      {0xc31bfa0fe5698db8, 0x486e494fcff30a63},
+      {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb},
+      {0x986ddb5c6b3a76b7, 0xf89629465a75e01d},
+      {0xbe89523386091465, 0xf6bbb397f1135824},
+      {0xee2ba6c0678b597f, 0x746aa07ded582e2d},
+      {0x94db483840b717ef, 0xa8c2a44eb4571cdd},
+      {0xba121a4650e4ddeb, 0x92f34d62616ce414},
+      {0xe896a0d7e51e1566, 0x77b020baf9c81d18},
+      {0x915e2486ef32cd60, 0x0ace1474dc1d122f},
+      {0xb5b5ada8aaff80b8, 0x0d819992132456bb},
+      {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a},
+      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+      {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3},
+      {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf},
+      {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c},
+      {0xad4ab7112eb3929d, 0x86c16c98d2c953c7},
+      {0xd89d64d57a607744, 0xe871c7bf077ba8b8},
+      {0x87625f056c7c4a8b, 0x11471cd764ad4973},
+      {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0},
+      {0xd389b47879823479, 0x4aff1d108d4ec2c4},
+      {0x843610cb4bf160cb, 0xcedf722a585139bb},
+      {0xa54394fe1eedb8fe, 0xc2974eb4ee658829},
+      {0xce947a3da6a9273e, 0x733d226229feea33},
+      {0x811ccc668829b887, 0x0806357d5a3f5260},
+      {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8},
+      {0xc9bcff6034c13052, 0xfc89b393dd02f0b6},
+      {0xfc2c3f3841f17c67, 0xbbac2078d443ace3},
+      {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e},
+      {0xc5029163f384a931, 0x0a9e795e65d4df12},
+      {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6},
+      {0x99ea0196163fa42e, 0x504bced1bf8e4e46},
+      {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7},
+      {0xf07da27a82c37088, 0x5d767327bb4e5a4d},
+      {0x964e858c91ba2655, 0x3a6a07f8d510f870},
+      {0xbbe226efb628afea, 0x890489f70a55368c},
+      {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f},
+      {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e},
+      {0xb77ada0617e3bbcb, 0x09ce6ebb40173745},
+      {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+      {0x8f57fa54c2a9eab6, 0x9fa946824a12232e},
+      {0xb32df8e9f3546564, 0x47939822dc96abfa},
+      {0xdff9772470297ebd, 0x59787e2b93bc56f8},
+      {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b},
+      {0xaefae51477a06b03, 0xede622920b6b23f2},
+      {0xdab99e59958885c4, 0xe95fab368e45ecee},
+      {0x88b402f7fd75539b, 0x11dbcb0218ebb415},
+      {0xaae103b5fcd2a881, 0xd652bdc29f26a11a},
+      {0xd59944a37c0752a2, 0x4be76d3346f04960},
+      {0x857fcae62d8493a5, 0x6f70a4400c562ddc},
+      {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953},
+      {0xd097ad07a71f26b2, 0x7e2000a41346a7a8},
+      {0x825ecc24c873782f, 0x8ed400668c0c28c9},
+      {0xa2f67f2dfa90563b, 0x728900802f0f32fb},
+      {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba},
+      {0xfea126b7d78186bc, 0xe2f610c84987bfa9},
+      {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca},
+      {0xc6ede63fa05d3143, 0x91503d1c79720dbc},
+      {0xf8a95fcf88747d94, 0x75a44c6397ce912b},
+      {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb},
+      {0xc24452da229b021b, 0xfbe85badce996169},
+      {0xf2d56790ab41c2a2, 0xfae27299423fb9c4},
+      {0x97c560ba6b0919a5, 0xdccd879fc967d41b},
+      {0xbdb6b8e905cb600f, 0x5400e987bbc1c921},
+      {0xed246723473e3813, 0x290123e9aab23b69},
+      {0x9436c0760c86e30b, 0xf9a0b6720aaf6522},
+      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+      {0xe7958cb87392c2c2, 0xb60b1d1230b20e05},
+      {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3},
+      {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4},
+      {0xe2280b6c20dd5232, 0x25c6da63c38de1b1},
+      {0x8d590723948a535f, 0x579c487e5a38ad0f},
+      {0xb0af48ec79ace837, 0x2d835a9df0c6d852},
+      {0xdcdb1b2798182244, 0xf8e431456cf88e66},
+      {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900},
+      {0xac8b2d36eed2dac5, 0xe272467e3d222f40},
+      {0xd7adf884aa879177, 0x5b0ed81dcc6abb10},
+      {0x86ccbb52ea94baea, 0x98e947129fc2b4ea},
+      {0xa87fea27a539e9a5, 0x3f2398d747b36225},
+      {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae},
+      {0x83a3eeeef9153e89, 0x1953cf68300424ad},
+      {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8},
+      {0xcdb02555653131b6, 0x3792f412cb06794e},
+      {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1},
+      {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5},
+      {0xc8de047564d20a8b, 0xf245825a5a445276},
+      {0xfb158592be068d2e, 0xeed6e2f0f0d56713},
+      {0x9ced737bb6c4183d, 0x55464dd69685606c},
+      {0xc428d05aa4751e4c, 0xaa97e14c3c26b887},
+      {0xf53304714d9265df, 0xd53dd99f4b3066a9},
+      {0x993fe2c6d07b7fab, 0xe546a8038efe402a},
+      {0xbf8fdb78849a5f96, 0xde98520472bdd034},
+      {0xef73d256a5c0f77c, 0x963e66858f6d4441},
+      {0x95a8637627989aad, 0xdde7001379a44aa9},
+      {0xbb127c53b17ec159, 0x5560c018580d5d53},
+      {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7},
+      {0x9226712162ab070d, 0xcab3961304ca70e9},
+      {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23},
+      {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b},
+      {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243},
+      {0xb267ed1940f1c61c, 0x55f038b237591ed4},
+      {0xdf01e85f912e37a3, 0x6b6c46dec52f6689},
+      {0x8b61313bbabce2c6, 0x2323ac4b3b3da016},
+      {0xae397d8aa96c1b77, 0xabec975e0a0d081b},
+      {0xd9c7dced53c72255, 0x96e7bd358c904a22},
+      {0x881cea14545c7575, 0x7e50d64177da2e55},
+      {0xaa242499697392d2, 0xdde50bd1d5d0b9ea},
+      {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865},
+      {0x84ec3c97da624ab4, 0xbd5af13bef0b113f},
+      {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f},
+      {0xcfb11ead453994ba, 0x67de18eda5814af3},
+      {0x81ceb32c4b43fcf4, 0x80eacf948770ced8},
+      {0xa2425ff75e14fc31, 0xa1258379a94d028e},
+      {0xcad2f7f5359a3b3e, 0x096ee45813a04331},
+      {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd},
+      {0x9e74d1b791e07e48, 0x775ea264cf55347e},
+      {0xc612062576589dda, 0x95364afe032a819e},
+      {0xf79687aed3eec551, 0x3a83ddbd83f52205},
+      {0x9abe14cd44753b52, 0xc4926a9672793543},
+      {0xc16d9a0095928a27, 0x75b7053c0f178294},
+      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+      {0x971da05074da7bee, 0xd3f6fc16ebca5e04},
+      {0xbce5086492111aea, 0x88f4bb1ca6bcf585},
+      {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6},
+      {0x9392ee8e921d5d07, 0x3aff322e62439fd0},
+      {0xb877aa3236a4b449, 0x09befeb9fad487c3},
+      {0xe69594bec44de15b, 0x4c2ebe687989a9b4},
+      {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11},
+      {0xb424dc35095cd80f, 0x538484c19ef38c95},
+      {0xe12e13424bb40e13, 0x2865a5f206b06fba},
+      {0x8cbccc096f5088cb, 0xf93f87b7442e45d4},
+      {0xafebff0bcb24aafe, 0xf78f69a51539d749},
+      {0xdbe6fecebdedd5be, 0xb573440e5a884d1c},
+      {0x89705f4136b4a597, 0x31680a88f8953031},
+      {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e},
+      {0xd6bf94d5e57a42bc, 0x3d32907604691b4d},
+      {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110},
+      {0xa7c5ac471b478423, 0x0fcf80dc33721d54},
+      {0xd1b71758e219652b, 0xd3c36113404ea4a9},
+      {0x83126e978d4fdf3b, 0x645a1cac083126ea},
+      {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4},
+      {0xcccccccccccccccc, 0xcccccccccccccccd},
+      {0x8000000000000000, 0x0000000000000000},
+      {0xa000000000000000, 0x0000000000000000},
+      {0xc800000000000000, 0x0000000000000000},
+      {0xfa00000000000000, 0x0000000000000000},
+      {0x9c40000000000000, 0x0000000000000000},
+      {0xc350000000000000, 0x0000000000000000},
+      {0xf424000000000000, 0x0000000000000000},
+      {0x9896800000000000, 0x0000000000000000},
+      {0xbebc200000000000, 0x0000000000000000},
+      {0xee6b280000000000, 0x0000000000000000},
+      {0x9502f90000000000, 0x0000000000000000},
+      {0xba43b74000000000, 0x0000000000000000},
+      {0xe8d4a51000000000, 0x0000000000000000},
+      {0x9184e72a00000000, 0x0000000000000000},
+      {0xb5e620f480000000, 0x0000000000000000},
+      {0xe35fa931a0000000, 0x0000000000000000},
+      {0x8e1bc9bf04000000, 0x0000000000000000},
+      {0xb1a2bc2ec5000000, 0x0000000000000000},
+      {0xde0b6b3a76400000, 0x0000000000000000},
+      {0x8ac7230489e80000, 0x0000000000000000},
+      {0xad78ebc5ac620000, 0x0000000000000000},
+      {0xd8d726b7177a8000, 0x0000000000000000},
+      {0x878678326eac9000, 0x0000000000000000},
+      {0xa968163f0a57b400, 0x0000000000000000},
+      {0xd3c21bcecceda100, 0x0000000000000000},
+      {0x84595161401484a0, 0x0000000000000000},
+      {0xa56fa5b99019a5c8, 0x0000000000000000},
+      {0xcecb8f27f4200f3a, 0x0000000000000000},
+      {0x813f3978f8940984, 0x4000000000000000},
+      {0xa18f07d736b90be5, 0x5000000000000000},
+      {0xc9f2c9cd04674ede, 0xa400000000000000},
+      {0xfc6f7c4045812296, 0x4d00000000000000},
+      {0x9dc5ada82b70b59d, 0xf020000000000000},
+      {0xc5371912364ce305, 0x6c28000000000000},
+      {0xf684df56c3e01bc6, 0xc732000000000000},
+      {0x9a130b963a6c115c, 0x3c7f400000000000},
+      {0xc097ce7bc90715b3, 0x4b9f100000000000},
+      {0xf0bdc21abb48db20, 0x1e86d40000000000},
+      {0x96769950b50d88f4, 0x1314448000000000},
+      {0xbc143fa4e250eb31, 0x17d955a000000000},
+      {0xeb194f8e1ae525fd, 0x5dcfab0800000000},
+      {0x92efd1b8d0cf37be, 0x5aa1cae500000000},
+      {0xb7abc627050305ad, 0xf14a3d9e40000000},
+      {0xe596b7b0c643c719, 0x6d9ccd05d0000000},
+      {0x8f7e32ce7bea5c6f, 0xe4820023a2000000},
+      {0xb35dbf821ae4f38b, 0xdda2802c8a800000},
+      {0xe0352f62a19e306e, 0xd50b2037ad200000},
+      {0x8c213d9da502de45, 0x4526f422cc340000},
+      {0xaf298d050e4395d6, 0x9670b12b7f410000},
+      {0xdaf3f04651d47b4c, 0x3c0cdd765f114000},
+      {0x88d8762bf324cd0f, 0xa5880a69fb6ac800},
+      {0xab0e93b6efee0053, 0x8eea0d047a457a00},
+      {0xd5d238a4abe98068, 0x72a4904598d6d880},
+      {0x85a36366eb71f041, 0x47a6da2b7f864750},
+      {0xa70c3c40a64e6c51, 0x999090b65f67d924},
+      {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d},
+      {0x82818f1281ed449f, 0xbff8f10e7a8921a4},
+      {0xa321f2d7226895c7, 0xaff72d52192b6a0d},
+      {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490},
+      {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+      {0x9f4f2726179a2245, 0x01d762422c946590},
+      {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5},
+      {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2},
+      {0x9b934c3b330c8577, 0x63cc55f49f88eb2f},
+      {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb},
+      {0xf316271c7fc3908a, 0x8bef464e3945ef7a},
+      {0x97edd871cfda3a56, 0x97758bf0e3cbb5ac},
+      {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317},
+      {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd},
+      {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a},
+      {0xb975d6b6ee39e436, 0xb3e2fd538e122b44},
+      {0xe7d34c64a9c85d44, 0x60dbbca87196b616},
+      {0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd},
+      {0xb51d13aea4a488dd, 0x6babab6398bdbe41},
+      {0xe264589a4dcdab14, 0xc696963c7eed2dd1},
+      {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2},
+      {0xb0de65388cc8ada8, 0x3b25a55f43294bcb},
+      {0xdd15fe86affad912, 0x49ef0eb713f39ebe},
+      {0x8a2dbf142dfcc7ab, 0x6e3569326c784337},
+      {0xacb92ed9397bf996, 0x49c2c37f07965404},
+      {0xd7e77a8f87daf7fb, 0xdc33745ec97be906},
+      {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3},
+      {0xa8acd7c0222311bc, 0xc40832ea0d68ce0c},
+      {0xd2d80db02aabd62b, 0xf50a3fa490c30190},
+      {0x83c7088e1aab65db, 0x792667c6da79e0fa},
+      {0xa4b8cab1a1563f52, 0x577001b891185938},
+      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+      {0x80b05e5ac60b6178, 0x544f8158315b05b4},
+      {0xa0dc75f1778e39d6, 0x696361ae3db1c721},
+      {0xc913936dd571c84c, 0x03bc3a19cd1e38e9},
+      {0xfb5878494ace3a5f, 0x04ab48a04065c723},
+      {0x9d174b2dcec0e47b, 0x62eb0d64283f9c76},
+      {0xc45d1df942711d9a, 0x3ba5d0bd324f8394},
+      {0xf5746577930d6500, 0xca8f44ec7ee36479},
+      {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb},
+      {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e},
+      {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e},
+      {0x95d04aee3b80ece5, 0xbba1f1d158724a12},
+      {0xbb445da9ca61281f, 0x2a8a6e45ae8edc97},
+      {0xea1575143cf97226, 0xf52d09d71a3293bd},
+      {0x924d692ca61be758, 0x593c2626705f9c56},
+      {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c},
+      {0xe498f455c38b997a, 0x0b6dfb9c0f956447},
+      {0x8edf98b59a373fec, 0x4724bd4189bd5eac},
+      {0xb2977ee300c50fe7, 0x58edec91ec2cb657},
+      {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed},
+      {0x8b865b215899f46c, 0xbd79e0d20082ee74},
+      {0xae67f1e9aec07187, 0xecd8590680a3aa11},
+      {0xda01ee641a708de9, 0xe80e6f4820cc9495},
+      {0x884134fe908658b2, 0x3109058d147fdcdd},
+      {0xaa51823e34a7eede, 0xbd4b46f0599fd415},
+      {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a},
+      {0x850fadc09923329e, 0x03e2cf6bc604ddb0},
+      {0xa6539930bf6bff45, 0x84db8346b786151c},
+      {0xcfe87f7cef46ff16, 0xe612641865679a63},
+      {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e},
+      {0xa26da3999aef7749, 0xe3be5e330f38f09d},
+      {0xcb090c8001ab551c, 0x5cadf5bfd3072cc5},
+      {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6},
+      {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa},
+      {0xc646d63501a1511d, 0xb281e1fd541501b8},
+      {0xf7d88bc24209a565, 0x1f225a7ca91a4226},
+      {0x9ae757596946075f, 0x3375788de9b06958},
+      {0xc1a12d2fc3978937, 0x0052d6b1641c83ae},
+      {0xf209787bb47d6b84, 0xc0678c5dbd23a49a},
+      {0x9745eb4d50ce6332, 0xf840b7ba963646e0},
+      {0xbd176620a501fbff, 0xb650e5a93bc3d898},
+      {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe},
+      {0x93ba47c980e98cdf, 0xc66f336c36b10137},
+      {0xb8a8d9bbe123f017, 0xb80b0047445d4184},
+      {0xe6d3102ad96cec1d, 0xa60dc059157491e5},
+      {0x9043ea1ac7e41392, 0x87c89837ad68db2f},
+      {0xb454e4a179dd1877, 0x29babe4598c311fb},
+      {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a},
+      {0x8ce2529e2734bb1d, 0x1899e4a65f58660c},
+      {0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f},
+      {0xdc21a1171d42645d, 0x76707543f4fa1f73},
+      {0x899504ae72497eba, 0x6a06494a791c53a8},
+      {0xabfa45da0edbde69, 0x0487db9d17636892},
+      {0xd6f8d7509292d603, 0x45a9d2845d3c42b6},
+      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+      {0xa7f26836f282b732, 0x8e6cac7768d7141e},
+      {0xd1ef0244af2364ff, 0x3207d795430cd926},
+      {0x8335616aed761f1f, 0x7f44e6bd49e807b8},
+      {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6},
+      {0xcd036837130890a1, 0x36dba887c37a8c0f},
+      {0x802221226be55a64, 0xc2494954da2c9789},
+      {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c},
+      {0xc83553c5c8965d3d, 0x6f92829494e5acc7},
+      {0xfa42a8b73abbf48c, 0xcb772339ba1f17f9},
+      {0x9c69a97284b578d7, 0xff2a760414536efb},
+      {0xc38413cf25e2d70d, 0xfef5138519684aba},
+      {0xf46518c2ef5b8cd1, 0x7eb258665fc25d69},
+      {0x98bf2f79d5993802, 0xef2f773ffbd97a61},
+      {0xbeeefb584aff8603, 0xaafb550ffacfd8fa},
+      {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38},
+      {0x952ab45cfa97a0b2, 0xdd945a747bf26183},
+      {0xba756174393d88df, 0x94f971119aeef9e4},
+      {0xe912b9d1478ceb17, 0x7a37cd5601aab85d},
+      {0x91abb422ccb812ee, 0xac62e055c10ab33a},
+      {0xb616a12b7fe617aa, 0x577b986b314d6009},
+      {0xe39c49765fdf9d94, 0xed5a7e85fda0b80b},
+      {0x8e41ade9fbebc27d, 0x14588f13be847307},
+      {0xb1d219647ae6b31c, 0x596eb2d8ae258fc8},
+      {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb},
+      {0x8aec23d680043bee, 0x25de7bb9480d5854},
+      {0xada72ccc20054ae9, 0xaf561aa79a10ae6a},
+      {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+      {0x87aa9aff79042286, 0x90fb44d2f05d0842},
+      {0xa99541bf57452b28, 0x353a1607ac744a53},
+      {0xd3fa922f2d1675f2, 0x42889b8997915ce8},
+      {0x847c9b5d7c2e09b7, 0x69956135febada11},
+      {0xa59bc234db398c25, 0x43fab9837e699095},
+      {0xcf02b2c21207ef2e, 0x94f967e45e03f4bb},
+      {0x8161afb94b44f57d, 0x1d1be0eebac278f5},
+      {0xa1ba1ba79e1632dc, 0x6462d92a69731732},
+      {0xca28a291859bbf93, 0x7d7b8f7503cfdcfe},
+      {0xfcb2cb35e702af78, 0x5cda735244c3d43e},
+      {0x9defbf01b061adab, 0x3a0888136afa64a7},
+      {0xc56baec21c7a1916, 0x088aaa1845b8fdd0},
+      {0xf6c69a72a3989f5b, 0x8aad549e57273d45},
+      {0x9a3c2087a63f6399, 0x36ac54e2f678864b},
+      {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd},
+      {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5},
+      {0x969eb7c47859e743, 0x9f644ae5a4b1b325},
+      {0xbc4665b596706114, 0x873d5d9f0dde1fee},
+      {0xeb57ff22fc0c7959, 0xa90cb506d155a7ea},
+      {0x9316ff75dd87cbd8, 0x09a7f12442d588f2},
+      {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb2f},
+      {0xe5d3ef282a242e81, 0x8f1668c8a86da5fa},
+      {0x8fa475791a569d10, 0xf96e017d694487bc},
+      {0xb38d92d760ec4455, 0x37c981dcc395a9ac},
+      {0xe070f78d3927556a, 0x85bbe253f47b1417},
+      {0x8c469ab843b89562, 0x93956d7478ccec8e},
+      {0xaf58416654a6babb, 0x387ac8d1970027b2},
+      {0xdb2e51bfe9d0696a, 0x06997b05fcc0319e},
+      {0x88fcf317f22241e2, 0x441fece3bdf81f03},
+      {0xab3c2fddeeaad25a, 0xd527e81cad7626c3},
+      {0xd60b3bd56a5586f1, 0x8a71e223d8d3b074},
+      {0x85c7056562757456, 0xf6872d5667844e49},
+      {0xa738c6bebb12d16c, 0xb428f8ac016561db},
+      {0xd106f86e69d785c7, 0xe13336d701beba52},
+      {0x82a45b450226b39c, 0xecc0024661173473},
+      {0xa34d721642b06084, 0x27f002d7f95d0190},
+      {0xcc20ce9bd35c78a5, 0x31ec038df7b441f4},
+      {0xff290242c83396ce, 0x7e67047175a15271},
+      {0x9f79a169bd203e41, 0x0f0062c6e984d386},
+      {0xc75809c42c684dd1, 0x52c07b78a3e60868},
+      {0xf92e0c3537826145, 0xa7709a56ccdf8a82},
+      {0x9bbcc7a142b17ccb, 0x88a66076400bb691},
+      {0xc2abf989935ddbfe, 0x6acff893d00ea435},
+      {0xf356f7ebf83552fe, 0x0583f6b8c4124d43},
+      {0x98165af37b2153de, 0xc3727a337a8b704a},
+      {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c},
+      {0xeda2ee1c7064130c, 0x1162def06f79df73},
+      {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8},
+      {0xb9a74a0637ce2ee1, 0x6d953e2bd7173692},
+      {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437},
+      {0x910ab1d4db9914a0, 0x1d9c9892400a22a2},
+      {0xb54d5e4a127f59c8, 0x2503beb6d00cab4b},
+      {0xe2a0b5dc971f303a, 0x2e44ae64840fd61d},
+      {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+      {0xb10d8e1456105dad, 0x7425a83e872c5f47},
+      {0xdd50f1996b947518, 0xd12f124e28f77719},
+      {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f},
+      {0xace73cbfdc0bfb7b, 0x636cc64d1001550b},
+      {0xd8210befd30efa5a, 0x3c47f7e05401aa4e},
+      {0x8714a775e3e95c78, 0x65acfaec34810a71},
+      {0xa8d9d1535ce3b396, 0x7f1839a741a14d0d},
+      {0xd31045a8341ca07c, 0x1ede48111209a050},
+      {0x83ea2b892091e44d, 0x934aed0aab460432},
+      {0xa4e4b66b68b65d60, 0xf81da84d5617853f},
+      {0xce1de40642e3f4b9, 0x36251260ab9d668e},
+      {0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019},
+      {0xa1075a24e4421730, 0xb24cf65b8612f81f},
+      {0xc94930ae1d529cfc, 0xdee033f26797b627},
+      {0xfb9b7cd9a4a7443c, 0x169840ef017da3b1},
+      {0x9d412e0806e88aa5, 0x8e1f289560ee864e},
+      {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2},
+      {0xf5b5d7ec8acb58a2, 0xae10af696774b1db},
+      {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29},
+      {0xbff610b0cc6edd3f, 0x17fd090a58d32af3},
+      {0xeff394dcff8a948e, 0xddfc4b4cef07f5b0},
+      {0x95f83d0a1fb69cd9, 0x4abdaf101564f98e},
+      {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1},
+      {0xea53df5fd18d5513, 0x84c86189216dc5ed},
+      {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4},
+      {0xb7118682dbb66a77, 0x3fbc8c33221dc2a1},
+      {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+      {0x8f05b1163ba6832d, 0x29cb4d87f2a7400e},
+      {0xb2c71d5bca9023f8, 0x743e20e9ef511012},
+      {0xdf78e4b2bd342cf6, 0x914da9246b255416},
+      {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e},
+      {0xae9672aba3d0c320, 0xa184ac2473b529b1},
+      {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e},
+      {0x8865899617fb1871, 0x7e2fa67c7a658892},
+      {0xaa7eebfb9df9de8d, 0xddbb901b98feeab7},
+      {0xd51ea6fa85785631, 0x552a74227f3ea565},
+      {0x8533285c936b35de, 0xd53a88958f87275f},
+      {0xa67ff273b8460356, 0x8a892abaf368f137},
+      {0xd01fef10a657842c, 0x2d2b7569b0432d85},
+      {0x8213f56a67f6b29b, 0x9c3b29620e29fc73},
+      {0xa298f2c501f45f42, 0x8349f3ba91b47b8f},
+      {0xcb3f2f7642717713, 0x241c70a936219a73},
+      {0xfe0efb53d30dd4d7, 0xed238cd383aa0110},
+      {0x9ec95d1463e8a506, 0xf4363804324a40aa},
+      {0xc67bb4597ce2ce48, 0xb143c6053edcd0d5},
+      {0xf81aa16fdc1b81da, 0xdd94b7868e94050a},
+      {0x9b10a4e5e9913128, 0xca7cf2b4191c8326},
+      {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0},
+      {0xf24a01a73cf2dccf, 0xbc633b39673c8cec},
+      {0x976e41088617ca01, 0xd5be0503e085d813},
+      {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18},
+      {0xec9c459d51852ba2, 0xddf8e7d60ed1219e},
+      {0x93e1ab8252f33b45, 0xcabb90e5c942b503},
+      {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+      {0xe7109bfba19c0c9d, 0x0cc512670a783ad4},
+      {0x906a617d450187e2, 0x27fb2b80668b24c5},
+      {0xb484f9dc9641e9da, 0xb1f9f660802dedf6},
+      {0xe1a63853bbd26451, 0x5e7873f8a0396973},
+      {0x8d07e33455637eb2, 0xdb0b487b6423e1e8},
+      {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62},
+      {0xdc5c5301c56b75f7, 0x7641a140cc7810fb},
+      {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d},
+      {0xac2820d9623bf429, 0x546345fa9fbdcd44},
+      {0xd732290fbacaf133, 0xa97c177947ad4095},
+      {0x867f59a9d4bed6c0, 0x49ed8eabcccc485d},
+      {0xa81f301449ee8c70, 0x5c68f256bfff5a74},
+      {0xd226fc195c6a2f8c, 0x73832eec6fff3111},
+      {0x83585d8fd9c25db7, 0xc831fd53c5ff7eab},
+      {0xa42e74f3d032f525, 0xba3e7ca8b77f5e55},
+      {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb},
+      {0x80444b5e7aa7cf85, 0x7980d163cf5b81b3},
+      {0xa0555e361951c366, 0xd7e105bcc332621f},
+      {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7},
+      {0xfa856334878fc150, 0xb14f98f6f0feb951},
+      {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3},
+      {0xc3b8358109e84f07, 0x0a862f80ec4700c8},
+      {0xf4a642e14c6262c8, 0xcd27bb612758c0fa},
+      {0x98e7e9cccfbd7dbd, 0x8038d51cb897789c},
+      {0xbf21e44003acdd2c, 0xe0470a63e6bd56c3},
+      {0xeeea5d5004981478, 0x1858ccfce06cac74},
+      {0x95527a5202df0ccb, 0x0f37801e0c43ebc8},
+      {0xbaa718e68396cffd, 0xd30560258f54e6ba},
+      {0xe950df20247c83fd, 0x47c6b82ef32a2069},
+      {0x91d28b7416cdd27e, 0x4cdc331d57fa5441},
+      {0xb6472e511c81471d, 0xe0133fe4adf8e952},
+      {0xe3d8f9e563a198e5, 0x58180fddd97723a6},
+      {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648},
+      {0xb201833b35d63f73, 0x2cd2cc6551e513da},
+      {0xde81e40a034bcf4f, 0xf8077f7ea65e58d1},
+      {0x8b112e86420f6191, 0xfb04afaf27faf782},
+      {0xadd57a27d29339f6, 0x79c5db9af1f9b563},
+      {0xd94ad8b1c7380874, 0x18375281ae7822bc},
+      {0x87cec76f1c830548, 0x8f2293910d0b15b5},
+      {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb22},
+      {0xd433179d9c8cb841, 0x5fa60692a46151eb},
+      {0x849feec281d7f328, 0xdbc7c41ba6bcd333},
+      {0xa5c7ea73224deff3, 0x12b9b522906c0800},
+      {0xcf39e50feae16bef, 0xd768226b34870a00},
+      {0x81842f29f2cce375, 0xe6a1158300d46640},
+      {0xa1e53af46f801c53, 0x60495ae3c1097fd0},
+      {0xca5e89b18b602368, 0x385bb19cb14bdfc4},
+      {0xfcf62c1dee382c42, 0x46729e03dd9ed7b5},
+      {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d1},
+      {0xc5a05277621be293, 0xc7098b7305241885},
+      { 0xf70867153aa2db38,
+        0xb8cbee4fc66d1ea7 }
+#else
+      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
+      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
+      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},
+      {0x86a8d39ef77164bc, 0xae5dff9c02033198},
+      {0xd98ddaee19068c76, 0x3badd624dd9b0958},
+      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},
+      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},
+      {0xe55990879ddcaabd, 0xcc420a6a101d0516},
+      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},
+      {0x95a8637627989aad, 0xdde7001379a44aa9},
+      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},
+      {0xc350000000000000, 0x0000000000000000},
+      {0x9dc5ada82b70b59d, 0xf020000000000000},
+      {0xfee50b7025c36a08, 0x02f236d04753d5b4},
+      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f86},
+      {0xa6539930bf6bff45, 0x84db8346b786151c},
+      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b2},
+      {0xd910f7ff28069da4, 0x1b2ba1518094da04},
+      {0xaf58416654a6babb, 0x387ac8d1970027b2},
+      {0x8da471a9de737e24, 0x5ceaecfed289e5d2},
+      {0xe4d5e82392a40515, 0x0fabaf3feaa5334a},
+      {0xb8da1662e7b00a17, 0x3d6a751f3b936243},
+      { 0x95527a5202df0ccb,
+        0x0f37801e0c43ebc8 }
+#endif
+    };
+
+#if FMT_USE_FULL_CACHE_DRAGONBOX
+    return pow10_significands[k - float_info<double>::min_k];
+#else
+    static constexpr const uint64_t powers_of_5_64[] = {
+        0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
+        0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
+        0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
+        0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd,
+        0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9,
+        0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5,
+        0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631,
+        0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed,
+        0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9};
+
+    static constexpr const uint32_t pow10_recovery_errors[] = {
+        0x50001400, 0x54044100, 0x54014555, 0x55954415, 0x54115555, 0x00000001,
+        0x50000000, 0x00104000, 0x54010004, 0x05004001, 0x55555544, 0x41545555,
+        0x54040551, 0x15445545, 0x51555514, 0x10000015, 0x00101100, 0x01100015,
+        0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x04450514, 0x45414110,
+        0x55555145, 0x50544050, 0x15040155, 0x11054140, 0x50111514, 0x11451454,
+        0x00400541, 0x00000000, 0x55555450, 0x10056551, 0x10054011, 0x55551014,
+        0x69514555, 0x05151109, 0x00155555};
+
+    static const int compression_ratio = 27;
+
+    // Compute base index.
+    int cache_index = (k - float_info<double>::min_k) / compression_ratio;
+    int kb = cache_index * compression_ratio + float_info<double>::min_k;
+    int offset = k - kb;
+
+    // Get base cache.
+    uint128_wrapper base_cache = pow10_significands[cache_index];
+    if (offset == 0) return base_cache;
+
+    // Compute the required amount of bit-shift.
+    int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset;
+    FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected");
+
+    // Try to recover the real cache.
+    uint64_t pow5 = powers_of_5_64[offset];
+    uint128_wrapper recovered_cache = umul128(base_cache.high(), pow5);
+    uint128_wrapper middle_low =
+        umul128(base_cache.low() - (kb < 0 ? 1u : 0u), pow5);
+
+    recovered_cache += middle_low.high();
+
+    uint64_t high_to_middle = recovered_cache.high() << (64 - alpha);
+    uint64_t middle_to_low = recovered_cache.low() << (64 - alpha);
+
+    recovered_cache =
+        uint128_wrapper{(recovered_cache.low() >> alpha) | high_to_middle,
+                        ((middle_low.low() >> alpha) | middle_to_low)};
+
+    if (kb < 0) recovered_cache += 1;
+
+    // Get error.
+    int error_idx = (k - float_info<double>::min_k) / 16;
+    uint32_t error = (pow10_recovery_errors[error_idx] >>
+                      ((k - float_info<double>::min_k) % 16) * 2) &
+                     0x3;
+
+    // Add the error back.
+    FMT_ASSERT(recovered_cache.low() + error >= recovered_cache.low(), "");
+    return {recovered_cache.high(), recovered_cache.low() + error};
+#endif
+  }
+
+  static carrier_uint compute_mul(carrier_uint u,
+                                  const cache_entry_type& cache) FMT_NOEXCEPT {
+    return umul192_upper64(u, cache);
+  }
+
+  static uint32_t compute_delta(cache_entry_type const& cache,
+                                int beta_minus_1) FMT_NOEXCEPT {
+    return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta_minus_1));
+  }
+
+  static bool compute_mul_parity(carrier_uint two_f,
+                                 const cache_entry_type& cache,
+                                 int beta_minus_1) FMT_NOEXCEPT {
+    FMT_ASSERT(beta_minus_1 >= 1, "");
+    FMT_ASSERT(beta_minus_1 < 64, "");
+
+    return ((umul192_middle64(two_f, cache) >> (64 - beta_minus_1)) & 1) != 0;
+  }
+
+  static carrier_uint compute_left_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (cache.high() -
+            (cache.high() >> (float_info<double>::significand_bits + 2))) >>
+           (64 - float_info<double>::significand_bits - 1 - beta_minus_1);
+  }
+
+  static carrier_uint compute_right_endpoint_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return (cache.high() +
+            (cache.high() >> (float_info<double>::significand_bits + 1))) >>
+           (64 - float_info<double>::significand_bits - 1 - beta_minus_1);
+  }
+
+  static carrier_uint compute_round_up_for_shorter_interval_case(
+      const cache_entry_type& cache, int beta_minus_1) FMT_NOEXCEPT {
+    return ((cache.high() >>
+             (64 - float_info<double>::significand_bits - 2 - beta_minus_1)) +
+            1) /
+           2;
+  }
+};
+
+// Various integer checks
+template <class T>
+bool is_left_endpoint_integer_shorter_interval(int exponent) FMT_NOEXCEPT {
+  return exponent >=
+             float_info<
+                 T>::case_shorter_interval_left_endpoint_lower_threshold &&
+         exponent <=
+             float_info<T>::case_shorter_interval_left_endpoint_upper_threshold;
+}
+template <class T>
+bool is_endpoint_integer(typename float_info<T>::carrier_uint two_f,
+                         int exponent, int minus_k) FMT_NOEXCEPT {
+  if (exponent < float_info<T>::case_fc_pm_half_lower_threshold) return false;
+  // For k >= 0.
+  if (exponent <= float_info<T>::case_fc_pm_half_upper_threshold) return true;
+  // For k < 0.
+  if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false;
+  return divisible_by_power_of_5(two_f, minus_k);
+}
+
+template <class T>
+bool is_center_integer(typename float_info<T>::carrier_uint two_f, int exponent,
+                       int minus_k) FMT_NOEXCEPT {
+  // Exponent for 5 is negative.
+  if (exponent > float_info<T>::divisibility_check_by_5_threshold) return false;
+  if (exponent > float_info<T>::case_fc_upper_threshold)
+    return divisible_by_power_of_5(two_f, minus_k);
+  // Both exponents are nonnegative.
+  if (exponent >= float_info<T>::case_fc_lower_threshold) return true;
+  // Exponent for 2 is negative.
+  return divisible_by_power_of_2(two_f, minus_k - exponent + 1);
+}
+
+// Remove trailing zeros from n and return the number of zeros removed (float)
+FMT_ALWAYS_INLINE int remove_trailing_zeros(uint32_t& n) FMT_NOEXCEPT {
+#ifdef FMT_BUILTIN_CTZ
+  int t = FMT_BUILTIN_CTZ(n);
+#else
+  int t = ctz(n);
+#endif
+  if (t > float_info<float>::max_trailing_zeros)
+    t = float_info<float>::max_trailing_zeros;
+
+  const uint32_t mod_inv1 = 0xcccccccd;
+  const uint32_t max_quotient1 = 0x33333333;
+  const uint32_t mod_inv2 = 0xc28f5c29;
+  const uint32_t max_quotient2 = 0x0a3d70a3;
+
+  int s = 0;
+  for (; s < t - 1; s += 2) {
+    if (n * mod_inv2 > max_quotient2) break;
+    n *= mod_inv2;
+  }
+  if (s < t && n * mod_inv1 <= max_quotient1) {
+    n *= mod_inv1;
+    ++s;
+  }
+  n >>= s;
+  return s;
+}
+
+// Removes trailing zeros and returns the number of zeros removed (double)
+FMT_ALWAYS_INLINE int remove_trailing_zeros(uint64_t& n) FMT_NOEXCEPT {
+#ifdef FMT_BUILTIN_CTZLL
+  int t = FMT_BUILTIN_CTZLL(n);
+#else
+  int t = ctzll(n);
+#endif
+  if (t > float_info<double>::max_trailing_zeros)
+    t = float_info<double>::max_trailing_zeros;
+  // Divide by 10^8 and reduce to 32-bits
+  // Since ret_value.significand <= (2^64 - 1) / 1000 < 10^17,
+  // both of the quotient and the r should fit in 32-bits
+
+  const uint32_t mod_inv1 = 0xcccccccd;
+  const uint32_t max_quotient1 = 0x33333333;
+  const uint64_t mod_inv8 = 0xc767074b22e90e21;
+  const uint64_t max_quotient8 = 0x00002af31dc46118;
+
+  // If the number is divisible by 1'0000'0000, work with the quotient
+  if (t >= 8) {
+    auto quotient_candidate = n * mod_inv8;
+
+    if (quotient_candidate <= max_quotient8) {
+      auto quotient = static_cast<uint32_t>(quotient_candidate >> 8);
+
+      int s = 8;
+      for (; s < t; ++s) {
+        if (quotient * mod_inv1 > max_quotient1) break;
+        quotient *= mod_inv1;
+      }
+      quotient >>= (s - 8);
+      n = quotient;
+      return s;
+    }
+  }
+
+  // Otherwise, work with the remainder
+  auto quotient = static_cast<uint32_t>(n / 100000000);
+  auto remainder = static_cast<uint32_t>(n - 100000000 * quotient);
+
+  if (t == 0 || remainder * mod_inv1 > max_quotient1) {
+    return 0;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 1 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 1) + quotient * 10000000ull;
+    return 1;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 2 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 2) + quotient * 1000000ull;
+    return 2;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 3 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 3) + quotient * 100000ull;
+    return 3;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 4 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 4) + quotient * 10000ull;
+    return 4;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 5 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 5) + quotient * 1000ull;
+    return 5;
+  }
+  remainder *= mod_inv1;
+
+  if (t == 6 || remainder * mod_inv1 > max_quotient1) {
+    n = (remainder >> 6) + quotient * 100ull;
+    return 6;
+  }
+  remainder *= mod_inv1;
+
+  n = (remainder >> 7) + quotient * 10ull;
+  return 7;
+}
+
+// The main algorithm for shorter interval case
+template <class T>
+FMT_ALWAYS_INLINE decimal_fp<T> shorter_interval_case(int exponent)
+    FMT_NOEXCEPT {
+  decimal_fp<T> ret_value;
+  // Compute k and beta
+  const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
+  const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k);
+
+  // Compute xi and zi
+  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;
+  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);
+
+  auto xi = cache_accessor<T>::compute_left_endpoint_for_shorter_interval_case(
+      cache, beta_minus_1);
+  auto zi = cache_accessor<T>::compute_right_endpoint_for_shorter_interval_case(
+      cache, beta_minus_1);
+
+  // If the left endpoint is not an integer, increase it
+  if (!is_left_endpoint_integer_shorter_interval<T>(exponent)) ++xi;
+
+  // Try bigger divisor
+  ret_value.significand = zi / 10;
+
+  // If succeed, remove trailing zeros if necessary and return
+  if (ret_value.significand * 10 >= xi) {
+    ret_value.exponent = minus_k + 1;
+    ret_value.exponent += remove_trailing_zeros(ret_value.significand);
+    return ret_value;
+  }
+
+  // Otherwise, compute the round-up of y
+  ret_value.significand =
+      cache_accessor<T>::compute_round_up_for_shorter_interval_case(
+          cache, beta_minus_1);
+  ret_value.exponent = minus_k;
+
+  // When tie occurs, choose one of them according to the rule
+  if (exponent >= float_info<T>::shorter_interval_tie_lower_threshold &&
+      exponent <= float_info<T>::shorter_interval_tie_upper_threshold) {
+    ret_value.significand = ret_value.significand % 2 == 0
+                                ? ret_value.significand
+                                : ret_value.significand - 1;
+  } else if (ret_value.significand < xi) {
+    ++ret_value.significand;
+  }
+  return ret_value;
+}
+
+template <typename T> decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT {
+  // Step 1: integer promotion & Schubfach multiplier calculation.
+
+  using carrier_uint = typename float_info<T>::carrier_uint;
+  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;
+  auto br = bit_cast<carrier_uint>(x);
+
+  // Extract significand bits and exponent bits.
+  const carrier_uint significand_mask =
+      (static_cast<carrier_uint>(1) << float_info<T>::significand_bits) - 1;
+  carrier_uint significand = (br & significand_mask);
+  int exponent = static_cast<int>((br & exponent_mask<T>()) >>
+                                  float_info<T>::significand_bits);
+
+  if (exponent != 0) {  // Check if normal.
+    exponent += float_info<T>::exponent_bias - float_info<T>::significand_bits;
+
+    // Shorter interval case; proceed like Schubfach.
+    if (significand == 0) return shorter_interval_case<T>(exponent);
+
+    significand |=
+        (static_cast<carrier_uint>(1) << float_info<T>::significand_bits);
+  } else {
+    // Subnormal case; the interval is always regular.
+    if (significand == 0) return {0, 0};
+    exponent = float_info<T>::min_exponent - float_info<T>::significand_bits;
+  }
+
+  const bool include_left_endpoint = (significand % 2 == 0);
+  const bool include_right_endpoint = include_left_endpoint;
+
+  // Compute k and beta.
+  const int minus_k = floor_log10_pow2(exponent) - float_info<T>::kappa;
+  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);
+  const int beta_minus_1 = exponent + floor_log2_pow10(-minus_k);
+
+  // Compute zi and deltai
+  // 10^kappa <= deltai < 10^(kappa + 1)
+  const uint32_t deltai = cache_accessor<T>::compute_delta(cache, beta_minus_1);
+  const carrier_uint two_fc = significand << 1;
+  const carrier_uint two_fr = two_fc | 1;
+  const carrier_uint zi =
+      cache_accessor<T>::compute_mul(two_fr << beta_minus_1, cache);
+
+  // Step 2: Try larger divisor; remove trailing zeros if necessary
+
+  // Using an upper bound on zi, we might be able to optimize the division
+  // better than the compiler; we are computing zi / big_divisor here
+  decimal_fp<T> ret_value;
+  ret_value.significand = divide_by_10_to_kappa_plus_1(zi);
+  uint32_t r = static_cast<uint32_t>(zi - float_info<T>::big_divisor *
+                                              ret_value.significand);
+
+  if (r > deltai) {
+    goto small_divisor_case_label;
+  } else if (r < deltai) {
+    // Exclude the right endpoint if necessary
+    if (r == 0 && !include_right_endpoint &&
+        is_endpoint_integer<T>(two_fr, exponent, minus_k)) {
+      --ret_value.significand;
+      r = float_info<T>::big_divisor;
+      goto small_divisor_case_label;
+    }
+  } else {
+    // r == deltai; compare fractional parts
+    // Check conditions in the order different from the paper
+    // to take advantage of short-circuiting
+    const carrier_uint two_fl = two_fc - 1;
+    if ((!include_left_endpoint ||
+         !is_endpoint_integer<T>(two_fl, exponent, minus_k)) &&
+        !cache_accessor<T>::compute_mul_parity(two_fl, cache, beta_minus_1)) {
+      goto small_divisor_case_label;
+    }
+  }
+  ret_value.exponent = minus_k + float_info<T>::kappa + 1;
+
+  // We may need to remove trailing zeros
+  ret_value.exponent += remove_trailing_zeros(ret_value.significand);
+  return ret_value;
+
+  // Step 3: Find the significand with the smaller divisor
+
+small_divisor_case_label:
+  ret_value.significand *= 10;
+  ret_value.exponent = minus_k + float_info<T>::kappa;
+
+  const uint32_t mask = (1u << float_info<T>::kappa) - 1;
+  auto dist = r - (deltai / 2) + (float_info<T>::small_divisor / 2);
+
+  // Is dist divisible by 2^kappa?
+  if ((dist & mask) == 0) {
+    const bool approx_y_parity =
+        ((dist ^ (float_info<T>::small_divisor / 2)) & 1) != 0;
+    dist >>= float_info<T>::kappa;
+
+    // Is dist divisible by 5^kappa?
+    if (check_divisibility_and_divide_by_pow5<float_info<T>::kappa>(dist)) {
+      ret_value.significand += dist;
+
+      // Check z^(f) >= epsilon^(f)
+      // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1,
+      // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f)
+      // Since there are only 2 possibilities, we only need to care about the
+      // parity. Also, zi and r should have the same parity since the divisor
+      // is an even number
+      if (cache_accessor<T>::compute_mul_parity(two_fc, cache, beta_minus_1) !=
+          approx_y_parity) {
+        --ret_value.significand;
+      } else {
+        // If z^(f) >= epsilon^(f), we might have a tie
+        // when z^(f) == epsilon^(f), or equivalently, when y is an integer
+        if (is_center_integer<T>(two_fc, exponent, minus_k)) {
+          ret_value.significand = ret_value.significand % 2 == 0
+                                      ? ret_value.significand
+                                      : ret_value.significand - 1;
+        }
+      }
+    }
+    // Is dist not divisible by 5^kappa?
+    else {
+      ret_value.significand += dist;
+    }
+  }
+  // Is dist not divisible by 2^kappa?
+  else {
+    // Since we know dist is small, we might be able to optimize the division
+    // better than the compiler; we are computing dist / small_divisor here
+    ret_value.significand +=
+        small_division_by_pow10<float_info<T>::kappa>(dist);
+  }
+  return ret_value;
+}
+}  // namespace dragonbox
+
+// Formats value using a variation of the Fixed-Precision Positive
+// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
+// https://fmt.dev/papers/p372-steele.pdf.
+template <typename Double>
+void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
+                     int& exp10) {
+  bigint numerator;    // 2 * R in (FPP)^2.
+  bigint denominator;  // 2 * S in (FPP)^2.
+  // lower and upper are differences between value and corresponding boundaries.
+  bigint lower;             // (M^- in (FPP)^2).
+  bigint upper_store;       // upper's value if different from lower.
+  bigint* upper = nullptr;  // (M^+ in (FPP)^2).
+  fp value;
+  // Shift numerator and denominator by an extra bit or two (if lower boundary
+  // is closer) to make lower and upper integers. This eliminates multiplication
+  // by 2 during later computations.
+  const bool is_predecessor_closer =
+      binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
+  int shift = is_predecessor_closer ? 2 : 1;
+  uint64_t significand = value.f << shift;
+  if (value.e >= 0) {
+    numerator.assign(significand);
+    numerator <<= value.e;
+    lower.assign(1);
+    lower <<= value.e;
+    if (shift != 1) {
+      upper_store.assign(1);
+      upper_store <<= value.e + 1;
+      upper = &upper_store;
+    }
+    denominator.assign_pow10(exp10);
+    denominator <<= shift;
+  } else if (exp10 < 0) {
+    numerator.assign_pow10(-exp10);
+    lower.assign(numerator);
+    if (shift != 1) {
+      upper_store.assign(numerator);
+      upper_store <<= 1;
+      upper = &upper_store;
+    }
+    numerator *= significand;
+    denominator.assign(1);
+    denominator <<= shift - value.e;
+  } else {
+    numerator.assign(significand);
+    denominator.assign_pow10(exp10);
+    denominator <<= shift - value.e;
+    lower.assign(1);
+    if (shift != 1) {
+      upper_store.assign(1ULL << 1);
+      upper = &upper_store;
+    }
+  }
+  // Invariant: value == (numerator / denominator) * pow(10, exp10).
+  if (num_digits < 0) {
+    // Generate the shortest representation.
+    if (!upper) upper = &lower;
+    bool even = (value.f & 1) == 0;
+    num_digits = 0;
+    char* data = buf.data();
+    for (;;) {
+      int digit = numerator.divmod_assign(denominator);
+      bool low = compare(numerator, lower) - even < 0;  // numerator <[=] lower.
+      // numerator + upper >[=] pow10:
+      bool high = add_compare(numerator, *upper, denominator) + even > 0;
+      data[num_digits++] = static_cast<char>('0' + digit);
+      if (low || high) {
+        if (!low) {
+          ++data[num_digits - 1];
+        } else if (high) {
+          int result = add_compare(numerator, numerator, denominator);
+          // Round half to even.
+          if (result > 0 || (result == 0 && (digit % 2) != 0))
+            ++data[num_digits - 1];
+        }
+        buf.try_resize(to_unsigned(num_digits));
+        exp10 -= num_digits - 1;
+        return;
+      }
+      numerator *= 10;
+      lower *= 10;
+      if (upper != &lower) *upper *= 10;
+    }
+  }
+  // Generate the given number of digits.
+  exp10 -= num_digits - 1;
+  if (num_digits == 0) {
+    buf.try_resize(1);
+    denominator *= 10;
+    buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+    return;
+  }
+  buf.try_resize(to_unsigned(num_digits));
+  for (int i = 0; i < num_digits - 1; ++i) {
+    int digit = numerator.divmod_assign(denominator);
+    buf[i] = static_cast<char>('0' + digit);
+    numerator *= 10;
+  }
+  int digit = numerator.divmod_assign(denominator);
+  auto result = add_compare(numerator, numerator, denominator);
+  if (result > 0 || (result == 0 && (digit % 2) != 0)) {
+    if (digit == 9) {
+      const auto overflow = '0' + 10;
+      buf[num_digits - 1] = overflow;
+      // Propagate the carry.
+      for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) {
+        buf[i] = '0';
+        ++buf[i - 1];
+      }
+      if (buf[0] == overflow) {
+        buf[0] = '1';
+        ++exp10;
+      }
+      return;
+    }
+    ++digit;
+  }
+  buf[num_digits - 1] = static_cast<char>('0' + digit);
+}
+
+template <typename T>
+int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
+  static_assert(!std::is_same<T, float>::value, "");
+  FMT_ASSERT(value >= 0, "value is negative");
+
+  const bool fixed = specs.format == float_format::fixed;
+  if (value <= 0) {  // <= instead of == to silence a warning.
+    if (precision <= 0 || !fixed) {
+      buf.push_back('0');
+      return 0;
+    }
+    buf.try_resize(to_unsigned(precision));
+    std::uninitialized_fill_n(buf.data(), precision, '0');
+    return -precision;
+  }
+
+  if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
+
+  if (precision < 0) {
+    // Use Dragonbox for the shortest format.
+    if (specs.binary32) {
+      auto dec = dragonbox::to_decimal(static_cast<float>(value));
+      write<char>(buffer_appender<char>(buf), dec.significand);
+      return dec.exponent;
+    }
+    auto dec = dragonbox::to_decimal(static_cast<double>(value));
+    write<char>(buffer_appender<char>(buf), dec.significand);
+    return dec.exponent;
+  }
+
+  // Use Grisu + Dragon4 for the given precision:
+  // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
+  int exp = 0;
+  const int min_exp = -60;  // alpha in Grisu.
+  int cached_exp10 = 0;     // K in Grisu.
+  fp normalized = normalize(fp(value));
+  const auto cached_pow = get_cached_power(
+      min_exp - (normalized.e + fp::significand_size), cached_exp10);
+  normalized = normalized * cached_pow;
+  // Limit precision to the maximum possible number of significant digits in an
+  // IEEE754 double because we don't need to generate zeros.
+  const int max_double_digits = 767;
+  if (precision > max_double_digits) precision = max_double_digits;
+  fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
+  if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
+    exp += handler.size - cached_exp10 - 1;
+    fallback_format(value, handler.precision, specs.binary32, buf, exp);
+  } else {
+    exp += handler.exp10;
+    buf.try_resize(to_unsigned(handler.size));
+  }
+  if (!fixed && !specs.showpoint) {
+    // Remove trailing zeros.
+    auto num_digits = buf.size();
+    while (num_digits > 0 && buf[num_digits - 1] == '0') {
+      --num_digits;
+      ++exp;
+    }
+    buf.try_resize(num_digits);
+  }
+  return exp;
+}  // namespace detail
+
+template <typename T>
+int snprintf_float(T value, int precision, float_specs specs,
+                   buffer<char>& buf) {
+  // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
+  FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer");
+  static_assert(!std::is_same<T, float>::value, "");
+
+  // Subtract 1 to account for the difference in precision since we use %e for
+  // both general and exponent format.
+  if (specs.format == float_format::general ||
+      specs.format == float_format::exp)
+    precision = (precision >= 0 ? precision : 6) - 1;
+
+  // Build the format string.
+  enum { max_format_size = 7 };  // The longest format is "%#.*Le".
+  char format[max_format_size];
+  char* format_ptr = format;
+  *format_ptr++ = '%';
+  if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#';
+  if (precision >= 0) {
+    *format_ptr++ = '.';
+    *format_ptr++ = '*';
+  }
+  if (std::is_same<T, long double>()) *format_ptr++ = 'L';
+  *format_ptr++ = specs.format != float_format::hex
+                      ? (specs.format == float_format::fixed ? 'f' : 'e')
+                      : (specs.upper ? 'A' : 'a');
+  *format_ptr = '\0';
+
+  // Format using snprintf.
+  auto offset = buf.size();
+  for (;;) {
+    auto begin = buf.data() + offset;
+    auto capacity = buf.capacity() - offset;
+#ifdef FMT_FUZZ
+    if (precision > 100000)
+      throw std::runtime_error(
+          "fuzz mode - avoid large allocation inside snprintf");
+#endif
+    // Suppress the warning about a nonliteral format string.
+    // Cannot use auto because of a bug in MinGW (#1532).
+    int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
+    int result = precision >= 0
+                     ? snprintf_ptr(begin, capacity, format, precision, value)
+                     : snprintf_ptr(begin, capacity, format, value);
+    if (result < 0) {
+      // The buffer will grow exponentially.
+      buf.try_reserve(buf.capacity() + 1);
+      continue;
+    }
+    auto size = to_unsigned(result);
+    // Size equal to capacity means that the last character was truncated.
+    if (size >= capacity) {
+      buf.try_reserve(size + offset + 1);  // Add 1 for the terminating '\0'.
+      continue;
+    }
+    auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
+    if (specs.format == float_format::fixed) {
+      if (precision == 0) {
+        buf.try_resize(size);
+        return 0;
+      }
+      // Find and remove the decimal point.
+      auto end = begin + size, p = end;
+      do {
+        --p;
+      } while (is_digit(*p));
+      int fraction_size = static_cast<int>(end - p - 1);
+      std::memmove(p, p + 1, to_unsigned(fraction_size));
+      buf.try_resize(size - 1);
+      return -fraction_size;
+    }
+    if (specs.format == float_format::hex) {
+      buf.try_resize(size + offset);
+      return 0;
+    }
+    // Find and parse the exponent.
+    auto end = begin + size, exp_pos = end;
+    do {
+      --exp_pos;
+    } while (*exp_pos != 'e');
+    char sign = exp_pos[1];
+    FMT_ASSERT(sign == '+' || sign == '-', "");
+    int exp = 0;
+    auto p = exp_pos + 2;  // Skip 'e' and sign.
+    do {
+      FMT_ASSERT(is_digit(*p), "");
+      exp = exp * 10 + (*p++ - '0');
+    } while (p != end);
+    if (sign == '-') exp = -exp;
+    int fraction_size = 0;
+    if (exp_pos != begin + 1) {
+      // Remove trailing zeros.
+      auto fraction_end = exp_pos - 1;
+      while (*fraction_end == '0') --fraction_end;
+      // Move the fractional part left to get rid of the decimal point.
+      fraction_size = static_cast<int>(fraction_end - begin - 1);
+      std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size));
+    }
+    buf.try_resize(to_unsigned(fraction_size) + offset + 1);
+    return exp - fraction_size;
+  }
+}
+
+struct stringifier {
+  template <typename T> FMT_INLINE std::string operator()(T value) const {
+    return to_string(value);
+  }
+  std::string operator()(basic_format_arg<format_context>::handle h) const {
+    memory_buffer buf;
+    format_parse_context parse_ctx({});
+    format_context format_ctx(buffer_appender<char>(buf), {}, {});
+    h.format(parse_ctx, format_ctx);
+    return to_string(buf);
+  }
+};
+}  // namespace detail
+
+template <> struct formatter<detail::bigint> {
+  FMT_CONSTEXPR format_parse_context::iterator parse(
+      format_parse_context& ctx) {
+    return ctx.begin();
+  }
+
+  format_context::iterator format(const detail::bigint& n,
+                                  format_context& ctx) {
+    auto out = ctx.out();
+    bool first = true;
+    for (auto i = n.bigits_.size(); i > 0; --i) {
+      auto value = n.bigits_[i - 1u];
+      if (first) {
+        out = format_to(out, FMT_STRING("{:x}"), value);
+        first = false;
+        continue;
+      }
+      out = format_to(out, FMT_STRING("{:08x}"), value);
+    }
+    if (n.exp_ > 0)
+      out = format_to(out, FMT_STRING("p{}"),
+                      n.exp_ * detail::bigint::bigit_bits);
+    return out;
+  }
+};
+
+FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
+  for_each_codepoint(s, [this](uint32_t cp, int error) {
+    if (error != 0) FMT_THROW(std::runtime_error("invalid utf8"));
+    if (cp <= 0xFFFF) {
+      buffer_.push_back(static_cast<wchar_t>(cp));
+    } else {
+      cp -= 0x10000;
+      buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
+      buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
+    }
+  });
+  buffer_.push_back(0);
+}
+
+FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
+                                  string_view message) FMT_NOEXCEPT {
+  FMT_TRY {
+    memory_buffer buf;
+    buf.resize(inline_buffer_size);
+    for (;;) {
+      char* system_message = &buf[0];
+      int result =
+          detail::safe_strerror(error_code, system_message, buf.size());
+      if (result == 0) {
+        format_to(detail::buffer_appender<char>(out), FMT_STRING("{}: {}"),
+                  message, system_message);
+        return;
+      }
+      if (result != ERANGE)
+        break;  // Can't get error message, report error code instead.
+      buf.resize(buf.size() * 2);
+    }
+  }
+  FMT_CATCH(...) {}
+  format_error_code(out, error_code, message);
+}
+
+FMT_FUNC void detail::error_handler::on_error(const char* message) {
+  FMT_THROW(format_error(message));
+}
+
+FMT_FUNC void report_system_error(int error_code,
+                                  fmt::string_view message) FMT_NOEXCEPT {
+  report_error(format_system_error, error_code, message);
+}
+
+FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) {
+  if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
+    auto arg = args.get(0);
+    if (!arg) error_handler().on_error("argument not found");
+    return visit_format_arg(stringifier(), arg);
+  }
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str, args);
+  return to_string(buffer);
+}
+
+#ifdef _WIN32
+namespace detail {
+using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
+extern "C" __declspec(dllimport) int __stdcall WriteConsoleW(  //
+    void*, const void*, dword, dword*, void*);
+}  // namespace detail
+#endif
+
+FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str, args);
+#ifdef _WIN32
+  auto fd = _fileno(f);
+  if (_isatty(fd)) {
+    detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
+    auto written = detail::dword();
+    if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
+                              u16.c_str(), static_cast<uint32_t>(u16.size()),
+                              &written, nullptr)) {
+      return;
+    }
+    // Fallback to fwrite on failure. It can happen if the output has been
+    // redirected to NUL.
+  }
+#endif
+  detail::fwrite_fully(buffer.data(), 1, buffer.size(), f);
+}
+
+#ifdef _WIN32
+// Print assuming legacy (non-Unicode) encoding.
+FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
+                                      format_args args) {
+  memory_buffer buffer;
+  detail::vformat_to(buffer, format_str,
+                     basic_format_args<buffer_context<char>>(args));
+  fwrite_fully(buffer.data(), 1, buffer.size(), f);
+}
+#endif
+
+FMT_FUNC void vprint(string_view format_str, format_args args) {
+  vprint(stdout, format_str, args);
+}
+
+FMT_END_NAMESPACE
+
+#endif  // FMT_FORMAT_INL_H_
diff --git a/libs/fmt/include/fmt/format.h b/libs/fmt/include/fmt/format.h
new file mode 100644
index 0000000000000000000000000000000000000000..83dee9cb84ccb67bf793991ed1cf7e9fb1818007
--- /dev/null
+++ b/libs/fmt/include/fmt/format.h
@@ -0,0 +1,3481 @@
+/*
+ Formatting library for C++
+
+ Copyright (c) 2012 - present, Victor Zverovich
+
+ 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 AUTHORS OR COPYRIGHT HOLDERS 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.
+
+ --- Optional exception to the license ---
+
+ As an exception, if, as a result of your compiling your source code, portions
+ of this Software are embedded into a machine-executable object form of such
+ source code, you may redistribute such embedded portions in such object form
+ without including the above copyright and permission notices.
+ */
+
+#ifndef FMT_FORMAT_H_
+#define FMT_FORMAT_H_
+
+#include <cerrno>  // errno
+#include <cmath>   // std::signbit
+#include <cstdint>
+#include <cwchar>
+#include <limits>     // std::numeric_limits
+#include <memory>     // std::uninitialized_copy
+#include <stdexcept>  // std::runtime_error
+#include <utility>    // std::swap
+
+#include "core.h"
+
+#ifdef __INTEL_COMPILER
+#  define FMT_ICC_VERSION __INTEL_COMPILER
+#elif defined(__ICL)
+#  define FMT_ICC_VERSION __ICL
+#else
+#  define FMT_ICC_VERSION 0
+#endif
+
+#ifdef __NVCC__
+#  define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__)
+#else
+#  define FMT_CUDA_VERSION 0
+#endif
+
+#ifdef __has_builtin
+#  define FMT_HAS_BUILTIN(x) __has_builtin(x)
+#else
+#  define FMT_HAS_BUILTIN(x) 0
+#endif
+
+#if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#  define FMT_NOINLINE __attribute__((noinline))
+#else
+#  define FMT_NOINLINE
+#endif
+
+#if FMT_MSC_VER
+#  define FMT_MSC_DEFAULT = default
+#else
+#  define FMT_MSC_DEFAULT
+#endif
+
+#if __cplusplus == 201103L || __cplusplus == 201402L
+#  if defined(__INTEL_COMPILER) || defined(__PGI)
+#    define FMT_FALLTHROUGH
+#  elif defined(__clang__)
+#    define FMT_FALLTHROUGH [[clang::fallthrough]]
+#  elif FMT_GCC_VERSION >= 700 && \
+      (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520)
+#    define FMT_FALLTHROUGH [[gnu::fallthrough]]
+#  else
+#    define FMT_FALLTHROUGH
+#  endif
+#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \
+    (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#  define FMT_FALLTHROUGH [[fallthrough]]
+#else
+#  define FMT_FALLTHROUGH
+#endif
+
+#ifndef FMT_THROW
+#  if FMT_EXCEPTIONS
+#    if FMT_MSC_VER || FMT_NVCC
+FMT_BEGIN_NAMESPACE
+namespace detail {
+template <typename Exception> inline void do_throw(const Exception& x) {
+  // Silence unreachable code warnings in MSVC and NVCC because these
+  // are nearly impossible to fix in a generic code.
+  volatile bool b = true;
+  if (b) throw x;
+}
+}  // namespace detail
+FMT_END_NAMESPACE
+#      define FMT_THROW(x) detail::do_throw(x)
+#    else
+#      define FMT_THROW(x) throw x
+#    endif
+#  else
+#    define FMT_THROW(x)               \
+      do {                             \
+        FMT_ASSERT(false, (x).what()); \
+      } while (false)
+#  endif
+#endif
+
+#if FMT_EXCEPTIONS
+#  define FMT_TRY try
+#  define FMT_CATCH(x) catch (x)
+#else
+#  define FMT_TRY if (true)
+#  define FMT_CATCH(x) if (false)
+#endif
+
+#ifndef FMT_USE_USER_DEFINED_LITERALS
+// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs.
+#  if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \
+       FMT_MSC_VER >= 1900) &&                                         \
+      (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480)
+#    define FMT_USE_USER_DEFINED_LITERALS 1
+#  else
+#    define FMT_USE_USER_DEFINED_LITERALS 0
+#  endif
+#endif
+
+#ifndef FMT_USE_FLOAT
+#  define FMT_USE_FLOAT 1
+#endif
+
+#ifndef FMT_USE_DOUBLE
+#  define FMT_USE_DOUBLE 1
+#endif
+
+#ifndef FMT_USE_LONG_DOUBLE
+#  define FMT_USE_LONG_DOUBLE 1
+#endif
+
+// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of
+// integer formatter template instantiations to just one by only using the
+// largest integer type. This results in a reduction in binary size but will
+// cause a decrease in integer formatting performance.
+#if !defined(FMT_REDUCE_INT_INSTANTIATIONS)
+#  define FMT_REDUCE_INT_INSTANTIATIONS 0
+#endif
+
+// __builtin_clz is broken in clang with Microsoft CodeGen:
+// https://github.com/fmtlib/fmt/issues/519
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
+#  define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER
+#  define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz))
+#  define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+#endif
+#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll))
+#  define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+#endif
+
+#if FMT_MSC_VER
+#  include <intrin.h>  // _BitScanReverse[64], _BitScanForward[64], _umul128
+#endif
+
+// Some compilers masquerade as both MSVC and GCC-likes or otherwise support
+// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the
+// MSVC intrinsics if the clz and clzll builtins are not available.
+#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(FMT_BUILTIN_CTZLL)
+FMT_BEGIN_NAMESPACE
+namespace detail {
+// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning.
+#  if !defined(__clang__)
+#    pragma managed(push, off)
+#    pragma intrinsic(_BitScanForward)
+#    pragma intrinsic(_BitScanReverse)
+#    if defined(_WIN64)
+#      pragma intrinsic(_BitScanForward64)
+#      pragma intrinsic(_BitScanReverse64)
+#    endif
+#  endif
+
+inline int clz(uint32_t x) {
+  unsigned long r = 0;
+  _BitScanReverse(&r, x);
+  FMT_ASSERT(x != 0, "");
+  // Static analysis complains about using uninitialized data
+  // "r", but the only way that can happen is if "x" is 0,
+  // which the callers guarantee to not happen.
+  FMT_MSC_WARNING(suppress : 6102)
+  return 31 ^ static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CLZ(n) detail::clz(n)
+
+inline int clzll(uint64_t x) {
+  unsigned long r = 0;
+#  ifdef _WIN64
+  _BitScanReverse64(&r, x);
+#  else
+  // Scan the high 32 bits.
+  if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) return 63 ^ (r + 32);
+  // Scan the low 32 bits.
+  _BitScanReverse(&r, static_cast<uint32_t>(x));
+#  endif
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+  return 63 ^ static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CLZLL(n) detail::clzll(n)
+
+inline int ctz(uint32_t x) {
+  unsigned long r = 0;
+  _BitScanForward(&r, x);
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+  return static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CTZ(n) detail::ctz(n)
+
+inline int ctzll(uint64_t x) {
+  unsigned long r = 0;
+  FMT_ASSERT(x != 0, "");
+  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.
+#  ifdef _WIN64
+  _BitScanForward64(&r, x);
+#  else
+  // Scan the low 32 bits.
+  if (_BitScanForward(&r, static_cast<uint32_t>(x))) return static_cast<int>(r);
+  // Scan the high 32 bits.
+  _BitScanForward(&r, static_cast<uint32_t>(x >> 32));
+  r += 32;
+#  endif
+  return static_cast<int>(r);
+}
+#  define FMT_BUILTIN_CTZLL(n) detail::ctzll(n)
+#  if !defined(__clang__)
+#    pragma managed(pop)
+#  endif
+}  // namespace detail
+FMT_END_NAMESPACE
+#endif
+
+#ifndef FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+#  if defined(__cpp_nontype_template_args) &&                \
+      ((FMT_GCC_VERSION >= 903 && __cplusplus >= 201709L) || \
+       __cpp_nontype_template_args >= 201911L)
+#    define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 1
+#  else
+#    define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0
+#  endif
+#endif
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+#if __cplusplus >= 202002L || \
+    (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
+#  define FMT_CONSTEXPR20 constexpr
+#else
+#  define FMT_CONSTEXPR20
+#endif
+
+// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
+// undefined behavior (e.g. due to type aliasing).
+// Example: uint64_t d = bit_cast<uint64_t>(2.718);
+template <typename Dest, typename Source>
+inline Dest bit_cast(const Source& source) {
+  static_assert(sizeof(Dest) == sizeof(Source), "size mismatch");
+  Dest dest;
+  std::memcpy(&dest, &source, sizeof(dest));
+  return dest;
+}
+
+inline bool is_big_endian() {
+  const auto u = 1u;
+  struct bytes {
+    char data[sizeof(u)];
+  };
+  return bit_cast<bytes>(u).data[0] == 0;
+}
+
+// A fallback implementation of uintptr_t for systems that lack it.
+struct fallback_uintptr {
+  unsigned char value[sizeof(void*)];
+
+  fallback_uintptr() = default;
+  explicit fallback_uintptr(const void* p) {
+    *this = bit_cast<fallback_uintptr>(p);
+    if (is_big_endian()) {
+      for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j)
+        std::swap(value[i], value[j]);
+    }
+  }
+};
+#ifdef UINTPTR_MAX
+using uintptr_t = ::uintptr_t;
+inline uintptr_t to_uintptr(const void* p) { return bit_cast<uintptr_t>(p); }
+#else
+using uintptr_t = fallback_uintptr;
+inline fallback_uintptr to_uintptr(const void* p) {
+  return fallback_uintptr(p);
+}
+#endif
+
+// Returns the largest possible value for type T. Same as
+// std::numeric_limits<T>::max() but shorter and not affected by the max macro.
+template <typename T> constexpr T max_value() {
+  return (std::numeric_limits<T>::max)();
+}
+template <typename T> constexpr int num_bits() {
+  return std::numeric_limits<T>::digits;
+}
+// std::numeric_limits<T>::digits may return 0 for 128-bit ints.
+template <> constexpr int num_bits<int128_t>() { return 128; }
+template <> constexpr int num_bits<uint128_t>() { return 128; }
+template <> constexpr int num_bits<fallback_uintptr>() {
+  return static_cast<int>(sizeof(void*) *
+                          std::numeric_limits<unsigned char>::digits);
+}
+
+FMT_INLINE void assume(bool condition) {
+  (void)condition;
+#if FMT_HAS_BUILTIN(__builtin_assume)
+  __builtin_assume(condition);
+#endif
+}
+
+// An approximation of iterator_t for pre-C++20 systems.
+template <typename T>
+using iterator_t = decltype(std::begin(std::declval<T&>()));
+template <typename T> using sentinel_t = decltype(std::end(std::declval<T&>()));
+
+// A workaround for std::string not having mutable data() until C++17.
+template <typename Char> inline Char* get_data(std::basic_string<Char>& s) {
+  return &s[0];
+}
+template <typename Container>
+inline typename Container::value_type* get_data(Container& c) {
+  return c.data();
+}
+
+#if defined(_SECURE_SCL) && _SECURE_SCL
+// Make a checked iterator to avoid MSVC warnings.
+template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
+template <typename T> checked_ptr<T> make_checked(T* p, size_t size) {
+  return {p, size};
+}
+#else
+template <typename T> using checked_ptr = T*;
+template <typename T> inline T* make_checked(T* p, size_t) { return p; }
+#endif
+
+template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
+#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
+__attribute__((no_sanitize("undefined")))
+#endif
+inline checked_ptr<typename Container::value_type>
+reserve(std::back_insert_iterator<Container> it, size_t n) {
+  Container& c = get_container(it);
+  size_t size = c.size();
+  c.resize(size + n);
+  return make_checked(get_data(c) + size, n);
+}
+
+template <typename T>
+inline buffer_appender<T> reserve(buffer_appender<T> it, size_t n) {
+  buffer<T>& buf = get_container(it);
+  buf.try_reserve(buf.size() + n);
+  return it;
+}
+
+template <typename Iterator> constexpr Iterator& reserve(Iterator& it, size_t) {
+  return it;
+}
+
+template <typename OutputIt>
+using reserve_iterator =
+    remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;
+
+template <typename T, typename OutputIt>
+constexpr T* to_pointer(OutputIt, size_t) {
+  return nullptr;
+}
+template <typename T> T* to_pointer(buffer_appender<T> it, size_t n) {
+  buffer<T>& buf = get_container(it);
+  auto size = buf.size();
+  if (buf.capacity() < size + n) return nullptr;
+  buf.try_resize(size + n);
+  return buf.data() + size;
+}
+
+template <typename Container, FMT_ENABLE_IF(is_contiguous<Container>::value)>
+inline std::back_insert_iterator<Container> base_iterator(
+    std::back_insert_iterator<Container>& it,
+    checked_ptr<typename Container::value_type>) {
+  return it;
+}
+
+template <typename Iterator>
+constexpr Iterator base_iterator(Iterator, Iterator it) {
+  return it;
+}
+
+// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n
+// instead (#1998).
+template <typename OutputIt, typename Size, typename T>
+FMT_CONSTEXPR OutputIt fill_n(OutputIt out, Size count, const T& value) {
+  for (Size i = 0; i < count; ++i) *out++ = value;
+  return out;
+}
+template <typename T, typename Size>
+FMT_CONSTEXPR20 T* fill_n(T* out, Size count, char value) {
+  if (is_constant_evaluated()) {
+    return fill_n<T*, Size, T>(out, count, value);
+  }
+  std::memset(out, value, to_unsigned(count));
+  return out + count;
+}
+
+#ifdef __cpp_char8_t
+using char8_type = char8_t;
+#else
+enum char8_type : unsigned char {};
+#endif
+
+template <typename InputIt, typename OutChar>
+using needs_conversion = bool_constant<
+    std::is_same<typename std::iterator_traits<InputIt>::value_type,
+                 char>::value &&
+    std::is_same<OutChar, char8_type>::value>;
+
+template <typename OutChar, typename InputIt, typename OutputIt,
+          FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
+FMT_CONSTEXPR OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
+  while (begin != end) *it++ = *begin++;
+  return it;
+}
+
+template <typename OutChar, typename InputIt,
+          FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
+FMT_CONSTEXPR20 OutChar* copy_str(InputIt begin, InputIt end, OutChar* out) {
+  if (is_constant_evaluated()) {
+    return copy_str<OutChar, InputIt, OutChar*>(begin, end, out);
+  }
+  auto size = to_unsigned(end - begin);
+  std::uninitialized_copy(begin, end, make_checked(out, size));
+  return out + size;
+}
+
+template <typename OutChar, typename InputIt, typename OutputIt,
+          FMT_ENABLE_IF(needs_conversion<InputIt, OutChar>::value)>
+OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) {
+  while (begin != end) *it++ = static_cast<char8_type>(*begin++);
+  return it;
+}
+
+template <typename OutChar, typename InputIt,
+          FMT_ENABLE_IF(!needs_conversion<InputIt, OutChar>::value)>
+buffer_appender<OutChar> copy_str(InputIt begin, InputIt end,
+                                  buffer_appender<OutChar> out) {
+  get_container(out).append(begin, end);
+  return out;
+}
+
+// A public domain branchless UTF-8 decoder by Christopher Wellons:
+// https://github.com/skeeto/branchless-utf8
+/* Decode the next character, c, from s, reporting errors in e.
+ *
+ * Since this is a branchless decoder, four bytes will be read from the
+ * buffer regardless of the actual length of the next character. This
+ * means the buffer _must_ have at least three bytes of zero padding
+ * following the end of the data stream.
+ *
+ * Errors are reported in e, which will be non-zero if the parsed
+ * character was somehow invalid: invalid byte sequence, non-canonical
+ * encoding, or a surrogate half.
+ *
+ * The function returns a pointer to the next character. When an error
+ * occurs, this pointer will be a guess that depends on the particular
+ * error, but it will always advance at least one byte.
+ */
+FMT_CONSTEXPR inline const char* utf8_decode(const char* s, uint32_t* c,
+                                             int* e) {
+  constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
+  constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
+  constexpr const int shiftc[] = {0, 18, 12, 6, 0};
+  constexpr const int shifte[] = {0, 6, 4, 2, 0};
+
+  int len = code_point_length(s);
+  const char* next = s + len;
+
+  // Assume a four-byte character and load four bytes. Unused bits are
+  // shifted out.
+  *c = uint32_t(s[0] & masks[len]) << 18;
+  *c |= uint32_t(s[1] & 0x3f) << 12;
+  *c |= uint32_t(s[2] & 0x3f) << 6;
+  *c |= uint32_t(s[3] & 0x3f) << 0;
+  *c >>= shiftc[len];
+
+  // Accumulate the various error conditions.
+  using uchar = unsigned char;
+  *e = (*c < mins[len]) << 6;       // non-canonical encoding
+  *e |= ((*c >> 11) == 0x1b) << 7;  // surrogate half?
+  *e |= (*c > 0x10FFFF) << 8;       // out of range?
+  *e |= (uchar(s[1]) & 0xc0) >> 2;
+  *e |= (uchar(s[2]) & 0xc0) >> 4;
+  *e |= uchar(s[3]) >> 6;
+  *e ^= 0x2a;  // top two bits of each tail byte correct?
+  *e >>= shifte[len];
+
+  return next;
+}
+
+template <typename F>
+FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
+  auto decode = [f](const char* p) {
+    auto cp = uint32_t();
+    auto error = 0;
+    p = utf8_decode(p, &cp, &error);
+    f(cp, error);
+    return p;
+  };
+  auto p = s.data();
+  const size_t block_size = 4;  // utf8_decode always reads blocks of 4 chars.
+  if (s.size() >= block_size) {
+    for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p);
+  }
+  if (auto num_chars_left = s.data() + s.size() - p) {
+    char buf[2 * block_size - 1] = {};
+    copy_str<char>(p, p + num_chars_left, buf);
+    p = buf;
+    do {
+      p = decode(p);
+    } while (p - buf < num_chars_left);
+  }
+}
+
+template <typename Char>
+inline size_t compute_width(basic_string_view<Char> s) {
+  return s.size();
+}
+
+// Computes approximate display width of a UTF-8 string.
+FMT_CONSTEXPR inline size_t compute_width(string_view s) {
+  size_t num_code_points = 0;
+  // It is not a lambda for compatibility with C++14.
+  struct count_code_points {
+    size_t* count;
+    FMT_CONSTEXPR void operator()(uint32_t cp, int error) const {
+      *count +=
+          1 +
+          (error == 0 && cp >= 0x1100 &&
+           (cp <= 0x115f ||  // Hangul Jamo init. consonants
+            cp == 0x2329 ||  // LEFT-POINTING ANGLE BRACKET〈
+            cp == 0x232a ||  // RIGHT-POINTING ANGLE BRACKET 〉
+            // CJK ... Yi except Unicode Character “〿”:
+            (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
+            (cp >= 0xac00 && cp <= 0xd7a3) ||    // Hangul Syllables
+            (cp >= 0xf900 && cp <= 0xfaff) ||    // CJK Compatibility Ideographs
+            (cp >= 0xfe10 && cp <= 0xfe19) ||    // Vertical Forms
+            (cp >= 0xfe30 && cp <= 0xfe6f) ||    // CJK Compatibility Forms
+            (cp >= 0xff00 && cp <= 0xff60) ||    // Fullwidth Forms
+            (cp >= 0xffe0 && cp <= 0xffe6) ||    // Fullwidth Forms
+            (cp >= 0x20000 && cp <= 0x2fffd) ||  // CJK
+            (cp >= 0x30000 && cp <= 0x3fffd) ||
+            // Miscellaneous Symbols and Pictographs + Emoticons:
+            (cp >= 0x1f300 && cp <= 0x1f64f) ||
+            // Supplemental Symbols and Pictographs:
+            (cp >= 0x1f900 && cp <= 0x1f9ff)));
+    }
+  };
+  for_each_codepoint(s, count_code_points{&num_code_points});
+  return num_code_points;
+}
+
+inline size_t compute_width(basic_string_view<char8_type> s) {
+  return compute_width(basic_string_view<char>(
+      reinterpret_cast<const char*>(s.data()), s.size()));
+}
+
+template <typename Char>
+inline size_t code_point_index(basic_string_view<Char> s, size_t n) {
+  size_t size = s.size();
+  return n < size ? n : size;
+}
+
+// Calculates the index of the nth code point in a UTF-8 string.
+inline size_t code_point_index(basic_string_view<char8_type> s, size_t n) {
+  const char8_type* data = s.data();
+  size_t num_code_points = 0;
+  for (size_t i = 0, size = s.size(); i != size; ++i) {
+    if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i;
+  }
+  return s.size();
+}
+
+template <typename T>
+using is_fast_float = bool_constant<std::numeric_limits<T>::is_iec559 &&
+                                    sizeof(T) <= sizeof(double)>;
+
+#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
+#  define FMT_USE_FULL_CACHE_DRAGONBOX 0
+#endif
+
+template <typename T>
+template <typename U>
+void buffer<T>::append(const U* begin, const U* end) {
+  while (begin != end) {
+    auto count = to_unsigned(end - begin);
+    try_reserve(size_ + count);
+    auto free_cap = capacity_ - size_;
+    if (free_cap < count) count = free_cap;
+    std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count));
+    size_ += count;
+    begin += count;
+  }
+}
+
+template <typename OutputIt, typename T, typename Traits>
+void iterator_buffer<OutputIt, T, Traits>::flush() {
+  auto size = this->size();
+  this->clear();
+  out_ = copy_str<T>(data_, data_ + this->limit(size), out_);
+}
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <> struct is_char<detail::char8_type> : std::true_type {};
+template <> struct is_char<char16_t> : std::true_type {};
+template <> struct is_char<char32_t> : std::true_type {};
+
+// The number of characters to store in the basic_memory_buffer object itself
+// to avoid dynamic memory allocation.
+enum { inline_buffer_size = 500 };
+
+/**
+  \rst
+  A dynamically growing memory buffer for trivially copyable/constructible types
+  with the first ``SIZE`` elements stored in the object itself.
+
+  You can use one of the following type aliases for common character types:
+
+  +----------------+------------------------------+
+  | Type           | Definition                   |
+  +================+==============================+
+  | memory_buffer  | basic_memory_buffer<char>    |
+  +----------------+------------------------------+
+  | wmemory_buffer | basic_memory_buffer<wchar_t> |
+  +----------------+------------------------------+
+
+  **Example**::
+
+     fmt::memory_buffer out;
+     format_to(out, "The answer is {}.", 42);
+
+  This will append the following output to the ``out`` object:
+
+  .. code-block:: none
+
+     The answer is 42.
+
+  The output can be converted to an ``std::string`` with ``to_string(out)``.
+  \endrst
+ */
+template <typename T, size_t SIZE = inline_buffer_size,
+          typename Allocator = std::allocator<T>>
+class basic_memory_buffer final : public detail::buffer<T> {
+ private:
+  T store_[SIZE];
+
+  // Don't inherit from Allocator avoid generating type_info for it.
+  Allocator alloc_;
+
+  // Deallocate memory allocated by the buffer.
+  void deallocate() {
+    T* data = this->data();
+    if (data != store_) alloc_.deallocate(data, this->capacity());
+  }
+
+ protected:
+  void grow(size_t size) final FMT_OVERRIDE;
+
+ public:
+  using value_type = T;
+  using const_reference = const T&;
+
+  explicit basic_memory_buffer(const Allocator& alloc = Allocator())
+      : alloc_(alloc) {
+    this->set(store_, SIZE);
+  }
+  ~basic_memory_buffer() { deallocate(); }
+
+ private:
+  // Move data from other to this buffer.
+  void move(basic_memory_buffer& other) {
+    alloc_ = std::move(other.alloc_);
+    T* data = other.data();
+    size_t size = other.size(), capacity = other.capacity();
+    if (data == other.store_) {
+      this->set(store_, capacity);
+      std::uninitialized_copy(other.store_, other.store_ + size,
+                              detail::make_checked(store_, capacity));
+    } else {
+      this->set(data, capacity);
+      // Set pointer to the inline array so that delete is not called
+      // when deallocating.
+      other.set(other.store_, 0);
+    }
+    this->resize(size);
+  }
+
+ public:
+  /**
+    \rst
+    Constructs a :class:`fmt::basic_memory_buffer` object moving the content
+    of the other object to it.
+    \endrst
+   */
+  basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); }
+
+  /**
+    \rst
+    Moves the content of the other ``basic_memory_buffer`` object to this one.
+    \endrst
+   */
+  basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT {
+    FMT_ASSERT(this != &other, "");
+    deallocate();
+    move(other);
+    return *this;
+  }
+
+  // Returns a copy of the allocator associated with this buffer.
+  Allocator get_allocator() const { return alloc_; }
+
+  /**
+    Resizes the buffer to contain *count* elements. If T is a POD type new
+    elements may not be initialized.
+   */
+  void resize(size_t count) { this->try_resize(count); }
+
+  /** Increases the buffer capacity to *new_capacity*. */
+  void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
+
+  // Directly append data into the buffer
+  using detail::buffer<T>::append;
+  template <typename ContiguousRange>
+  void append(const ContiguousRange& range) {
+    append(range.data(), range.data() + range.size());
+  }
+};
+
+template <typename T, size_t SIZE, typename Allocator>
+void basic_memory_buffer<T, SIZE, Allocator>::grow(size_t size) {
+#ifdef FMT_FUZZ
+  if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much");
+#endif
+  const size_t max_size = std::allocator_traits<Allocator>::max_size(alloc_);
+  size_t old_capacity = this->capacity();
+  size_t new_capacity = old_capacity + old_capacity / 2;
+  if (size > new_capacity)
+    new_capacity = size;
+  else if (new_capacity > max_size)
+    new_capacity = size > max_size ? size : max_size;
+  T* old_data = this->data();
+  T* new_data =
+      std::allocator_traits<Allocator>::allocate(alloc_, new_capacity);
+  // The following code doesn't throw, so the raw pointer above doesn't leak.
+  std::uninitialized_copy(old_data, old_data + this->size(),
+                          detail::make_checked(new_data, new_capacity));
+  this->set(new_data, new_capacity);
+  // deallocate must not throw according to the standard, but even if it does,
+  // the buffer already uses the new storage and will deallocate it in
+  // destructor.
+  if (old_data != store_) alloc_.deallocate(old_data, old_capacity);
+}
+
+using memory_buffer = basic_memory_buffer<char>;
+using wmemory_buffer = basic_memory_buffer<wchar_t>;
+
+template <typename T, size_t SIZE, typename Allocator>
+struct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {
+};
+
+/** A formatting error such as invalid format string. */
+FMT_CLASS_API
+class FMT_API format_error : public std::runtime_error {
+ public:
+  explicit format_error(const char* message) : std::runtime_error(message) {}
+  explicit format_error(const std::string& message)
+      : std::runtime_error(message) {}
+  format_error(const format_error&) = default;
+  format_error& operator=(const format_error&) = default;
+  format_error(format_error&&) = default;
+  format_error& operator=(format_error&&) = default;
+  ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT;
+};
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+
+template <typename T>
+using is_signed =
+    std::integral_constant<bool, std::numeric_limits<T>::is_signed ||
+                                     std::is_same<T, int128_t>::value>;
+
+// Returns true if value is negative, false otherwise.
+// Same as `value < 0` but doesn't produce warnings if T is an unsigned type.
+template <typename T, FMT_ENABLE_IF(is_signed<T>::value)>
+FMT_CONSTEXPR bool is_negative(T value) {
+  return value < 0;
+}
+template <typename T, FMT_ENABLE_IF(!is_signed<T>::value)>
+FMT_CONSTEXPR bool is_negative(T) {
+  return false;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR bool is_supported_floating_point(T) {
+  return (std::is_same<T, float>::value && FMT_USE_FLOAT) ||
+         (std::is_same<T, double>::value && FMT_USE_DOUBLE) ||
+         (std::is_same<T, long double>::value && FMT_USE_LONG_DOUBLE);
+}
+
+// Smallest of uint32_t, uint64_t, uint128_t that is large enough to
+// represent all values of an integral type T.
+template <typename T>
+using uint32_or_64_or_128_t =
+    conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS,
+                  uint32_t,
+                  conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>;
+template <typename T>
+using uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;
+
+#define FMT_POWERS_OF_10(factor)                                             \
+  factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
+      (factor)*1000000, (factor)*10000000, (factor)*100000000,               \
+      (factor)*1000000000
+
+// Static data is placed in this class template for the header-only config.
+template <typename T = void> struct basic_data {
+  // log10(2) = 0x0.4d104d427de7fbcc...
+  static const uint64_t log10_2_significand = 0x4d104d427de7fbcc;
+
+  // GCC generates slightly better code for pairs than chars.
+  // using digit_pair = char;
+  static constexpr const char digits[][2] = {
+      {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
+      {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
+      {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
+      {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
+      {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
+      {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
+      {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
+      {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
+      {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
+      {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
+      {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
+      {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
+      {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
+      {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
+      {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
+      {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
+      {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
+
+  static constexpr const char hex_digits[] = "0123456789abcdef";
+  static constexpr const char signs[] = {0, '-', '+', ' '};
+  static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+                                                 0x1000000u | ' '};
+  static constexpr const char left_padding_shifts[] = {31, 31, 0, 1, 0};
+  static constexpr const char right_padding_shifts[] = {0, 31, 0, 1, 0};
+};
+
+// This is a struct rather than an alias to avoid shadowing warnings in gcc.
+struct data : basic_data<> {};
+
+// Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
+// This is a function instead of an array to workaround a bug in GCC10 (#1810).
+FMT_INLINE uint16_t bsr2log10(int bsr) {
+  static constexpr uint16_t data[] = {
+      1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,
+      6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9,  10, 10, 10,
+      10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
+      15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
+  return data[bsr];
+}
+
+template <typename T> FMT_CONSTEXPR int count_digits_fallback(T n) {
+  int count = 1;
+  for (;;) {
+    // Integer division is slow so do it for a group of four digits instead
+    // of for every digit. The idea comes from the talk by Alexandrescu
+    // "Three Optimization Tips for C++". See speed-test for a comparison.
+    if (n < 10) return count;
+    if (n < 100) return count + 1;
+    if (n < 1000) return count + 2;
+    if (n < 10000) return count + 3;
+    n /= 10000u;
+    count += 4;
+  }
+}
+#if FMT_USE_INT128
+FMT_CONSTEXPR inline int count_digits(uint128_t n) {
+  return count_digits_fallback(n);
+}
+#endif
+
+// Returns the number of decimal digits in n. Leading zeros are not counted
+// except for n == 0 in which case count_digits returns 1.
+FMT_CONSTEXPR20 inline int count_digits(uint64_t n) {
+  if (is_constant_evaluated()) {
+    return count_digits_fallback(n);
+  }
+#ifdef FMT_BUILTIN_CLZLL
+  // https://github.com/fmtlib/format-benchmark/blob/master/digits10
+  auto t = bsr2log10(FMT_BUILTIN_CLZLL(n | 1) ^ 63);
+  constexpr const uint64_t zero_or_powers_of_10[] = {
+      0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
+      10000000000000000000ULL};
+  return t - (n < zero_or_powers_of_10[t]);
+#else
+  return count_digits_fallback(n);
+#endif
+}
+
+// Counts the number of digits in n. BITS = log2(radix).
+template <int BITS, typename UInt> FMT_CONSTEXPR int count_digits(UInt n) {
+#ifdef FMT_BUILTIN_CLZ
+  if (num_bits<UInt>() == 32)
+    return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1;
+#endif
+  int num_digits = 0;
+  do {
+    ++num_digits;
+  } while ((n >>= BITS) != 0);
+  return num_digits;
+}
+
+template <> int count_digits<4>(detail::fallback_uintptr n);
+
+#if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#  define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
+#elif FMT_MSC_VER
+#  define FMT_ALWAYS_INLINE __forceinline
+#else
+#  define FMT_ALWAYS_INLINE inline
+#endif
+
+#ifdef FMT_BUILTIN_CLZ
+// Optional version of count_digits for better performance on 32-bit platforms.
+FMT_CONSTEXPR20 inline int count_digits(uint32_t n) {
+  if (is_constant_evaluated()) {
+    return count_digits_fallback(n);
+  }
+  auto t = bsr2log10(FMT_BUILTIN_CLZ(n | 1) ^ 31);
+  constexpr const uint32_t zero_or_powers_of_10[] = {0, 0,
+                                                     FMT_POWERS_OF_10(1U)};
+  return t - (n < zero_or_powers_of_10[t]);
+}
+#endif
+
+template <typename Int> constexpr int digits10() FMT_NOEXCEPT {
+  return std::numeric_limits<Int>::digits10;
+}
+template <> constexpr int digits10<int128_t>() FMT_NOEXCEPT { return 38; }
+template <> constexpr int digits10<uint128_t>() FMT_NOEXCEPT { return 38; }
+
+// DEPRECATED! grouping will be merged into thousands_sep.
+template <typename Char> FMT_API std::string grouping_impl(locale_ref loc);
+template <typename Char> inline std::string grouping(locale_ref loc) {
+  return grouping_impl<char>(loc);
+}
+template <> inline std::string grouping<wchar_t>(locale_ref loc) {
+  return grouping_impl<wchar_t>(loc);
+}
+
+template <typename Char> FMT_API Char thousands_sep_impl(locale_ref loc);
+template <typename Char> inline Char thousands_sep(locale_ref loc) {
+  return Char(thousands_sep_impl<char>(loc));
+}
+template <> inline wchar_t thousands_sep(locale_ref loc) {
+  return thousands_sep_impl<wchar_t>(loc);
+}
+
+template <typename Char> FMT_API Char decimal_point_impl(locale_ref loc);
+template <typename Char> inline Char decimal_point(locale_ref loc) {
+  return Char(decimal_point_impl<char>(loc));
+}
+template <> inline wchar_t decimal_point(locale_ref loc) {
+  return decimal_point_impl<wchar_t>(loc);
+}
+
+// Compares two characters for equality.
+template <typename Char> bool equal2(const Char* lhs, const char* rhs) {
+  return lhs[0] == rhs[0] && lhs[1] == rhs[1];
+}
+inline bool equal2(const char* lhs, const char* rhs) {
+  return memcmp(lhs, rhs, 2) == 0;
+}
+
+// Copies two characters from src to dst.
+template <typename Char> void copy2(Char* dst, const char* src) {
+  *dst++ = static_cast<Char>(*src++);
+  *dst = static_cast<Char>(*src);
+}
+FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); }
+
+template <typename Iterator> struct format_decimal_result {
+  Iterator begin;
+  Iterator end;
+};
+
+// Formats a decimal unsigned integer value writing into out pointing to a
+// buffer of specified size. The caller must ensure that the buffer is large
+// enough.
+template <typename Char, typename UInt>
+FMT_CONSTEXPR20 format_decimal_result<Char*> format_decimal(Char* out,
+                                                            UInt value,
+                                                            int size) {
+  FMT_ASSERT(size >= count_digits(value), "invalid digit count");
+  out += size;
+  Char* end = out;
+  if (is_constant_evaluated()) {
+    while (value >= 10) {
+      *--out = static_cast<Char>('0' + value % 10);
+      value /= 10;
+    }
+    *--out = static_cast<Char>('0' + value);
+    return {out, end};
+  }
+  while (value >= 100) {
+    // Integer division is slow so do it for a group of two digits instead
+    // of for every digit. The idea comes from the talk by Alexandrescu
+    // "Three Optimization Tips for C++". See speed-test for a comparison.
+    out -= 2;
+    copy2(out, data::digits[value % 100]);
+    value /= 100;
+  }
+  if (value < 10) {
+    *--out = static_cast<Char>('0' + value);
+    return {out, end};
+  }
+  out -= 2;
+  copy2(out, data::digits[value]);
+  return {out, end};
+}
+
+template <typename Char, typename UInt, typename Iterator,
+          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<Iterator>>::value)>
+inline format_decimal_result<Iterator> format_decimal(Iterator out, UInt value,
+                                                      int size) {
+  // Buffer is large enough to hold all digits (digits10 + 1).
+  Char buffer[digits10<UInt>() + 1];
+  auto end = format_decimal(buffer, value, size).end;
+  return {out, detail::copy_str<Char>(buffer, end, out)};
+}
+
+template <unsigned BASE_BITS, typename Char, typename UInt>
+FMT_CONSTEXPR Char* format_uint(Char* buffer, UInt value, int num_digits,
+                                bool upper = false) {
+  buffer += num_digits;
+  Char* end = buffer;
+  do {
+    const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits;
+    unsigned digit = (value & ((1 << BASE_BITS) - 1));
+    *--buffer = static_cast<Char>(BASE_BITS < 4 ? static_cast<char>('0' + digit)
+                                                : digits[digit]);
+  } while ((value >>= BASE_BITS) != 0);
+  return end;
+}
+
+template <unsigned BASE_BITS, typename Char>
+Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits,
+                  bool = false) {
+  auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
+  int start = (num_digits + char_digits - 1) / char_digits - 1;
+  if (int start_digits = num_digits % char_digits) {
+    unsigned value = n.value[start--];
+    buffer = format_uint<BASE_BITS>(buffer, value, start_digits);
+  }
+  for (; start >= 0; --start) {
+    unsigned value = n.value[start];
+    buffer += char_digits;
+    auto p = buffer;
+    for (int i = 0; i < char_digits; ++i) {
+      unsigned digit = (value & ((1 << BASE_BITS) - 1));
+      *--p = static_cast<Char>(data::hex_digits[digit]);
+      value >>= BASE_BITS;
+    }
+  }
+  return buffer;
+}
+
+template <unsigned BASE_BITS, typename Char, typename It, typename UInt>
+inline It format_uint(It out, UInt value, int num_digits, bool upper = false) {
+  if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {
+    format_uint<BASE_BITS>(ptr, value, num_digits, upper);
+    return out;
+  }
+  // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1).
+  char buffer[num_bits<UInt>() / BASE_BITS + 1];
+  format_uint<BASE_BITS>(buffer, value, num_digits, upper);
+  return detail::copy_str<Char>(buffer, buffer + num_digits, out);
+}
+
+// A converter from UTF-8 to UTF-16.
+class utf8_to_utf16 {
+ private:
+  wmemory_buffer buffer_;
+
+ public:
+  FMT_API explicit utf8_to_utf16(string_view s);
+  operator wstring_view() const { return {&buffer_[0], size()}; }
+  size_t size() const { return buffer_.size() - 1; }
+  const wchar_t* c_str() const { return &buffer_[0]; }
+  std::wstring str() const { return {&buffer_[0], size()}; }
+};
+
+template <typename T = void> struct null {};
+
+// Workaround an array initialization issue in gcc 4.8.
+template <typename Char> struct fill_t {
+ private:
+  enum { max_size = 4 };
+  Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)};
+  unsigned char size_ = 1;
+
+ public:
+  FMT_CONSTEXPR void operator=(basic_string_view<Char> s) {
+    auto size = s.size();
+    if (size > max_size) {
+      FMT_THROW(format_error("invalid fill"));
+      return;
+    }
+    for (size_t i = 0; i < size; ++i) data_[i] = s[i];
+    size_ = static_cast<unsigned char>(size);
+  }
+
+  constexpr size_t size() const { return size_; }
+  constexpr const Char* data() const { return data_; }
+
+  FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; }
+  FMT_CONSTEXPR const Char& operator[](size_t index) const {
+    return data_[index];
+  }
+};
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+namespace sign {
+enum type { none, minus, plus, space };
+}
+using sign_t = sign::type;
+
+// Format specifiers for built-in and string types.
+template <typename Char> struct basic_format_specs {
+  int width;
+  int precision;
+  char type;
+  align_t align : 4;
+  sign_t sign : 3;
+  bool alt : 1;  // Alternate form ('#').
+  bool localized : 1;
+  detail::fill_t<Char> fill;
+
+  constexpr basic_format_specs()
+      : width(0),
+        precision(-1),
+        type(0),
+        align(align::none),
+        sign(sign::none),
+        alt(false),
+        localized(false) {}
+};
+
+using format_specs = basic_format_specs<char>;
+
+FMT_MODULE_EXPORT_END
+namespace detail {
+namespace dragonbox {
+
+// Type-specific information that Dragonbox uses.
+template <class T> struct float_info;
+
+template <> struct float_info<float> {
+  using carrier_uint = uint32_t;
+  static const int significand_bits = 23;
+  static const int exponent_bits = 8;
+  static const int min_exponent = -126;
+  static const int max_exponent = 127;
+  static const int exponent_bias = -127;
+  static const int decimal_digits = 9;
+  static const int kappa = 1;
+  static const int big_divisor = 100;
+  static const int small_divisor = 10;
+  static const int min_k = -31;
+  static const int max_k = 46;
+  static const int cache_bits = 64;
+  static const int divisibility_check_by_5_threshold = 39;
+  static const int case_fc_pm_half_lower_threshold = -1;
+  static const int case_fc_pm_half_upper_threshold = 6;
+  static const int case_fc_lower_threshold = -2;
+  static const int case_fc_upper_threshold = 6;
+  static const int case_shorter_interval_left_endpoint_lower_threshold = 2;
+  static const int case_shorter_interval_left_endpoint_upper_threshold = 3;
+  static const int shorter_interval_tie_lower_threshold = -35;
+  static const int shorter_interval_tie_upper_threshold = -35;
+  static const int max_trailing_zeros = 7;
+};
+
+template <> struct float_info<double> {
+  using carrier_uint = uint64_t;
+  static const int significand_bits = 52;
+  static const int exponent_bits = 11;
+  static const int min_exponent = -1022;
+  static const int max_exponent = 1023;
+  static const int exponent_bias = -1023;
+  static const int decimal_digits = 17;
+  static const int kappa = 2;
+  static const int big_divisor = 1000;
+  static const int small_divisor = 100;
+  static const int min_k = -292;
+  static const int max_k = 326;
+  static const int cache_bits = 128;
+  static const int divisibility_check_by_5_threshold = 86;
+  static const int case_fc_pm_half_lower_threshold = -2;
+  static const int case_fc_pm_half_upper_threshold = 9;
+  static const int case_fc_lower_threshold = -4;
+  static const int case_fc_upper_threshold = 9;
+  static const int case_shorter_interval_left_endpoint_lower_threshold = 2;
+  static const int case_shorter_interval_left_endpoint_upper_threshold = 3;
+  static const int shorter_interval_tie_lower_threshold = -77;
+  static const int shorter_interval_tie_upper_threshold = -77;
+  static const int max_trailing_zeros = 16;
+};
+
+template <typename T> struct decimal_fp {
+  using significand_type = typename float_info<T>::carrier_uint;
+  significand_type significand;
+  int exponent;
+};
+
+template <typename T> FMT_API decimal_fp<T> to_decimal(T x) FMT_NOEXCEPT;
+}  // namespace dragonbox
+
+template <typename T>
+constexpr typename dragonbox::float_info<T>::carrier_uint exponent_mask() {
+  using uint = typename dragonbox::float_info<T>::carrier_uint;
+  return ((uint(1) << dragonbox::float_info<T>::exponent_bits) - 1)
+         << dragonbox::float_info<T>::significand_bits;
+}
+
+// A floating-point presentation format.
+enum class float_format : unsigned char {
+  general,  // General: exponent notation or fixed point based on magnitude.
+  exp,      // Exponent notation with the default precision of 6, e.g. 1.2e-3.
+  fixed,    // Fixed point with the default precision of 6, e.g. 0.0012.
+  hex
+};
+
+struct float_specs {
+  int precision;
+  float_format format : 8;
+  sign_t sign : 8;
+  bool upper : 1;
+  bool locale : 1;
+  bool binary32 : 1;
+  bool use_grisu : 1;
+  bool showpoint : 1;
+};
+
+// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
+template <typename Char, typename It> It write_exponent(int exp, It it) {
+  FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
+  if (exp < 0) {
+    *it++ = static_cast<Char>('-');
+    exp = -exp;
+  } else {
+    *it++ = static_cast<Char>('+');
+  }
+  if (exp >= 100) {
+    const char* top = data::digits[exp / 100];
+    if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
+    *it++ = static_cast<Char>(top[1]);
+    exp %= 100;
+  }
+  const char* d = data::digits[exp];
+  *it++ = static_cast<Char>(d[0]);
+  *it++ = static_cast<Char>(d[1]);
+  return it;
+}
+
+template <typename T>
+int format_float(T value, int precision, float_specs specs, buffer<char>& buf);
+
+// Formats a floating-point number with snprintf.
+template <typename T>
+int snprintf_float(T value, int precision, float_specs specs,
+                   buffer<char>& buf);
+
+template <typename T> T promote_float(T value) { return value; }
+inline double promote_float(float value) { return static_cast<double>(value); }
+
+template <typename ErrorHandler = error_handler, typename Char>
+FMT_CONSTEXPR float_specs parse_float_type_spec(
+    const basic_format_specs<Char>& specs, ErrorHandler&& eh = {}) {
+  auto result = float_specs();
+  result.showpoint = specs.alt;
+  result.locale = specs.localized;
+  switch (specs.type) {
+  case 0:
+    result.format = float_format::general;
+    break;
+  case 'G':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'g':
+    result.format = float_format::general;
+    break;
+  case 'E':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'e':
+    result.format = float_format::exp;
+    result.showpoint |= specs.precision != 0;
+    break;
+  case 'F':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'f':
+    result.format = float_format::fixed;
+    result.showpoint |= specs.precision != 0;
+    break;
+  case 'A':
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'a':
+    result.format = float_format::hex;
+    break;
+  default:
+    eh.on_error("invalid type specifier");
+    break;
+  }
+  return result;
+}
+
+template <typename ErrorHandler>
+FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) {
+  switch (spec) {
+  case 0:
+  case 'd':
+  case 'x':
+  case 'X':
+  case 'b':
+  case 'B':
+  case 'o':
+  case 'c':
+    break;
+  default:
+    eh.on_error("invalid type specifier");
+    break;
+  }
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR void handle_char_specs(const basic_format_specs<Char>& specs,
+                                     Handler&& handler) {
+  if (specs.type && specs.type != 'c') return handler.on_int();
+  if (specs.align == align::numeric || specs.sign != sign::none || specs.alt)
+    handler.on_error("invalid format specifier for char");
+  handler.on_char();
+}
+
+template <typename Char, typename Handler>
+FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler&& handler) {
+  if (spec == 0 || spec == 's')
+    handler.on_string();
+  else if (spec == 'p')
+    handler.on_pointer();
+  else
+    handler.on_error("invalid type specifier");
+}
+
+template <typename Char, typename ErrorHandler>
+FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) {
+  if (spec != 0 && spec != 's') eh.on_error("invalid type specifier");
+}
+
+template <typename Char, typename ErrorHandler>
+FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) {
+  if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier");
+}
+
+template <typename ErrorHandler>
+class char_specs_checker : public ErrorHandler {
+ private:
+  char type_;
+
+ public:
+  FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh)
+      : ErrorHandler(eh), type_(type) {}
+
+  FMT_CONSTEXPR void on_int() { check_int_type_spec(type_, *this); }
+  FMT_CONSTEXPR void on_char() {}
+};
+
+template <typename ErrorHandler>
+class cstring_type_checker : public ErrorHandler {
+ public:
+  FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh)
+      : ErrorHandler(eh) {}
+
+  FMT_CONSTEXPR void on_string() {}
+  FMT_CONSTEXPR void on_pointer() {}
+};
+
+template <typename OutputIt, typename Char>
+FMT_NOINLINE FMT_CONSTEXPR OutputIt fill(OutputIt it, size_t n,
+                                         const fill_t<Char>& fill) {
+  auto fill_size = fill.size();
+  if (fill_size == 1) return detail::fill_n(it, n, fill[0]);
+  auto data = fill.data();
+  for (size_t i = 0; i < n; ++i)
+    it = copy_str<Char>(data, data + fill_size, it);
+  return it;
+}
+
+// Writes the output of f, padded according to format specifications in specs.
+// size: output size in code units.
+// width: output display width in (terminal) column positions.
+template <align::type align = align::left, typename OutputIt, typename Char,
+          typename F>
+FMT_CONSTEXPR OutputIt write_padded(OutputIt out,
+                                    const basic_format_specs<Char>& specs,
+                                    size_t size, size_t width, F&& f) {
+  static_assert(align == align::left || align == align::right, "");
+  unsigned spec_width = to_unsigned(specs.width);
+  size_t padding = spec_width > width ? spec_width - width : 0;
+  auto* shifts = align == align::left ? data::left_padding_shifts
+                                      : data::right_padding_shifts;
+  size_t left_padding = padding >> shifts[specs.align];
+  size_t right_padding = padding - left_padding;
+  auto it = reserve(out, size + padding * specs.fill.size());
+  if (left_padding != 0) it = fill(it, left_padding, specs.fill);
+  it = f(it);
+  if (right_padding != 0) it = fill(it, right_padding, specs.fill);
+  return base_iterator(out, it);
+}
+
+template <align::type align = align::left, typename OutputIt, typename Char,
+          typename F>
+constexpr OutputIt write_padded(OutputIt out,
+                                const basic_format_specs<Char>& specs,
+                                size_t size, F&& f) {
+  return write_padded<align>(out, specs, size, size, f);
+}
+
+template <typename Char, typename OutputIt>
+OutputIt write_bytes(OutputIt out, string_view bytes,
+                     const basic_format_specs<Char>& specs) {
+  return write_padded(out, specs, bytes.size(),
+                      [bytes](reserve_iterator<OutputIt> it) {
+                        const char* data = bytes.data();
+                        return copy_str<Char>(data, data + bytes.size(), it);
+                      });
+}
+
+template <typename Char, typename OutputIt>
+constexpr OutputIt write_char(OutputIt out, Char value,
+                              const basic_format_specs<Char>& specs) {
+  return write_padded(out, specs, 1, [=](reserve_iterator<OutputIt> it) {
+    *it++ = value;
+    return it;
+  });
+}
+
+// Data for write_int that doesn't depend on output iterator type. It is used to
+// avoid template code bloat.
+template <typename Char> struct write_int_data {
+  size_t size;
+  size_t padding;
+
+  FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix,
+                               const basic_format_specs<Char>& specs)
+      : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) {
+    if (specs.align == align::numeric) {
+      auto width = to_unsigned(specs.width);
+      if (width > size) {
+        padding = width - size;
+        size = width;
+      }
+    } else if (specs.precision > num_digits) {
+      size = (prefix >> 24) + to_unsigned(specs.precision);
+      padding = to_unsigned(specs.precision - num_digits);
+    }
+  }
+};
+
+// Writes an integer in the format
+//   <left-padding><prefix><numeric-padding><digits><right-padding>
+// where <digits> are written by write_digits(it).
+// prefix contains chars in three lower bytes and the size in the fourth byte.
+template <typename OutputIt, typename Char, typename W>
+FMT_CONSTEXPR FMT_INLINE OutputIt
+write_int(OutputIt out, int num_digits, unsigned prefix,
+          const basic_format_specs<Char>& specs, W write_digits) {
+  // Slightly faster check for specs.width == 0 && specs.precision == -1.
+  if ((specs.width | (specs.precision + 1)) == 0) {
+    auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24));
+    if (prefix != 0) {
+      for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+        *it++ = static_cast<Char>(p & 0xff);
+    }
+    return base_iterator(out, write_digits(it));
+  }
+  auto data = write_int_data<Char>(num_digits, prefix, specs);
+  return write_padded<align::right>(
+      out, specs, data.size, [=](reserve_iterator<OutputIt> it) {
+        for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)
+          *it++ = static_cast<Char>(p & 0xff);
+        it = detail::fill_n(it, data.padding, static_cast<Char>('0'));
+        return write_digits(it);
+      });
+}
+
+template <typename OutputIt, typename UInt, typename Char>
+bool write_int_localized(OutputIt& out, UInt value, unsigned prefix,
+                         const basic_format_specs<Char>& specs,
+                         locale_ref loc) {
+  static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, "");
+  const auto sep_size = 1;
+  std::string groups = grouping<Char>(loc);
+  if (groups.empty()) return false;
+  auto sep = thousands_sep<Char>(loc);
+  if (!sep) return false;
+  int num_digits = count_digits(value);
+  int size = num_digits, n = num_digits;
+  std::string::const_iterator group = groups.cbegin();
+  while (group != groups.cend() && n > *group && *group > 0 &&
+         *group != max_value<char>()) {
+    size += sep_size;
+    n -= *group;
+    ++group;
+  }
+  if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back());
+  char digits[40];
+  format_decimal(digits, value, num_digits);
+  basic_memory_buffer<Char> buffer;
+  if (prefix != 0) ++size;
+  const auto usize = to_unsigned(size);
+  buffer.resize(usize);
+  basic_string_view<Char> s(&sep, sep_size);
+  // Index of a decimal digit with the least significant digit having index 0.
+  int digit_index = 0;
+  group = groups.cbegin();
+  auto p = buffer.data() + size - 1;
+  for (int i = num_digits - 1; i > 0; --i) {
+    *p-- = static_cast<Char>(digits[i]);
+    if (*group <= 0 || ++digit_index % *group != 0 ||
+        *group == max_value<char>())
+      continue;
+    if (group + 1 != groups.cend()) {
+      digit_index = 0;
+      ++group;
+    }
+    std::uninitialized_copy(s.data(), s.data() + s.size(),
+                            make_checked(p, s.size()));
+    p -= s.size();
+  }
+  *p-- = static_cast<Char>(*digits);
+  if (prefix != 0) *p = static_cast<Char>(prefix);
+  auto data = buffer.data();
+  out = write_padded<align::right>(
+      out, specs, usize, usize, [=](reserve_iterator<OutputIt> it) {
+        return copy_str<Char>(data, data + size, it);
+      });
+  return true;
+}
+
+FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {
+  prefix |= prefix != 0 ? value << 8 : value;
+  prefix += (1u + (value > 0xff ? 1 : 0)) << 24;
+}
+
+template <typename OutputIt, typename T, typename Char>
+FMT_CONSTEXPR OutputIt write_int(OutputIt out, T value,
+                                 const basic_format_specs<Char>& specs,
+                                 locale_ref loc) {
+  auto prefix = 0u;
+  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
+  if (is_negative(value)) {
+    prefix = 0x01000000 | '-';
+    abs_value = 0 - abs_value;
+  } else {
+    prefix = data::prefixes[specs.sign];
+  }
+  auto utype = static_cast<unsigned>(specs.type);
+  switch (specs.type) {
+  case 0:
+  case 'd': {
+    if (specs.localized &&
+        write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value),
+                            prefix, specs, loc)) {
+      return out;
+    }
+    auto num_digits = count_digits(abs_value);
+    return write_int(
+        out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+          return format_decimal<Char>(it, abs_value, num_digits).end;
+        });
+  }
+  case 'x':
+  case 'X': {
+    if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
+    bool upper = specs.type != 'x';
+    int num_digits = count_digits<4>(abs_value);
+    return write_int(
+        out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
+          return format_uint<4, Char>(it, abs_value, num_digits, upper);
+        });
+  }
+  case 'b':
+  case 'B': {
+    if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
+    int num_digits = count_digits<1>(abs_value);
+    return write_int(out, num_digits, prefix, specs,
+                     [=](reserve_iterator<OutputIt> it) {
+                       return format_uint<1, Char>(it, abs_value, num_digits);
+                     });
+  }
+  case 'o': {
+    int num_digits = count_digits<3>(abs_value);
+    if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
+      // Octal prefix '0' is counted as a digit, so only add it if precision
+      // is not greater than the number of digits.
+      prefix_append(prefix, '0');
+    }
+    return write_int(out, num_digits, prefix, specs,
+                     [=](reserve_iterator<OutputIt> it) {
+                       return format_uint<3, Char>(it, abs_value, num_digits);
+                     });
+  }
+  case 'c':
+    return write_char(out, static_cast<Char>(abs_value), specs);
+  default:
+    FMT_THROW(format_error("invalid type specifier"));
+  }
+  return out;
+}
+
+template <typename OutputIt, typename StrChar, typename Char>
+FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<StrChar> s,
+                             const basic_format_specs<Char>& specs) {
+  auto data = s.data();
+  auto size = s.size();
+  if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
+    size = code_point_index(s, to_unsigned(specs.precision));
+  auto width = specs.width != 0
+                   ? compute_width(basic_string_view<StrChar>(data, size))
+                   : 0;
+  return write_padded(out, specs, size, width,
+                      [=](reserve_iterator<OutputIt> it) {
+                        return copy_str<Char>(data, data + size, it);
+                      });
+}
+
+template <typename Char, typename OutputIt>
+OutputIt write_nonfinite(OutputIt out, bool isinf,
+                         const basic_format_specs<Char>& specs,
+                         const float_specs& fspecs) {
+  auto str =
+      isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan");
+  constexpr size_t str_size = 3;
+  auto sign = fspecs.sign;
+  auto size = str_size + (sign ? 1 : 0);
+  return write_padded(out, specs, size, [=](reserve_iterator<OutputIt> it) {
+    if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+    return copy_str<Char>(str, str + str_size, it);
+  });
+}
+
+// A decimal floating-point number significand * pow(10, exp).
+struct big_decimal_fp {
+  const char* significand;
+  int significand_size;
+  int exponent;
+};
+
+inline int get_significand_size(const big_decimal_fp& fp) {
+  return fp.significand_size;
+}
+template <typename T>
+inline int get_significand_size(const dragonbox::decimal_fp<T>& fp) {
+  return count_digits(fp.significand);
+}
+
+template <typename Char, typename OutputIt>
+inline OutputIt write_significand(OutputIt out, const char* significand,
+                                  int& significand_size) {
+  return copy_str<Char>(significand, significand + significand_size, out);
+}
+template <typename Char, typename OutputIt, typename UInt>
+inline OutputIt write_significand(OutputIt out, UInt significand,
+                                  int significand_size) {
+  return format_decimal<Char>(out, significand, significand_size).end;
+}
+
+template <typename Char, typename UInt,
+          FMT_ENABLE_IF(std::is_integral<UInt>::value)>
+inline Char* write_significand(Char* out, UInt significand,
+                               int significand_size, int integral_size,
+                               Char decimal_point) {
+  if (!decimal_point)
+    return format_decimal(out, significand, significand_size).end;
+  auto end = format_decimal(out + 1, significand, significand_size).end;
+  if (integral_size == 1) {
+    out[0] = out[1];
+  } else {
+    std::uninitialized_copy_n(out + 1, integral_size,
+                              make_checked(out, to_unsigned(integral_size)));
+  }
+  out[integral_size] = decimal_point;
+  return end;
+}
+
+template <typename OutputIt, typename UInt, typename Char,
+          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>
+inline OutputIt write_significand(OutputIt out, UInt significand,
+                                  int significand_size, int integral_size,
+                                  Char decimal_point) {
+  // Buffer is large enough to hold digits (digits10 + 1) and a decimal point.
+  Char buffer[digits10<UInt>() + 2];
+  auto end = write_significand(buffer, significand, significand_size,
+                               integral_size, decimal_point);
+  return detail::copy_str<Char>(buffer, end, out);
+}
+
+template <typename OutputIt, typename Char>
+inline OutputIt write_significand(OutputIt out, const char* significand,
+                                  int significand_size, int integral_size,
+                                  Char decimal_point) {
+  out = detail::copy_str<Char>(significand, significand + integral_size, out);
+  if (!decimal_point) return out;
+  *out++ = decimal_point;
+  return detail::copy_str<Char>(significand + integral_size,
+                                significand + significand_size, out);
+}
+
+template <typename OutputIt, typename DecimalFP, typename Char>
+OutputIt write_float(OutputIt out, const DecimalFP& fp,
+                     const basic_format_specs<Char>& specs, float_specs fspecs,
+                     Char decimal_point) {
+  auto significand = fp.significand;
+  int significand_size = get_significand_size(fp);
+  static const Char zero = static_cast<Char>('0');
+  auto sign = fspecs.sign;
+  size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
+  using iterator = reserve_iterator<OutputIt>;
+
+  int output_exp = fp.exponent + significand_size - 1;
+  auto use_exp_format = [=]() {
+    if (fspecs.format == float_format::exp) return true;
+    if (fspecs.format != float_format::general) return false;
+    // Use the fixed notation if the exponent is in [exp_lower, exp_upper),
+    // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
+    const int exp_lower = -4, exp_upper = 16;
+    return output_exp < exp_lower ||
+           output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper);
+  };
+  if (use_exp_format()) {
+    int num_zeros = 0;
+    if (fspecs.showpoint) {
+      num_zeros = fspecs.precision - significand_size;
+      if (num_zeros < 0) num_zeros = 0;
+      size += to_unsigned(num_zeros);
+    } else if (significand_size == 1) {
+      decimal_point = Char();
+    }
+    auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
+    int exp_digits = 2;
+    if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
+
+    size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
+    char exp_char = fspecs.upper ? 'E' : 'e';
+    auto write = [=](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      // Insert a decimal point after the first digit and add an exponent.
+      it = write_significand(it, significand, significand_size, 1,
+                             decimal_point);
+      if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);
+      *it++ = static_cast<Char>(exp_char);
+      return write_exponent<Char>(output_exp, it);
+    };
+    return specs.width > 0 ? write_padded<align::right>(out, specs, size, write)
+                           : base_iterator(out, write(reserve(out, size)));
+  }
+
+  int exp = fp.exponent + significand_size;
+  if (fp.exponent >= 0) {
+    // 1234e5 -> 123400000[.0+]
+    size += to_unsigned(fp.exponent);
+    int num_zeros = fspecs.precision - exp;
+#ifdef FMT_FUZZ
+    if (num_zeros > 5000)
+      throw std::runtime_error("fuzz mode - avoiding excessive cpu use");
+#endif
+    if (fspecs.showpoint) {
+      if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1;
+      if (num_zeros > 0) size += to_unsigned(num_zeros) + 1;
+    }
+    return write_padded<align::right>(out, specs, size, [&](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      it = write_significand<Char>(it, significand, significand_size);
+      it = detail::fill_n(it, fp.exponent, zero);
+      if (!fspecs.showpoint) return it;
+      *it++ = decimal_point;
+      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
+    });
+  } else if (exp > 0) {
+    // 1234e-2 -> 12.34[0+]
+    int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0;
+    size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0);
+    return write_padded<align::right>(out, specs, size, [&](iterator it) {
+      if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+      it = write_significand(it, significand, significand_size, exp,
+                             decimal_point);
+      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
+    });
+  }
+  // 1234e-6 -> 0.001234
+  int num_zeros = -exp;
+  if (significand_size == 0 && fspecs.precision >= 0 &&
+      fspecs.precision < num_zeros) {
+    num_zeros = fspecs.precision;
+  }
+  bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint;
+  size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
+  return write_padded<align::right>(out, specs, size, [&](iterator it) {
+    if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+    *it++ = zero;
+    if (!pointy) return it;
+    *it++ = decimal_point;
+    it = detail::fill_n(it, num_zeros, zero);
+    return write_significand<Char>(it, significand, significand_size);
+  });
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+OutputIt write(OutputIt out, T value, basic_format_specs<Char> specs,
+               locale_ref loc = {}) {
+  if (const_check(!is_supported_floating_point(value))) return out;
+  float_specs fspecs = parse_float_type_spec(specs);
+  fspecs.sign = specs.sign;
+  if (std::signbit(value)) {  // value < 0 is false for NaN so use signbit.
+    fspecs.sign = sign::minus;
+    value = -value;
+  } else if (fspecs.sign == sign::minus) {
+    fspecs.sign = sign::none;
+  }
+
+  if (!std::isfinite(value))
+    return write_nonfinite(out, std::isinf(value), specs, fspecs);
+
+  if (specs.align == align::numeric && fspecs.sign) {
+    auto it = reserve(out, 1);
+    *it++ = static_cast<Char>(data::signs[fspecs.sign]);
+    out = base_iterator(out, it);
+    fspecs.sign = sign::none;
+    if (specs.width != 0) --specs.width;
+  }
+
+  memory_buffer buffer;
+  if (fspecs.format == float_format::hex) {
+    if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]);
+    snprintf_float(promote_float(value), specs.precision, fspecs, buffer);
+    return write_bytes(out, {buffer.data(), buffer.size()}, specs);
+  }
+  int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
+  if (fspecs.format == float_format::exp) {
+    if (precision == max_value<int>())
+      FMT_THROW(format_error("number is too big"));
+    else
+      ++precision;
+  }
+  if (const_check(std::is_same<T, float>())) fspecs.binary32 = true;
+  fspecs.use_grisu = is_fast_float<T>();
+  int exp = format_float(promote_float(value), precision, fspecs, buffer);
+  fspecs.precision = precision;
+  Char point =
+      fspecs.locale ? decimal_point<Char>(loc) : static_cast<Char>('.');
+  auto fp = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};
+  return write_float(out, fp, specs, fspecs, point);
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_fast_float<T>::value)>
+OutputIt write(OutputIt out, T value) {
+  if (const_check(!is_supported_floating_point(value))) return out;
+
+  using floaty = conditional_t<std::is_same<T, long double>::value, double, T>;
+  using uint = typename dragonbox::float_info<floaty>::carrier_uint;
+  auto bits = bit_cast<uint>(value);
+
+  auto fspecs = float_specs();
+  auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1));
+  if (sign_bit != 0) {
+    fspecs.sign = sign::minus;
+    value = -value;
+  }
+
+  static const auto specs = basic_format_specs<Char>();
+  uint mask = exponent_mask<floaty>();
+  if ((bits & mask) == mask)
+    return write_nonfinite(out, std::isinf(value), specs, fspecs);
+
+  auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
+  return write_float(out, dec, specs, fspecs, static_cast<Char>('.'));
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(std::is_floating_point<T>::value &&
+                        !is_fast_float<T>::value)>
+inline OutputIt write(OutputIt out, T value) {
+  return write(out, value, basic_format_specs<Char>());
+}
+
+template <typename Char, typename OutputIt, typename UIntPtr>
+OutputIt write_ptr(OutputIt out, UIntPtr value,
+                   const basic_format_specs<Char>* specs) {
+  int num_digits = count_digits<4>(value);
+  auto size = to_unsigned(num_digits) + size_t(2);
+  auto write = [=](reserve_iterator<OutputIt> it) {
+    *it++ = static_cast<Char>('0');
+    *it++ = static_cast<Char>('x');
+    return format_uint<4, Char>(it, value, num_digits);
+  };
+  return specs ? write_padded<align::right>(out, *specs, size, write)
+               : base_iterator(out, write(reserve(out, size)));
+}
+
+template <typename T> struct is_integral : std::is_integral<T> {};
+template <> struct is_integral<int128_t> : std::true_type {};
+template <> struct is_integral<uint128_t> : std::true_type {};
+
+template <typename Char, typename OutputIt>
+OutputIt write(OutputIt out, monostate) {
+  FMT_ASSERT(false, "");
+  return out;
+}
+
+template <typename Char, typename OutputIt,
+          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+OutputIt write(OutputIt out, string_view value) {
+  auto it = reserve(out, value.size());
+  it = copy_str<Char>(value.begin(), value.end(), it);
+  return base_iterator(out, it);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR OutputIt write(OutputIt out, basic_string_view<Char> value) {
+  auto it = reserve(out, value.size());
+  it = copy_str<Char>(value.begin(), value.end(), it);
+  return base_iterator(out, it);
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_string<T>::value)>
+constexpr OutputIt write(OutputIt out, const T& value) {
+  return write<Char>(out, to_string_view(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+          FMT_ENABLE_IF(is_integral<T>::value &&
+                        !std::is_same<T, bool>::value &&
+                        !std::is_same<T, Char>::value)>
+FMT_CONSTEXPR OutputIt write(OutputIt out, T value) {
+  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);
+  bool negative = is_negative(value);
+  // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
+  if (negative) abs_value = ~abs_value + 1;
+  int num_digits = count_digits(abs_value);
+  auto size = (negative ? 1 : 0) + static_cast<size_t>(num_digits);
+  auto it = reserve(out, size);
+  if (auto ptr = to_pointer<Char>(it, size)) {
+    if (negative) *ptr++ = static_cast<Char>('-');
+    format_decimal<Char>(ptr, abs_value, num_digits);
+    return out;
+  }
+  if (negative) *it++ = static_cast<Char>('-');
+  it = format_decimal<Char>(it, abs_value, num_digits).end;
+  return base_iterator(out, it);
+}
+
+// FMT_ENABLE_IF() condition separated to workaround MSVC bug
+template <
+    typename Char, typename OutputIt, typename T,
+    bool check =
+        std::is_enum<T>::value && !std::is_same<T, Char>::value &&
+        mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value !=
+            type::custom_type,
+    FMT_ENABLE_IF(check)>
+FMT_CONSTEXPR OutputIt write(OutputIt out, T value) {
+  return write<Char>(
+      out, static_cast<typename std::underlying_type<T>::type>(value));
+}
+
+template <typename Char, typename OutputIt>
+constexpr OutputIt write(OutputIt out, bool value) {
+  return write<Char>(out, string_view(value ? "true" : "false"));
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR OutputIt write(OutputIt out, Char value) {
+  auto it = reserve(out, 1);
+  *it++ = value;
+  return base_iterator(out, it);
+}
+
+template <typename Char, typename OutputIt>
+FMT_CONSTEXPR_CHAR_TRAITS OutputIt write(OutputIt out, const Char* value) {
+  if (!value) {
+    FMT_THROW(format_error("string pointer is null"));
+  } else {
+    auto length = std::char_traits<Char>::length(value);
+    out = write(out, basic_string_view<Char>(value, length));
+  }
+  return out;
+}
+
+template <typename Char, typename OutputIt>
+OutputIt write(OutputIt out, const void* value) {
+  return write_ptr<Char>(out, to_uintptr(value), nullptr);
+}
+
+template <typename Char, typename OutputIt, typename T>
+auto write(OutputIt out, const T& value) -> typename std::enable_if<
+    mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value ==
+        type::custom_type,
+    OutputIt>::type {
+  using context_type = basic_format_context<OutputIt, Char>;
+  using formatter_type =
+      conditional_t<has_formatter<T, context_type>::value,
+                    typename context_type::template formatter_type<T>,
+                    fallback_formatter<T, Char>>;
+  context_type ctx(out, {}, {});
+  return formatter_type().format(value, ctx);
+}
+
+// An argument visitor that formats the argument and writes it via the output
+// iterator. It's a class and not a generic lambda for compatibility with C++11.
+template <typename OutputIt, typename Char> struct default_arg_formatter {
+  using context = basic_format_context<OutputIt, Char>;
+
+  OutputIt out;
+  basic_format_args<context> args;
+  locale_ref loc;
+
+  template <typename T> OutputIt operator()(T value) {
+    return write<Char>(out, value);
+  }
+
+  OutputIt operator()(typename basic_format_arg<context>::handle handle) {
+    basic_format_parse_context<Char> parse_ctx({});
+    basic_format_context<OutputIt, Char> format_ctx(out, args, loc);
+    handle.format(parse_ctx, format_ctx);
+    return format_ctx.out();
+  }
+};
+
+template <typename OutputIt, typename Char,
+          typename ErrorHandler = error_handler>
+class arg_formatter_base {
+ public:
+  using iterator = OutputIt;
+  using char_type = Char;
+  using format_specs = basic_format_specs<Char>;
+
+ private:
+  iterator out_;
+  const format_specs& specs_;
+  locale_ref locale_;
+
+  // Attempts to reserve space for n extra characters in the output range.
+  // Returns a pointer to the reserved range or a reference to out_.
+  auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) {
+    return detail::reserve(out_, n);
+  }
+
+  void write(char value) {
+    auto&& it = reserve(1);
+    *it++ = value;
+  }
+
+  template <typename Ch, FMT_ENABLE_IF(std::is_same<Ch, Char>::value)>
+  void write(Ch value) {
+    out_ = detail::write<Char>(out_, value);
+  }
+
+  void write(string_view value) {
+    auto&& it = reserve(value.size());
+    it = copy_str<Char>(value.begin(), value.end(), it);
+  }
+  void write(wstring_view value) {
+    static_assert(std::is_same<Char, wchar_t>::value, "");
+    auto&& it = reserve(value.size());
+    it = copy_str<Char>(value.begin(), value.end(), it);
+  }
+
+  template <typename Ch>
+  void write(const Ch* s, size_t size, const format_specs& specs) {
+    auto width =
+        specs.width != 0 ? compute_width(basic_string_view<Ch>(s, size)) : 0;
+    out_ = write_padded(out_, specs, size, width,
+                        [=](reserve_iterator<OutputIt> it) {
+                          return copy_str<Char>(s, s + size, it);
+                        });
+  }
+
+  template <typename Ch>
+  FMT_CONSTEXPR void write(basic_string_view<Ch> s,
+                           const format_specs& specs = {}) {
+    out_ = detail::write(out_, s, specs);
+  }
+
+  void write_pointer(const void* p) {
+    out_ = write_ptr<char_type>(out_, to_uintptr(p), &specs_);
+  }
+
+  struct char_spec_handler : ErrorHandler {
+    arg_formatter_base& formatter;
+    Char value;
+
+    constexpr char_spec_handler(arg_formatter_base& f, Char val)
+        : formatter(f), value(val) {}
+
+    FMT_CONSTEXPR void on_int() {
+      // char is only formatted as int if there are specs.
+      formatter.out_ =
+          detail::write_int(formatter.out_, static_cast<int>(value),
+                            formatter.specs_, formatter.locale_);
+    }
+    FMT_CONSTEXPR void on_char() {
+      formatter.out_ = write_char(formatter.out_, value, formatter.specs_);
+    }
+  };
+
+  struct cstring_spec_handler : error_handler {
+    arg_formatter_base& formatter;
+    const Char* value;
+
+    cstring_spec_handler(arg_formatter_base& f, const Char* val)
+        : formatter(f), value(val) {}
+
+    void on_string() { formatter.write(value); }
+    void on_pointer() { formatter.write_pointer(value); }
+  };
+
+ protected:
+  iterator out() { return out_; }
+  const format_specs& specs() { return specs_; }
+
+  FMT_CONSTEXPR void write(bool value) {
+    write(string_view(value ? "true" : "false"), specs_);
+  }
+
+  void write(const Char* value) {
+    if (value)
+      write(basic_string_view<char_type>(value), specs_);
+    else
+      FMT_THROW(format_error("string pointer is null"));
+  }
+
+ public:
+  constexpr arg_formatter_base(OutputIt out, const format_specs& s,
+                               locale_ref loc)
+      : out_(out), specs_(s), locale_(loc) {}
+
+  iterator operator()(monostate) {
+    FMT_ASSERT(false, "invalid argument type");
+    return out_;
+  }
+
+  template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
+  FMT_CONSTEXPR FMT_INLINE iterator operator()(T value) {
+    return out_ = detail::write_int(out_, value, specs_, locale_);
+  }
+
+  FMT_CONSTEXPR iterator operator()(Char value) {
+    handle_char_specs(specs_,
+                      char_spec_handler(*this, static_cast<Char>(value)));
+    return out_;
+  }
+
+  FMT_CONSTEXPR iterator operator()(bool value) {
+    if (specs_.type && specs_.type != 's') return (*this)(value ? 1 : 0);
+    write(value != 0);
+    return out_;
+  }
+
+  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+  iterator operator()(T value) {
+    if (const_check(is_supported_floating_point(value)))
+      out_ = detail::write(out_, value, specs_, locale_);
+    else
+      FMT_ASSERT(false, "unsupported float argument type");
+    return out_;
+  }
+
+  iterator operator()(const Char* value) {
+    handle_cstring_type_spec(specs_.type, cstring_spec_handler(*this, value));
+    return out_;
+  }
+
+  FMT_CONSTEXPR iterator operator()(basic_string_view<Char> value) {
+    check_string_type_spec(specs_.type, error_handler());
+    write(value, specs_);
+    return out_;
+  }
+
+  iterator operator()(const void* value) {
+    check_pointer_type_spec(specs_.type, error_handler());
+    write_pointer(value);
+    return out_;
+  }
+};
+
+/** The default argument formatter. */
+template <typename OutputIt, typename Char>
+class arg_formatter : public arg_formatter_base<OutputIt, Char> {
+ private:
+  using char_type = Char;
+  using base = arg_formatter_base<OutputIt, Char>;
+  using context_type = basic_format_context<OutputIt, Char>;
+
+  context_type& ctx_;
+
+ public:
+  using iterator = typename base::iterator;
+  using format_specs = typename base::format_specs;
+
+  /**
+    \rst
+    Constructs an argument formatter object.
+    *ctx* is a reference to the formatting context,
+    *specs* contains format specifier information for standard argument types.
+    \endrst
+   */
+  constexpr explicit arg_formatter(context_type& ctx, const format_specs& specs)
+      : base(ctx.out(), specs, ctx.locale()), ctx_(ctx) {}
+
+  using base::operator();
+
+  iterator operator()(typename basic_format_arg<context_type>::handle) {
+    // User-defined types are handled separately because they require access to
+    // the parse context.
+    return ctx_.out();
+  }
+};
+
+template <typename Context> class custom_formatter {
+ private:
+  using char_type = typename Context::char_type;
+
+  basic_format_parse_context<char_type>& parse_ctx_;
+  Context& ctx_;
+
+ public:
+  explicit custom_formatter(basic_format_parse_context<char_type>& parse_ctx,
+                            Context& ctx)
+      : parse_ctx_(parse_ctx), ctx_(ctx) {}
+
+  void operator()(typename basic_format_arg<Context>::handle h) const {
+    h.format(parse_ctx_, ctx_);
+  }
+
+  template <typename T> void operator()(T) const {}
+};
+
+template <typename T>
+using is_integer =
+    bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
+                  !std::is_same<T, char>::value &&
+                  !std::is_same<T, wchar_t>::value>;
+
+template <typename ErrorHandler> class width_checker {
+ public:
+  explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {}
+
+  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
+  FMT_CONSTEXPR unsigned long long operator()(T value) {
+    if (is_negative(value)) handler_.on_error("negative width");
+    return static_cast<unsigned long long>(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
+  FMT_CONSTEXPR unsigned long long operator()(T) {
+    handler_.on_error("width is not integer");
+    return 0;
+  }
+
+ private:
+  ErrorHandler& handler_;
+};
+
+template <typename ErrorHandler> class precision_checker {
+ public:
+  explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {}
+
+  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>
+  FMT_CONSTEXPR unsigned long long operator()(T value) {
+    if (is_negative(value)) handler_.on_error("negative precision");
+    return static_cast<unsigned long long>(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>
+  FMT_CONSTEXPR unsigned long long operator()(T) {
+    handler_.on_error("precision is not integer");
+    return 0;
+  }
+
+ private:
+  ErrorHandler& handler_;
+};
+
+// A format specifier handler that sets fields in basic_format_specs.
+template <typename Char> class specs_setter {
+ public:
+  explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs)
+      : specs_(specs) {}
+
+  FMT_CONSTEXPR specs_setter(const specs_setter& other)
+      : specs_(other.specs_) {}
+
+  FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; }
+  FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
+    specs_.fill = fill;
+  }
+  FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; }
+  FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; }
+  FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; }
+  FMT_CONSTEXPR void on_hash() { specs_.alt = true; }
+  FMT_CONSTEXPR void on_localized() { specs_.localized = true; }
+
+  FMT_CONSTEXPR void on_zero() {
+    specs_.align = align::numeric;
+    specs_.fill[0] = Char('0');
+  }
+
+  FMT_CONSTEXPR void on_width(int width) { specs_.width = width; }
+  FMT_CONSTEXPR void on_precision(int precision) {
+    specs_.precision = precision;
+  }
+  FMT_CONSTEXPR void end_precision() {}
+
+  FMT_CONSTEXPR void on_type(Char type) {
+    specs_.type = static_cast<char>(type);
+  }
+
+ protected:
+  basic_format_specs<Char>& specs_;
+};
+
+template <typename ErrorHandler> class numeric_specs_checker {
+ public:
+  FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type)
+      : error_handler_(eh), arg_type_(arg_type) {}
+
+  FMT_CONSTEXPR void require_numeric_argument() {
+    if (!is_arithmetic_type(arg_type_))
+      error_handler_.on_error("format specifier requires numeric argument");
+  }
+
+  FMT_CONSTEXPR void check_sign() {
+    require_numeric_argument();
+    if (is_integral_type(arg_type_) && arg_type_ != type::int_type &&
+        arg_type_ != type::long_long_type && arg_type_ != type::char_type) {
+      error_handler_.on_error("format specifier requires signed argument");
+    }
+  }
+
+  FMT_CONSTEXPR void check_precision() {
+    if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type)
+      error_handler_.on_error("precision not allowed for this argument type");
+  }
+
+ private:
+  ErrorHandler& error_handler_;
+  detail::type arg_type_;
+};
+
+// A format specifier handler that checks if specifiers are consistent with the
+// argument type.
+template <typename Handler> class specs_checker : public Handler {
+ private:
+  numeric_specs_checker<Handler> checker_;
+
+  // Suppress an MSVC warning about using this in initializer list.
+  FMT_CONSTEXPR Handler& error_handler() { return *this; }
+
+ public:
+  FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type)
+      : Handler(handler), checker_(error_handler(), arg_type) {}
+
+  FMT_CONSTEXPR specs_checker(const specs_checker& other)
+      : Handler(other), checker_(error_handler(), other.arg_type_) {}
+
+  FMT_CONSTEXPR void on_align(align_t align) {
+    if (align == align::numeric) checker_.require_numeric_argument();
+    Handler::on_align(align);
+  }
+
+  FMT_CONSTEXPR void on_plus() {
+    checker_.check_sign();
+    Handler::on_plus();
+  }
+
+  FMT_CONSTEXPR void on_minus() {
+    checker_.check_sign();
+    Handler::on_minus();
+  }
+
+  FMT_CONSTEXPR void on_space() {
+    checker_.check_sign();
+    Handler::on_space();
+  }
+
+  FMT_CONSTEXPR void on_hash() {
+    checker_.require_numeric_argument();
+    Handler::on_hash();
+  }
+
+  FMT_CONSTEXPR void on_localized() {
+    checker_.require_numeric_argument();
+    Handler::on_localized();
+  }
+
+  FMT_CONSTEXPR void on_zero() {
+    checker_.require_numeric_argument();
+    Handler::on_zero();
+  }
+
+  FMT_CONSTEXPR void end_precision() { checker_.check_precision(); }
+};
+
+template <template <typename> class Handler, typename FormatArg,
+          typename ErrorHandler>
+FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) {
+  unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
+  if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
+  return static_cast<int>(value);
+}
+
+template <typename Context, typename ID>
+FMT_CONSTEXPR typename Context::format_arg get_arg(Context& ctx, ID id) {
+  auto arg = ctx.arg(id);
+  if (!arg) ctx.on_error("argument not found");
+  return arg;
+}
+
+// The standard format specifier handler with checking.
+template <typename ParseContext, typename Context>
+class specs_handler : public specs_setter<typename Context::char_type> {
+ public:
+  using char_type = typename Context::char_type;
+
+  FMT_CONSTEXPR specs_handler(basic_format_specs<char_type>& specs,
+                              ParseContext& parse_ctx, Context& ctx)
+      : specs_setter<char_type>(specs),
+        parse_context_(parse_ctx),
+        context_(ctx) {}
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+    this->specs_.width = get_dynamic_spec<width_checker>(
+        get_arg(arg_id), context_.error_handler());
+  }
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+    this->specs_.precision = get_dynamic_spec<precision_checker>(
+        get_arg(arg_id), context_.error_handler());
+  }
+
+  void on_error(const char* message) { context_.on_error(message); }
+
+ private:
+  // This is only needed for compatibility with gcc 4.4.
+  using format_arg = typename Context::format_arg;
+
+  FMT_CONSTEXPR format_arg get_arg(auto_id) {
+    return detail::get_arg(context_, parse_context_.next_arg_id());
+  }
+
+  FMT_CONSTEXPR format_arg get_arg(int arg_id) {
+    parse_context_.check_arg_id(arg_id);
+    return detail::get_arg(context_, arg_id);
+  }
+
+  FMT_CONSTEXPR format_arg get_arg(basic_string_view<char_type> arg_id) {
+    parse_context_.check_arg_id(arg_id);
+    return detail::get_arg(context_, arg_id);
+  }
+
+  ParseContext& parse_context_;
+  Context& context_;
+};
+
+enum class arg_id_kind { none, index, name };
+
+// An argument reference.
+template <typename Char> struct arg_ref {
+  FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
+
+  FMT_CONSTEXPR explicit arg_ref(int index)
+      : kind(arg_id_kind::index), val(index) {}
+  FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
+      : kind(arg_id_kind::name), val(name) {}
+
+  FMT_CONSTEXPR arg_ref& operator=(int idx) {
+    kind = arg_id_kind::index;
+    val.index = idx;
+    return *this;
+  }
+
+  arg_id_kind kind;
+  union value {
+    FMT_CONSTEXPR value(int id = 0) : index{id} {}
+    FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
+
+    int index;
+    basic_string_view<Char> name;
+  } val;
+};
+
+// Format specifiers with width and precision resolved at formatting rather
+// than parsing time to allow re-using the same parsed specifiers with
+// different sets of arguments (precompilation of format strings).
+template <typename Char>
+struct dynamic_format_specs : basic_format_specs<Char> {
+  arg_ref<Char> width_ref;
+  arg_ref<Char> precision_ref;
+};
+
+// Format spec handler that saves references to arguments representing dynamic
+// width and precision to be resolved at formatting time.
+template <typename ParseContext>
+class dynamic_specs_handler
+    : public specs_setter<typename ParseContext::char_type> {
+ public:
+  using char_type = typename ParseContext::char_type;
+
+  FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs,
+                                      ParseContext& ctx)
+      : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {}
+
+  FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other)
+      : specs_setter<char_type>(other),
+        specs_(other.specs_),
+        context_(other.context_) {}
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
+    specs_.width_ref = make_arg_ref(arg_id);
+  }
+
+  template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
+    specs_.precision_ref = make_arg_ref(arg_id);
+  }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    context_.on_error(message);
+  }
+
+ private:
+  using arg_ref_type = arg_ref<char_type>;
+
+  FMT_CONSTEXPR arg_ref_type make_arg_ref(int arg_id) {
+    context_.check_arg_id(arg_id);
+    return arg_ref_type(arg_id);
+  }
+
+  FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) {
+    return arg_ref_type(context_.next_arg_id());
+  }
+
+  FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<char_type> arg_id) {
+    context_.check_arg_id(arg_id);
+    basic_string_view<char_type> format_str(
+        context_.begin(), to_unsigned(context_.end() - context_.begin()));
+    return arg_ref_type(arg_id);
+  }
+
+  dynamic_format_specs<char_type>& specs_;
+  ParseContext& context_;
+};
+
+template <typename OutputIt, typename Char, typename Context>
+struct format_handler : detail::error_handler {
+  basic_format_parse_context<Char> parse_context;
+  Context context;
+
+  format_handler(OutputIt out, basic_string_view<Char> str,
+                 basic_format_args<Context> format_args, detail::locale_ref loc)
+      : parse_context(str), context(out, format_args, loc) {}
+
+  void on_text(const Char* begin, const Char* end) {
+    auto text = basic_string_view<Char>(begin, to_unsigned(end - begin));
+    context.advance_to(write<Char>(context.out(), text));
+  }
+
+  int on_arg_id() { return parse_context.next_arg_id(); }
+  int on_arg_id(int id) { return parse_context.check_arg_id(id), id; }
+  int on_arg_id(basic_string_view<Char> id) {
+    int arg_id = context.arg_id(id);
+    if (arg_id < 0) on_error("argument not found");
+    return arg_id;
+  }
+
+  FMT_INLINE void on_replacement_field(int id, const Char*) {
+    auto arg = get_arg(context, id);
+    context.advance_to(visit_format_arg(
+        default_arg_formatter<OutputIt, Char>{context.out(), context.args(),
+                                              context.locale()},
+        arg));
+  }
+
+  const Char* on_format_specs(int id, const Char* begin, const Char* end) {
+    auto arg = get_arg(context, id);
+    if (arg.type() == type::custom_type) {
+      advance_to(parse_context, begin);
+      visit_format_arg(custom_formatter<Context>(parse_context, context), arg);
+      return parse_context.begin();
+    }
+    auto specs = basic_format_specs<Char>();
+    using parse_context_t = basic_format_parse_context<Char>;
+    specs_checker<specs_handler<parse_context_t, Context>> handler(
+        specs_handler<parse_context_t, Context>(specs, parse_context, context),
+        arg.type());
+    begin = parse_format_specs(begin, end, handler);
+    if (begin == end || *begin != '}') on_error("missing '}' in format string");
+    context.advance_to(
+        visit_format_arg(arg_formatter<OutputIt, Char>(context, specs), arg));
+    return begin;
+  }
+};
+
+template <template <typename> class Handler, typename Context>
+FMT_CONSTEXPR void handle_dynamic_spec(int& value,
+                                       arg_ref<typename Context::char_type> ref,
+                                       Context& ctx) {
+  switch (ref.kind) {
+  case arg_id_kind::none:
+    break;
+  case arg_id_kind::index:
+    value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
+                                              ctx.error_handler());
+    break;
+  case arg_id_kind::name:
+    value = detail::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
+                                              ctx.error_handler());
+    break;
+  }
+}
+
+using format_func = void (*)(detail::buffer<char>&, int, string_view);
+
+FMT_API void format_error_code(buffer<char>& out, int error_code,
+                               string_view message) FMT_NOEXCEPT;
+
+FMT_API void report_error(format_func func, int error_code,
+                          string_view message) FMT_NOEXCEPT;
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename OutputIt, typename Char>
+using arg_formatter FMT_DEPRECATED_ALIAS =
+    detail::arg_formatter<OutputIt, Char>;
+
+/**
+ An error returned by an operating system or a language runtime,
+ for example a file opening error.
+*/
+FMT_CLASS_API
+class FMT_API system_error : public std::runtime_error {
+ private:
+  void init(int err_code, string_view format_str, format_args args);
+
+ protected:
+  int error_code_;
+
+  system_error() : std::runtime_error(""), error_code_(0) {}
+
+ public:
+  /**
+   \rst
+   Constructs a :class:`fmt::system_error` object with a description
+   formatted with `fmt::format_system_error`. *message* and additional
+   arguments passed into the constructor are formatted similarly to
+   `fmt::format`.
+
+   **Example**::
+
+     // This throws a system_error with the description
+     //   cannot open file 'madeup': No such file or directory
+     // or similar (system message may vary).
+     const char *filename = "madeup";
+     std::FILE *file = std::fopen(filename, "r");
+     if (!file)
+       throw fmt::system_error(errno, "cannot open file '{}'", filename);
+   \endrst
+  */
+  template <typename... Args>
+  system_error(int error_code, string_view message, const Args&... args)
+      : std::runtime_error("") {
+    init(error_code, message, make_format_args(args...));
+  }
+  system_error(const system_error&) = default;
+  system_error& operator=(const system_error&) = default;
+  system_error(system_error&&) = default;
+  system_error& operator=(system_error&&) = default;
+  ~system_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT;
+
+  int error_code() const { return error_code_; }
+};
+
+/**
+  \rst
+  Formats an error returned by an operating system or a language runtime,
+  for example a file opening error, and writes it to *out* in the following
+  form:
+
+  .. parsed-literal::
+     *<message>*: *<system-message>*
+
+  where *<message>* is the passed message and *<system-message>* is
+  the system message corresponding to the error code.
+  *error_code* is a system error code as given by ``errno``.
+  If *error_code* is not a valid error code such as -1, the system message
+  may look like "Unknown error -1" and is platform-dependent.
+  \endrst
+ */
+FMT_API void format_system_error(detail::buffer<char>& out, int error_code,
+                                 string_view message) FMT_NOEXCEPT;
+
+// Reports a system error without throwing an exception.
+// Can be used to report errors from destructors.
+FMT_API void report_system_error(int error_code,
+                                 string_view message) FMT_NOEXCEPT;
+
+/** Fast integer formatter. */
+class format_int {
+ private:
+  // Buffer should be large enough to hold all digits (digits10 + 1),
+  // a sign and a null character.
+  enum { buffer_size = std::numeric_limits<unsigned long long>::digits10 + 3 };
+  mutable char buffer_[buffer_size];
+  char* str_;
+
+  template <typename UInt> char* format_unsigned(UInt value) {
+    auto n = static_cast<detail::uint32_or_64_or_128_t<UInt>>(value);
+    return detail::format_decimal(buffer_, n, buffer_size - 1).begin;
+  }
+
+  template <typename Int> char* format_signed(Int value) {
+    auto abs_value = static_cast<detail::uint32_or_64_or_128_t<Int>>(value);
+    bool negative = value < 0;
+    if (negative) abs_value = 0 - abs_value;
+    auto begin = format_unsigned(abs_value);
+    if (negative) *--begin = '-';
+    return begin;
+  }
+
+ public:
+  explicit format_int(int value) : str_(format_signed(value)) {}
+  explicit format_int(long value) : str_(format_signed(value)) {}
+  explicit format_int(long long value) : str_(format_signed(value)) {}
+  explicit format_int(unsigned value) : str_(format_unsigned(value)) {}
+  explicit format_int(unsigned long value) : str_(format_unsigned(value)) {}
+  explicit format_int(unsigned long long value)
+      : str_(format_unsigned(value)) {}
+
+  /** Returns the number of characters written to the output buffer. */
+  size_t size() const {
+    return detail::to_unsigned(buffer_ - str_ + buffer_size - 1);
+  }
+
+  /**
+    Returns a pointer to the output buffer content. No terminating null
+    character is appended.
+   */
+  const char* data() const { return str_; }
+
+  /**
+    Returns a pointer to the output buffer content with terminating null
+    character appended.
+   */
+  const char* c_str() const {
+    buffer_[buffer_size - 1] = '\0';
+    return str_;
+  }
+
+  /**
+    \rst
+    Returns the content of the output buffer as an ``std::string``.
+    \endrst
+   */
+  std::string str() const { return std::string(str_, size()); }
+};
+
+// A formatter specialization for the core types corresponding to detail::type
+// constants.
+template <typename T, typename Char>
+struct formatter<T, Char,
+                 enable_if_t<detail::type_constant<T, Char>::value !=
+                             detail::type::custom_type>> {
+  FMT_CONSTEXPR formatter() = default;
+
+  // Parses format specifiers stopping either at the end of the range or at the
+  // terminating '}'.
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto begin = ctx.begin(), end = ctx.end();
+    if (begin == end) return begin;
+    using handler_type = detail::dynamic_specs_handler<ParseContext>;
+    auto type = detail::type_constant<T, Char>::value;
+    detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+                                                type);
+    auto it = detail::parse_format_specs(begin, end, handler);
+    auto eh = ctx.error_handler();
+    switch (type) {
+    case detail::type::none_type:
+      FMT_ASSERT(false, "invalid argument type");
+      break;
+    case detail::type::bool_type:
+      if (!specs_.type || specs_.type == 's') break;
+      FMT_FALLTHROUGH;
+    case detail::type::int_type:
+    case detail::type::uint_type:
+    case detail::type::long_long_type:
+    case detail::type::ulong_long_type:
+    case detail::type::int128_type:
+    case detail::type::uint128_type:
+      detail::check_int_type_spec(specs_.type, eh);
+      break;
+    case detail::type::char_type:
+      detail::handle_char_specs(
+          specs_, detail::char_specs_checker<decltype(eh)>(specs_.type, eh));
+      break;
+    case detail::type::float_type:
+      if (detail::const_check(FMT_USE_FLOAT))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "float support disabled");
+      break;
+    case detail::type::double_type:
+      if (detail::const_check(FMT_USE_DOUBLE))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "double support disabled");
+      break;
+    case detail::type::long_double_type:
+      if (detail::const_check(FMT_USE_LONG_DOUBLE))
+        detail::parse_float_type_spec(specs_, eh);
+      else
+        FMT_ASSERT(false, "long double support disabled");
+      break;
+    case detail::type::cstring_type:
+      detail::handle_cstring_type_spec(
+          specs_.type, detail::cstring_type_checker<decltype(eh)>(eh));
+      break;
+    case detail::type::string_type:
+      detail::check_string_type_spec(specs_.type, eh);
+      break;
+    case detail::type::pointer_type:
+      detail::check_pointer_type_spec(specs_.type, eh);
+      break;
+    case detail::type::custom_type:
+      // Custom format specifiers should be checked in parse functions of
+      // formatter specializations.
+      break;
+    }
+    return it;
+  }
+
+  template <typename FormatContext>
+  FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    auto specs = specs_;
+    detail::handle_dynamic_spec<detail::width_checker>(specs.width,
+                                                       specs.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs.precision, specs.precision_ref, ctx);
+    using af = detail::arg_formatter<typename FormatContext::iterator,
+                                     typename FormatContext::char_type>;
+    return visit_format_arg(af(ctx, specs),
+                            detail::make_arg<FormatContext>(val));
+  }
+
+ private:
+  detail::dynamic_format_specs<Char> specs_;
+};
+
+#define FMT_FORMAT_AS(Type, Base)                                        \
+  template <typename Char>                                               \
+  struct formatter<Type, Char> : formatter<Base, Char> {                 \
+    template <typename FormatContext>                                    \
+    auto format(Type const& val, FormatContext& ctx) const               \
+        -> decltype(ctx.out()) {                                         \
+      return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \
+    }                                                                    \
+  }
+
+FMT_FORMAT_AS(signed char, int);
+FMT_FORMAT_AS(unsigned char, unsigned);
+FMT_FORMAT_AS(short, int);
+FMT_FORMAT_AS(unsigned short, unsigned);
+FMT_FORMAT_AS(long, long long);
+FMT_FORMAT_AS(unsigned long, unsigned long long);
+FMT_FORMAT_AS(Char*, const Char*);
+FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
+FMT_FORMAT_AS(std::nullptr_t, const void*);
+FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
+
+template <typename Char>
+struct formatter<void*, Char> : formatter<const void*, Char> {
+  template <typename FormatContext>
+  auto format(void* val, FormatContext& ctx) const -> decltype(ctx.out()) {
+    return formatter<const void*, Char>::format(val, ctx);
+  }
+};
+
+template <typename Char, size_t N>
+struct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {
+  template <typename FormatContext>
+  FMT_CONSTEXPR auto format(const Char* val, FormatContext& ctx) const
+      -> decltype(ctx.out()) {
+    return formatter<basic_string_view<Char>, Char>::format(val, ctx);
+  }
+};
+
+// A formatter for types known only at run time such as variant alternatives.
+//
+// Usage:
+//   using variant = std::variant<int, std::string>;
+//   template <>
+//   struct formatter<variant>: dynamic_formatter<> {
+//     auto format(const variant& v, format_context& ctx) {
+//       return visit([&](const auto& val) {
+//           return dynamic_formatter<>::format(val, ctx);
+//       }, v);
+//     }
+//   };
+template <typename Char = char> class dynamic_formatter {
+ private:
+  struct null_handler : detail::error_handler {
+    void on_align(align_t) {}
+    void on_plus() {}
+    void on_minus() {}
+    void on_space() {}
+    void on_hash() {}
+  };
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    format_str_ = ctx.begin();
+    // Checks are deferred to formatting time when the argument type is known.
+    detail::dynamic_specs_handler<ParseContext> handler(specs_, ctx);
+    return detail::parse_format_specs(ctx.begin(), ctx.end(), handler);
+  }
+
+  template <typename T, typename FormatContext>
+  auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
+    handle_specs(ctx);
+    detail::specs_checker<null_handler> checker(
+        null_handler(), detail::mapped_type_constant<T, FormatContext>::value);
+    checker.on_align(specs_.align);
+    switch (specs_.sign) {
+    case sign::none:
+      break;
+    case sign::plus:
+      checker.on_plus();
+      break;
+    case sign::minus:
+      checker.on_minus();
+      break;
+    case sign::space:
+      checker.on_space();
+      break;
+    }
+    if (specs_.alt) checker.on_hash();
+    if (specs_.precision >= 0) checker.end_precision();
+    using af = detail::arg_formatter<typename FormatContext::iterator,
+                                     typename FormatContext::char_type>;
+    visit_format_arg(af(ctx, specs_), detail::make_arg<FormatContext>(val));
+    return ctx.out();
+  }
+
+ private:
+  template <typename Context> void handle_specs(Context& ctx) {
+    detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+                                                       specs_.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+  }
+
+  detail::dynamic_format_specs<Char> specs_;
+  const Char* format_str_;
+};
+
+template <typename Char, typename ErrorHandler>
+FMT_CONSTEXPR void advance_to(
+    basic_format_parse_context<Char, ErrorHandler>& ctx, const Char* p) {
+  ctx.advance_to(ctx.begin() + (p - &*ctx.begin()));
+}
+
+/**
+  \rst
+  Converts ``p`` to ``const void*`` for pointer formatting.
+
+  **Example**::
+
+    auto s = fmt::format("{}", fmt::ptr(p));
+  \endrst
+ */
+template <typename T> const void* ptr(T p) {
+  static_assert(std::is_pointer<T>::value, "");
+  return detail::bit_cast<const void*>(p);
+}
+template <typename T> const void* ptr(const std::unique_ptr<T>& p) {
+  return p.get();
+}
+template <typename T> const void* ptr(const std::shared_ptr<T>& p) {
+  return p.get();
+}
+
+class bytes {
+ private:
+  string_view data_;
+  friend struct formatter<bytes>;
+
+ public:
+  explicit bytes(string_view data) : data_(data) {}
+};
+
+template <> struct formatter<bytes> {
+ private:
+  detail::dynamic_format_specs<char> specs_;
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    using handler_type = detail::dynamic_specs_handler<ParseContext>;
+    detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+                                                detail::type::string_type);
+    auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+    detail::check_string_type_spec(specs_.type, ctx.error_handler());
+    return it;
+  }
+
+  template <typename FormatContext>
+  auto format(bytes b, FormatContext& ctx) -> decltype(ctx.out()) {
+    detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+                                                       specs_.width_ref, ctx);
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+    return detail::write_bytes(ctx.out(), b.data_, specs_);
+  }
+};
+
+template <typename It, typename Sentinel, typename Char>
+struct arg_join : detail::view {
+  It begin;
+  Sentinel end;
+  basic_string_view<Char> sep;
+
+  arg_join(It b, Sentinel e, basic_string_view<Char> s)
+      : begin(b), end(e), sep(s) {}
+};
+
+template <typename It, typename Sentinel, typename Char>
+struct formatter<arg_join<It, Sentinel, Char>, Char> {
+ private:
+  using value_type = typename std::iterator_traits<It>::value_type;
+  using context = buffer_context<Char>;
+  using mapper = detail::arg_mapper<context>;
+
+  template <typename T, FMT_ENABLE_IF(has_formatter<T, context>::value)>
+  static auto map(const T& value) -> const T& {
+    return value;
+  }
+  template <typename T, FMT_ENABLE_IF(!has_formatter<T, context>::value)>
+  static auto map(const T& value) -> decltype(mapper().map(value)) {
+    return mapper().map(value);
+  }
+
+  using formatter_type =
+      conditional_t<is_formattable<value_type>::value,
+                    formatter<remove_cvref_t<decltype(
+                                  map(std::declval<const value_type&>()))>,
+                              Char>,
+                    detail::fallback_formatter<value_type, Char>>;
+
+  formatter_type value_formatter_;
+
+ public:
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return value_formatter_.parse(ctx);
+  }
+
+  template <typename FormatContext>
+  auto format(const arg_join<It, Sentinel, Char>& value, FormatContext& ctx)
+      -> decltype(ctx.out()) {
+    auto it = value.begin;
+    auto out = ctx.out();
+    if (it != value.end) {
+      out = value_formatter_.format(map(*it++), ctx);
+      while (it != value.end) {
+        out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out);
+        ctx.advance_to(out);
+        out = value_formatter_.format(map(*it++), ctx);
+      }
+    }
+    return out;
+  }
+};
+
+/**
+  Returns an object that formats the iterator range `[begin, end)` with elements
+  separated by `sep`.
+ */
+template <typename It, typename Sentinel>
+arg_join<It, Sentinel, char> join(It begin, Sentinel end, string_view sep) {
+  return {begin, end, sep};
+}
+
+template <typename It, typename Sentinel>
+arg_join<It, Sentinel, wchar_t> join(It begin, Sentinel end, wstring_view sep) {
+  return {begin, end, sep};
+}
+
+/**
+  \rst
+  Returns an object that formats `range` with elements separated by `sep`.
+
+  **Example**::
+
+    std::vector<int> v = {1, 2, 3};
+    fmt::print("{}", fmt::join(v, ", "));
+    // Output: "1, 2, 3"
+
+  ``fmt::join`` applies passed format specifiers to the range elements::
+
+    fmt::print("{:02}", fmt::join(v, ", "));
+    // Output: "01, 02, 03"
+  \endrst
+ */
+template <typename Range>
+arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, char> join(
+    Range&& range, string_view sep) {
+  return join(std::begin(range), std::end(range), sep);
+}
+
+template <typename Range>
+arg_join<detail::iterator_t<Range>, detail::sentinel_t<Range>, wchar_t> join(
+    Range&& range, wstring_view sep) {
+  return join(std::begin(range), std::end(range), sep);
+}
+
+/**
+  \rst
+  Converts *value* to ``std::string`` using the default format for type *T*.
+
+  **Example**::
+
+    #include <fmt/format.h>
+
+    std::string answer = fmt::to_string(42);
+  \endrst
+ */
+template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+inline std::string to_string(const T& value) {
+  std::string result;
+  detail::write<char>(std::back_inserter(result), value);
+  return result;
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline std::string to_string(T value) {
+  // The buffer should be large enough to store the number including the sign or
+  // "false" for bool.
+  constexpr int max_size = detail::digits10<T>() + 2;
+  char buffer[max_size > 5 ? static_cast<unsigned>(max_size) : 5];
+  char* begin = buffer;
+  return std::string(begin, detail::write<char>(begin, value));
+}
+
+/**
+  Converts *value* to ``std::wstring`` using the default format for type *T*.
+ */
+template <typename T> inline std::wstring to_wstring(const T& value) {
+  return format(FMT_STRING(L"{}"), value);
+}
+
+template <typename Char, size_t SIZE>
+std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE>& buf) {
+  auto size = buf.size();
+  detail::assume(size < std::basic_string<Char>().max_size());
+  return std::basic_string<Char>(buf.data(), size);
+}
+
+FMT_MODULE_EXPORT_END
+
+template <typename Char>
+void detail::vformat_to(
+    detail::buffer<Char>& buf, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args,
+    detail::locale_ref loc) {
+  using iterator = typename buffer_context<Char>::iterator;
+  auto out = buffer_appender<Char>(buf);
+  if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
+    auto arg = args.get(0);
+    if (!arg) error_handler().on_error("argument not found");
+    visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc},
+                     arg);
+    return;
+  }
+  format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args,
+                                                         loc);
+  parse_format_string<false>(format_str, h);
+}
+
+#ifndef FMT_HEADER_ONLY
+extern template void detail::vformat_to(detail::buffer<char>&, string_view,
+                                        basic_format_args<format_context>,
+                                        detail::locale_ref);
+namespace detail {
+
+extern template FMT_API std::string grouping_impl<char>(locale_ref loc);
+extern template FMT_API std::string grouping_impl<wchar_t>(locale_ref loc);
+extern template FMT_API char thousands_sep_impl<char>(locale_ref loc);
+extern template FMT_API wchar_t thousands_sep_impl<wchar_t>(locale_ref loc);
+extern template FMT_API char decimal_point_impl(locale_ref loc);
+extern template FMT_API wchar_t decimal_point_impl(locale_ref loc);
+extern template int format_float<double>(double value, int precision,
+                                         float_specs specs, buffer<char>& buf);
+extern template int format_float<long double>(long double value, int precision,
+                                              float_specs specs,
+                                              buffer<char>& buf);
+int snprintf_float(float value, int precision, float_specs specs,
+                   buffer<char>& buf) = delete;
+extern template int snprintf_float<double>(double value, int precision,
+                                           float_specs specs,
+                                           buffer<char>& buf);
+extern template int snprintf_float<long double>(long double value,
+                                                int precision,
+                                                float_specs specs,
+                                                buffer<char>& buf);
+}  // namespace detail
+#endif
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename S, typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+inline void vformat_to(
+    detail::buffer<Char>& buf, const S& format_str,
+    basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args) {
+  return detail::vformat_to(buf, to_string_view(format_str), args);
+}
+
+template <typename S, typename... Args, size_t SIZE = inline_buffer_size,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+inline typename buffer_context<Char>::iterator format_to(
+    basic_memory_buffer<Char, SIZE>& buf, const S& format_str, Args&&... args) {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  detail::vformat_to(buf, to_string_view(format_str), vargs);
+  return detail::buffer_appender<Char>(buf);
+}
+
+template <typename OutputIt, typename Char = char>
+using format_context_t = basic_format_context<OutputIt, Char>;
+
+template <typename OutputIt, typename Char = char>
+using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>;
+
+template <typename OutputIt, typename Char = typename OutputIt::value_type>
+using format_to_n_context FMT_DEPRECATED_ALIAS = buffer_context<Char>;
+
+template <typename OutputIt, typename Char = typename OutputIt::value_type>
+using format_to_n_args FMT_DEPRECATED_ALIAS =
+    basic_format_args<buffer_context<Char>>;
+
+template <typename OutputIt, typename Char, typename... Args>
+FMT_DEPRECATED format_arg_store<buffer_context<Char>, Args...>
+make_format_to_n_args(const Args&... args) {
+  return format_arg_store<buffer_context<Char>, Args...>(args...);
+}
+
+#if FMT_COMPILE_TIME_CHECKS
+template <typename... Args> struct format_string {
+  string_view str;
+
+  template <size_t N> consteval format_string(const char (&s)[N]) : str(s) {
+    if constexpr (detail::count_named_args<Args...>() == 0) {
+      using checker = detail::format_string_checker<char, detail::error_handler,
+                                                    remove_cvref_t<Args>...>;
+      detail::parse_format_string<true>(string_view(s, N), checker(s, {}));
+    }
+  }
+
+  template <typename T,
+            FMT_ENABLE_IF(std::is_constructible_v<string_view, const T&>)>
+  format_string(const T& s) : str(s) {}
+};
+
+template <typename... Args>
+FMT_INLINE std::string format(
+    format_string<std::type_identity_t<Args>...> format_str, Args&&... args) {
+  return detail::vformat(format_str.str, make_format_args(args...));
+}
+#endif
+
+template <typename Char, enable_if_t<(!std::is_same<Char, char>::value), int>>
+std::basic_string<Char> detail::vformat(
+    basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args);
+  return to_string(buffer);
+}
+
+template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
+void vprint(std::FILE* f, basic_string_view<Char> format_str,
+            wformat_args args) {
+  wmemory_buffer buffer;
+  detail::vformat_to(buffer, format_str, args);
+  buffer.push_back(L'\0');
+  if (std::fputws(buffer.data(), f) == -1)
+    FMT_THROW(system_error(errno, "cannot write to file"));
+}
+
+template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
+void vprint(basic_string_view<Char> format_str, wformat_args args) {
+  vprint(stdout, format_str, args);
+}
+
+FMT_MODULE_EXPORT_END
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+namespace detail {
+template <typename Char, size_t N> struct fixed_string {
+  constexpr fixed_string(const Char (&str)[N]) {
+    copy_str<Char, const Char*, Char*>(static_cast<const Char*>(str), str + N,
+                                       data);
+  }
+  Char data[N]{};
+};
+}  // namespace detail
+#endif
+
+#if FMT_USE_USER_DEFINED_LITERALS
+namespace detail {
+template <typename Char> struct udl_formatter {
+  basic_string_view<Char> str;
+
+  template <typename... Args>
+  std::basic_string<Char> operator()(Args&&... args) const {
+    return format(str, std::forward<Args>(args)...);
+  }
+};
+
+template <typename T, typename = void>
+struct is_statically_named_arg : std::false_type {};
+
+#  if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
+struct statically_named_arg : view {
+  static constexpr auto name = Str.data;
+
+  const T& value;
+  statically_named_arg(const T& v) : value(v) {}
+};
+
+template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
+struct is_named_arg<statically_named_arg<T, Char, N, Str>> : std::true_type {};
+
+template <typename T, typename Char, size_t N, fixed_string<Char, N> Str>
+struct is_statically_named_arg<statically_named_arg<T, Char, N, Str>>
+    : std::true_type {};
+
+template <typename Char, size_t N, fixed_string<Char, N> Str> struct udl_arg {
+  template <typename T> auto operator=(T&& value) const {
+    return statically_named_arg<T, Char, N, Str>(std::forward<T>(value));
+  }
+};
+#  else
+template <typename Char> struct udl_arg {
+  const Char* str;
+
+  template <typename T> named_arg<Char, T> operator=(T&& value) const {
+    return {str, std::forward<T>(value)};
+  }
+};
+#  endif
+}  // namespace detail
+FMT_MODULE_EXPORT_BEGIN
+
+inline namespace literals {
+/**
+  \rst
+  User-defined literal equivalent of :func:`fmt::format`.
+
+  **Example**::
+
+    using namespace fmt::literals;
+    std::string message = "The answer is {}"_format(42);
+  \endrst
+ */
+constexpr detail::udl_formatter<char> operator"" _format(const char* s,
+                                                         size_t n) {
+  return {{s, n}};
+}
+constexpr detail::udl_formatter<wchar_t> operator"" _format(const wchar_t* s,
+                                                            size_t n) {
+  return {{s, n}};
+}
+
+/**
+  \rst
+  User-defined literal equivalent of :func:`fmt::arg`.
+
+  **Example**::
+
+    using namespace fmt::literals;
+    fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23);
+  \endrst
+ */
+#  if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+template <detail::fixed_string Str>
+constexpr detail::udl_arg<remove_cvref_t<decltype(Str.data[0])>,
+                          sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
+operator""_a() {
+  return {};
+}
+#  else
+constexpr detail::udl_arg<char> operator"" _a(const char* s, size_t) {
+  return {s};
+}
+constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
+  return {s};
+}
+#  endif
+}  // namespace literals
+
+FMT_MODULE_EXPORT_END
+#endif  // FMT_USE_USER_DEFINED_LITERALS
+FMT_END_NAMESPACE
+
+#ifdef FMT_HEADER_ONLY
+#  define FMT_FUNC inline
+#  include "format-inl.h"
+#else
+#  define FMT_FUNC
+#endif
+
+#endif  // FMT_FORMAT_H_
diff --git a/libs/fmt/include/fmt/locale.h b/libs/fmt/include/fmt/locale.h
new file mode 100644
index 0000000000000000000000000000000000000000..e79140a1a19a1ad7095aa1046d4397a3482c1c89
--- /dev/null
+++ b/libs/fmt/include/fmt/locale.h
@@ -0,0 +1,67 @@
+// Formatting library for C++ - std::locale support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_LOCALE_H_
+#define FMT_LOCALE_H_
+
+#include <locale>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+namespace detail {
+template <typename Char>
+std::basic_string<Char> vformat(
+    const std::locale& loc, basic_string_view<Char> format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
+  return fmt::to_string(buffer);
+}
+}  // namespace detail
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename S, typename Char = char_t<S>>
+inline std::basic_string<Char> vformat(
+    const std::locale& loc, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  return detail::vformat(loc, to_string_view(format_str), args);
+}
+
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline std::basic_string<Char> format(const std::locale& loc,
+                                      const S& format_str, Args&&... args) {
+  return detail::vformat(loc, to_string_view(format_str),
+                         fmt::make_args_checked<Args...>(format_str, args...));
+}
+
+template <typename S, typename OutputIt, typename... Args,
+          typename Char = char_t<S>,
+          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
+inline OutputIt vformat_to(
+    OutputIt out, const std::locale& loc, const S& format_str,
+    basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
+  vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
+  return detail::get_iterator(buf);
+}
+
+template <typename OutputIt, typename S, typename... Args,
+          bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
+inline auto format_to(OutputIt out, const std::locale& loc, const S& format_str,
+                      Args&&... args) ->
+    typename std::enable_if<enable, OutputIt>::type {
+  const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
+  return vformat_to(out, loc, to_string_view(format_str), vargs);
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_LOCALE_H_
diff --git a/libs/fmt/include/fmt/os.h b/libs/fmt/include/fmt/os.h
new file mode 100644
index 0000000000000000000000000000000000000000..14c8c8b6fb8e0d3032e7800ec90647c80784d190
--- /dev/null
+++ b/libs/fmt/include/fmt/os.h
@@ -0,0 +1,502 @@
+// Formatting library for C++ - optional OS-specific functionality
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_OS_H_
+#define FMT_OS_H_
+
+#include <cerrno>
+#include <clocale>  // for locale_t
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>  // for strtod_l
+
+#if defined __APPLE__ || defined(__FreeBSD__)
+#  include <xlocale.h>  // for LC_NUMERIC_MASK on OS X
+#endif
+
+#include "format.h"
+
+// UWP doesn't provide _pipe.
+#if FMT_HAS_INCLUDE("winapifamily.h")
+#  include <winapifamily.h>
+#endif
+#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
+     defined(__linux__)) &&                              \
+    (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+#  include <fcntl.h>  // for O_RDONLY
+#  define FMT_USE_FCNTL 1
+#else
+#  define FMT_USE_FCNTL 0
+#endif
+
+#ifndef FMT_POSIX
+#  if defined(_WIN32) && !defined(__MINGW32__)
+// Fix warnings about deprecated symbols.
+#    define FMT_POSIX(call) _##call
+#  else
+#    define FMT_POSIX(call) call
+#  endif
+#endif
+
+// Calls to system functions are wrapped in FMT_SYSTEM for testability.
+#ifdef FMT_SYSTEM
+#  define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
+#else
+#  define FMT_SYSTEM(call) ::call
+#  ifdef _WIN32
+// Fix warnings about deprecated symbols.
+#    define FMT_POSIX_CALL(call) ::_##call
+#  else
+#    define FMT_POSIX_CALL(call) ::call
+#  endif
+#endif
+
+// Retries the expression while it evaluates to error_result and errno
+// equals to EINTR.
+#ifndef _WIN32
+#  define FMT_RETRY_VAL(result, expression, error_result) \
+    do {                                                  \
+      (result) = (expression);                            \
+    } while ((result) == (error_result) && errno == EINTR)
+#else
+#  define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
+#endif
+
+#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
+
+FMT_BEGIN_NAMESPACE
+
+/**
+  \rst
+  A reference to a null-terminated string. It can be constructed from a C
+  string or ``std::string``.
+
+  You can use one of the following type aliases for common character types:
+
+  +---------------+-----------------------------+
+  | Type          | Definition                  |
+  +===============+=============================+
+  | cstring_view  | basic_cstring_view<char>    |
+  +---------------+-----------------------------+
+  | wcstring_view | basic_cstring_view<wchar_t> |
+  +---------------+-----------------------------+
+
+  This class is most useful as a parameter type to allow passing
+  different types of strings to a function, for example::
+
+    template <typename... Args>
+    std::string format(cstring_view format_str, const Args & ... args);
+
+    format("{}", 42);
+    format(std::string("{}"), 42);
+  \endrst
+ */
+template <typename Char> class basic_cstring_view {
+ private:
+  const Char* data_;
+
+ public:
+  /** Constructs a string reference object from a C string. */
+  basic_cstring_view(const Char* s) : data_(s) {}
+
+  /**
+    \rst
+    Constructs a string reference from an ``std::string`` object.
+    \endrst
+   */
+  basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
+
+  /** Returns the pointer to a C string. */
+  const Char* c_str() const { return data_; }
+};
+
+using cstring_view = basic_cstring_view<char>;
+using wcstring_view = basic_cstring_view<wchar_t>;
+
+// An error code.
+class error_code {
+ private:
+  int value_;
+
+ public:
+  explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
+
+  int get() const FMT_NOEXCEPT { return value_; }
+};
+
+#ifdef _WIN32
+namespace detail {
+// A converter from UTF-16 to UTF-8.
+// It is only provided for Windows since other systems support UTF-8 natively.
+class utf16_to_utf8 {
+ private:
+  memory_buffer buffer_;
+
+ public:
+  utf16_to_utf8() {}
+  FMT_API explicit utf16_to_utf8(wstring_view s);
+  operator string_view() const { return string_view(&buffer_[0], size()); }
+  size_t size() const { return buffer_.size() - 1; }
+  const char* c_str() const { return &buffer_[0]; }
+  std::string str() const { return std::string(&buffer_[0], size()); }
+
+  // Performs conversion returning a system error code instead of
+  // throwing exception on conversion error. This method may still throw
+  // in case of memory allocation error.
+  FMT_API int convert(wstring_view s);
+};
+
+FMT_API void format_windows_error(buffer<char>& out, int error_code,
+                                  string_view message) FMT_NOEXCEPT;
+}  // namespace detail
+
+/** A Windows error. */
+class windows_error : public system_error {
+ private:
+  FMT_API void init(int error_code, string_view format_str, format_args args);
+
+ public:
+  /**
+   \rst
+   Constructs a :class:`fmt::windows_error` object with the description
+   of the form
+
+   .. parsed-literal::
+     *<message>*: *<system-message>*
+
+   where *<message>* is the formatted message and *<system-message>* is the
+   system message corresponding to the error code.
+   *error_code* is a Windows error code as given by ``GetLastError``.
+   If *error_code* is not a valid error code such as -1, the system message
+   will look like "error -1".
+
+   **Example**::
+
+     // This throws a windows_error with the description
+     //   cannot open file 'madeup': The system cannot find the file specified.
+     // or similar (system message may vary).
+     const char *filename = "madeup";
+     LPOFSTRUCT of = LPOFSTRUCT();
+     HFILE file = OpenFile(filename, &of, OF_READ);
+     if (file == HFILE_ERROR) {
+       throw fmt::windows_error(GetLastError(),
+                                "cannot open file '{}'", filename);
+     }
+   \endrst
+  */
+  template <typename... Args>
+  windows_error(int error_code, string_view message, const Args&... args) {
+    init(error_code, message, make_format_args(args...));
+  }
+};
+
+// Reports a Windows error without throwing an exception.
+// Can be used to report errors from destructors.
+FMT_API void report_windows_error(int error_code,
+                                  string_view message) FMT_NOEXCEPT;
+#endif  // _WIN32
+
+// std::system is not available on some platforms such as iOS (#2248).
+#ifdef __OSX__
+template <typename S, typename... Args, typename Char = char_t<S>>
+void say(const S& format_str, Args&&... args) {
+  std::system(format("say \"{}\"", format(format_str, args...)).c_str());
+}
+#endif
+
+// A buffered file.
+class buffered_file {
+ private:
+  FILE* file_;
+
+  friend class file;
+
+  explicit buffered_file(FILE* f) : file_(f) {}
+
+ public:
+  buffered_file(const buffered_file&) = delete;
+  void operator=(const buffered_file&) = delete;
+
+  // Constructs a buffered_file object which doesn't represent any file.
+  buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
+
+  // Destroys the object closing the file it represents if any.
+  FMT_API ~buffered_file() FMT_NOEXCEPT;
+
+ public:
+  buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
+    other.file_ = nullptr;
+  }
+
+  buffered_file& operator=(buffered_file&& other) {
+    close();
+    file_ = other.file_;
+    other.file_ = nullptr;
+    return *this;
+  }
+
+  // Opens a file.
+  FMT_API buffered_file(cstring_view filename, cstring_view mode);
+
+  // Closes the file.
+  FMT_API void close();
+
+  // Returns the pointer to a FILE object representing this file.
+  FILE* get() const FMT_NOEXCEPT { return file_; }
+
+  // We place parentheses around fileno to workaround a bug in some versions
+  // of MinGW that define fileno as a macro.
+  FMT_API int(fileno)() const;
+
+  void vprint(string_view format_str, format_args args) {
+    fmt::vprint(file_, format_str, args);
+  }
+
+  template <typename... Args>
+  inline void print(string_view format_str, const Args&... args) {
+    vprint(format_str, make_format_args(args...));
+  }
+};
+
+#if FMT_USE_FCNTL
+// A file. Closed file is represented by a file object with descriptor -1.
+// Methods that are not declared with FMT_NOEXCEPT may throw
+// fmt::system_error in case of failure. Note that some errors such as
+// closing the file multiple times will cause a crash on Windows rather
+// than an exception. You can get standard behavior by overriding the
+// invalid parameter handler with _set_invalid_parameter_handler.
+class file {
+ private:
+  int fd_;  // File descriptor.
+
+  // Constructs a file object with a given descriptor.
+  explicit file(int fd) : fd_(fd) {}
+
+ public:
+  // Possible values for the oflag argument to the constructor.
+  enum {
+    RDONLY = FMT_POSIX(O_RDONLY),  // Open for reading only.
+    WRONLY = FMT_POSIX(O_WRONLY),  // Open for writing only.
+    RDWR = FMT_POSIX(O_RDWR),      // Open for reading and writing.
+    CREATE = FMT_POSIX(O_CREAT),   // Create if the file doesn't exist.
+    APPEND = FMT_POSIX(O_APPEND),  // Open in append mode.
+    TRUNC = FMT_POSIX(O_TRUNC)     // Truncate the content of the file.
+  };
+
+  // Constructs a file object which doesn't represent any file.
+  file() FMT_NOEXCEPT : fd_(-1) {}
+
+  // Opens a file and constructs a file object representing this file.
+  FMT_API file(cstring_view path, int oflag);
+
+ public:
+  file(const file&) = delete;
+  void operator=(const file&) = delete;
+
+  file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
+
+  // Move assignment is not noexcept because close may throw.
+  file& operator=(file&& other) {
+    close();
+    fd_ = other.fd_;
+    other.fd_ = -1;
+    return *this;
+  }
+
+  // Destroys the object closing the file it represents if any.
+  FMT_API ~file() FMT_NOEXCEPT;
+
+  // Returns the file descriptor.
+  int descriptor() const FMT_NOEXCEPT { return fd_; }
+
+  // Closes the file.
+  FMT_API void close();
+
+  // Returns the file size. The size has signed type for consistency with
+  // stat::st_size.
+  FMT_API long long size() const;
+
+  // Attempts to read count bytes from the file into the specified buffer.
+  FMT_API size_t read(void* buffer, size_t count);
+
+  // Attempts to write count bytes from the specified buffer to the file.
+  FMT_API size_t write(const void* buffer, size_t count);
+
+  // Duplicates a file descriptor with the dup function and returns
+  // the duplicate as a file object.
+  FMT_API static file dup(int fd);
+
+  // Makes fd be the copy of this file descriptor, closing fd first if
+  // necessary.
+  FMT_API void dup2(int fd);
+
+  // Makes fd be the copy of this file descriptor, closing fd first if
+  // necessary.
+  FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
+
+  // Creates a pipe setting up read_end and write_end file objects for reading
+  // and writing respectively.
+  FMT_API static void pipe(file& read_end, file& write_end);
+
+  // Creates a buffered_file object associated with this file and detaches
+  // this file object from the file.
+  FMT_API buffered_file fdopen(const char* mode);
+};
+
+// Returns the memory page size.
+long getpagesize();
+
+namespace detail {
+
+struct buffer_size {
+  buffer_size() = default;
+  size_t value = 0;
+  buffer_size operator=(size_t val) const {
+    auto bs = buffer_size();
+    bs.value = val;
+    return bs;
+  }
+};
+
+struct ostream_params {
+  int oflag = file::WRONLY | file::CREATE | file::TRUNC;
+  size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
+
+  ostream_params() {}
+
+  template <typename... T>
+  ostream_params(T... params, int new_oflag) : ostream_params(params...) {
+    oflag = new_oflag;
+  }
+
+  template <typename... T>
+  ostream_params(T... params, detail::buffer_size bs)
+      : ostream_params(params...) {
+    this->buffer_size = bs.value;
+  }
+};
+}  // namespace detail
+
+static constexpr detail::buffer_size buffer_size;
+
+/** A fast output stream which is not thread-safe. */
+class ostream final : private detail::buffer<char> {
+ private:
+  file file_;
+
+  void flush() {
+    if (size() == 0) return;
+    file_.write(data(), size());
+    clear();
+  }
+
+  FMT_API void grow(size_t) override final;
+
+  ostream(cstring_view path, const detail::ostream_params& params)
+      : file_(path, params.oflag) {
+    set(new char[params.buffer_size], params.buffer_size);
+  }
+
+ public:
+  ostream(ostream&& other)
+      : detail::buffer<char>(other.data(), other.size(), other.capacity()),
+        file_(std::move(other.file_)) {
+    other.clear();
+    other.set(nullptr, 0);
+  }
+  ~ostream() {
+    flush();
+    delete[] data();
+  }
+
+  template <typename... T>
+  friend ostream output_file(cstring_view path, T... params);
+
+  void close() {
+    flush();
+    file_.close();
+  }
+
+  /**
+    Formats ``args`` according to specifications in ``format_str`` and writes
+    the output to the file.
+   */
+  template <typename S, typename... Args>
+  void print(const S& format_str, Args&&... args) {
+    format_to(detail::buffer_appender<char>(*this), format_str,
+              std::forward<Args>(args)...);
+  }
+};
+
+/**
+  \rst
+  Opens a file for writing. Supported parameters passed in *params*:
+
+  * ``<integer>``: Flags passed to `open
+    <https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
+    (``file::WRONLY | file::CREATE`` by default)
+  * ``buffer_size=<integer>``: Output buffer size
+
+  **Example**::
+
+    auto out = fmt::output_file("guide.txt");
+    out.print("Don't {}", "Panic");
+  \endrst
+ */
+template <typename... T>
+inline ostream output_file(cstring_view path, T... params) {
+  return {path, detail::ostream_params(params...)};
+}
+#endif  // FMT_USE_FCNTL
+
+#ifdef FMT_LOCALE
+// A "C" numeric locale.
+class locale {
+ private:
+#  ifdef _WIN32
+  using locale_t = _locale_t;
+
+  static void freelocale(locale_t loc) { _free_locale(loc); }
+
+  static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
+    return _strtod_l(nptr, endptr, loc);
+  }
+#  endif
+
+  locale_t locale_;
+
+ public:
+  using type = locale_t;
+  locale(const locale&) = delete;
+  void operator=(const locale&) = delete;
+
+  locale() {
+#  ifndef _WIN32
+    locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
+#  else
+    locale_ = _create_locale(LC_NUMERIC, "C");
+#  endif
+    if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
+  }
+  ~locale() { freelocale(locale_); }
+
+  type get() const { return locale_; }
+
+  // Converts string to floating-point number and advances str past the end
+  // of the parsed input.
+  double strtod(const char*& str) const {
+    char* end = nullptr;
+    double result = strtod_l(str, &end, locale_);
+    str = end;
+    return result;
+  }
+};
+using Locale FMT_DEPRECATED_ALIAS = locale;
+#endif  // FMT_LOCALE
+FMT_END_NAMESPACE
+
+#endif  // FMT_OS_H_
diff --git a/libs/fmt/include/fmt/ostream.h b/libs/fmt/include/fmt/ostream.h
new file mode 100644
index 0000000000000000000000000000000000000000..d66248a6012e87d06cd607fcd5366db65a22e14e
--- /dev/null
+++ b/libs/fmt/include/fmt/ostream.h
@@ -0,0 +1,181 @@
+// Formatting library for C++ - std::ostream support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_OSTREAM_H_
+#define FMT_OSTREAM_H_
+
+#include <ostream>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+template <typename Char> class basic_printf_parse_context;
+template <typename OutputIt, typename Char> class basic_printf_context;
+
+namespace detail {
+
+template <class Char> class formatbuf : public std::basic_streambuf<Char> {
+ private:
+  using int_type = typename std::basic_streambuf<Char>::int_type;
+  using traits_type = typename std::basic_streambuf<Char>::traits_type;
+
+  buffer<Char>& buffer_;
+
+ public:
+  formatbuf(buffer<Char>& buf) : buffer_(buf) {}
+
+ protected:
+  // The put-area is actually always empty. This makes the implementation
+  // simpler and has the advantage that the streambuf and the buffer are always
+  // in sync and sputc never writes into uninitialized memory. The obvious
+  // disadvantage is that each call to sputc always results in a (virtual) call
+  // to overflow. There is no disadvantage here for sputn since this always
+  // results in a call to xsputn.
+
+  int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
+    if (!traits_type::eq_int_type(ch, traits_type::eof()))
+      buffer_.push_back(static_cast<Char>(ch));
+    return ch;
+  }
+
+  std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
+    buffer_.append(s, s + count);
+    return count;
+  }
+};
+
+struct converter {
+  template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
+};
+
+template <typename Char> struct test_stream : std::basic_ostream<Char> {
+ private:
+  void_t<> operator<<(converter);
+};
+
+// Hide insertion operators for built-in types.
+template <typename Char, typename Traits>
+void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
+template <typename Char, typename Traits>
+void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
+template <typename Traits>
+void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
+
+// Checks if T has a user-defined operator<< (e.g. not a member of
+// std::ostream).
+template <typename T, typename Char> class is_streamable {
+ private:
+  template <typename U>
+  static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
+                                              << std::declval<U>()),
+                                     void_t<>>::value>
+  test(int);
+
+  template <typename> static std::false_type test(...);
+
+  using result = decltype(test<T>(0));
+
+ public:
+  is_streamable() = default;
+
+  static const bool value = result::value;
+};
+
+// Write the content of buf to os.
+template <typename Char>
+void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
+  const Char* buf_data = buf.data();
+  using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
+  unsigned_streamsize size = buf.size();
+  unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
+  do {
+    unsigned_streamsize n = size <= max_size ? size : max_size;
+    os.write(buf_data, static_cast<std::streamsize>(n));
+    buf_data += n;
+    size -= n;
+  } while (size != 0);
+}
+
+template <typename Char, typename T>
+void format_value(buffer<Char>& buf, const T& value,
+                  locale_ref loc = locale_ref()) {
+  formatbuf<Char> format_buf(buf);
+  std::basic_ostream<Char> output(&format_buf);
+#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
+  if (loc) output.imbue(loc.get<std::locale>());
+#endif
+  output << value;
+  output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+  buf.try_resize(buf.size());
+}
+
+// Formats an object of type T that has an overloaded ostream operator<<.
+template <typename T, typename Char>
+struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
+    : private formatter<basic_string_view<Char>, Char> {
+  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
+      -> decltype(ctx.begin()) {
+    return formatter<basic_string_view<Char>, Char>::parse(ctx);
+  }
+  template <typename ParseCtx,
+            FMT_ENABLE_IF(std::is_same<
+                          ParseCtx, basic_printf_parse_context<Char>>::value)>
+  auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename OutputIt>
+  auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
+      -> OutputIt {
+    basic_memory_buffer<Char> buffer;
+    format_value(buffer, value, ctx.locale());
+    basic_string_view<Char> str(buffer.data(), buffer.size());
+    return formatter<basic_string_view<Char>, Char>::format(str, ctx);
+  }
+  template <typename OutputIt>
+  auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
+      -> OutputIt {
+    basic_memory_buffer<Char> buffer;
+    format_value(buffer, value, ctx.locale());
+    return std::copy(buffer.begin(), buffer.end(), ctx.out());
+  }
+};
+}  // namespace detail
+
+FMT_MODULE_EXPORT
+template <typename Char>
+void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
+            basic_format_args<buffer_context<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  detail::vformat_to(buffer, format_str, args);
+  detail::write_buffer(os, buffer);
+}
+
+/**
+  \rst
+  Prints formatted data to the stream *os*.
+
+  **Example**::
+
+    fmt::print(cerr, "Don't {}!", "panic");
+  \endrst
+ */
+FMT_MODULE_EXPORT
+template <typename S, typename... Args,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
+  vprint(os, to_string_view(format_str),
+         fmt::make_args_checked<Args...>(format_str, args...));
+}
+FMT_END_NAMESPACE
+
+#endif  // FMT_OSTREAM_H_
diff --git a/libs/fmt/include/fmt/printf.h b/libs/fmt/include/fmt/printf.h
new file mode 100644
index 0000000000000000000000000000000000000000..ee6a20c8c12f8195fcbd5090c9c6ed2bcc8c4c2b
--- /dev/null
+++ b/libs/fmt/include/fmt/printf.h
@@ -0,0 +1,730 @@
+// Formatting library for C++ - legacy printf implementation
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_PRINTF_H_
+#define FMT_PRINTF_H_
+
+#include <algorithm>  // std::max
+#include <limits>     // std::numeric_limits
+#include <ostream>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+// Checks if a value fits in int - used to avoid warnings about comparing
+// signed and unsigned integers.
+template <bool IsSigned> struct int_checker {
+  template <typename T> static bool fits_in_int(T value) {
+    unsigned max = max_value<int>();
+    return value <= max;
+  }
+  static bool fits_in_int(bool) { return true; }
+};
+
+template <> struct int_checker<true> {
+  template <typename T> static bool fits_in_int(T value) {
+    return value >= (std::numeric_limits<int>::min)() &&
+           value <= max_value<int>();
+  }
+  static bool fits_in_int(int) { return true; }
+};
+
+class printf_precision_handler {
+ public:
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  int operator()(T value) {
+    if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
+      FMT_THROW(format_error("number is too big"));
+    return (std::max)(static_cast<int>(value), 0);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  int operator()(T) {
+    FMT_THROW(format_error("precision is not integer"));
+    return 0;
+  }
+};
+
+// An argument visitor that returns true iff arg is a zero integer.
+class is_zero_int {
+ public:
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  bool operator()(T value) {
+    return value == 0;
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  bool operator()(T) {
+    return false;
+  }
+};
+
+template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
+
+template <> struct make_unsigned_or_bool<bool> { using type = bool; };
+
+template <typename T, typename Context> class arg_converter {
+ private:
+  using char_type = typename Context::char_type;
+
+  basic_format_arg<Context>& arg_;
+  char_type type_;
+
+ public:
+  arg_converter(basic_format_arg<Context>& arg, char_type type)
+      : arg_(arg), type_(type) {}
+
+  void operator()(bool value) {
+    if (type_ != 's') operator()<bool>(value);
+  }
+
+  template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
+  void operator()(U value) {
+    bool is_signed = type_ == 'd' || type_ == 'i';
+    using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
+    if (const_check(sizeof(target_type) <= sizeof(int))) {
+      // Extra casts are used to silence warnings.
+      if (is_signed) {
+        arg_ = detail::make_arg<Context>(
+            static_cast<int>(static_cast<target_type>(value)));
+      } else {
+        using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
+        arg_ = detail::make_arg<Context>(
+            static_cast<unsigned>(static_cast<unsigned_type>(value)));
+      }
+    } else {
+      if (is_signed) {
+        // glibc's printf doesn't sign extend arguments of smaller types:
+        //   std::printf("%lld", -42);  // prints "4294967254"
+        // but we don't have to do the same because it's a UB.
+        arg_ = detail::make_arg<Context>(static_cast<long long>(value));
+      } else {
+        arg_ = detail::make_arg<Context>(
+            static_cast<typename make_unsigned_or_bool<U>::type>(value));
+      }
+    }
+  }
+
+  template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
+  void operator()(U) {}  // No conversion needed for non-integral types.
+};
+
+// Converts an integer argument to T for printf, if T is an integral type.
+// If T is void, the argument is converted to corresponding signed or unsigned
+// type depending on the type specifier: 'd' and 'i' - signed, other -
+// unsigned).
+template <typename T, typename Context, typename Char>
+void convert_arg(basic_format_arg<Context>& arg, Char type) {
+  visit_format_arg(arg_converter<T, Context>(arg, type), arg);
+}
+
+// Converts an integer argument to char for printf.
+template <typename Context> class char_converter {
+ private:
+  basic_format_arg<Context>& arg_;
+
+ public:
+  explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
+
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  void operator()(T value) {
+    arg_ = detail::make_arg<Context>(
+        static_cast<typename Context::char_type>(value));
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  void operator()(T) {}  // No conversion needed for non-integral types.
+};
+
+// An argument visitor that return a pointer to a C string if argument is a
+// string or null otherwise.
+template <typename Char> struct get_cstring {
+  template <typename T> const Char* operator()(T) { return nullptr; }
+  const Char* operator()(const Char* s) { return s; }
+};
+
+// Checks if an argument is a valid printf width specifier and sets
+// left alignment if it is negative.
+template <typename Char> class printf_width_handler {
+ private:
+  using format_specs = basic_format_specs<Char>;
+
+  format_specs& specs_;
+
+ public:
+  explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
+
+  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  unsigned operator()(T value) {
+    auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
+    if (detail::is_negative(value)) {
+      specs_.align = align::left;
+      width = 0 - width;
+    }
+    unsigned int_max = max_value<int>();
+    if (width > int_max) FMT_THROW(format_error("number is too big"));
+    return static_cast<unsigned>(width);
+  }
+
+  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+  unsigned operator()(T) {
+    FMT_THROW(format_error("width is not integer"));
+    return 0;
+  }
+};
+
+template <typename Char, typename Context>
+void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
+             basic_format_args<Context> args) {
+  Context(buffer_appender<Char>(buf), format, args).format();
+}
+}  // namespace detail
+
+// For printing into memory_buffer.
+template <typename Char, typename Context>
+FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
+                           basic_string_view<Char> format,
+                           basic_format_args<Context> args) {
+  return detail::vprintf(buf, format, args);
+}
+using detail::vprintf;
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename Char>
+class basic_printf_parse_context : public basic_format_parse_context<Char> {
+  using basic_format_parse_context<Char>::basic_format_parse_context;
+};
+template <typename OutputIt, typename Char> class basic_printf_context;
+
+FMT_MODULE_EXPORT_END
+
+/**
+  \rst
+  The ``printf`` argument formatter.
+  \endrst
+ */
+template <typename OutputIt, typename Char>
+class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
+ private:
+  using base = detail::arg_formatter_base<OutputIt, Char>;
+  using context_type = basic_printf_context<OutputIt, Char>;
+  using format_specs = typename base::format_specs;
+
+  context_type& context_;
+
+  OutputIt write_null_pointer(bool is_string = false) {
+    auto s = this->specs();
+    s.type = 0;
+    return detail::write(this->out(),
+                         string_view(is_string ? "(null)" : "(nil)"), s);
+  }
+
+ public:
+  printf_arg_formatter(OutputIt iter, format_specs& specs, context_type& ctx)
+      : base(iter, specs, detail::locale_ref()), context_(ctx) {}
+
+  OutputIt operator()(monostate value) { return base::operator()(value); }
+
+  template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
+  OutputIt operator()(T value) {
+    // MSVC2013 fails to compile separate overloads for bool and Char so use
+    // std::is_same instead.
+    if (std::is_same<T, Char>::value) {
+      format_specs fmt_specs = this->specs();
+      if (fmt_specs.type && fmt_specs.type != 'c')
+        return (*this)(static_cast<int>(value));
+      fmt_specs.sign = sign::none;
+      fmt_specs.alt = false;
+      fmt_specs.fill[0] = ' ';  // Ignore '0' flag for char types.
+      // align::numeric needs to be overwritten here since the '0' flag is
+      // ignored for non-numeric types
+      if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
+        fmt_specs.align = align::right;
+      return write_char(this->out(), static_cast<Char>(value), fmt_specs);
+    }
+    return base::operator()(value);
+  }
+
+  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+  OutputIt operator()(T value) {
+    return base::operator()(value);
+  }
+
+  /** Formats a null-terminated C string. */
+  OutputIt operator()(const char* value) {
+    if (value) return base::operator()(value);
+    return write_null_pointer(this->specs().type != 'p');
+  }
+
+  /** Formats a null-terminated wide C string. */
+  OutputIt operator()(const wchar_t* value) {
+    if (value) return base::operator()(value);
+    return write_null_pointer(this->specs().type != 'p');
+  }
+
+  OutputIt operator()(basic_string_view<Char> value) {
+    return base::operator()(value);
+  }
+
+  /** Formats a pointer. */
+  OutputIt operator()(const void* value) {
+    return value ? base::operator()(value) : write_null_pointer();
+  }
+
+  /** Formats an argument of a custom (user-defined) type. */
+  OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
+    handle.format(context_.parse_context(), context_);
+    return this->out();
+  }
+};
+
+template <typename T> struct printf_formatter {
+  printf_formatter() = delete;
+
+  template <typename ParseContext>
+  auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto format(const T&, FormatContext& ctx) -> decltype(ctx.out()) {
+    return ctx.out();
+  }
+};
+
+/**
+ This template formats data and writes the output through an output iterator.
+ */
+template <typename OutputIt, typename Char> class basic_printf_context {
+ public:
+  /** The character type for the output. */
+  using char_type = Char;
+  using iterator = OutputIt;
+  using format_arg = basic_format_arg<basic_printf_context>;
+  using parse_context_type = basic_printf_parse_context<Char>;
+  template <typename T> using formatter_type = printf_formatter<T>;
+
+ private:
+  using format_specs = basic_format_specs<char_type>;
+
+  OutputIt out_;
+  basic_format_args<basic_printf_context> args_;
+  parse_context_type parse_ctx_;
+
+  static void parse_flags(format_specs& specs, const Char*& it,
+                          const Char* end);
+
+  // Returns the argument with specified index or, if arg_index is -1, the next
+  // argument.
+  format_arg get_arg(int arg_index = -1);
+
+  // Parses argument index, flags and width and returns the argument index.
+  int parse_header(const Char*& it, const Char* end, format_specs& specs);
+
+ public:
+  /**
+   \rst
+   Constructs a ``printf_context`` object. References to the arguments are
+   stored in the context object so make sure they have appropriate lifetimes.
+   \endrst
+   */
+  basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
+                       basic_format_args<basic_printf_context> args)
+      : out_(out), args_(args), parse_ctx_(format_str) {}
+
+  OutputIt out() { return out_; }
+  void advance_to(OutputIt it) { out_ = it; }
+
+  detail::locale_ref locale() { return {}; }
+
+  format_arg arg(int id) const { return args_.get(id); }
+
+  parse_context_type& parse_context() { return parse_ctx_; }
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    parse_ctx_.on_error(message);
+  }
+
+  /** Formats stored arguments and writes the output to the range. */
+  template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
+  OutputIt format();
+};
+
+template <typename OutputIt, typename Char>
+void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
+                                                       const Char*& it,
+                                                       const Char* end) {
+  for (; it != end; ++it) {
+    switch (*it) {
+    case '-':
+      specs.align = align::left;
+      break;
+    case '+':
+      specs.sign = sign::plus;
+      break;
+    case '0':
+      specs.fill[0] = '0';
+      break;
+    case ' ':
+      if (specs.sign != sign::plus) {
+        specs.sign = sign::space;
+      }
+      break;
+    case '#':
+      specs.alt = true;
+      break;
+    default:
+      return;
+    }
+  }
+}
+
+template <typename OutputIt, typename Char>
+typename basic_printf_context<OutputIt, Char>::format_arg
+basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
+  if (arg_index < 0)
+    arg_index = parse_ctx_.next_arg_id();
+  else
+    parse_ctx_.check_arg_id(--arg_index);
+  return detail::get_arg(*this, arg_index);
+}
+
+template <typename OutputIt, typename Char>
+int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
+                                                       const Char* end,
+                                                       format_specs& specs) {
+  int arg_index = -1;
+  char_type c = *it;
+  if (c >= '0' && c <= '9') {
+    // Parse an argument index (if followed by '$') or a width possibly
+    // preceded with '0' flag(s).
+    detail::error_handler eh;
+    int value = parse_nonnegative_int(it, end, eh);
+    if (it != end && *it == '$') {  // value is an argument index
+      ++it;
+      arg_index = value;
+    } else {
+      if (c == '0') specs.fill[0] = '0';
+      if (value != 0) {
+        // Nonzero value means that we parsed width and don't need to
+        // parse it or flags again, so return now.
+        specs.width = value;
+        return arg_index;
+      }
+    }
+  }
+  parse_flags(specs, it, end);
+  // Parse width.
+  if (it != end) {
+    if (*it >= '0' && *it <= '9') {
+      detail::error_handler eh;
+      specs.width = parse_nonnegative_int(it, end, eh);
+    } else if (*it == '*') {
+      ++it;
+      specs.width = static_cast<int>(visit_format_arg(
+          detail::printf_width_handler<char_type>(specs), get_arg()));
+    }
+  }
+  return arg_index;
+}
+
+template <typename OutputIt, typename Char>
+template <typename ArgFormatter>
+OutputIt basic_printf_context<OutputIt, Char>::format() {
+  auto out = this->out();
+  const Char* start = parse_ctx_.begin();
+  const Char* end = parse_ctx_.end();
+  auto it = start;
+  while (it != end) {
+    if (!detail::find<false, Char>(it, end, '%', it)) {
+      it = end;  // detail::find leaves it == nullptr if it doesn't find '%'
+      break;
+    }
+    char_type c = *it++;
+    if (it != end && *it == c) {
+      out = detail::write(
+          out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
+      start = ++it;
+      continue;
+    }
+    out = detail::write(out, basic_string_view<Char>(
+                                 start, detail::to_unsigned(it - 1 - start)));
+
+    format_specs specs;
+    specs.align = align::right;
+
+    // Parse argument index, flags and width.
+    int arg_index = parse_header(it, end, specs);
+    if (arg_index == 0) on_error("argument not found");
+
+    // Parse precision.
+    if (it != end && *it == '.') {
+      ++it;
+      c = it != end ? *it : 0;
+      if ('0' <= c && c <= '9') {
+        detail::error_handler eh;
+        specs.precision = parse_nonnegative_int(it, end, eh);
+      } else if (c == '*') {
+        ++it;
+        specs.precision = static_cast<int>(
+            visit_format_arg(detail::printf_precision_handler(), get_arg()));
+      } else {
+        specs.precision = 0;
+      }
+    }
+
+    format_arg arg = get_arg(arg_index);
+    // For d, i, o, u, x, and X conversion specifiers, if a precision is
+    // specified, the '0' flag is ignored
+    if (specs.precision >= 0 && arg.is_integral())
+      specs.fill[0] =
+          ' ';  // Ignore '0' flag for non-numeric types or if '-' present.
+    if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
+      auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
+      auto str_end = str + specs.precision;
+      auto nul = std::find(str, str_end, Char());
+      arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
+          str,
+          detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
+    }
+    if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
+      specs.alt = false;
+    if (specs.fill[0] == '0') {
+      if (arg.is_arithmetic() && specs.align != align::left)
+        specs.align = align::numeric;
+      else
+        specs.fill[0] = ' ';  // Ignore '0' flag for non-numeric types or if '-'
+                              // flag is also present.
+    }
+
+    // Parse length and convert the argument to the required type.
+    c = it != end ? *it++ : 0;
+    char_type t = it != end ? *it : 0;
+    using detail::convert_arg;
+    switch (c) {
+    case 'h':
+      if (t == 'h') {
+        ++it;
+        t = it != end ? *it : 0;
+        convert_arg<signed char>(arg, t);
+      } else {
+        convert_arg<short>(arg, t);
+      }
+      break;
+    case 'l':
+      if (t == 'l') {
+        ++it;
+        t = it != end ? *it : 0;
+        convert_arg<long long>(arg, t);
+      } else {
+        convert_arg<long>(arg, t);
+      }
+      break;
+    case 'j':
+      convert_arg<intmax_t>(arg, t);
+      break;
+    case 'z':
+      convert_arg<size_t>(arg, t);
+      break;
+    case 't':
+      convert_arg<std::ptrdiff_t>(arg, t);
+      break;
+    case 'L':
+      // printf produces garbage when 'L' is omitted for long double, no
+      // need to do the same.
+      break;
+    default:
+      --it;
+      convert_arg<void>(arg, c);
+    }
+
+    // Parse type.
+    if (it == end) FMT_THROW(format_error("invalid format string"));
+    specs.type = static_cast<char>(*it++);
+    if (arg.is_integral()) {
+      // Normalize type.
+      switch (specs.type) {
+      case 'i':
+      case 'u':
+        specs.type = 'd';
+        break;
+      case 'c':
+        visit_format_arg(detail::char_converter<basic_printf_context>(arg),
+                         arg);
+        break;
+      }
+    }
+
+    start = it;
+
+    // Format argument.
+    out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
+  }
+  return detail::write(
+      out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
+}
+
+FMT_MODULE_EXPORT_BEGIN
+
+template <typename Char>
+using basic_printf_context_t =
+    basic_printf_context<detail::buffer_appender<Char>, Char>;
+
+using printf_context = basic_printf_context_t<char>;
+using wprintf_context = basic_printf_context_t<wchar_t>;
+
+using printf_args = basic_format_args<printf_context>;
+using wprintf_args = basic_format_args<wprintf_context>;
+
+/**
+  \rst
+  Constructs an `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::printf_args`.
+  \endrst
+ */
+template <typename... Args>
+inline format_arg_store<printf_context, Args...> make_printf_args(
+    const Args&... args) {
+  return {args...};
+}
+
+/**
+  \rst
+  Constructs an `~fmt::format_arg_store` object that contains references to
+  arguments and can be implicitly converted to `~fmt::wprintf_args`.
+  \endrst
+ */
+template <typename... Args>
+inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
+    const Args&... args) {
+  return {args...};
+}
+
+template <typename S, typename Char = char_t<S>>
+inline std::basic_string<Char> vsprintf(
+    const S& format,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(format), args);
+  return to_string(buffer);
+}
+
+/**
+  \rst
+  Formats arguments and returns the result as a string.
+
+  **Example**::
+
+    std::string message = fmt::sprintf("The answer is %d", 42);
+  \endrst
+*/
+template <typename S, typename... Args,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
+  using context = basic_printf_context_t<Char>;
+  return vsprintf(to_string_view(format), make_format_args<context>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+inline int vfprintf(
+    std::FILE* f, const S& format,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(format), args);
+  size_t size = buffer.size();
+  return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
+             ? -1
+             : static_cast<int>(size);
+}
+
+/**
+  \rst
+  Prints formatted data to the file *f*.
+
+  **Example**::
+
+    fmt::fprintf(stderr, "Don't %s!", "panic");
+  \endrst
+ */
+template <typename S, typename... Args,
+          typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
+inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
+  using context = basic_printf_context_t<Char>;
+  return vfprintf(f, to_string_view(format),
+                  make_format_args<context>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+inline int vprintf(
+    const S& format,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
+  return vfprintf(stdout, to_string_view(format), args);
+}
+
+/**
+  \rst
+  Prints formatted data to ``stdout``.
+
+  **Example**::
+
+    fmt::printf("Elapsed time: %.2f seconds", 1.23);
+  \endrst
+ */
+template <typename S, typename... Args,
+          FMT_ENABLE_IF(detail::is_string<S>::value)>
+inline int printf(const S& format_str, const Args&... args) {
+  using context = basic_printf_context_t<char_t<S>>;
+  return vprintf(to_string_view(format_str),
+                 make_format_args<context>(args...));
+}
+
+template <typename S, typename Char = char_t<S>>
+inline int vfprintf(
+    std::basic_ostream<Char>& os, const S& format,
+    basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
+  basic_memory_buffer<Char> buffer;
+  vprintf(buffer, to_string_view(format), args);
+  os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
+  return static_cast<int>(buffer.size());
+}
+
+/** Formats arguments and writes the output to the range. */
+template <typename ArgFormatter, typename Char,
+          typename Context =
+              basic_printf_context<typename ArgFormatter::iterator, Char>>
+typename ArgFormatter::iterator vprintf(
+    detail::buffer<Char>& out, basic_string_view<Char> format_str,
+    basic_format_args<type_identity_t<Context>> args) {
+  typename ArgFormatter::iterator iter(out);
+  Context(iter, format_str, args).template format<ArgFormatter>();
+  return iter;
+}
+
+/**
+  \rst
+  Prints formatted data to the stream *os*.
+
+  **Example**::
+
+    fmt::fprintf(cerr, "Don't %s!", "panic");
+  \endrst
+ */
+template <typename S, typename... Args, typename Char = char_t<S>>
+inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
+                   const Args&... args) {
+  using context = basic_printf_context_t<Char>;
+  return vfprintf(os, to_string_view(format_str),
+                  make_format_args<context>(args...));
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_PRINTF_H_
diff --git a/libs/fmt/include/fmt/ranges.h b/libs/fmt/include/fmt/ranges.h
new file mode 100644
index 0000000000000000000000000000000000000000..367d7a6ba340889542ad8c4ff01b6f479226f34a
--- /dev/null
+++ b/libs/fmt/include/fmt/ranges.h
@@ -0,0 +1,469 @@
+// Formatting library for C++ - experimental range support
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+//
+// Copyright (c) 2018 - present, Remotion (Igor Schulz)
+// All Rights Reserved
+// {fmt} support for ranges, containers and types tuple interface.
+
+#ifndef FMT_RANGES_H_
+#define FMT_RANGES_H_
+
+#include <initializer_list>
+#include <type_traits>
+
+#include "format.h"
+
+FMT_BEGIN_NAMESPACE
+
+template <typename Char> struct formatting_base {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+};
+
+template <typename Char, typename Enable = void>
+struct formatting_range : formatting_base<Char> {
+#ifdef FMT_DEPRECATED_BRACED_RANGES
+  Char prefix = '{';
+  Char postfix = '}';
+#else
+  Char prefix = '[';
+  Char postfix = ']';
+#endif
+};
+
+template <typename Char, typename Enable = void>
+struct formatting_tuple : formatting_base<Char> {
+  Char prefix = '(';
+  Char postfix = ')';
+};
+
+namespace detail {
+
+template <typename RangeT, typename OutputIterator>
+OutputIterator copy(const RangeT& range, OutputIterator out) {
+  for (auto it = range.begin(), end = range.end(); it != end; ++it)
+    *out++ = *it;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(const char* str, OutputIterator out) {
+  while (*str) *out++ = *str++;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(char ch, OutputIterator out) {
+  *out++ = ch;
+  return out;
+}
+
+template <typename OutputIterator>
+OutputIterator copy(wchar_t ch, OutputIterator out) {
+  *out++ = ch;
+  return out;
+}
+
+/// Return true value if T has std::string interface, like std::string_view.
+template <typename T> class is_std_string_like {
+  template <typename U>
+  static auto check(U* p)
+      -> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
+  template <typename> static void check(...);
+
+ public:
+  static FMT_CONSTEXPR_DECL const bool value =
+      is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
+};
+
+template <typename Char>
+struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
+
+template <typename... Ts> struct conditional_helper {};
+
+template <typename T, typename _ = void> struct is_range_ : std::false_type {};
+
+#if !FMT_MSC_VER || FMT_MSC_VER > 1800
+
+#  define FMT_DECLTYPE_RETURN(val)  \
+    ->decltype(val) { return val; } \
+    static_assert(                  \
+        true, "")  // This makes it so that a semicolon is required after the
+                   // macro, which helps clang-format handle the formatting.
+
+// C array overload
+template <typename T, std::size_t N>
+auto range_begin(const T (&arr)[N]) -> const T* {
+  return arr;
+}
+template <typename T, std::size_t N>
+auto range_end(const T (&arr)[N]) -> const T* {
+  return arr + N;
+}
+
+template <typename T, typename Enable = void>
+struct has_member_fn_begin_end_t : std::false_type {};
+
+template <typename T>
+struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
+                                           decltype(std::declval<T>().end())>>
+    : std::true_type {};
+
+// Member function overload
+template <typename T>
+auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
+template <typename T>
+auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
+
+// ADL overload. Only participates in overload resolution if member functions
+// are not found.
+template <typename T>
+auto range_begin(T&& rng)
+    -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
+                   decltype(begin(static_cast<T&&>(rng)))> {
+  return begin(static_cast<T&&>(rng));
+}
+template <typename T>
+auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
+                                       decltype(end(static_cast<T&&>(rng)))> {
+  return end(static_cast<T&&>(rng));
+}
+
+template <typename T, typename Enable = void>
+struct has_const_begin_end : std::false_type {};
+template <typename T, typename Enable = void>
+struct has_mutable_begin_end : std::false_type {};
+
+template <typename T>
+struct has_const_begin_end<
+    T, void_t<decltype(detail::range_begin(
+                  std::declval<const remove_cvref_t<T>&>())),
+              decltype(detail::range_begin(
+                  std::declval<const remove_cvref_t<T>&>()))>>
+    : std::true_type {};
+
+template <typename T>
+struct has_mutable_begin_end<
+    T, void_t<decltype(detail::range_begin(std::declval<T>())),
+              decltype(detail::range_begin(std::declval<T>())),
+              enable_if_t<std::is_copy_constructible<T>::value>>>
+    : std::true_type {};
+
+template <typename T>
+struct is_range_<T, void>
+    : std::integral_constant<bool, (has_const_begin_end<T>::value ||
+                                    has_mutable_begin_end<T>::value)> {};
+
+template <typename T, typename Enable = void> struct range_to_view;
+template <typename T>
+struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
+  struct view_t {
+    const T* m_range_ptr;
+
+    auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
+    auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
+  };
+  static auto view(const T& range) -> view_t { return {&range}; }
+};
+
+template <typename T>
+struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
+                                    has_mutable_begin_end<T>::value>> {
+  struct view_t {
+    T m_range_copy;
+
+    auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
+    auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
+  };
+  static auto view(const T& range) -> view_t { return {range}; }
+};
+#  undef FMT_DECLTYPE_RETURN
+#endif
+
+/// tuple_size and tuple_element check.
+template <typename T> class is_tuple_like_ {
+  template <typename U>
+  static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
+  template <typename> static void check(...);
+
+ public:
+  static FMT_CONSTEXPR_DECL const bool value =
+      !std::is_void<decltype(check<T>(nullptr))>::value;
+};
+
+// Check for integer_sequence
+#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
+template <typename T, T... N>
+using integer_sequence = std::integer_sequence<T, N...>;
+template <size_t... N> using index_sequence = std::index_sequence<N...>;
+template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
+#else
+template <typename T, T... N> struct integer_sequence {
+  using value_type = T;
+
+  static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
+};
+
+template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
+
+template <typename T, size_t N, T... Ns>
+struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
+template <typename T, T... Ns>
+struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
+
+template <size_t N>
+using make_index_sequence = make_integer_sequence<size_t, N>;
+#endif
+
+template <class Tuple, class F, size_t... Is>
+void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
+  using std::get;
+  // using free function get<I>(T) now.
+  const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
+  (void)_;  // blocks warnings
+}
+
+template <class T>
+FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
+    T const&) {
+  return {};
+}
+
+template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
+  const auto indexes = get_indexes(tup);
+  for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
+}
+
+template <typename Range>
+using value_type =
+    remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
+
+template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
+  *out++ = ',';
+  *out++ = ' ';
+  return out;
+}
+
+template <
+    typename Char, typename OutputIt, typename Arg,
+    FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg& v) {
+  *out++ = '"';
+  out = write<Char>(out, v);
+  *out++ = '"';
+  return out;
+}
+
+template <typename Char, typename OutputIt, typename Arg,
+          FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg v) {
+  *out++ = '\'';
+  *out++ = v;
+  *out++ = '\'';
+  return out;
+}
+
+template <
+    typename Char, typename OutputIt, typename Arg,
+    FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
+                  !std::is_same<Arg, Char>::value)>
+OutputIt write_range_entry(OutputIt out, const Arg& v) {
+  return write<Char>(out, v);
+}
+
+}  // namespace detail
+
+template <typename T> struct is_tuple_like {
+  static FMT_CONSTEXPR_DECL const bool value =
+      detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
+};
+
+template <typename TupleT, typename Char>
+struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
+ private:
+  // C++11 generic lambda for format()
+  template <typename FormatContext> struct format_each {
+    template <typename T> void operator()(const T& v) {
+      if (i > 0) out = detail::write_delimiter(out);
+      out = detail::write_range_entry<Char>(out, v);
+      ++i;
+    }
+    formatting_tuple<Char>& formatting;
+    size_t& i;
+    typename std::add_lvalue_reference<decltype(
+        std::declval<FormatContext>().out())>::type out;
+  };
+
+ public:
+  formatting_tuple<Char> formatting;
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return formatting.parse(ctx);
+  }
+
+  template <typename FormatContext = format_context>
+  auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
+    auto out = ctx.out();
+    size_t i = 0;
+
+    detail::copy(formatting.prefix, out);
+    detail::for_each(values, format_each<FormatContext>{formatting, i, out});
+    detail::copy(formatting.postfix, out);
+
+    return ctx.out();
+  }
+};
+
+template <typename T, typename Char> struct is_range {
+  static FMT_CONSTEXPR_DECL const bool value =
+      detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
+      !std::is_convertible<T, std::basic_string<Char>>::value &&
+      !std::is_constructible<detail::std_string_view<Char>, T>::value;
+};
+
+template <typename T, typename Char>
+struct formatter<
+    T, Char,
+    enable_if_t<
+        fmt::is_range<T, Char>::value
+// Workaround a bug in MSVC 2017 and earlier.
+#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
+        && (has_formatter<detail::value_type<T>, format_context>::value ||
+            detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
+#endif
+        >> {
+  formatting_range<Char> formatting;
+
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return formatting.parse(ctx);
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
+    auto out = detail::copy(formatting.prefix, ctx.out());
+    size_t i = 0;
+    auto view = detail::range_to_view<T>::view(values);
+    auto it = view.begin();
+    auto end = view.end();
+    for (; it != end; ++it) {
+      if (i > 0) out = detail::write_delimiter(out);
+      out = detail::write_range_entry<Char>(out, *it);
+      ++i;
+    }
+    return detail::copy(formatting.postfix, out);
+  }
+};
+
+template <typename Char, typename... T> struct tuple_arg_join : detail::view {
+  const std::tuple<T...>& tuple;
+  basic_string_view<Char> sep;
+
+  tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
+      : tuple(t), sep{s} {}
+};
+
+template <typename Char, typename... T>
+struct formatter<tuple_arg_join<Char, T...>, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
+    return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
+  }
+
+ private:
+  template <typename FormatContext, size_t... N>
+  typename FormatContext::iterator format(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
+      detail::index_sequence<N...>) {
+    return format_args(value, ctx, std::get<N>(value.tuple)...);
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format_args(
+      const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
+    // NOTE: for compilers that support C++17, this empty function instantiation
+    // can be replaced with a constexpr branch in the variadic overload.
+    return ctx.out();
+  }
+
+  template <typename FormatContext, typename Arg, typename... Args>
+  typename FormatContext::iterator format_args(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
+      const Arg& arg, const Args&... args) {
+    using base = formatter<typename std::decay<Arg>::type, Char>;
+    auto out = ctx.out();
+    out = base{}.format(arg, ctx);
+    if (sizeof...(Args) > 0) {
+      out = std::copy(value.sep.begin(), value.sep.end(), out);
+      ctx.advance_to(out);
+      return format_args(value, ctx, args...);
+    }
+    return out;
+  }
+};
+
+FMT_MODULE_EXPORT_BEGIN
+
+/**
+  \rst
+  Returns an object that formats `tuple` with elements separated by `sep`.
+
+  **Example**::
+
+    std::tuple<int, char> t = {1, 'a'};
+    fmt::print("{}", fmt::join(t, ", "));
+    // Output: "1, a"
+  \endrst
+ */
+template <typename... T>
+FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
+                                              string_view sep) {
+  return {tuple, sep};
+}
+
+template <typename... T>
+FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
+                                                 wstring_view sep) {
+  return {tuple, sep};
+}
+
+/**
+  \rst
+  Returns an object that formats `initializer_list` with elements separated by
+  `sep`.
+
+  **Example**::
+
+    fmt::print("{}", fmt::join({1, 2, 3}, ", "));
+    // Output: "1, 2, 3"
+  \endrst
+ */
+template <typename T>
+arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
+                                        string_view sep) {
+  return join(std::begin(list), std::end(list), sep);
+}
+
+template <typename T>
+arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
+                                           wstring_view sep) {
+  return join(std::begin(list), std::end(list), sep);
+}
+
+FMT_MODULE_EXPORT_END
+FMT_END_NAMESPACE
+
+#endif  // FMT_RANGES_H_
diff --git a/libs/fmt/src/fmt.cc b/libs/fmt/src/fmt.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f0dc572193cc769b2d32642d5dfa574fe689407a
--- /dev/null
+++ b/libs/fmt/src/fmt.cc
@@ -0,0 +1,87 @@
+module;
+// put all implementation-provided headers into the global module fragment
+// to prevent attachment to this module
+#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
+#  define _CRT_SECURE_NO_WARNINGS
+#endif
+#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
+#  define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <clocale>
+#include <cmath>
+#include <cstdarg>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <cwchar>
+
+#include <algorithm>
+#include <chrono>
+#include <exception>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <locale>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#if _MSC_VER
+#  include <intrin.h>
+#endif
+#if defined __APPLE__ || defined(__FreeBSD__)
+#  include <xlocale.h>
+#endif
+#if __has_include(<winapifamily.h>)
+#  include <winapifamily.h>
+#endif
+#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
+     defined(__linux__)) &&                            \
+    (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+#  include <fcntl.h>
+#  include <sys/stat.h>
+#  include <sys/types.h>
+#  ifndef _WIN32
+#    include <unistd.h>
+#  else
+#    include <io.h>
+#  endif
+#endif
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+export module fmt;
+
+#define FMT_MODULE_EXPORT export
+#define FMT_MODULE_EXPORT_BEGIN export {
+#define FMT_MODULE_EXPORT_END }
+
+// all library-provided declarations and definitions
+// must be in the module purview to be exported
+#include "fmt/format.h"
+#include "fmt/args.h"
+#include "fmt/color.h"
+#include "fmt/compile.h"
+#include "fmt/locale.h"
+#include "fmt/chrono.h"
+#include "fmt/printf.h"
+#include "fmt/os.h"
+
+module : private;
+
+#include "format.cc"
+#include "os.cc"
diff --git a/libs/fmt/src/format.cc b/libs/fmt/src/format.cc
new file mode 100644
index 0000000000000000000000000000000000000000..618aa07d0bb6ca2a9f7fcdc5b210b417e4b8953e
--- /dev/null
+++ b/libs/fmt/src/format.cc
@@ -0,0 +1,97 @@
+// Formatting library for C++
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/format-inl.h"
+
+FMT_BEGIN_NAMESPACE
+namespace detail {
+
+template <typename T>
+int format_float(char* buf, std::size_t size, const char* format, int precision,
+                 T value) {
+#ifdef FMT_FUZZ
+  if (precision > 100000)
+    throw std::runtime_error(
+        "fuzz mode - avoid large allocation inside snprintf");
+#endif
+  // Suppress the warning about nonliteral format string.
+  int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
+  return precision < 0 ? snprintf_ptr(buf, size, format, value)
+                       : snprintf_ptr(buf, size, format, precision, value);
+}
+
+template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
+    FMT_NOEXCEPT;
+template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
+    FMT_NOEXCEPT;
+
+// DEPRECATED! This function exists for ABI compatibility.
+template <typename Char>
+typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
+                              Char>::iterator
+vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
+           basic_format_args<basic_format_context<
+               std::back_insert_iterator<buffer<type_identity_t<Char>>>,
+               type_identity_t<Char>>>
+               args) {
+  using iterator = std::back_insert_iterator<buffer<char>>;
+  using context = basic_format_context<
+      std::back_insert_iterator<buffer<type_identity_t<Char>>>,
+      type_identity_t<Char>>;
+  auto out = iterator(buf);
+  format_handler<iterator, Char, context> h(out, format_str, args, {});
+  parse_format_string<false>(format_str, h);
+  return out;
+}
+template basic_format_context<std::back_insert_iterator<buffer<char>>,
+                              char>::iterator
+vformat_to(buffer<char>&, string_view,
+           basic_format_args<basic_format_context<
+               std::back_insert_iterator<buffer<type_identity_t<char>>>,
+               type_identity_t<char>>>);
+}  // namespace detail
+
+// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
+int (*instantiate_format_float)(double, int, detail::float_specs,
+                                detail::buffer<char>&) = detail::format_float;
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
+template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
+#endif
+
+// Explicit instantiations for char.
+
+template FMT_API std::string detail::grouping_impl<char>(locale_ref);
+template FMT_API char detail::thousands_sep_impl(locale_ref);
+template FMT_API char detail::decimal_point_impl(locale_ref);
+
+template FMT_API void detail::buffer<char>::append(const char*, const char*);
+
+template FMT_API void detail::vformat_to(
+    detail::buffer<char>&, string_view,
+    basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
+
+template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
+                                            detail::buffer<char>&);
+template FMT_API int detail::snprintf_float(long double, int,
+                                            detail::float_specs,
+                                            detail::buffer<char>&);
+template FMT_API int detail::format_float(double, int, detail::float_specs,
+                                          detail::buffer<char>&);
+template FMT_API int detail::format_float(long double, int, detail::float_specs,
+                                          detail::buffer<char>&);
+
+// Explicit instantiations for wchar_t.
+
+template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
+template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
+template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
+
+template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
+                                                      const wchar_t*);
+FMT_END_NAMESPACE
diff --git a/libs/fmt/src/os.cc b/libs/fmt/src/os.cc
new file mode 100644
index 0000000000000000000000000000000000000000..42a88b32291f0dc4911705d952b097cc3a467190
--- /dev/null
+++ b/libs/fmt/src/os.cc
@@ -0,0 +1,321 @@
+// Formatting library for C++ - optional OS-specific functionality
+//
+// Copyright (c) 2012 - 2016, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+// Disable bogus MSVC warnings.
+#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
+#  define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "fmt/os.h"
+
+#include <climits>
+
+#if FMT_USE_FCNTL
+#  include <sys/stat.h>
+#  include <sys/types.h>
+
+#  ifndef _WIN32
+#    include <unistd.h>
+#  else
+#    ifndef WIN32_LEAN_AND_MEAN
+#      define WIN32_LEAN_AND_MEAN
+#    endif
+#    include <io.h>
+
+#    define O_CREAT _O_CREAT
+#    define O_TRUNC _O_TRUNC
+
+#    ifndef S_IRUSR
+#      define S_IRUSR _S_IREAD
+#    endif
+
+#    ifndef S_IWUSR
+#      define S_IWUSR _S_IWRITE
+#    endif
+
+#    ifdef __MINGW32__
+#      define _SH_DENYNO 0x40
+#    endif
+#  endif  // _WIN32
+#endif    // FMT_USE_FCNTL
+
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+#ifdef fileno
+#  undef fileno
+#endif
+
+namespace {
+#ifdef _WIN32
+// Return type of read and write functions.
+using rwresult = int;
+
+// On Windows the count argument to read and write is unsigned, so convert
+// it from size_t preventing integer overflow.
+inline unsigned convert_rwcount(std::size_t count) {
+  return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
+}
+#elif FMT_USE_FCNTL
+// Return type of read and write functions.
+using rwresult = ssize_t;
+
+inline std::size_t convert_rwcount(std::size_t count) { return count; }
+#endif
+}  // namespace
+
+FMT_BEGIN_NAMESPACE
+
+#ifdef _WIN32
+detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
+  if (int error_code = convert(s)) {
+    FMT_THROW(windows_error(error_code,
+                            "cannot convert string from UTF-16 to UTF-8"));
+  }
+}
+
+int detail::utf16_to_utf8::convert(wstring_view s) {
+  if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
+  int s_size = static_cast<int>(s.size());
+  if (s_size == 0) {
+    // WideCharToMultiByte does not support zero length, handle separately.
+    buffer_.resize(1);
+    buffer_[0] = 0;
+    return 0;
+  }
+
+  int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
+                                   nullptr, nullptr);
+  if (length == 0) return GetLastError();
+  buffer_.resize(length + 1);
+  length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
+                               length, nullptr, nullptr);
+  if (length == 0) return GetLastError();
+  buffer_[length] = 0;
+  return 0;
+}
+
+void windows_error::init(int err_code, string_view format_str,
+                         format_args args) {
+  error_code_ = err_code;
+  memory_buffer buffer;
+  detail::format_windows_error(buffer, err_code, vformat(format_str, args));
+  std::runtime_error& base = *this;
+  base = std::runtime_error(to_string(buffer));
+}
+
+void detail::format_windows_error(detail::buffer<char>& out, int error_code,
+                                  string_view message) FMT_NOEXCEPT {
+  FMT_TRY {
+    wmemory_buffer buf;
+    buf.resize(inline_buffer_size);
+    for (;;) {
+      wchar_t* system_message = &buf[0];
+      int result = FormatMessageW(
+          FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
+          error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message,
+          static_cast<uint32_t>(buf.size()), nullptr);
+      if (result != 0) {
+        utf16_to_utf8 utf8_message;
+        if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
+          format_to(buffer_appender<char>(out), "{}: {}", message,
+                    utf8_message);
+          return;
+        }
+        break;
+      }
+      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+        break;  // Can't get error message, report error code instead.
+      buf.resize(buf.size() * 2);
+    }
+  }
+  FMT_CATCH(...) {}
+  format_error_code(out, error_code, message);
+}
+
+void report_windows_error(int error_code,
+                          fmt::string_view message) FMT_NOEXCEPT {
+  report_error(detail::format_windows_error, error_code, message);
+}
+#endif  // _WIN32
+
+buffered_file::~buffered_file() FMT_NOEXCEPT {
+  if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
+    report_system_error(errno, "cannot close file");
+}
+
+buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
+  FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
+                nullptr);
+  if (!file_)
+    FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
+}
+
+void buffered_file::close() {
+  if (!file_) return;
+  int result = FMT_SYSTEM(fclose(file_));
+  file_ = nullptr;
+  if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
+}
+
+// A macro used to prevent expansion of fileno on broken versions of MinGW.
+#define FMT_ARGS
+
+int buffered_file::fileno() const {
+  int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
+  if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
+  return fd;
+}
+
+#if FMT_USE_FCNTL
+file::file(cstring_view path, int oflag) {
+  int mode = S_IRUSR | S_IWUSR;
+#  if defined(_WIN32) && !defined(__MINGW32__)
+  fd_ = -1;
+  FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
+#  else
+  FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
+#  endif
+  if (fd_ == -1)
+    FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
+}
+
+file::~file() FMT_NOEXCEPT {
+  // Don't retry close in case of EINTR!
+  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
+  if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
+    report_system_error(errno, "cannot close file");
+}
+
+void file::close() {
+  if (fd_ == -1) return;
+  // Don't retry close in case of EINTR!
+  // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
+  int result = FMT_POSIX_CALL(close(fd_));
+  fd_ = -1;
+  if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
+}
+
+long long file::size() const {
+#  ifdef _WIN32
+  // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
+  // is less than 0x0500 as is the case with some default MinGW builds.
+  // Both functions support large file sizes.
+  DWORD size_upper = 0;
+  HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
+  DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
+  if (size_lower == INVALID_FILE_SIZE) {
+    DWORD error = GetLastError();
+    if (error != NO_ERROR)
+      FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
+  }
+  unsigned long long long_size = size_upper;
+  return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
+#  else
+  using Stat = struct stat;
+  Stat file_stat = Stat();
+  if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
+    FMT_THROW(system_error(errno, "cannot get file attributes"));
+  static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
+                "return type of file::size is not large enough");
+  return file_stat.st_size;
+#  endif
+}
+
+std::size_t file::read(void* buffer, std::size_t count) {
+  rwresult result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
+  if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
+  return detail::to_unsigned(result);
+}
+
+std::size_t file::write(const void* buffer, std::size_t count) {
+  rwresult result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
+  if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
+  return detail::to_unsigned(result);
+}
+
+file file::dup(int fd) {
+  // Don't retry as dup doesn't return EINTR.
+  // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
+  int new_fd = FMT_POSIX_CALL(dup(fd));
+  if (new_fd == -1)
+    FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
+  return file(new_fd);
+}
+
+void file::dup2(int fd) {
+  int result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
+  if (result == -1) {
+    FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
+                           fd_, fd));
+  }
+}
+
+void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT {
+  int result = 0;
+  FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
+  if (result == -1) ec = error_code(errno);
+}
+
+void file::pipe(file& read_end, file& write_end) {
+  // Close the descriptors first to make sure that assignments don't throw
+  // and there are no leaks.
+  read_end.close();
+  write_end.close();
+  int fds[2] = {};
+#  ifdef _WIN32
+  // Make the default pipe capacity same as on Linux 2.6.11+.
+  enum { DEFAULT_CAPACITY = 65536 };
+  int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
+#  else
+  // Don't retry as the pipe function doesn't return EINTR.
+  // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
+  int result = FMT_POSIX_CALL(pipe(fds));
+#  endif
+  if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
+  // The following assignments don't throw because read_fd and write_fd
+  // are closed.
+  read_end = file(fds[0]);
+  write_end = file(fds[1]);
+}
+
+buffered_file file::fdopen(const char* mode) {
+// Don't retry as fdopen doesn't return EINTR.
+#  if defined(__MINGW32__) && defined(_POSIX_)
+  FILE* f = ::fdopen(fd_, mode);
+#  else
+  FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
+#  endif
+  if (!f)
+    FMT_THROW(
+        system_error(errno, "cannot associate stream with file descriptor"));
+  buffered_file bf(f);
+  fd_ = -1;
+  return bf;
+}
+
+long getpagesize() {
+#  ifdef _WIN32
+  SYSTEM_INFO si;
+  GetSystemInfo(&si);
+  return si.dwPageSize;
+#  else
+  long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
+  if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
+  return size;
+#  endif
+}
+
+FMT_API void ostream::grow(size_t) {
+  if (this->size() == this->capacity()) flush();
+}
+#endif  // FMT_USE_FCNTL
+FMT_END_NAMESPACE
diff --git a/libs/fmt/support/Android.mk b/libs/fmt/support/Android.mk
new file mode 100644
index 0000000000000000000000000000000000000000..84a3e32f07181cfc2f4d66875cd919c2f844ee71
--- /dev/null
+++ b/libs/fmt/support/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := fmt_static
+LOCAL_MODULE_FILENAME := libfmt
+
+LOCAL_SRC_FILES := ../src/format.cc
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
+
+LOCAL_CFLAGS += -std=c++11 -fexceptions
+
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/libs/fmt/support/AndroidManifest.xml b/libs/fmt/support/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b5281fee181e19fcf6a3b19dd177de8cb08b36d7
--- /dev/null
+++ b/libs/fmt/support/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="net.fmtlib" />
diff --git a/libs/fmt/support/C++.sublime-syntax b/libs/fmt/support/C++.sublime-syntax
new file mode 100644
index 0000000000000000000000000000000000000000..9dfb5cbe4324c81808fd02d0c7e371265a5141cf
--- /dev/null
+++ b/libs/fmt/support/C++.sublime-syntax
@@ -0,0 +1,2061 @@
+%YAML 1.2
+---
+# http://www.sublimetext.com/docs/3/syntax.html
+name: C++ (fmt)
+comment: I don't think anyone uses .hp. .cp tends to be paired with .h. (I could be wrong. :) -- chris
+file_extensions:
+  - cpp
+  - cc
+  - cp
+  - cxx
+  - c++
+  - C
+  - h
+  - hh
+  - hpp
+  - hxx
+  - h++
+  - inl
+  - ipp
+first_line_match: '-\*- C\+\+ -\*-'
+scope: source.c++
+variables:
+  identifier: \b[[:alpha:]_][[:alnum:]_]*\b # upper and lowercase
+  macro_identifier: \b[[:upper:]_][[:upper:][:digit:]_]{2,}\b # only uppercase, at least 3 chars
+  path_lookahead: '(?:::\s*)?(?:{{identifier}}\s*::\s*)*(?:template\s+)?{{identifier}}'
+  operator_method_name: '\boperator\s*(?:[-+*/%^&|~!=<>]|[-+*/%^&|=!<>]=|<<=?|>>=?|&&|\|\||\+\+|--|,|->\*?|\(\)|\[\]|""\s*{{identifier}})'
+  casts: 'const_cast|dynamic_cast|reinterpret_cast|static_cast'
+  operator_keywords: 'and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq|noexcept'
+  control_keywords: 'break|case|catch|continue|default|do|else|for|goto|if|_Pragma|return|switch|throw|try|while'
+  memory_operators: 'new|delete'
+  basic_types: 'asm|__asm__|auto|bool|_Bool|char|_Complex|double|float|_Imaginary|int|long|short|signed|unsigned|void'
+  before_tag: 'struct|union|enum\s+class|enum\s+struct|enum|class'
+  declspec: '__declspec\(\s*\w+(?:\([^)]+\))?\s*\)'
+  storage_classes: 'static|export|extern|friend|explicit|virtual|register|thread_local'
+  type_qualifier: 'const|constexpr|mutable|typename|volatile'
+  compiler_directive: 'inline|restrict|__restrict__|__restrict'
+  visibility_modifiers: 'private|protected|public'
+  other_keywords: 'typedef|nullptr|{{visibility_modifiers}}|static_assert|sizeof|using|typeid|alignof|alignas|namespace|template'
+  modifiers: '{{storage_classes}}|{{type_qualifier}}|{{compiler_directive}}'
+  non_angle_brackets: '(?=<<|<=)'
+
+  regular: '[^(){}&;*^%=<>-]*'
+  paren_open: (?:\(
+  paren_close: '\))?'
+  generic_open: (?:<
+  generic_close: '>)?'
+  balance_parentheses: '{{regular}}{{paren_open}}{{regular}}{{paren_close}}{{regular}}'
+  generic_lookahead: <{{regular}}{{generic_open}}{{regular}}{{generic_open}}{{regular}}{{generic_close}}\s*{{generic_close}}{{balance_parentheses}}>
+
+  data_structures_forward_decl_lookahead: '(\s+{{macro_identifier}})*\s*(:\s*({{path_lookahead}}|{{visibility_modifiers}}|,|\s|<[^;]*>)+)?;'
+  non_func_keywords: 'if|for|switch|while|decltype|sizeof|__declspec|__attribute__|typeid|alignof|alignas|static_assert'
+
+  format_spec: |-
+    (?x:
+      (?:.? [<>=^])?     # fill align
+      [ +-]?             # sign
+      \#?                # alternate form
+      # technically, octal and hexadecimal integers are also supported as 'width', but rarely used
+      \d*                # width
+      ,?                 # thousands separator
+      (?:\.\d+)?         # precision
+      [bcdeEfFgGnosxX%]? # type
+    )
+
+contexts:
+  main:
+    - include: preprocessor-global
+    - include: global
+
+  #############################################################################
+  # Reusable contexts
+  #
+  # The follow contexts are currently constructed to be reused in the
+  # Objetive-C++ syntax. They are specifically constructed to not push into
+  # sub-contexts, which ensures that Objective-C++ code isn't accidentally
+  # lexed as plain C++.
+  #
+  # The "unique-*" contexts are additions that C++ makes over C, and thus can
+  # be directly reused in Objective-C++ along with contexts from Objective-C
+  # and C.
+  #############################################################################
+
+  unique-late-expressions:
+    # This is highlighted after all of the other control keywords
+    # to allow operator overloading to be lexed properly
+    - match: \boperator\b
+      scope: keyword.control.c++
+
+  unique-modifiers:
+    - match: \b({{modifiers}})\b
+      scope: storage.modifier.c++
+
+  unique-variables:
+    - match: \bthis\b
+      scope: variable.language.c++
+    # common C++ instance var naming idiom -- fMemberName
+    - match: '\b(f|m)[[:upper:]]\w*\b'
+      scope: variable.other.readwrite.member.c++
+    # common C++ instance var naming idiom -- m_member_name
+    - match: '\bm_[[:alnum:]_]+\b'
+      scope: variable.other.readwrite.member.c++
+
+  unique-constants:
+    - match: \bnullptr\b
+      scope: constant.language.c++
+
+  unique-keywords:
+    - match: \busing\b
+      scope: keyword.control.c++
+    - match: \bbreak\b
+      scope: keyword.control.flow.break.c++
+    - match: \bcontinue\b
+      scope: keyword.control.flow.continue.c++
+    - match: \bgoto\b
+      scope: keyword.control.flow.goto.c++
+    - match: \breturn\b
+      scope: keyword.control.flow.return.c++
+    - match: \bthrow\b
+      scope: keyword.control.flow.throw.c++
+    - match: \b({{control_keywords}})\b
+      scope: keyword.control.c++
+    - match: '\bdelete\b(\s*\[\])?|\bnew\b(?!])'
+      scope: keyword.control.c++
+    - match: \b({{operator_keywords}})\b
+      scope: keyword.operator.word.c++
+
+  unique-types:
+    - match: \b(char16_t|char32_t|wchar_t|nullptr_t)\b
+      scope: storage.type.c++
+    - match: \bclass\b
+      scope: storage.type.c++
+
+  unique-strings:
+    - match: '((?:L|u8|u|U)?R)("([^\(\)\\ ]{0,16})\()'
+      captures:
+        1: storage.type.string.c++
+        2: punctuation.definition.string.begin.c++
+      push:
+        - meta_scope: string.quoted.double.c++
+        - match: '\)\3"'
+          scope: punctuation.definition.string.end.c++
+          pop: true
+        - match: '\{\{|\}\}'
+          scope: constant.character.escape.c++
+        - include: formatting-syntax
+
+  unique-numbers:
+    - match: |-
+        (?x)
+        (?:
+        # floats
+          (?:
+          (?:\b\d(?:[\d']*\d)?\.\d(?:[\d']*\d)?|\B\.\d(?:[\d']*\d)?)(?:[Ee][+-]?\d(?:[\d']*\d)?)?(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b
+          |
+          (?:\b\d(?:[\d']*\d)?\.)(?:\B|(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))\b|(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b)
+          |
+          \b\d(?:[\d']*\d)?(?:[Ee][+-]?\d(?:[\d']*\d)?)(?:[fFlL]|(?:i[fl]?|h|min|[mun]?s|_\w*))?\b
+          )
+        |
+        # ints
+          \b(?:
+          (?:
+          # dec
+          [1-9](?:[\d']*\d)?
+          |
+          # oct
+          0(?:[0-7']*[0-7])?
+          |
+          # hex
+          0[Xx][\da-fA-F](?:[\da-fA-F']*[\da-fA-F])?
+          |
+          # bin
+          0[Bb][01](?:[01']*[01])?
+          )
+          # int suffixes
+          (?:(?:l{1,2}|L{1,2})[uU]?|[uU](?:l{0,2}|L{0,2})|(?:i[fl]?|h|min|[mun]?s|_\w*))?)\b
+        )
+        (?!\.) # Number must not be followed by a decimal point
+      scope: constant.numeric.c++
+
+  identifiers:
+    - match: '{{identifier}}\s*(::)\s*'
+      captures:
+        1: punctuation.accessor.c++
+    - match: '(?:(::)\s*)?{{identifier}}'
+      captures:
+        1: punctuation.accessor.c++
+
+  function-specifiers:
+    - match: \b(const|final|noexcept|override)\b
+      scope: storage.modifier.c++
+
+  #############################################################################
+  # The following are C++-specific contexts that should not be reused. This is
+  # because they push into subcontexts and use variables that are C++-specific.
+  #############################################################################
+
+  ## Common context layout
+
+  global:
+    - match: '(?=\btemplate\b)'
+      push:
+        - include: template
+        - match: (?=\S)
+          set: global-modifier
+    - include: namespace
+    - include: keywords-angle-brackets
+    - match: '(?={{path_lookahead}}\s*<)'
+      push: global-modifier
+    # Take care of comments just before a function definition.
+    - match: /\*
+      scope: punctuation.definition.comment.c
+      push:
+        - - match: \s*(?=\w)
+            set: global-modifier
+          - match: ""
+            pop: true
+        - - meta_scope: comment.block.c
+          - match: \*/
+            scope: punctuation.definition.comment.c
+            pop: true
+    - include: early-expressions
+    - match: ^\s*\b(extern)(?=\s+"C(\+\+)?")
+      scope: storage.modifier.c++
+      push:
+        - include: comments
+        - include: strings
+        - match: '\{'
+          scope: punctuation.section.block.begin.c++
+          set:
+            - meta_scope: meta.extern-c.c++
+            - match: '^\s*(#\s*ifdef)\s*__cplusplus\s*'
+              scope: meta.preprocessor.c++
+              captures:
+                1: keyword.control.import.c++
+              set:
+                - match: '\}'
+                  scope: punctuation.section.block.end.c++
+                  pop: true
+                - include: preprocessor-global
+                - include: global
+            - match: '\}'
+              scope: punctuation.section.block.end.c++
+              pop: true
+            - include: preprocessor-global
+            - include: global
+        - match: (?=\S)
+          set: global-modifier
+    - match: ^\s*(?=\w)
+      push: global-modifier
+    - include: late-expressions
+
+  statements:
+    - include: preprocessor-statements
+    - include: scope:source.c#label
+    - include: expressions
+
+  expressions:
+    - include: early-expressions
+    - include: late-expressions
+
+  early-expressions:
+    - include: early-expressions-before-generic-type
+    - include: generic-type
+    - include: early-expressions-after-generic-type
+
+  early-expressions-before-generic-type:
+    - include: preprocessor-expressions
+    - include: comments
+    - include: case-default
+    - include: typedef
+    - include: keywords-angle-brackets
+    - include: keywords-parens
+    - include: keywords
+    - include: numbers
+    # Prevent a '<' from getting scoped as the start of another template
+    # parameter list, if in reality a less-than-or-equals sign is meant.
+    - match: <=
+      scope: keyword.operator.comparison.c
+
+  early-expressions-after-generic-type:
+    - include: members-arrow
+    - include: operators
+    - include: members-dot
+    - include: strings
+    - include: parens
+    - include: brackets
+    - include: block
+    - include: variables
+    - include: constants
+    - match: ','
+      scope: punctuation.separator.c++
+    - match: '\)|\}'
+      scope: invalid.illegal.stray-bracket-end.c++
+
+  expressions-minus-generic-type:
+    - include: early-expressions-before-generic-type
+    - include: angle-brackets
+    - include: early-expressions-after-generic-type
+    - include: late-expressions
+
+  expressions-minus-generic-type-function-call:
+    - include: early-expressions-before-generic-type
+    - include: angle-brackets
+    - include: early-expressions-after-generic-type
+    - include: late-expressions-before-function-call
+    - include: identifiers
+    - match: ';'
+      scope: punctuation.terminator.c++
+
+  late-expressions:
+    - include: late-expressions-before-function-call
+    - include: function-call
+    - include: identifiers
+    - match: ';'
+      scope: punctuation.terminator.c++
+
+  late-expressions-before-function-call:
+    - include: unique-late-expressions
+    - include: modifiers-parens
+    - include: modifiers
+    - include: types
+
+  expressions-minus-function-call:
+    - include: early-expressions
+    - include: late-expressions-before-function-call
+    - include: identifiers
+    - match: ';'
+      scope: punctuation.terminator.c++
+
+  comments:
+    - include: scope:source.c#comments
+
+  operators:
+    - include: scope:source.c#operators
+
+  modifiers:
+    - include: unique-modifiers
+    - include: scope:source.c#modifiers
+
+  variables:
+    - include: unique-variables
+    - include: scope:source.c#variables
+
+  constants:
+    - include: unique-constants
+    - include: scope:source.c#constants
+
+  keywords:
+    - include: unique-keywords
+    - include: scope:source.c#keywords
+
+  types:
+    - include: unique-types
+    - include: types-parens
+    - include: scope:source.c#types
+
+  strings:
+    - include: unique-strings
+    - match: '(L|u8|u|U)?(")'
+      captures:
+        1: storage.type.string.c++
+        2: punctuation.definition.string.begin.c++
+      push:
+        - meta_scope: string.quoted.double.c++
+        - match: '"'
+          scope: punctuation.definition.string.end.c++
+          pop: true
+        - include: scope:source.c#string_escaped_char
+        - match: |-
+            (?x)%
+              (\d+\$)?                                      # field (argument #)
+              [#0\- +']*                                    # flags
+              [,;:_]?                                       # separator character (AltiVec)
+              ((-?\d+)|\*(-?\d+\$)?)?                       # minimum field width
+              (\.((-?\d+)|\*(-?\d+\$)?)?)?                  # precision
+              (hh|h|ll|l|j|t|z|q|L|vh|vl|v|hv|hl)?          # length modifier
+              (\[[^\]]+\]|[am]s|[diouxXDOUeEfFgGaACcSspn%]) # conversion type
+          scope: constant.other.placeholder.c++
+        - match: '\{\{|\}\}'
+          scope: constant.character.escape.c++
+        - include: formatting-syntax
+    - include: scope:source.c#strings
+
+  formatting-syntax:
+    # https://docs.python.org/3.6/library/string.html#formatstrings
+    - match: |- # simple form
+        (?x)
+        (\{)
+          (?: [\w.\[\]]+)?             # field_name
+          (   ! [ars])?                # conversion
+          (   : (?:{{format_spec}}|    # format_spec OR
+                   [^}%]*%.[^}]*)      # any format-like string
+          )?
+        (\})
+      scope: constant.other.placeholder.c++
+      captures:
+        1: punctuation.definition.placeholder.begin.c++
+        2: storage.modifier.c++onversion.c++
+        3: constant.other.format-spec.c++
+        4: punctuation.definition.placeholder.end.c++
+    - match: \{(?=[^\}"']+\{[^"']*\}) # complex (nested) form
+      scope: punctuation.definition.placeholder.begin.c++
+      push:
+        - meta_scope: constant.other.placeholder.c++
+        - match: \}
+          scope: punctuation.definition.placeholder.end.c++
+          pop: true
+        - match: '[\w.\[\]]+'
+        - match: '![ars]'
+          scope: storage.modifier.conversion.c++
+        - match: ':'
+          push:
+            - meta_scope: meta.format-spec.c++ constant.other.format-spec.c++
+            - match: (?=\})
+              pop: true
+            - include: formatting-syntax
+
+  numbers:
+    - include: unique-numbers
+    - include: scope:source.c#numbers
+
+  ## C++-specific contexts
+
+  case-default:
+    - match: '\b(default|case)\b'
+      scope: keyword.control.c++
+      push:
+        - match: (?=[);,])
+          pop: true
+        - match: ':'
+          scope: punctuation.separator.c++
+          pop: true
+        - include: expressions
+
+  modifiers-parens:
+    - match: '\b(alignas)\b\s*(\()'
+      captures:
+        1: storage.modifier.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+    - match: \b(__attribute__)\s*(\(\()
+      captures:
+        1: storage.modifier.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push :
+        - meta_scope: meta.attribute.c++
+        - meta_content_scope: meta.group.c++
+        - include: parens
+        - include: strings
+        - match: \)\)
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+    - match: \b(__declspec)(\()
+      captures:
+        1: storage.modifier.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - match: '\b(align|allocate|code_seg|deprecated|property|uuid)\b\s*(\()'
+          captures:
+            1: storage.modifier.c++
+            2: meta.group.c++ punctuation.section.group.begin.c++
+          push:
+            - meta_content_scope: meta.group.c++
+            - match: '\)'
+              scope: meta.group.c++ punctuation.section.group.end.c++
+              pop: true
+            - include: numbers
+            - include: strings
+            - match: \b(get|put)\b
+              scope: variable.parameter.c++
+            - match: ','
+              scope: punctuation.separator.c++
+            - match: '='
+              scope: keyword.operator.assignment.c++
+        - match: '\b(appdomain|deprecated|dllimport|dllexport|jintrinsic|naked|noalias|noinline|noreturn|nothrow|novtable|process|restrict|safebuffers|selectany|thread)\b'
+          scope: constant.other.c++
+
+  types-parens:
+    - match: '\b(decltype)\b\s*(\()'
+      captures:
+        1: storage.type.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+
+  keywords-angle-brackets:
+    - match: \b({{casts}})\b\s*
+      scope: keyword.operator.word.cast.c++
+      push:
+        - match: '>'
+          scope: punctuation.section.generic.end.c++
+          pop: true
+        - match: '<'
+          scope: punctuation.section.generic.begin.c++
+          push:
+            - match: '(?=>)'
+              pop: true
+            - include: expressions-minus-generic-type-function-call
+
+  keywords-parens:
+    - match: '\b(alignof|typeid|static_assert|sizeof)\b\s*(\()'
+      captures:
+        1: keyword.operator.word.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+
+  namespace:
+    - match: '\b(using)\s+(namespace)\s+(?={{path_lookahead}})'
+      captures:
+        1: keyword.control.c++
+        2: keyword.control.c++
+      push:
+        - include: identifiers
+        - match: ''
+          pop: true
+    - match: '\b(namespace)\s+(?=({{path_lookahead}})?(?!\s*[;,]))'
+      scope: meta.namespace.c++
+      captures:
+        1: keyword.control.c++
+      push:
+        - meta_content_scope: meta.namespace.c++ entity.name.namespace.c++
+        - include: identifiers
+        - match: ''
+          set:
+            - meta_scope: meta.namespace.c++
+            - include: comments
+            - match: '='
+              scope: keyword.operator.alias.c++
+            - match: '(?=;)'
+              pop: true
+            - match: '\}'
+              scope: meta.block.c++ punctuation.section.block.end.c++
+              pop: true
+            - match: '\{'
+              scope: punctuation.section.block.begin.c++
+              push:
+                - meta_scope: meta.block.c++
+                - match: '(?=\})'
+                  pop: true
+                - include: preprocessor-global
+                - include: global
+            - include: expressions
+
+  template-common:
+    # Exit the template scope if we hit some basic invalid characters. This
+    # helps when a user is in the middle of typing their template types and
+    # prevents re-highlighting the whole file until the next > is found.
+    - match: (?=[{};])
+      pop: true
+    - include: expressions
+
+  template:
+    - match: \btemplate\b
+      scope: storage.type.template.c++
+      push:
+        - meta_scope: meta.template.c++
+        # Explicitly include comments here at the top, in order to NOT match the
+        # \S lookahead in the case of comments.
+        - include: comments
+        - match: <
+          scope: punctuation.section.generic.begin.c++
+          set:
+            - meta_content_scope: meta.template.c++
+            - match: '>'
+              scope: meta.template.c++ punctuation.section.generic.end.c++
+              pop: true
+            - match: \.{3}
+              scope: keyword.operator.variadic.c++
+            - match: \b(typename|{{before_tag}})\b
+              scope: storage.type.c++
+            - include: template # include template here for nested templates
+            - include: template-common
+        - match: (?=\S)
+          set:
+            - meta_content_scope: meta.template.c++
+            - match: \b({{before_tag}})\b
+              scope: storage.type.c++
+            - include: template-common
+
+  generic-type:
+    - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}}\s*\()'
+      push:
+        - meta_scope: meta.function-call.c++
+        - match: \btemplate\b
+          scope: storage.type.template.c++
+        - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*'
+          captures:
+            1: punctuation.accessor.double-colon.c++
+            2: punctuation.accessor.double-colon.c++
+        - match: (?:(::)\s*)?({{identifier}})\s*(<)
+          captures:
+            1: punctuation.accessor.double-colon.c++
+            2: variable.function.c++
+            3: punctuation.section.generic.begin.c++
+          push:
+            - match: '>'
+              scope: punctuation.section.generic.end.c++
+              pop: true
+            - include: expressions-minus-generic-type-function-call
+        - match: (?:(::)\s*)?({{identifier}})\s*(\()
+          captures:
+            1: punctuation.accessor.double-colon.c++
+            2: variable.function.c++
+            3: punctuation.section.group.begin.c++
+          set:
+            - meta_scope: meta.function-call.c++
+            - meta_content_scope: meta.group.c++
+            - match: '\)'
+              scope: meta.group.c++ punctuation.section.group.end.c++
+              pop: true
+            - include: expressions
+        - include: angle-brackets
+        - match: '\('
+          scope: meta.group.c++ punctuation.section.group.begin.c++
+          set:
+            - meta_scope: meta.function-call.c++
+            - meta_content_scope: meta.group.c++
+            - match: '\)'
+              scope: meta.group.c++ punctuation.section.group.end.c++
+              pop: true
+            - include: expressions
+    - match: '(?=(?!template){{path_lookahead}}\s*{{generic_lookahead}})'
+      push:
+        - include: identifiers
+        - match: '<'
+          scope: punctuation.section.generic.begin.c++
+          set:
+            - match: '>'
+              scope: punctuation.section.generic.end.c++
+              pop: true
+            - include: expressions-minus-generic-type-function-call
+
+  angle-brackets:
+    - match: '<(?!<)'
+      scope: punctuation.section.generic.begin.c++
+      push:
+        - match: '>'
+          scope: punctuation.section.generic.end.c++
+          pop: true
+        - include: expressions-minus-generic-type-function-call
+
+  block:
+    - match: '\{'
+      scope: punctuation.section.block.begin.c++
+      push:
+        - meta_scope: meta.block.c++
+        - match: (?=^\s*#\s*(elif|else|endif)\b)
+          pop: true
+        - match: '\}'
+          scope: punctuation.section.block.end.c++
+          pop: true
+        - include: statements
+
+  function-call:
+    - match: (?={{path_lookahead}}\s*\()
+      push:
+        - meta_scope: meta.function-call.c++
+        - include: scope:source.c#c99
+        - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*'
+          scope: variable.function.c++
+          captures:
+            1: punctuation.accessor.c++
+            2: punctuation.accessor.c++
+        - match: '(?:(::)\s*)?{{identifier}}'
+          scope: variable.function.c++
+          captures:
+            1: punctuation.accessor.c++
+        - match: '\('
+          scope: meta.group.c++ punctuation.section.group.begin.c++
+          set:
+            - meta_content_scope: meta.function-call.c++ meta.group.c++
+            - match: '\)'
+              scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++
+              pop: true
+            - include: expressions
+
+  members-inside-function-call:
+    - meta_content_scope: meta.method-call.c++ meta.group.c++
+    - match: \)
+      scope: meta.method-call.c++ meta.group.c++ punctuation.section.group.end.c++
+      pop: true
+    - include: expressions
+
+  members-after-accessor-junction:
+    # After we've seen an accessor (dot or arrow), this context decides what
+    # kind of entity we're accessing.
+    - include: comments
+    - match: \btemplate\b
+      scope: meta.method-call.c++ storage.type.template.c++
+      # Guaranteed to be a template member function call after we match this
+      set:
+        - meta_content_scope: meta.method-call.c++
+        - include: comments
+        - match: '{{identifier}}'
+          scope: variable.function.member.c++
+          set:
+            - meta_content_scope: meta.method-call.c++
+            - match: \(
+              scope: meta.group.c++ punctuation.section.group.begin.c++
+              set: members-inside-function-call
+            - include: comments
+            - include: angle-brackets
+            - match: (?=\S) # safety pop
+              pop: true
+        - match: (?=\S) # safety pop
+          pop: true
+    # Operator overloading
+    - match: '({{operator_method_name}})\s*(\()'
+      captures:
+        0: meta.method-call.c++
+        1: variable.function.member.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      set: members-inside-function-call
+    # Non-templated member function call
+    - match: (~?{{identifier}})\s*(\()
+      captures:
+        0: meta.method-call.c++
+        1: variable.function.member.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      set: members-inside-function-call
+    # Templated member function call
+    - match: (~?{{identifier}})\s*(?={{generic_lookahead}})
+      captures:
+        1: variable.function.member.c++
+      set:
+        - meta_scope: meta.method-call.c++
+        - match: <
+          scope: punctuation.section.generic.begin.c++
+          set:
+            - meta_content_scope: meta.method-call.c++
+            - match: '>'
+              scope: punctuation.section.generic.end.c++
+              set:
+                - meta_content_scope: meta.method-call.c++
+                - include: comments
+                - match: \(
+                  scope: punctuation.section.group.begin.c++
+                  set: members-inside-function-call
+                - match: (?=\S) # safety pop
+                  pop: true
+            - include: expressions
+    # Explicit base-class access
+    - match: ({{identifier}})\s*(::)
+      captures:
+        1: variable.other.base-class.c++
+        2: punctuation.accessor.double-colon.c++
+      set: members-after-accessor-junction # reset
+    # Just a regular member variable
+    - match: '{{identifier}}'
+      scope: variable.other.readwrite.member.c++
+      pop: true
+
+  members-dot:
+    - include: scope:source.c#access-illegal
+    # No lookahead required because members-dot goes after operators in the
+    # early-expressions-after-generic-type context. This means triple dots
+    # (i.e. "..." or "variadic") is attempted first.
+    - match: \.
+      scope: punctuation.accessor.dot.c++
+      push: members-after-accessor-junction
+
+  members-arrow:
+    # This needs to be before operators in the
+    # early-expressions-after-generic-type context because otherwise the "->"
+    # from the C language will match.
+    - match: ->
+      scope: punctuation.accessor.arrow.c++
+      push: members-after-accessor-junction
+
+  typedef:
+    - match: \btypedef\b
+      scope: storage.type.c++
+      push:
+        - match: ({{identifier}})?\s*(?=;)
+          captures:
+            1: entity.name.type.typedef.c++
+          pop: true
+        - match: \b(struct)\s+({{identifier}})\b
+          captures:
+            1: storage.type.c++
+        - include: expressions-minus-generic-type
+
+  parens:
+    - match: \(
+      scope: punctuation.section.group.begin.c++
+      push:
+        - meta_scope: meta.group.c++
+        - match: \)
+          scope: punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+
+  brackets:
+    - match: \[
+      scope: punctuation.section.brackets.begin.c++
+      push:
+        - meta_scope: meta.brackets.c++
+        - match: \]
+          scope: punctuation.section.brackets.end.c++
+          pop: true
+        - include: expressions
+
+  function-trailing-return-type:
+    - match: '{{non_angle_brackets}}'
+      pop: true
+    - include: angle-brackets
+    - include: types
+    - include: modifiers-parens
+    - include: modifiers
+    - include: identifiers
+    - match: \*|&
+      scope: keyword.operator.c++
+    - include: function-trailing-return-type-parens
+    - match: '(?=\S)'
+      pop: true
+
+  function-trailing-return-type-parens:
+    - match: \(
+      scope: punctuation.section.group.begin.c++
+      push:
+        - meta_scope: meta.group.c++
+        - match: \)
+          scope: punctuation.section.group.end.c++
+          pop: true
+        - include: function-trailing-return-type
+
+  ## Detection of function and data structure definitions at the global level
+
+  global-modifier:
+    - include: comments
+    - include: modifiers-parens
+    - include: modifiers
+    # Constructors and destructors don't have a type
+    - match: '(?={{path_lookahead}}\s*::\s*{{identifier}}\s*(\(|$))'
+      set:
+        - meta_content_scope: meta.function.c++ entity.name.function.constructor.c++
+        - include: identifiers
+        - match: '(?=[^\w\s])'
+          set: function-definition-params
+    - match: '(?={{path_lookahead}}\s*::\s*~{{identifier}}\s*(\(|$))'
+      set:
+        - meta_content_scope: meta.function.c++ entity.name.function.destructor.c++
+        - include: identifiers
+        - match: '~{{identifier}}'
+        - match: '(?=[^\w\s])'
+          set: function-definition-params
+    # If we see a path ending in :: before a newline, we don't know if it is
+    # a constructor or destructor, or a long return type, so we are just going
+    # to treat it like a regular function. Most likely it is a constructor,
+    # since it doesn't seem most developers would create such a long typename.
+    - match: '(?={{path_lookahead}}\s*::\s*$)'
+      set:
+        - meta_content_scope: meta.function.c++ entity.name.function.c++
+        - include: identifiers
+        - match: '~{{identifier}}'
+        - match: '(?=[^\w\s])'
+          set: function-definition-params
+    - include: unique-strings
+    - match: '(?=\S)'
+      set: global-type
+
+  global-type:
+    - include: comments
+    - match: \*|&
+      scope: keyword.operator.c++
+    - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)'
+      pop: true
+    - match: '(?=\s)'
+      set: global-maybe-function
+    # If a class/struct/enum followed by a name that is not a macro or declspec
+    # then this is likely a return type of a function. This is uncommon.
+    - match: |-
+        (?x:
+          ({{before_tag}})
+          \s+
+          (?=
+            (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}})
+            {{path_lookahead}}
+            (\s+{{identifier}}\s*\(|\s*[*&])
+          )
+        )
+      captures:
+        1: storage.type.c++
+      set:
+        - include: identifiers
+        - match: ''
+          set: global-maybe-function
+    # The previous match handles return types of struct/enum/etc from a func,
+    # there this one exits the context to allow matching an actual struct/class
+    - match: '(?=\b({{before_tag}})\b)'
+      set: data-structures
+    - match: '(?=\b({{casts}})\b\s*<)'
+      pop: true
+    - match: '{{non_angle_brackets}}'
+      pop: true
+    - include: angle-brackets
+    - include: types
+    # Allow a macro call
+    - match: '({{identifier}})\s*(\()(?=[^\)]+\))'
+      captures:
+        1: variable.function.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_scope: meta.function-call.c++
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+    - match: '(?={{path_lookahead}}\s*\()'
+      set:
+        - include: function-call
+        - match: ''
+          pop: true
+    - include: variables
+    - include: constants
+    - include: identifiers
+    - match: (?=\W)
+      pop: true
+
+  global-maybe-function:
+    - include: comments
+    # Consume pointer info, macros and any type info that was offset by macros
+    - match: \*|&
+      scope: keyword.operator.c++
+    - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)'
+      pop: true
+    - match: '\b({{type_qualifier}})\b'
+      scope: storage.modifier.c++
+    - match: '{{non_angle_brackets}}'
+      pop: true
+    - include: angle-brackets
+    - include: types
+    - include: modifiers-parens
+    - include: modifiers
+    # All uppercase identifier just before a newline is most likely a macro
+    - match: '[[:upper:][:digit:]_]+\s*$'
+    # Operator overloading
+    - match: '(?=({{path_lookahead}}\s*(?:{{generic_lookahead}})?::\s*)?{{operator_method_name}}\s*(\(|$))'
+      set:
+        - meta_content_scope: meta.function.c++ entity.name.function.c++
+        - include: identifiers
+        - match: '(?=\s*(\(|$))'
+          set: function-definition-params
+    # Identifier that is not the function name - likely a macro or type
+    - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\(|$)))'
+      push:
+        - include: identifiers
+        - match: ''
+          pop: true
+    # Real function definition
+    - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*(\(|$))'
+      set: [function-definition-params, global-function-identifier-generic]
+    - match: '(?={{path_lookahead}}\s*(\(|$))'
+      set: [function-definition-params, global-function-identifier]
+    - match: '(?={{path_lookahead}}\s*::\s*$)'
+      set: [function-definition-params, global-function-identifier]
+    - match: '(?=\S)'
+      pop: true
+
+  global-function-identifier-generic:
+    - include: angle-brackets
+    - match: '::'
+      scope: punctuation.accessor.c++
+    - match: '(?={{identifier}}<.*>\s*\()'
+      push:
+        - meta_content_scope: entity.name.function.c++
+        - include: identifiers
+        - match: '(?=<)'
+          pop: true
+    - match: '(?={{identifier}}\s*\()'
+      push:
+        - meta_content_scope: entity.name.function.c++
+        - include: identifiers
+        - match: ''
+          pop: true
+    - match: '(?=\()'
+      pop: true
+
+  global-function-identifier:
+    - meta_content_scope: entity.name.function.c++
+    - include: identifiers
+    - match: '(?=\S)'
+      pop: true
+
+  function-definition-params:
+    - meta_content_scope: meta.function.c++
+    - include: comments
+    - match: '(?=\()'
+      set:
+        - match: \(
+          scope: meta.function.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++
+          set:
+            - meta_content_scope: meta.function.parameters.c++ meta.group.c++
+            - match : \)
+              scope: punctuation.section.group.end.c++
+              set: function-definition-continue
+            - match: '\bvoid\b'
+              scope: storage.type.c++
+            - match: '{{identifier}}(?=\s*(\[|,|\)|=))'
+              scope: variable.parameter.c++
+            - match: '='
+              scope: keyword.operator.assignment.c++
+              push:
+                - match: '(?=,|\))'
+                  pop: true
+                - include: expressions-minus-generic-type
+                - include: scope:source.c#preprocessor-line-continuation
+            - include: expressions-minus-generic-type
+            - include: scope:source.c#preprocessor-line-continuation
+    - match: (?=\S)
+      pop: true
+
+  function-definition-continue:
+    - meta_content_scope: meta.function.c++
+    - include: comments
+    - match: '(?=;)'
+      pop: true
+    - match: '->'
+      scope: punctuation.separator.c++
+      set: function-definition-trailing-return
+    - include: function-specifiers
+    - match: '='
+      scope: keyword.operator.assignment.c++
+    - match: '&'
+      scope: keyword.operator.c++
+    - match: \b0\b
+      scope: constant.numeric.c++
+    - match: \b(default|delete)\b
+      scope: storage.modifier.c++
+    - match: '(?=\{)'
+      set: function-definition-body
+    - match: '(?=\S)'
+      pop: true
+
+  function-definition-trailing-return:
+    - include: comments
+    - match: '(?=;)'
+      pop: true
+    - match: '(?=\{)'
+      set: function-definition-body
+    - include: function-specifiers
+    - include: function-trailing-return-type
+
+  function-definition-body:
+    - meta_content_scope: meta.function.c++ meta.block.c++
+    - match: '\{'
+      scope: punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.function.c++ meta.block.c++
+        - match: '\}'
+          scope: meta.function.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - match: (?=^\s*#\s*(elif|else|endif)\b)
+          pop: true
+        - match: '(?=({{before_tag}})([^(;]+$|.*\{))'
+          push: data-structures
+        - include: statements
+
+  ## Data structures including classes, structs, unions and enums
+
+  data-structures:
+    - match: '\bclass\b'
+      scope: storage.type.c++
+      set: data-structures-class-definition
+    # Detect variable type definitions using struct/enum/union followed by a tag
+    - match: '\b({{before_tag}})(?=\s+{{path_lookahead}}\s+{{path_lookahead}}\s*[=;\[])'
+      scope: storage.type.c++
+    - match: '\bstruct\b'
+      scope: storage.type.c++
+      set: data-structures-struct-definition
+    - match: '\benum(\s+(class|struct))?\b'
+      scope: storage.type.c++
+      set: data-structures-enum-definition
+    - match: '\bunion\b'
+      scope: storage.type.c++
+      set: data-structures-union-definition
+    - match: '(?=\S)'
+      pop: true
+
+  preprocessor-workaround-eat-macro-before-identifier:
+    # Handle macros so they aren't matched as the class name
+    - match: ({{macro_identifier}})(?=\s+~?{{identifier}})
+      captures:
+        1: meta.assumed-macro.c
+
+  data-structures-class-definition:
+    - meta_scope: meta.class.c++
+    - include: data-structures-definition-common-begin
+    - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})'
+      scope: entity.name.class.forward-decl.c++
+      set: data-structures-class-definition-after-identifier
+    - match: '{{identifier}}'
+      scope: entity.name.class.c++
+      set: data-structures-class-definition-after-identifier
+    - match: '(?=[:{])'
+      set: data-structures-class-definition-after-identifier
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-class-definition-after-identifier:
+    - meta_content_scope: meta.class.c++
+    - include: data-structures-definition-common-begin
+    # No matching of identifiers since they should all be macros at this point
+    - include: data-structures-definition-common-end
+    - match: '\{'
+      scope: meta.block.c++ punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.class.c++ meta.block.c++
+        - match: '\}'
+          scope: meta.class.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - include: data-structures-body
+
+  data-structures-struct-definition:
+    - meta_scope: meta.struct.c++
+    - include: data-structures-definition-common-begin
+    - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})'
+      scope: entity.name.struct.forward-decl.c++
+      set: data-structures-struct-definition-after-identifier
+    - match: '{{identifier}}'
+      scope: entity.name.struct.c++
+      set: data-structures-struct-definition-after-identifier
+    - match: '(?=[:{])'
+      set: data-structures-struct-definition-after-identifier
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-struct-definition-after-identifier:
+    - meta_content_scope: meta.struct.c++
+    - include: data-structures-definition-common-begin
+    # No matching of identifiers since they should all be macros at this point
+    - include: data-structures-definition-common-end
+    - match: '\{'
+      scope: meta.block.c++ punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.struct.c++ meta.block.c++
+        - match: '\}'
+          scope: meta.struct.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - include: data-structures-body
+
+  data-structures-enum-definition:
+    - meta_scope: meta.enum.c++
+    - include: data-structures-definition-common-begin
+    - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})'
+      scope: entity.name.enum.forward-decl.c++
+      set: data-structures-enum-definition-after-identifier
+    - match: '{{identifier}}'
+      scope: entity.name.enum.c++
+      set: data-structures-enum-definition-after-identifier
+    - match: '(?=[:{])'
+      set: data-structures-enum-definition-after-identifier
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-enum-definition-after-identifier:
+    - meta_content_scope: meta.enum.c++
+    - include: data-structures-definition-common-begin
+    # No matching of identifiers since they should all be macros at this point
+    - include: data-structures-definition-common-end
+    - match: '\{'
+      scope: meta.block.c++ punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.enum.c++ meta.block.c++
+        # Enums don't support methods so we have a simplified body
+        - match: '\}'
+          scope: meta.enum.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - include: statements
+
+  data-structures-union-definition:
+    - meta_scope: meta.union.c++
+    - include: data-structures-definition-common-begin
+    - match: '{{identifier}}(?={{data_structures_forward_decl_lookahead}})'
+      scope: entity.name.union.forward-decl.c++
+      set: data-structures-union-definition-after-identifier
+    - match: '{{identifier}}'
+      scope: entity.name.union.c++
+      set: data-structures-union-definition-after-identifier
+    - match: '(?=[{])'
+      set: data-structures-union-definition-after-identifier
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-union-definition-after-identifier:
+    - meta_content_scope: meta.union.c++
+    - include: data-structures-definition-common-begin
+    # No matching of identifiers since they should all be macros at this point
+    # Unions don't support base classes
+    - include: angle-brackets
+    - match: '\{'
+      scope: meta.block.c++ punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.union.c++ meta.block.c++
+        - match: '\}'
+          scope: meta.union.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - include: data-structures-body
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-definition-common-begin:
+    - include: comments
+    - match: '(?=\b(?:{{before_tag}}|{{control_keywords}})\b)'
+      pop: true
+    - include: preprocessor-other
+    - include: modifiers-parens
+    - include: modifiers
+    - include: preprocessor-workaround-eat-macro-before-identifier
+
+  data-structures-definition-common-end:
+    - include: angle-brackets
+    - match: \bfinal\b
+      scope: storage.modifier.c++
+    - match: ':'
+      scope: punctuation.separator.c++
+      push:
+        - include: comments
+        - include: preprocessor-other
+        - include: modifiers-parens
+        - include: modifiers
+        - match: '\b(virtual|{{visibility_modifiers}})\b'
+          scope: storage.modifier.c++
+        - match: (?={{path_lookahead}})
+          push:
+            - meta_scope: entity.other.inherited-class.c++
+            - include: identifiers
+            - match: ''
+              pop: true
+        - include: angle-brackets
+        - match: ','
+          scope: punctuation.separator.c++
+        - match: (?=\{|;)
+          pop: true
+    - match: '(?=;)'
+      pop: true
+
+  data-structures-body:
+    - include: preprocessor-data-structures
+    - match: '(?=\btemplate\b)'
+      push:
+        - include: template
+        - match: (?=\S)
+          set: data-structures-modifier
+    - include: typedef
+    - match: \b({{visibility_modifiers}})\s*(:)(?!:)
+      captures:
+        1: storage.modifier.c++
+        2: punctuation.section.class.c++
+    - match: '^\s*(?=(?:~?\w+|::))'
+      push: data-structures-modifier
+    - include: expressions-minus-generic-type
+
+  data-structures-modifier:
+    - match: '\bfriend\b'
+      scope: storage.modifier.c++
+      push:
+        - match: (?=;)
+          pop: true
+        - match: '\{'
+          scope: punctuation.section.block.begin.c++
+          set:
+            - meta_scope: meta.block.c++
+            - match: '\}'
+              scope: punctuation.section.block.end.c++
+              pop: true
+            - include: statements
+        - match: '\b({{before_tag}})\b'
+          scope: storage.type.c++
+        - include: expressions-minus-function-call
+    - include: comments
+    - include: modifiers-parens
+    - include: modifiers
+    - match: '\bstatic_assert(?=\s*\()'
+      scope: meta.static-assert.c++ keyword.operator.word.c++
+      push:
+        - match: '\('
+          scope: meta.group.c++ punctuation.section.group.begin.c++
+          set:
+            - meta_content_scope: meta.function-call.c++ meta.group.c++
+            - match: '\)'
+              scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++
+              pop: true
+            - include: expressions
+    # Destructor
+    - match: '(?:{{identifier}}\s*(::)\s*)?~{{identifier}}(?=\s*(\(|$))'
+      scope: meta.method.destructor.c++ entity.name.function.destructor.c++
+      captures:
+        1: punctuation.accessor.c++
+      set: method-definition-params
+    # It's a macro, not a constructor if there is no type in the first param
+    - match: '({{identifier}})\s*(\()(?=\s*(?!void){{identifier}}\s*[),])'
+      captures:
+        1: variable.function.c++
+        2: meta.group.c++ punctuation.section.group.begin.c++
+      push:
+        - meta_scope: meta.function-call.c++
+        - meta_content_scope: meta.group.c++
+        - match: '\)'
+          scope: meta.group.c++ punctuation.section.group.end.c++
+          pop: true
+        - include: expressions
+    # Constructor
+    - include: preprocessor-workaround-eat-macro-before-identifier
+    - match: '((?!{{before_tag}}|template){{identifier}})(?=\s*\()'
+      scope: meta.method.constructor.c++ entity.name.function.constructor.c++
+      set: method-definition-params
+    # Long form constructor
+    - match: '({{identifier}}\s*(::)\s*{{identifier}})(?=\s*\()'
+      captures:
+        1: meta.method.constructor.c++ entity.name.function.constructor.c++
+        2: punctuation.accessor.c++
+      push: method-definition-params
+    - match: '(?=\S)'
+      set: data-structures-type
+
+  data-structures-type:
+    - include: comments
+    - match: \*|&
+      scope: keyword.operator.c++
+      # Cast methods
+    - match: '(operator)\s+({{identifier}})(?=\s*(\(|$))'
+      captures:
+        1: keyword.control.c++
+        2: meta.method.c++ entity.name.function.c++
+      set: method-definition-params
+    - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}}|operator)\b)'
+      pop: true
+    - match: '(?=\s)'
+      set: data-structures-maybe-method
+    # If a class/struct/enum followed by a name that is not a macro or declspec
+    # then this is likely a return type of a function. This is uncommon.
+    - match: |-
+        (?x:
+          ({{before_tag}})
+          \s+
+          (?=
+            (?![[:upper:][:digit:]_]+\b|__declspec|{{before_tag}})
+            {{path_lookahead}}
+            (\s+{{identifier}}\s*\(|\s*[*&])
+          )
+        )
+      captures:
+        1: storage.type.c++
+      set:
+        - include: identifiers
+        - match: ''
+          set: data-structures-maybe-method
+    # The previous match handles return types of struct/enum/etc from a func,
+    # there this one exits the context to allow matching an actual struct/class
+    - match: '(?=\b({{before_tag}})\b)'
+      set: data-structures
+    - match: '(?=\b({{casts}})\b\s*<)'
+      pop: true
+    - match: '{{non_angle_brackets}}'
+      pop: true
+    - include: angle-brackets
+    - include: types
+    - include: variables
+    - include: constants
+    - include: identifiers
+    - match: (?=[&*])
+      set: data-structures-maybe-method
+    - match: (?=\W)
+      pop: true
+
+  data-structures-maybe-method:
+    - include: comments
+    # Consume pointer info, macros and any type info that was offset by macros
+    - match: \*|&
+      scope: keyword.operator.c++
+    - match: '(?=\b({{control_keywords}}|{{operator_keywords}}|{{casts}}|{{memory_operators}}|{{other_keywords}})\b)'
+      pop: true
+    - match: '\b({{type_qualifier}})\b'
+      scope: storage.modifier.c++
+    - match: '{{non_angle_brackets}}'
+      pop: true
+    - include: angle-brackets
+    - include: types
+    - include: modifiers-parens
+    - include: modifiers
+    # Operator overloading
+    - match: '{{operator_method_name}}(?=\s*(\(|$))'
+      scope: meta.method.c++ entity.name.function.c++
+      set: method-definition-params
+    # Identifier that is not the function name - likely a macro or type
+    - match: '(?={{path_lookahead}}([ \t]+|[*&])(?!\s*(<|::|\()))'
+      push:
+        - include: identifiers
+        - match: ''
+          pop: true
+    # Real function definition
+    - match: '(?={{path_lookahead}}({{generic_lookahead}})\s*(\())'
+      set: [method-definition-params, data-structures-function-identifier-generic]
+    - match: '(?={{path_lookahead}}\s*(\())'
+      set: [method-definition-params, data-structures-function-identifier]
+    - match: '(?={{path_lookahead}}\s*::\s*$)'
+      set: [method-definition-params, data-structures-function-identifier]
+    - match: '(?=\S)'
+      pop: true
+
+  data-structures-function-identifier-generic:
+    - include: angle-brackets
+    - match: '(?={{identifier}})'
+      push:
+        - meta_content_scope: entity.name.function.c++
+        - include: identifiers
+        - match: '(?=<)'
+          pop: true
+    - match: '(?=\()'
+      pop: true
+
+  data-structures-function-identifier:
+    - meta_content_scope: entity.name.function.c++
+    - include: identifiers
+    - match: '(?=\S)'
+      pop: true
+
+  method-definition-params:
+    - meta_content_scope: meta.method.c++
+    - include: comments
+    - match: '(?=\()'
+      set:
+        - match: \(
+          scope: meta.method.parameters.c++ meta.group.c++ punctuation.section.group.begin.c++
+          set:
+            - meta_content_scope: meta.method.parameters.c++ meta.group.c++
+            - match : \)
+              scope: punctuation.section.group.end.c++
+              set: method-definition-continue
+            - match: '\bvoid\b'
+              scope: storage.type.c++
+            - match: '{{identifier}}(?=\s*(\[|,|\)|=))'
+              scope: variable.parameter.c++
+            - match: '='
+              scope: keyword.operator.assignment.c++
+              push:
+                - match: '(?=,|\))'
+                  pop: true
+                - include: expressions-minus-generic-type
+            - include: expressions-minus-generic-type
+    - match: '(?=\S)'
+      pop: true
+
+  method-definition-continue:
+    - meta_content_scope: meta.method.c++
+    - include: comments
+    - match: '(?=;)'
+      pop: true
+    - match: '->'
+      scope: punctuation.separator.c++
+      set: method-definition-trailing-return
+    - include: function-specifiers
+    - match: '='
+      scope: keyword.operator.assignment.c++
+    - match: '&'
+      scope: keyword.operator.c++
+    - match: \b0\b
+      scope: constant.numeric.c++
+    - match: \b(default|delete)\b
+      scope: storage.modifier.c++
+    - match: '(?=:)'
+      set:
+        - match: ':'
+          scope: punctuation.separator.initializer-list.c++
+          set:
+            - meta_scope: meta.method.constructor.initializer-list.c++
+            - match: '{{identifier}}'
+              scope: variable.other.readwrite.member.c++
+              push:
+                - match: \(
+                  scope: meta.group.c++ punctuation.section.group.begin.c++
+                  set:
+                    - meta_content_scope: meta.group.c++
+                    - match: \)
+                      scope: meta.group.c++ punctuation.section.group.end.c++
+                      pop: true
+                    - include: expressions
+                - match: \{
+                  scope: meta.group.c++ punctuation.section.group.begin.c++
+                  set:
+                    - meta_content_scope: meta.group.c++
+                    - match: \}
+                      scope: meta.group.c++ punctuation.section.group.end.c++
+                      pop: true
+                    - include: expressions
+                - include: comments
+            - match: (?=\{|;)
+              set: method-definition-continue
+            - include: expressions
+    - match: '(?=\{)'
+      set: method-definition-body
+    - match: '(?=\S)'
+      pop: true
+
+  method-definition-trailing-return:
+    - include: comments
+    - match: '(?=;)'
+      pop: true
+    - match: '(?=\{)'
+      set: method-definition-body
+    - include: function-specifiers
+    - include: function-trailing-return-type
+
+  method-definition-body:
+    - meta_content_scope: meta.method.c++ meta.block.c++
+    - match: '\{'
+      scope: punctuation.section.block.begin.c++
+      set:
+        - meta_content_scope: meta.method.c++ meta.block.c++
+        - match: '\}'
+          scope: meta.method.c++ meta.block.c++ punctuation.section.block.end.c++
+          pop: true
+        - match: (?=^\s*#\s*(elif|else|endif)\b)
+          pop: true
+        - match: '(?=({{before_tag}})([^(;]+$|.*\{))'
+          push: data-structures
+        - include: statements
+
+  ## Preprocessor for data-structures
+
+  preprocessor-data-structures:
+    - include: preprocessor-rule-enabled-data-structures
+    - include: preprocessor-rule-disabled-data-structures
+    - include: preprocessor-practical-workarounds
+
+  preprocessor-rule-disabled-data-structures:
+    - match: ^\s*((#if)\s+(0))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: negated-block
+            - include: data-structures-body
+        - match: ""
+          push:
+            - meta_scope: comment.block.preprocessor.if-branch.c++
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+
+  preprocessor-rule-enabled-data-structures:
+    - match: ^\s*((#if)\s+(0*1))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - meta_content_scope: comment.block.preprocessor.else-branch.c++
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+        - match: ""
+          push:
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: negated-block
+            - include: data-structures-body
+
+  ## Preprocessor for global
+
+  preprocessor-global:
+    - include: preprocessor-rule-enabled-global
+    - include: preprocessor-rule-disabled-global
+    - include: preprocessor-rule-other-global
+
+  preprocessor-statements:
+    - include: preprocessor-rule-enabled-statements
+    - include: preprocessor-rule-disabled-statements
+    - include: preprocessor-rule-other-statements
+
+  preprocessor-expressions:
+    - include: scope:source.c#incomplete-inc
+    - include: preprocessor-macro-define
+    - include: scope:source.c#pragma-mark
+    - include: preprocessor-other
+
+  preprocessor-rule-disabled-global:
+    - match: ^\s*((#if)\s+(0))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: preprocessor-global
+            - include: negated-block
+            - include: global
+        - match: ""
+          push:
+            - meta_scope: comment.block.preprocessor.if-branch.c++
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+
+  preprocessor-rule-enabled-global:
+    - match: ^\s*((#if)\s+(0*1))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - meta_content_scope: comment.block.preprocessor.else-branch.c++
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+        - match: ""
+          push:
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: preprocessor-global
+            - include: negated-block
+            - include: global
+
+  preprocessor-rule-other-global:
+    - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b
+      captures:
+        1: keyword.control.import.c++
+      push:
+        - meta_scope: meta.preprocessor.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-comments
+        - match: \bdefined\b
+          scope: keyword.control.c++
+        # Enter a new scope where all elif/else branches have their
+        # contexts popped by a subsequent elif/else/endif. This ensures that
+        # preprocessor branches don't push multiple meta.block scopes on
+        # the stack, thus messing up the "global" context's detection of
+        # functions.
+        - match: $\n
+          set: preprocessor-if-branch-global
+
+  # These gymnastics here ensure that we are properly handling scope even
+  # when the preprocessor is used to create different scope beginnings, such
+  # as a different if/while condition
+  preprocessor-if-branch-global:
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      pop: true
+    - match: (?=^\s*#\s*(elif|else)\b)
+      push: preprocessor-elif-else-branch-global
+    - match: \{
+      scope: punctuation.section.block.begin.c++
+      set: preprocessor-block-if-branch-global
+    - include: preprocessor-global
+    - include: negated-block
+    - include: global
+
+  preprocessor-block-if-branch-global:
+    - meta_scope: meta.block.c++
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-block-finish-global
+    - match: (?=^\s*#\s*(elif|else)\b)
+      push: preprocessor-elif-else-branch-global
+    - match: \}
+      scope: punctuation.section.block.end.c++
+      set: preprocessor-if-branch-global
+    - include: statements
+
+  preprocessor-block-finish-global:
+    - meta_scope: meta.block.c++
+    - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-block-finish-if-branch-global
+    - match: \}
+      scope: punctuation.section.block.end.c++
+      pop: true
+    - include: statements
+
+  preprocessor-block-finish-if-branch-global:
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: keyword.control.import.c++
+      pop: true
+    - match: \}
+      scope: punctuation.section.block.end.c++
+      set: preprocessor-if-branch-global
+    - include: statements
+
+  preprocessor-elif-else-branch-global:
+    - match: (?=^\s*#\s*(endif)\b)
+      pop: true
+    - include: preprocessor-global
+    - include: negated-block
+    - include: global
+
+  ## Preprocessor for statements
+
+  preprocessor-rule-disabled-statements:
+    - match: ^\s*((#if)\s+(0))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: negated-block
+            - include: statements
+        - match: ""
+          push:
+            - meta_scope: comment.block.preprocessor.if-branch.c++
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+
+  preprocessor-rule-enabled-statements:
+    - match: ^\s*((#if)\s+(0*1))\b
+      captures:
+        1: meta.preprocessor.c++
+        2: keyword.control.import.c++
+        3: constant.numeric.preprocessor.c++
+      push:
+        - match: ^\s*(#\s*endif)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.c++
+          pop: true
+        - match: ^\s*(#\s*else)\b
+          captures:
+            1: meta.preprocessor.c++ keyword.control.import.else.c++
+          push:
+            - meta_content_scope: comment.block.preprocessor.else-branch.c++
+            - match: (?=^\s*#\s*endif\b)
+              pop: true
+            - include: scope:source.c#preprocessor-disabled
+        - match: ""
+          push:
+            - match: (?=^\s*#\s*(else|endif)\b)
+              pop: true
+            - include: negated-block
+            - include: statements
+
+  preprocessor-rule-other-statements:
+    - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b
+      captures:
+        1: keyword.control.import.c++
+      push:
+        - meta_scope: meta.preprocessor.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-comments
+        - match: \bdefined\b
+          scope: keyword.control.c++
+        # Enter a new scope where all elif/else branches have their
+        # contexts popped by a subsequent elif/else/endif. This ensures that
+        # preprocessor branches don't push multiple meta.block scopes on
+        # the stack, thus messing up the "global" context's detection of
+        # functions.
+        - match: $\n
+          set: preprocessor-if-branch-statements
+
+  # These gymnastics here ensure that we are properly handling scope even
+  # when the preprocessor is used to create different scope beginnings, such
+  # as a different if/while condition
+  preprocessor-if-branch-statements:
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      pop: true
+    - match: (?=^\s*#\s*(elif|else)\b)
+      push: preprocessor-elif-else-branch-statements
+    - match: \{
+      scope: punctuation.section.block.begin.c++
+      set: preprocessor-block-if-branch-statements
+    - match: (?=(?!{{non_func_keywords}}){{path_lookahead}}\s*\()
+      set: preprocessor-if-branch-function-call
+    - include: negated-block
+    - include: statements
+
+  preprocessor-if-branch-function-call:
+    - meta_content_scope: meta.function-call.c++
+    - include: scope:source.c#c99
+    - match: '(?:(::)\s*)?{{identifier}}\s*(::)\s*'
+      scope: variable.function.c++
+      captures:
+        1: punctuation.accessor.c++
+        2: punctuation.accessor.c++
+    - match: '(?:(::)\s*)?{{identifier}}'
+      scope: variable.function.c++
+      captures:
+        1: punctuation.accessor.c++
+    - match: '\('
+      scope: meta.group.c++ punctuation.section.group.begin.c++
+      set: preprocessor-if-branch-function-call-arguments
+
+  preprocessor-if-branch-function-call-arguments:
+    - meta_content_scope: meta.function-call.c++ meta.group.c++
+    - match : \)
+      scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++
+      set: preprocessor-if-branch-statements
+    - match: ^\s*(#\s*(?:elif|else))\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-if-branch-statements
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-if-branch-function-call-arguments-finish
+    - include: expressions
+
+  preprocessor-if-branch-function-call-arguments-finish:
+    - meta_content_scope: meta.function-call.c++ meta.group.c++
+    - match: \)
+      scope: meta.function-call.c++ meta.group.c++ punctuation.section.group.end.c++
+      pop: true
+    - include: expressions
+
+  preprocessor-block-if-branch-statements:
+    - meta_scope: meta.block.c++
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-block-finish-statements
+    - match: (?=^\s*#\s*(elif|else)\b)
+      push: preprocessor-elif-else-branch-statements
+    - match: \}
+      scope: punctuation.section.block.end.c++
+      set: preprocessor-if-branch-statements
+    - include: statements
+
+  preprocessor-block-finish-statements:
+    - meta_scope: meta.block.c++
+    - match: ^\s*(#\s*(?:if|ifdef|ifndef))\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+      set: preprocessor-block-finish-if-branch-statements
+    - match: \}
+      scope: punctuation.section.block.end.c++
+      pop: true
+    - include: statements
+
+  preprocessor-block-finish-if-branch-statements:
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: keyword.control.import.c++
+      pop: true
+    - match: \}
+      scope: meta.block.c++ punctuation.section.block.end.c++
+      set: preprocessor-if-branch-statements
+    - include: statements
+
+  preprocessor-elif-else-branch-statements:
+    - match: (?=^\s*#\s*endif\b)
+      pop: true
+    - include: negated-block
+    - include: statements
+
+  ## Preprocessor other
+
+  negated-block:
+    - match: '\}'
+      scope: punctuation.section.block.end.c++
+      push:
+        - match: '\{'
+          scope: punctuation.section.block.begin.c++
+          pop: true
+        - match: (?=^\s*#\s*(elif|else|endif)\b)
+          pop: true
+        - include: statements
+
+  preprocessor-macro-define:
+    - match: ^\s*(\#\s*define)\b
+      captures:
+        1: meta.preprocessor.macro.c++ keyword.control.import.define.c++
+      push:
+        - meta_content_scope: meta.preprocessor.macro.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-line-ending
+        - include: scope:source.c#preprocessor-comments
+        - match: '({{identifier}})(?=\()'
+          scope: entity.name.function.preprocessor.c++
+          set:
+            - match: '\('
+              scope: punctuation.section.group.begin.c++
+              set: preprocessor-macro-params
+        - match: '{{identifier}}'
+          scope: entity.name.constant.preprocessor.c++
+          set: preprocessor-macro-definition
+
+  preprocessor-macro-params:
+    - meta_scope: meta.preprocessor.macro.parameters.c++ meta.group.c++
+    - match: '{{identifier}}'
+      scope: variable.parameter.c++
+    - match: \)
+      scope: punctuation.section.group.end.c++
+      set: preprocessor-macro-definition
+    - match: ','
+      scope: punctuation.separator.c++
+      push:
+        - match: '{{identifier}}'
+          scope: variable.parameter.c++
+          pop: true
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-comments
+        - match: '\.\.\.'
+          scope: keyword.operator.variadic.c++
+        - match: '(?=\))'
+          pop: true
+        - match: (/\*).*(\*/)
+          scope: comment.block.c++
+          captures:
+            1: punctuation.definition.comment.c++
+            2: punctuation.definition.comment.c++
+        - match: '\S+'
+          scope: invalid.illegal.unexpected-character.c++
+    - include: scope:source.c#preprocessor-line-continuation
+    - include: scope:source.c#preprocessor-comments
+    - match: '\.\.\.'
+      scope: keyword.operator.variadic.c++
+    - match: (/\*).*(\*/)
+      scope: comment.block.c++
+      captures:
+        1: punctuation.definition.comment.c++
+        2: punctuation.definition.comment.c++
+    - match: $\n
+      scope: invalid.illegal.unexpected-end-of-line.c++
+
+  preprocessor-macro-definition:
+    - meta_content_scope: meta.preprocessor.macro.c++
+    - include: scope:source.c#preprocessor-line-continuation
+    - include: scope:source.c#preprocessor-line-ending
+    - include: scope:source.c#preprocessor-comments
+    # Don't define blocks in define statements
+    - match: '\{'
+      scope: punctuation.section.block.begin.c++
+    - match: '\}'
+      scope: punctuation.section.block.end.c++
+    - include: expressions
+
+  preprocessor-practical-workarounds:
+    - include: preprocessor-convention-ignore-uppercase-ident-lines
+    - include: scope:source.c#preprocessor-convention-ignore-uppercase-calls-without-semicolon
+
+  preprocessor-convention-ignore-uppercase-ident-lines:
+    - match: ^(\s*{{macro_identifier}})+\s*$
+      scope: meta.assumed-macro.c++
+      push:
+        # It's possible that we are dealing with a function return type on its own line, and the
+        # name of the function is on the subsequent line.
+        - match: '(?={{path_lookahead}}({{generic_lookahead}}({{path_lookahead}})?)\s*\()'
+          set: [function-definition-params, global-function-identifier-generic]
+        - match: '(?={{path_lookahead}}\s*\()'
+          set: [function-definition-params, global-function-identifier]
+        - match: ^
+          pop: true
+
+  preprocessor-other:
+    - match: ^\s*(#\s*(?:if|ifdef|ifndef|elif|else|line|pragma|undef))\b
+      captures:
+        1: keyword.control.import.c++
+      push:
+        - meta_scope: meta.preprocessor.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-line-ending
+        - include: scope:source.c#preprocessor-comments
+        - match: \bdefined\b
+          scope: keyword.control.c++
+    - match: ^\s*(#\s*endif)\b
+      captures:
+        1: meta.preprocessor.c++ keyword.control.import.c++
+    - match: ^\s*(#\s*(?:error|warning))\b
+      captures:
+        1: keyword.control.import.error.c++
+      push:
+        - meta_scope: meta.preprocessor.diagnostic.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-line-ending
+        - include: scope:source.c#preprocessor-comments
+        - include: strings
+        - match: '\S+'
+          scope: string.unquoted.c++
+    - match: ^\s*(#\s*(?:include|include_next|import))\b
+      captures:
+        1: keyword.control.import.include.c++
+      push:
+        - meta_scope: meta.preprocessor.include.c++
+        - include: scope:source.c#preprocessor-line-continuation
+        - include: scope:source.c#preprocessor-line-ending
+        - include: scope:source.c#preprocessor-comments
+        - match: '"'
+          scope: punctuation.definition.string.begin.c++
+          push:
+            - meta_scope: string.quoted.double.include.c++
+            - match: '"'
+              scope: punctuation.definition.string.end.c++
+              pop: true
+        - match: <
+          scope: punctuation.definition.string.begin.c++
+          push:
+            - meta_scope: string.quoted.other.lt-gt.include.c++
+            - match: '>'
+              scope: punctuation.definition.string.end.c++
+              pop: true
+    - include: preprocessor-practical-workarounds
diff --git a/libs/fmt/support/README b/libs/fmt/support/README
new file mode 100644
index 0000000000000000000000000000000000000000..468f5485d9e476c547b65ea92b31fcbc1fef9b31
--- /dev/null
+++ b/libs/fmt/support/README
@@ -0,0 +1,4 @@
+This directory contains build support files such as
+
+* CMake modules
+* Build scripts
diff --git a/libs/fmt/support/Vagrantfile b/libs/fmt/support/Vagrantfile
new file mode 100644
index 0000000000000000000000000000000000000000..f6b5f9366259be1d882089eb96e4dca731873e9b
--- /dev/null
+++ b/libs/fmt/support/Vagrantfile
@@ -0,0 +1,20 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+# A vagrant config for testing against gcc-4.8.
+Vagrant.configure("2") do |config|
+  config.vm.box = "ubuntu/xenial64"
+  config.disksize.size = '15GB'
+
+  config.vm.provider "virtualbox" do |vb|
+    vb.memory = "4096"
+  end
+
+  config.vm.provision "shell", inline: <<-SHELL
+    apt-get update
+    apt-get install -y g++ make wget git
+    wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
+    tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
+    ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
+  SHELL
+end
diff --git a/libs/fmt/support/appveyor-build.py b/libs/fmt/support/appveyor-build.py
new file mode 100644
index 0000000000000000000000000000000000000000..6544610326739f279947fc2b2a4309a742f23743
--- /dev/null
+++ b/libs/fmt/support/appveyor-build.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# Build the project on AppVeyor.
+
+import os
+from subprocess import check_call
+
+build = os.environ['BUILD']
+config = os.environ['CONFIGURATION']
+platform = os.environ['PLATFORM']
+path = os.environ['PATH']
+image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
+jobid = os.environ['APPVEYOR_JOB_ID']
+cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
+if build == 'mingw':
+    cmake_command.append('-GMinGW Makefiles')
+    build_command = ['mingw32-make', '-j4']
+    test_command = ['mingw32-make', 'test']
+    # Remove the path to Git bin directory from $PATH because it breaks
+    # MinGW config.
+    path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
+    os.environ['PATH'] = r'C:\MinGW\bin;' + path
+else:
+    # Add MSBuild 14.0 to PATH as described in
+    # http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
+    os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
+    if image == 'Visual Studio 2019':
+        generator = 'Visual Studio 16 2019'
+        if platform == 'x64':
+            cmake_command.extend(['-A', 'x64'])
+    else:
+        if image == 'Visual Studio 2015':
+            generator = 'Visual Studio 14 2015'
+        elif image == 'Visual Studio 2017':
+            generator = 'Visual Studio 15 2017'
+        if platform == 'x64':
+            generator += ' Win64'
+    cmake_command.append('-G' + generator)
+    build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
+    test_command = ['ctest', '-C', config]
+
+check_call(cmake_command)
+check_call(build_command)
+check_call(test_command)
diff --git a/libs/fmt/support/appveyor.yml b/libs/fmt/support/appveyor.yml
new file mode 100644
index 0000000000000000000000000000000000000000..64ca58a949c243e5b6f87cc953446ea6e0d1d146
--- /dev/null
+++ b/libs/fmt/support/appveyor.yml
@@ -0,0 +1,31 @@
+configuration:
+  - Debug
+  - Release
+
+clone_depth: 1
+
+image:
+  - Visual Studio 2015
+
+platform:
+  - x64
+
+environment:
+  CTEST_OUTPUT_ON_FAILURE: 1
+  MSVC_DEFAULT_OPTIONS: ON
+  BUILD: msvc
+
+before_build:
+ - mkdir build
+ - cd build
+
+build_script:
+  - python ../support/appveyor-build.py
+
+on_failure:
+  - appveyor PushArtifact Testing/Temporary/LastTest.log
+  - appveyor AddTest test
+
+# Uncomment this to debug AppVeyor failures.
+#on_finish:
+#  - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
diff --git a/libs/fmt/support/build-docs.py b/libs/fmt/support/build-docs.py
new file mode 100644
index 0000000000000000000000000000000000000000..70100ed4dfe8f86c210c13bac6f6c6c954575440
--- /dev/null
+++ b/libs/fmt/support/build-docs.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# Build the documentation in CI.
+
+from __future__ import print_function
+import errno, os, shutil, subprocess, sys, urllib
+from subprocess import call, check_call, Popen, PIPE, STDOUT
+
+def rmtree_if_exists(dir):
+    try:
+        shutil.rmtree(dir)
+    except OSError as e:
+        if e.errno == errno.ENOENT:
+            pass
+
+# Build the docs.
+fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
+import build
+build.create_build_env()
+html_dir = build.build_docs()
+
+repo = 'fmtlib.github.io'
+branch = os.environ['GITHUB_REF']
+is_ci = 'CI' in os.environ
+if is_ci and branch != 'refs/heads/master':
+    print('Branch: ' + branch)
+    exit(0) # Ignore non-master branches
+if is_ci and 'KEY' not in os.environ:
+    # Don't update the repo if building in CI from an account that doesn't have
+    # push access.
+    print('Skipping update of ' + repo)
+    exit(0)
+
+# Clone the fmtlib.github.io repo.
+rmtree_if_exists(repo)
+git_url = 'https://github.com/' if is_ci else 'git@github.com:'
+check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
+
+# Copy docs to the repo.
+target_dir = os.path.join(repo, 'dev')
+rmtree_if_exists(target_dir)
+shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
+if is_ci:
+    check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
+    check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
+
+# Push docs to GitHub pages.
+check_call(['git', 'add', '--all'], cwd=repo)
+if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
+    check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
+    cmd = 'git push'
+    if is_ci:
+        cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
+    p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
+    # Print the output without the key.
+    print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
+    if p.returncode != 0:
+        raise subprocess.CalledProcessError(p.returncode, cmd)
diff --git a/libs/fmt/support/build.gradle b/libs/fmt/support/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..8d18da6bc691f5e904c0767296624be24252963e
--- /dev/null
+++ b/libs/fmt/support/build.gradle
@@ -0,0 +1,132 @@
+import java.nio.file.Paths
+
+// General gradle arguments for root project
+buildscript {    
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        //
+        // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
+        //
+        // Notice that 4.0.0 here is the version of [Android Gradle Plugin]
+        // Accroding to URL above you will need Gradle 6.1 or higher
+        //
+        classpath "com.android.tools.build:gradle:4.1.1"
+    }
+}
+repositories {
+    google()
+    jcenter()
+}
+
+// Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir
+def rootDir = Paths.get(project.buildDir.getParent()).getParent()
+println("rootDir: ${rootDir}")
+
+// Output: Shared library (.so) for Android 
+apply plugin: "com.android.library"
+android {
+    compileSdkVersion 25    // Android 7.0
+
+    // Target ABI
+    //  - This option controls target platform of module
+    //  - The platform might be limited by compiler's support
+    //    some can work with Clang(default), but some can work only with GCC...
+    //    if bad, both toolchains might not support it
+    splits {
+        abi {
+            enable true
+            // Specify platforms for Application
+            reset()
+            include  "arm64-v8a", "armeabi-v7a", "x86_64"
+        }
+    }
+    ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit
+
+    defaultConfig {
+        minSdkVersion 21    // Android 5.0+
+        targetSdkVersion 25 // Follow Compile SDK
+        versionCode 34      // Follow release count
+        versionName "7.1.2" // Follow Official version
+        
+        externalNativeBuild {
+            cmake {
+                arguments "-DANDROID_STL=c++_shared"    // Specify Android STL
+                arguments "-DBUILD_SHARED_LIBS=true"    // Build shared object
+                arguments "-DFMT_TEST=false"            // Skip test
+                arguments "-DFMT_DOC=false"             // Skip document
+                cppFlags  "-std=c++17"
+                targets   "fmt"
+            }
+        }
+        println(externalNativeBuild.cmake.cppFlags)
+        println(externalNativeBuild.cmake.arguments)
+    }
+
+    // External Native build
+    //  - Use existing CMakeList.txt
+    //  - Give path to CMake. This gradle file should be 
+    //    neighbor of the top level cmake
+    externalNativeBuild {
+        cmake {
+            version "3.10.0+"
+            path "${rootDir}/CMakeLists.txt"
+            // buildStagingDirectory "./build"  // Custom path for cmake output
+        }
+    }
+    
+    sourceSets{
+        // Android Manifest for Gradle
+        main {
+            manifest.srcFile "AndroidManifest.xml"
+        }
+    }
+
+    // https://developer.android.com/studio/build/native-dependencies#build_system_configuration
+    buildFeatures {
+        prefab true
+        prefabPublishing true
+    }
+    prefab {
+        fmt {
+            headers "${rootDir}/include"
+        }
+    }
+}
+
+assemble.doLast
+{
+    // Instead of `ninja install`, Gradle will deploy the files.
+    // We are doing this since FMT is dependent to the ANDROID_STL after build
+    copy {
+        from "build/intermediates/cmake"
+        into "${rootDir}/libs"
+    }
+    // Copy debug binaries
+    copy {
+        from "${rootDir}/libs/debug/obj"
+        into "${rootDir}/libs/debug"
+    }
+    // Copy Release binaries
+    copy {
+        from "${rootDir}/libs/release/obj"
+        into "${rootDir}/libs/release"
+    }
+    // Remove empty directory
+    delete "${rootDir}/libs/debug/obj"
+    delete "${rootDir}/libs/release/obj"
+
+    // Copy AAR files. Notice that the aar is named after the folder of this script.
+    copy {
+        from "build/outputs/aar/support-release.aar"
+        into "${rootDir}/libs"
+        rename "support-release.aar", "fmt-release.aar"
+    }
+    copy {
+        from "build/outputs/aar/support-debug.aar"
+        into "${rootDir}/libs"
+        rename "support-debug.aar", "fmt-debug.aar"
+    }
+}
diff --git a/libs/fmt/support/cmake/FindSetEnv.cmake b/libs/fmt/support/cmake/FindSetEnv.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..4e2da5408ac3c54975ff9acc7091ffdbb6c83b0d
--- /dev/null
+++ b/libs/fmt/support/cmake/FindSetEnv.cmake
@@ -0,0 +1,7 @@
+# A CMake script to find SetEnv.cmd.
+
+find_program(WINSDK_SETENV NAMES SetEnv.cmd
+  PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin")
+if (WINSDK_SETENV AND PRINT_PATH)
+  execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}")
+endif ()
diff --git a/libs/fmt/support/cmake/JoinPaths.cmake b/libs/fmt/support/cmake/JoinPaths.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..32d6d6685c06e84a18ff23c8eec8cf0f9eec9bff
--- /dev/null
+++ b/libs/fmt/support/cmake/JoinPaths.cmake
@@ -0,0 +1,26 @@
+# This module provides function for joining paths
+# known from from most languages
+#
+# Original license:
+# SPDX-License-Identifier: (MIT OR CC0-1.0)
+# Explicit permission given to distribute this module under
+# the terms of the project as described in /LICENSE.rst.
+# Copyright 2020 Jan Tojnar
+# https://github.com/jtojnar/cmake-snips
+#
+# Modelled after Python’s os.path.join
+# https://docs.python.org/3.7/library/os.path.html#os.path.join
+# Windows not supported
+function(join_paths joined_path first_path_segment)
+    set(temp_path "${first_path_segment}")
+    foreach(current_segment IN LISTS ARGN)
+        if(NOT ("${current_segment}" STREQUAL ""))
+            if(IS_ABSOLUTE "${current_segment}")
+                set(temp_path "${current_segment}")
+            else()
+                set(temp_path "${temp_path}/${current_segment}")
+            endif()
+        endif()
+    endforeach()
+    set(${joined_path} "${temp_path}" PARENT_SCOPE)
+endfunction()
diff --git a/libs/fmt/support/cmake/cxx14.cmake b/libs/fmt/support/cmake/cxx14.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..16ff57541b18c005ade84d0c9fe87e0676c3f35c
--- /dev/null
+++ b/libs/fmt/support/cmake/cxx14.cmake
@@ -0,0 +1,70 @@
+# C++14 feature support detection
+
+include(CheckCXXSourceCompiles)
+include(CheckCXXCompilerFlag)
+
+if (NOT CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 11)
+endif()
+message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
+
+if (CMAKE_CXX_STANDARD EQUAL 20)
+  check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
+  check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
+
+  if (has_std_20_flag)
+    set(CXX_STANDARD_FLAG -std=c++20)
+  elseif (has_std_2a_flag)
+    set(CXX_STANDARD_FLAG -std=c++2a)
+  endif ()
+elseif (CMAKE_CXX_STANDARD EQUAL 17)
+  check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
+  check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
+
+  if (has_std_17_flag)
+    set(CXX_STANDARD_FLAG -std=c++17)
+  elseif (has_std_1z_flag)
+    set(CXX_STANDARD_FLAG -std=c++1z)
+  endif ()
+elseif (CMAKE_CXX_STANDARD EQUAL 14)
+  check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
+  check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
+
+  if (has_std_14_flag)
+    set(CXX_STANDARD_FLAG -std=c++14)
+  elseif (has_std_1y_flag)
+    set(CXX_STANDARD_FLAG -std=c++1y)
+  endif ()
+elseif (CMAKE_CXX_STANDARD EQUAL 11)
+  check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
+  check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
+
+  if (has_std_11_flag)
+    set(CXX_STANDARD_FLAG -std=c++11)
+  elseif (has_std_0x_flag)
+    set(CXX_STANDARD_FLAG -std=c++0x)
+  endif ()
+endif ()
+
+set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
+
+# Check if user-defined literals are available
+check_cxx_source_compiles("
+  void operator\"\" _udl(long double);
+  int main() {}"
+  SUPPORTS_USER_DEFINED_LITERALS)
+if (NOT SUPPORTS_USER_DEFINED_LITERALS)
+  set (SUPPORTS_USER_DEFINED_LITERALS OFF)
+endif ()
+
+# Check if <variant> is available
+set(CMAKE_REQUIRED_FLAGS -std=c++1z)
+check_cxx_source_compiles("
+  #include <variant>
+  int main() {}"
+  FMT_HAS_VARIANT)
+if (NOT FMT_HAS_VARIANT)
+  set (FMT_HAS_VARIANT OFF)
+endif ()
+
+set(CMAKE_REQUIRED_FLAGS )
diff --git a/libs/fmt/support/cmake/fmt-config.cmake.in b/libs/fmt/support/cmake/fmt-config.cmake.in
new file mode 100644
index 0000000000000000000000000000000000000000..71e302860bc9c1dbc2db2b02cf341279ff82bfd7
--- /dev/null
+++ b/libs/fmt/support/cmake/fmt-config.cmake.in
@@ -0,0 +1,4 @@
+@PACKAGE_INIT@
+
+include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
+check_required_components(fmt)
diff --git a/libs/fmt/support/cmake/fmt.pc.in b/libs/fmt/support/cmake/fmt.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..29976a8af0b241881fd6ba30c27582f619821c95
--- /dev/null
+++ b/libs/fmt/support/cmake/fmt.pc.in
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@libdir_for_pc_file@
+includedir=@includedir_for_pc_file@
+
+Name: fmt
+Description: A modern formatting library
+Version: @FMT_VERSION@
+Libs: -L${libdir} -l@FMT_LIB_NAME@
+Cflags: -I${includedir}
+
diff --git a/libs/fmt/support/compute-powers.py b/libs/fmt/support/compute-powers.py
new file mode 100644
index 0000000000000000000000000000000000000000..601063d4248f47858f51d854de627d70c307bbe0
--- /dev/null
+++ b/libs/fmt/support/compute-powers.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# Compute 10 ** exp with exp in the range [min_exponent, max_exponent] and print
+# normalized (with most-significant bit equal to 1) significands in hexadecimal.
+
+from __future__ import print_function
+
+min_exponent = -348
+max_exponent = 340
+step = 8
+significand_size = 64
+exp_offset = 2000
+
+class fp:
+    pass
+
+powers = []
+for i, exp in enumerate(range(min_exponent, max_exponent + 1, step)):
+    result = fp()
+    n = 10 ** exp if exp >= 0 else 2 ** exp_offset / 10 ** -exp
+    k = significand_size + 1
+    # Convert to binary and round.
+    binary = '{:b}'.format(n)
+    result.f = (int('{:0<{}}'.format(binary[:k], k), 2) + 1) / 2
+    result.e = len(binary) - (exp_offset if exp < 0 else 0) - significand_size
+    powers.append(result)
+    # Sanity check.
+    exp_offset10 = 400
+    actual = result.f * 10 ** exp_offset10
+    if result.e > 0:
+        actual *= 2 ** result.e
+    else:
+        for j in range(-result.e):
+            actual /= 2
+    expected = 10 ** (exp_offset10 + exp)
+    precision = len('{}'.format(expected)) - len('{}'.format(actual - expected))
+    if precision < 19:
+        print('low precision:', precision)
+        exit(1)
+
+print('Significands:', end='')
+for i, fp in enumerate(powers):
+    if i % 3 == 0:
+        print(end='\n ')
+    print(' {:0<#16x}'.format(fp.f, ), end=',')
+
+print('\n\nExponents:', end='')
+for i, fp in enumerate(powers):
+    if i % 11 == 0:
+        print(end='\n ')
+    print(' {:5}'.format(fp.e), end=',')
+
+print('\n\nMax exponent difference:',
+      max([x.e - powers[i - 1].e for i, x in enumerate(powers)][1:]))
diff --git a/libs/fmt/support/docopt.py b/libs/fmt/support/docopt.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e43f7cef8193b2273e687e27204c98115078d57
--- /dev/null
+++ b/libs/fmt/support/docopt.py
@@ -0,0 +1,581 @@
+"""Pythonic command-line interface parser that will make you smile.
+
+ * http://docopt.org
+ * Repository and issue-tracker: https://github.com/docopt/docopt
+ * Licensed under terms of MIT license (see LICENSE-MIT)
+ * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com
+
+"""
+import sys
+import re
+
+
+__all__ = ['docopt']
+__version__ = '0.6.1'
+
+
+class DocoptLanguageError(Exception):
+
+    """Error in construction of usage-message by developer."""
+
+
+class DocoptExit(SystemExit):
+
+    """Exit in case user invoked program with incorrect arguments."""
+
+    usage = ''
+
+    def __init__(self, message=''):
+        SystemExit.__init__(self, (message + '\n' + self.usage).strip())
+
+
+class Pattern(object):
+
+    def __eq__(self, other):
+        return repr(self) == repr(other)
+
+    def __hash__(self):
+        return hash(repr(self))
+
+    def fix(self):
+        self.fix_identities()
+        self.fix_repeating_arguments()
+        return self
+
+    def fix_identities(self, uniq=None):
+        """Make pattern-tree tips point to same object if they are equal."""
+        if not hasattr(self, 'children'):
+            return self
+        uniq = list(set(self.flat())) if uniq is None else uniq
+        for i, child in enumerate(self.children):
+            if not hasattr(child, 'children'):
+                assert child in uniq
+                self.children[i] = uniq[uniq.index(child)]
+            else:
+                child.fix_identities(uniq)
+
+    def fix_repeating_arguments(self):
+        """Fix elements that should accumulate/increment values."""
+        either = [list(child.children) for child in transform(self).children]
+        for case in either:
+            for e in [child for child in case if case.count(child) > 1]:
+                if type(e) is Argument or type(e) is Option and e.argcount:
+                    if e.value is None:
+                        e.value = []
+                    elif type(e.value) is not list:
+                        e.value = e.value.split()
+                if type(e) is Command or type(e) is Option and e.argcount == 0:
+                    e.value = 0
+        return self
+
+
+def transform(pattern):
+    """Expand pattern into an (almost) equivalent one, but with single Either.
+
+    Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d)
+    Quirks: [-a] => (-a), (-a...) => (-a -a)
+
+    """
+    result = []
+    groups = [[pattern]]
+    while groups:
+        children = groups.pop(0)
+        parents = [Required, Optional, OptionsShortcut, Either, OneOrMore]
+        if any(t in map(type, children) for t in parents):
+            child = [c for c in children if type(c) in parents][0]
+            children.remove(child)
+            if type(child) is Either:
+                for c in child.children:
+                    groups.append([c] + children)
+            elif type(child) is OneOrMore:
+                groups.append(child.children * 2 + children)
+            else:
+                groups.append(child.children + children)
+        else:
+            result.append(children)
+    return Either(*[Required(*e) for e in result])
+
+
+class LeafPattern(Pattern):
+
+    """Leaf/terminal node of a pattern tree."""
+
+    def __init__(self, name, value=None):
+        self.name, self.value = name, value
+
+    def __repr__(self):
+        return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value)
+
+    def flat(self, *types):
+        return [self] if not types or type(self) in types else []
+
+    def match(self, left, collected=None):
+        collected = [] if collected is None else collected
+        pos, match = self.single_match(left)
+        if match is None:
+            return False, left, collected
+        left_ = left[:pos] + left[pos + 1:]
+        same_name = [a for a in collected if a.name == self.name]
+        if type(self.value) in (int, list):
+            if type(self.value) is int:
+                increment = 1
+            else:
+                increment = ([match.value] if type(match.value) is str
+                             else match.value)
+            if not same_name:
+                match.value = increment
+                return True, left_, collected + [match]
+            same_name[0].value += increment
+            return True, left_, collected
+        return True, left_, collected + [match]
+
+
+class BranchPattern(Pattern):
+
+    """Branch/inner node of a pattern tree."""
+
+    def __init__(self, *children):
+        self.children = list(children)
+
+    def __repr__(self):
+        return '%s(%s)' % (self.__class__.__name__,
+                           ', '.join(repr(a) for a in self.children))
+
+    def flat(self, *types):
+        if type(self) in types:
+            return [self]
+        return sum([child.flat(*types) for child in self.children], [])
+
+
+class Argument(LeafPattern):
+
+    def single_match(self, left):
+        for n, pattern in enumerate(left):
+            if type(pattern) is Argument:
+                return n, Argument(self.name, pattern.value)
+        return None, None
+
+    @classmethod
+    def parse(class_, source):
+        name = re.findall('(<\S*?>)', source)[0]
+        value = re.findall('\[default: (.*)\]', source, flags=re.I)
+        return class_(name, value[0] if value else None)
+
+
+class Command(Argument):
+
+    def __init__(self, name, value=False):
+        self.name, self.value = name, value
+
+    def single_match(self, left):
+        for n, pattern in enumerate(left):
+            if type(pattern) is Argument:
+                if pattern.value == self.name:
+                    return n, Command(self.name, True)
+                else:
+                    break
+        return None, None
+
+
+class Option(LeafPattern):
+
+    def __init__(self, short=None, long=None, argcount=0, value=False):
+        assert argcount in (0, 1)
+        self.short, self.long, self.argcount = short, long, argcount
+        self.value = None if value is False and argcount else value
+
+    @classmethod
+    def parse(class_, option_description):
+        short, long, argcount, value = None, None, 0, False
+        options, _, description = option_description.strip().partition('  ')
+        options = options.replace(',', ' ').replace('=', ' ')
+        for s in options.split():
+            if s.startswith('--'):
+                long = s
+            elif s.startswith('-'):
+                short = s
+            else:
+                argcount = 1
+        if argcount:
+            matched = re.findall('\[default: (.*)\]', description, flags=re.I)
+            value = matched[0] if matched else None
+        return class_(short, long, argcount, value)
+
+    def single_match(self, left):
+        for n, pattern in enumerate(left):
+            if self.name == pattern.name:
+                return n, pattern
+        return None, None
+
+    @property
+    def name(self):
+        return self.long or self.short
+
+    def __repr__(self):
+        return 'Option(%r, %r, %r, %r)' % (self.short, self.long,
+                                           self.argcount, self.value)
+
+
+class Required(BranchPattern):
+
+    def match(self, left, collected=None):
+        collected = [] if collected is None else collected
+        l = left
+        c = collected
+        for pattern in self.children:
+            matched, l, c = pattern.match(l, c)
+            if not matched:
+                return False, left, collected
+        return True, l, c
+
+
+class Optional(BranchPattern):
+
+    def match(self, left, collected=None):
+        collected = [] if collected is None else collected
+        for pattern in self.children:
+            m, left, collected = pattern.match(left, collected)
+        return True, left, collected
+
+
+class OptionsShortcut(Optional):
+
+    """Marker/placeholder for [options] shortcut."""
+
+
+class OneOrMore(BranchPattern):
+
+    def match(self, left, collected=None):
+        assert len(self.children) == 1
+        collected = [] if collected is None else collected
+        l = left
+        c = collected
+        l_ = None
+        matched = True
+        times = 0
+        while matched:
+            # could it be that something didn't match but changed l or c?
+            matched, l, c = self.children[0].match(l, c)
+            times += 1 if matched else 0
+            if l_ == l:
+                break
+            l_ = l
+        if times >= 1:
+            return True, l, c
+        return False, left, collected
+
+
+class Either(BranchPattern):
+
+    def match(self, left, collected=None):
+        collected = [] if collected is None else collected
+        outcomes = []
+        for pattern in self.children:
+            matched, _, _ = outcome = pattern.match(left, collected)
+            if matched:
+                outcomes.append(outcome)
+        if outcomes:
+            return min(outcomes, key=lambda outcome: len(outcome[1]))
+        return False, left, collected
+
+
+class Tokens(list):
+
+    def __init__(self, source, error=DocoptExit):
+        self += source.split() if hasattr(source, 'split') else source
+        self.error = error
+
+    @staticmethod
+    def from_pattern(source):
+        source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source)
+        source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s]
+        return Tokens(source, error=DocoptLanguageError)
+
+    def move(self):
+        return self.pop(0) if len(self) else None
+
+    def current(self):
+        return self[0] if len(self) else None
+
+
+def parse_long(tokens, options):
+    """long ::= '--' chars [ ( ' ' | '=' ) chars ] ;"""
+    long, eq, value = tokens.move().partition('=')
+    assert long.startswith('--')
+    value = None if eq == value == '' else value
+    similar = [o for o in options if o.long == long]
+    if tokens.error is DocoptExit and similar == []:  # if no exact match
+        similar = [o for o in options if o.long and o.long.startswith(long)]
+    if len(similar) > 1:  # might be simply specified ambiguously 2+ times?
+        raise tokens.error('%s is not a unique prefix: %s?' %
+                           (long, ', '.join(o.long for o in similar)))
+    elif len(similar) < 1:
+        argcount = 1 if eq == '=' else 0
+        o = Option(None, long, argcount)
+        options.append(o)
+        if tokens.error is DocoptExit:
+            o = Option(None, long, argcount, value if argcount else True)
+    else:
+        o = Option(similar[0].short, similar[0].long,
+                   similar[0].argcount, similar[0].value)
+        if o.argcount == 0:
+            if value is not None:
+                raise tokens.error('%s must not have an argument' % o.long)
+        else:
+            if value is None:
+                if tokens.current() in [None, '--']:
+                    raise tokens.error('%s requires argument' % o.long)
+                value = tokens.move()
+        if tokens.error is DocoptExit:
+            o.value = value if value is not None else True
+    return [o]
+
+
+def parse_shorts(tokens, options):
+    """shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;"""
+    token = tokens.move()
+    assert token.startswith('-') and not token.startswith('--')
+    left = token.lstrip('-')
+    parsed = []
+    while left != '':
+        short, left = '-' + left[0], left[1:]
+        similar = [o for o in options if o.short == short]
+        if len(similar) > 1:
+            raise tokens.error('%s is specified ambiguously %d times' %
+                               (short, len(similar)))
+        elif len(similar) < 1:
+            o = Option(short, None, 0)
+            options.append(o)
+            if tokens.error is DocoptExit:
+                o = Option(short, None, 0, True)
+        else:  # why copying is necessary here?
+            o = Option(short, similar[0].long,
+                       similar[0].argcount, similar[0].value)
+            value = None
+            if o.argcount != 0:
+                if left == '':
+                    if tokens.current() in [None, '--']:
+                        raise tokens.error('%s requires argument' % short)
+                    value = tokens.move()
+                else:
+                    value = left
+                    left = ''
+            if tokens.error is DocoptExit:
+                o.value = value if value is not None else True
+        parsed.append(o)
+    return parsed
+
+
+def parse_pattern(source, options):
+    tokens = Tokens.from_pattern(source)
+    result = parse_expr(tokens, options)
+    if tokens.current() is not None:
+        raise tokens.error('unexpected ending: %r' % ' '.join(tokens))
+    return Required(*result)
+
+
+def parse_expr(tokens, options):
+    """expr ::= seq ( '|' seq )* ;"""
+    seq = parse_seq(tokens, options)
+    if tokens.current() != '|':
+        return seq
+    result = [Required(*seq)] if len(seq) > 1 else seq
+    while tokens.current() == '|':
+        tokens.move()
+        seq = parse_seq(tokens, options)
+        result += [Required(*seq)] if len(seq) > 1 else seq
+    return [Either(*result)] if len(result) > 1 else result
+
+
+def parse_seq(tokens, options):
+    """seq ::= ( atom [ '...' ] )* ;"""
+    result = []
+    while tokens.current() not in [None, ']', ')', '|']:
+        atom = parse_atom(tokens, options)
+        if tokens.current() == '...':
+            atom = [OneOrMore(*atom)]
+            tokens.move()
+        result += atom
+    return result
+
+
+def parse_atom(tokens, options):
+    """atom ::= '(' expr ')' | '[' expr ']' | 'options'
+             | long | shorts | argument | command ;
+    """
+    token = tokens.current()
+    result = []
+    if token in '([':
+        tokens.move()
+        matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token]
+        result = pattern(*parse_expr(tokens, options))
+        if tokens.move() != matching:
+            raise tokens.error("unmatched '%s'" % token)
+        return [result]
+    elif token == 'options':
+        tokens.move()
+        return [OptionsShortcut()]
+    elif token.startswith('--') and token != '--':
+        return parse_long(tokens, options)
+    elif token.startswith('-') and token not in ('-', '--'):
+        return parse_shorts(tokens, options)
+    elif token.startswith('<') and token.endswith('>') or token.isupper():
+        return [Argument(tokens.move())]
+    else:
+        return [Command(tokens.move())]
+
+
+def parse_argv(tokens, options, options_first=False):
+    """Parse command-line argument vector.
+
+    If options_first:
+        argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ;
+    else:
+        argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ;
+
+    """
+    parsed = []
+    while tokens.current() is not None:
+        if tokens.current() == '--':
+            return parsed + [Argument(None, v) for v in tokens]
+        elif tokens.current().startswith('--'):
+            parsed += parse_long(tokens, options)
+        elif tokens.current().startswith('-') and tokens.current() != '-':
+            parsed += parse_shorts(tokens, options)
+        elif options_first:
+            return parsed + [Argument(None, v) for v in tokens]
+        else:
+            parsed.append(Argument(None, tokens.move()))
+    return parsed
+
+
+def parse_defaults(doc):
+    defaults = []
+    for s in parse_section('options:', doc):
+        # FIXME corner case "bla: options: --foo"
+        _, _, s = s.partition(':')  # get rid of "options:"
+        split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:]
+        split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])]
+        options = [Option.parse(s) for s in split if s.startswith('-')]
+        defaults += options
+    return defaults
+
+
+def parse_section(name, source):
+    pattern = re.compile('^([^\n]*' + name + '[^\n]*\n?(?:[ \t].*?(?:\n|$))*)',
+                         re.IGNORECASE | re.MULTILINE)
+    return [s.strip() for s in pattern.findall(source)]
+
+
+def formal_usage(section):
+    _, _, section = section.partition(':')  # drop "usage:"
+    pu = section.split()
+    return '( ' + ' '.join(') | (' if s == pu[0] else s for s in pu[1:]) + ' )'
+
+
+def extras(help, version, options, doc):
+    if help and any((o.name in ('-h', '--help')) and o.value for o in options):
+        print(doc.strip("\n"))
+        sys.exit()
+    if version and any(o.name == '--version' and o.value for o in options):
+        print(version)
+        sys.exit()
+
+
+class Dict(dict):
+    def __repr__(self):
+        return '{%s}' % ',\n '.join('%r: %r' % i for i in sorted(self.items()))
+
+
+def docopt(doc, argv=None, help=True, version=None, options_first=False):
+    """Parse `argv` based on command-line interface described in `doc`.
+
+    `docopt` creates your command-line interface based on its
+    description that you pass as `doc`. Such description can contain
+    --options, <positional-argument>, commands, which could be
+    [optional], (required), (mutually | exclusive) or repeated...
+
+    Parameters
+    ----------
+    doc : str
+        Description of your command-line interface.
+    argv : list of str, optional
+        Argument vector to be parsed. sys.argv[1:] is used if not
+        provided.
+    help : bool (default: True)
+        Set to False to disable automatic help on -h or --help
+        options.
+    version : any object
+        If passed, the object will be printed if --version is in
+        `argv`.
+    options_first : bool (default: False)
+        Set to True to require options precede positional arguments,
+        i.e. to forbid options and positional arguments intermix.
+
+    Returns
+    -------
+    args : dict
+        A dictionary, where keys are names of command-line elements
+        such as e.g. "--verbose" and "<path>", and values are the
+        parsed values of those elements.
+
+    Example
+    -------
+    >>> from docopt import docopt
+    >>> doc = '''
+    ... Usage:
+    ...     my_program tcp <host> <port> [--timeout=<seconds>]
+    ...     my_program serial <port> [--baud=<n>] [--timeout=<seconds>]
+    ...     my_program (-h | --help | --version)
+    ...
+    ... Options:
+    ...     -h, --help  Show this screen and exit.
+    ...     --baud=<n>  Baudrate [default: 9600]
+    ... '''
+    >>> argv = ['tcp', '127.0.0.1', '80', '--timeout', '30']
+    >>> docopt(doc, argv)
+    {'--baud': '9600',
+     '--help': False,
+     '--timeout': '30',
+     '--version': False,
+     '<host>': '127.0.0.1',
+     '<port>': '80',
+     'serial': False,
+     'tcp': True}
+
+    See also
+    --------
+    * For video introduction see http://docopt.org
+    * Full documentation is available in README.rst as well as online
+      at https://github.com/docopt/docopt#readme
+
+    """
+    argv = sys.argv[1:] if argv is None else argv
+
+    usage_sections = parse_section('usage:', doc)
+    if len(usage_sections) == 0:
+        raise DocoptLanguageError('"usage:" (case-insensitive) not found.')
+    if len(usage_sections) > 1:
+        raise DocoptLanguageError('More than one "usage:" (case-insensitive).')
+    DocoptExit.usage = usage_sections[0]
+
+    options = parse_defaults(doc)
+    pattern = parse_pattern(formal_usage(DocoptExit.usage), options)
+    # [default] syntax for argument is disabled
+    #for a in pattern.flat(Argument):
+    #    same_name = [d for d in arguments if d.name == a.name]
+    #    if same_name:
+    #        a.value = same_name[0].value
+    argv = parse_argv(Tokens(argv), list(options), options_first)
+    pattern_options = set(pattern.flat(Option))
+    for options_shortcut in pattern.flat(OptionsShortcut):
+        doc_options = parse_defaults(doc)
+        options_shortcut.children = list(set(doc_options) - pattern_options)
+        #if any_options:
+        #    options_shortcut.children += [Option(o.short, o.long, o.argcount)
+        #                    for o in argv if type(o) is Option]
+    extras(help, version, argv, doc)
+    matched, left, collected = pattern.fix().match(argv)
+    if matched and left == []:  # better error message if left?
+        return Dict((a.name, a.value) for a in (pattern.flat() + collected))
+    raise DocoptExit()
diff --git a/libs/fmt/support/manage.py b/libs/fmt/support/manage.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f2706694002d719f072ce1a1ab2f1e0d8d6bbf8
--- /dev/null
+++ b/libs/fmt/support/manage.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+
+"""Manage site and releases.
+
+Usage:
+  manage.py release [<branch>]
+  manage.py site
+
+For the release command $FMT_TOKEN should contain a GitHub personal access token
+obtained from https://github.com/settings/tokens.
+"""
+
+from __future__ import print_function
+import datetime, docopt, errno, fileinput, json, os
+import re, requests, shutil, sys, tempfile
+from contextlib import contextmanager
+from distutils.version import LooseVersion
+from subprocess import check_call
+
+
+class Git:
+    def __init__(self, dir):
+        self.dir = dir
+
+    def call(self, method, args, **kwargs):
+        return check_call(['git', method] + list(args), **kwargs)
+
+    def add(self, *args):
+        return self.call('add', args, cwd=self.dir)
+
+    def checkout(self, *args):
+        return self.call('checkout', args, cwd=self.dir)
+
+    def clean(self, *args):
+        return self.call('clean', args, cwd=self.dir)
+
+    def clone(self, *args):
+        return self.call('clone', list(args) + [self.dir])
+
+    def commit(self, *args):
+        return self.call('commit', args, cwd=self.dir)
+
+    def pull(self, *args):
+        return self.call('pull', args, cwd=self.dir)
+
+    def push(self, *args):
+        return self.call('push', args, cwd=self.dir)
+
+    def reset(self, *args):
+        return self.call('reset', args, cwd=self.dir)
+
+    def update(self, *args):
+        clone = not os.path.exists(self.dir)
+        if clone:
+            self.clone(*args)
+        return clone
+
+
+def clean_checkout(repo, branch):
+    repo.clean('-f', '-d')
+    repo.reset('--hard')
+    repo.checkout(branch)
+
+
+class Runner:
+    def __init__(self, cwd):
+        self.cwd = cwd
+
+    def __call__(self, *args, **kwargs):
+        kwargs['cwd'] = kwargs.get('cwd', self.cwd)
+        check_call(args, **kwargs)
+
+
+def create_build_env():
+    """Create a build environment."""
+    class Env:
+        pass
+    env = Env()
+
+    # Import the documentation build module.
+    env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    sys.path.insert(0, os.path.join(env.fmt_dir, 'doc'))
+    import build
+
+    env.build_dir = 'build'
+    env.versions = build.versions
+
+    # Virtualenv and repos are cached to speed up builds.
+    build.create_build_env(os.path.join(env.build_dir, 'virtualenv'))
+
+    env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
+    return env
+
+
+@contextmanager
+def rewrite(filename):
+    class Buffer:
+        pass
+    buffer = Buffer()
+    if not os.path.exists(filename):
+        buffer.data = ''
+        yield buffer
+        return
+    with open(filename) as f:
+        buffer.data = f.read()
+    yield buffer
+    with open(filename, 'w') as f:
+        f.write(buffer.data)
+
+
+fmt_repo_url = 'git@github.com:fmtlib/fmt'
+
+
+def update_site(env):
+    env.fmt_repo.update(fmt_repo_url)
+
+    doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io'))
+    doc_repo.update('git@github.com:fmtlib/fmtlib.github.io')
+
+    for version in env.versions:
+        clean_checkout(env.fmt_repo, version)
+        target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
+        # Remove the old theme.
+        for entry in os.listdir(target_doc_dir):
+            path = os.path.join(target_doc_dir, entry)
+            if os.path.isdir(path):
+                shutil.rmtree(path)
+        # Copy the new theme.
+        for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap',
+                      'conf.py', 'fmt.less']:
+            src = os.path.join(env.fmt_dir, 'doc', entry)
+            dst = os.path.join(target_doc_dir, entry)
+            copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile
+            copy(src, dst)
+        # Rename index to contents.
+        contents = os.path.join(target_doc_dir, 'contents.rst')
+        if not os.path.exists(contents):
+            os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
+        # Fix issues in reference.rst/api.rst.
+        for filename in ['reference.rst', 'api.rst', 'index.rst']:
+            pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
+            with rewrite(os.path.join(target_doc_dir, filename)) as b:
+                b.data = b.data.replace('std::ostream &', 'std::ostream&')
+                b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
+                b.data = b.data.replace('std::FILE*', 'std::FILE *')
+                b.data = b.data.replace('unsigned int', 'unsigned')
+                #b.data = b.data.replace('operator""_', 'operator"" _')
+                b.data = b.data.replace(
+                    'format_to_n(OutputIt, size_t, string_view, Args&&',
+                    'format_to_n(OutputIt, size_t, const S&, const Args&')
+                b.data = b.data.replace(
+                    'format_to_n(OutputIt, std::size_t, string_view, Args&&',
+                    'format_to_n(OutputIt, std::size_t, const S&, const Args&')
+                if version == ('3.0.2'):
+                    b.data = b.data.replace(
+                        'fprintf(std::ostream&', 'fprintf(std::ostream &')
+                if version == ('5.3.0'):
+                    b.data = b.data.replace(
+                        'format_to(OutputIt, const S&, const Args&...)',
+                        'format_to(OutputIt, const S &, const Args &...)')
+                if version.startswith('5.') or version.startswith('6.'):
+                    b.data = b.data.replace(', size_t', ', std::size_t')
+                if version.startswith('7.'):
+                    b.data = b.data.replace(', std::size_t', ', size_t')
+                    b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
+                b.data = b.data.replace('aa long', 'a long')
+                b.data = b.data.replace('serveral', 'several')
+                if version.startswith('6.2.'):
+                    b.data = b.data.replace(
+                        'vformat(const S&, basic_format_args<' +
+                        'buffer_context<Char>>)',
+                        'vformat(const S&, basic_format_args<' +
+                        'buffer_context<type_identity_t<Char>>>)')
+        # Fix a broken link in index.rst.
+        index = os.path.join(target_doc_dir, 'index.rst')
+        with rewrite(index) as b:
+            b.data = b.data.replace(
+                'doc/latest/index.html#format-string-syntax', 'syntax.html')
+        # Build the docs.
+        html_dir = os.path.join(env.build_dir, 'html')
+        if os.path.exists(html_dir):
+            shutil.rmtree(html_dir)
+        include_dir = env.fmt_repo.dir
+        if LooseVersion(version) >= LooseVersion('5.0.0'):
+            include_dir = os.path.join(include_dir, 'include', 'fmt')
+        elif LooseVersion(version) >= LooseVersion('3.0.0'):
+            include_dir = os.path.join(include_dir, 'fmt')
+        import build
+        build.build_docs(version, doc_dir=target_doc_dir,
+                         include_dir=include_dir, work_dir=env.build_dir)
+        shutil.rmtree(os.path.join(html_dir, '.doctrees'))
+        # Create symlinks for older versions.
+        for link, target in {'index': 'contents', 'api': 'reference'}.items():
+            link = os.path.join(html_dir, link) + '.html'
+            target += '.html'
+            if os.path.exists(os.path.join(html_dir, target)) and \
+               not os.path.exists(link):
+                os.symlink(target, link)
+        # Copy docs to the website.
+        version_doc_dir = os.path.join(doc_repo.dir, version)
+        try:
+            shutil.rmtree(version_doc_dir)
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+        shutil.move(html_dir, version_doc_dir)
+
+
+def release(args):
+    env = create_build_env()
+    fmt_repo = env.fmt_repo
+
+    branch = args.get('<branch>')
+    if branch is None:
+        branch = 'master'
+    if not fmt_repo.update('-b', branch, fmt_repo_url):
+        clean_checkout(fmt_repo, branch)
+
+    # Convert changelog from RST to GitHub-flavored Markdown and get the
+    # version.
+    changelog = 'ChangeLog.rst'
+    changelog_path = os.path.join(fmt_repo.dir, changelog)
+    import rst2md
+    changes, version = rst2md.convert(changelog_path)
+    cmakelists = 'CMakeLists.txt'
+    for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
+                                inplace=True):
+        prefix = 'set(FMT_VERSION '
+        if line.startswith(prefix):
+            line = prefix + version + ')\n'
+        sys.stdout.write(line)
+
+    # Update the version in the changelog.
+    title_len = 0
+    for line in fileinput.input(changelog_path, inplace=True):
+        if line.decode('utf-8').startswith(version + ' - TBD'):
+            line = version + ' - ' + datetime.date.today().isoformat()
+            title_len = len(line)
+            line += '\n'
+        elif title_len:
+            line = '-' * title_len + '\n'
+            title_len = 0
+        sys.stdout.write(line)
+
+    # Add the version to the build script.
+    script = os.path.join('doc', 'build.py')
+    script_path = os.path.join(fmt_repo.dir, script)
+    for line in fileinput.input(script_path, inplace=True):
+      m = re.match(r'( *versions = )\[(.+)\]', line)
+      if m:
+        line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version)
+      sys.stdout.write(line)
+
+    fmt_repo.checkout('-B', 'release')
+    fmt_repo.add(changelog, cmakelists, script)
+    fmt_repo.commit('-m', 'Update version')
+
+    # Build the docs and package.
+    run = Runner(fmt_repo.dir)
+    run('cmake', '.')
+    run('make', 'doc', 'package_source')
+    update_site(env)
+
+    # Create a release on GitHub.
+    fmt_repo.push('origin', 'release')
+    params = {'access_token': os.getenv('FMT_TOKEN')}
+    r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
+                      params=params,
+                      data=json.dumps({'tag_name': version,
+                                       'target_commitish': 'release',
+                                       'body': changes, 'draft': True}))
+    if r.status_code != 201:
+        raise Exception('Failed to create a release ' + str(r))
+    id = r.json()['id']
+    uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
+    package = 'fmt-{}.zip'.format(version)
+    r = requests.post(
+        '{}/{}/assets?name={}'.format(uploads_url, id, package),
+        headers={'Content-Type': 'application/zip'},
+        params=params, data=open('build/fmt/' + package, 'rb'))
+    if r.status_code != 201:
+        raise Exception('Failed to upload an asset ' + str(r))
+
+
+if __name__ == '__main__':
+    args = docopt.docopt(__doc__)
+    if args.get('release'):
+        release(args)
+    elif args.get('site'):
+        update_site(create_build_env())
diff --git a/libs/fmt/support/rst2md.py b/libs/fmt/support/rst2md.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf170fe2bd4c2479e194c091aeea0773414efd48
--- /dev/null
+++ b/libs/fmt/support/rst2md.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# reStructuredText (RST) to GitHub-flavored Markdown converter
+
+import re, sys
+from docutils import core, nodes, writers
+
+
+def is_github_ref(node):
+    return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
+
+
+class Translator(nodes.NodeVisitor):
+    def __init__(self, document):
+        nodes.NodeVisitor.__init__(self, document)
+        self.output = ''
+        self.indent = 0
+        self.preserve_newlines = False
+
+    def write(self, text):
+        self.output += text.replace('\n', '\n' + ' ' * self.indent)
+
+    def visit_document(self, node):
+        pass
+
+    def depart_document(self, node):
+        pass
+
+    def visit_section(self, node):
+        pass
+
+    def depart_section(self, node):
+        # Skip all sections except the first one.
+        raise nodes.StopTraversal
+
+    def visit_title(self, node):
+        self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
+        raise nodes.SkipChildren
+
+    def visit_title_reference(self, node):
+        raise Exception(node)
+
+    def depart_title(self, node):
+        pass
+
+    def visit_Text(self, node):
+        if not self.preserve_newlines:
+            node = node.replace('\n', ' ')
+        self.write(node)
+
+    def depart_Text(self, node):
+        pass
+
+    def visit_bullet_list(self, node):
+        pass
+
+    def depart_bullet_list(self, node):
+        pass
+
+    def visit_list_item(self, node):
+        self.write('* ')
+        self.indent += 2
+
+    def depart_list_item(self, node):
+        self.indent -= 2
+        self.write('\n\n')
+
+    def visit_paragraph(self, node):
+        pass
+
+    def depart_paragraph(self, node):
+        pass
+
+    def visit_reference(self, node):
+        if not is_github_ref(node):
+            self.write('[')
+
+    def depart_reference(self, node):
+        if not is_github_ref(node):
+            self.write('](' + node['refuri'] + ')')
+
+    def visit_target(self, node):
+        pass
+
+    def depart_target(self, node):
+        pass
+
+    def visit_literal(self, node):
+        self.write('`')
+
+    def depart_literal(self, node):
+        self.write('`')
+
+    def visit_literal_block(self, node):
+        self.write('\n\n```')
+        if 'c++' in node['classes']:
+            self.write('c++')
+        self.write('\n')
+        self.preserve_newlines = True
+
+    def depart_literal_block(self, node):
+        self.write('\n```\n')
+        self.preserve_newlines = False
+
+    def visit_inline(self, node):
+        pass
+
+    def depart_inline(self, node):
+        pass
+
+    def visit_image(self, node):
+        self.write('![](' + node['uri'] + ')')
+
+    def depart_image(self, node):
+        pass
+
+    def write_row(self, row, widths):
+        for i, entry in enumerate(row):
+            text = entry[0][0] if len(entry) > 0 else ''
+            if i != 0:
+                self.write('|')
+            self.write('{:{}}'.format(text, widths[i]))
+        self.write('\n')
+
+    def visit_table(self, node):
+        table = node.children[0]
+        colspecs = table[:-2]
+        thead = table[-2]
+        tbody = table[-1]
+        widths = [int(cs['colwidth']) for cs in colspecs]
+        sep = '|'.join(['-' * w for w in widths]) + '\n'
+        self.write('\n\n')
+        self.write_row(thead[0], widths)
+        self.write(sep)
+        for row in tbody:
+            self.write_row(row, widths)
+        raise nodes.SkipChildren
+
+    def depart_table(self, node):
+        pass
+
+class MDWriter(writers.Writer):
+    """GitHub-flavored markdown writer"""
+
+    supported = ('md',)
+    """Formats this writer supports."""
+
+    def translate(self):
+        translator = Translator(self.document)
+        self.document.walkabout(translator)
+        self.output = (translator.output, translator.version)
+
+
+def convert(rst_path):
+    """Converts RST file to Markdown."""
+    return core.publish_file(source_path=rst_path, writer=MDWriter())
+
+
+if __name__ == '__main__':
+    convert(sys.argv[1])
diff --git a/libs/fmt/support/rtd/conf.py b/libs/fmt/support/rtd/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..124fb9d4c84c3a15623552056a2adafdfffe46b7
--- /dev/null
+++ b/libs/fmt/support/rtd/conf.py
@@ -0,0 +1,7 @@
+# Sphinx configuration for readthedocs.
+
+import os, sys
+
+master_doc = 'index'
+html_theme = 'theme'
+html_theme_path = ["."]
diff --git a/libs/fmt/support/rtd/index.rst b/libs/fmt/support/rtd/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..7c88322f6246a9f66b9ade5f62d70e76bbf0d347
--- /dev/null
+++ b/libs/fmt/support/rtd/index.rst
@@ -0,0 +1,2 @@
+If you are not redirected automatically, follow the
+`link to the fmt documentation <https://fmt.dev/latest/>`_.
diff --git a/libs/fmt/support/rtd/theme/layout.html b/libs/fmt/support/rtd/theme/layout.html
new file mode 100644
index 0000000000000000000000000000000000000000..29ebc55bb42e51977b1aba567d629efafcfa2eae
--- /dev/null
+++ b/libs/fmt/support/rtd/theme/layout.html
@@ -0,0 +1,17 @@
+{% extends "basic/layout.html" %}
+
+{% block extrahead %}
+<meta charset="UTF-8">
+<meta http-equiv="refresh" content="1;url=https://fmt.dev/latest/">
+<script type="text/javascript">
+    window.location.href = "https://fmt.dev/latest/"
+</script>
+<title>Page Redirection</title>
+{% endblock %}
+
+{% block document %}
+If you are not redirected automatically, follow the <a href='https://fmt.dev/latest/'>link to the fmt documentation</a>.
+{% endblock %}
+
+{% block footer %}
+{% endblock %}
diff --git a/libs/fmt/support/rtd/theme/theme.conf b/libs/fmt/support/rtd/theme/theme.conf
new file mode 100644
index 0000000000000000000000000000000000000000..89e03bbdac5e2ff5c08eb4bc4918067516932c5c
--- /dev/null
+++ b/libs/fmt/support/rtd/theme/theme.conf
@@ -0,0 +1,2 @@
+[theme]
+inherit = basic
diff --git a/libs/fmt/test/CMakeLists.txt b/libs/fmt/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f1dcf16a3b92d3f9fe9a4808aad01e1d6b111470
--- /dev/null
+++ b/libs/fmt/test/CMakeLists.txt
@@ -0,0 +1,206 @@
+add_subdirectory(gtest)
+
+set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
+add_library(test-main STATIC ${TEST_MAIN_SRC})
+target_link_libraries(test-main gtest fmt)
+
+include(CheckCXXCompilerFlag)
+
+# Workaround GTest bug https://github.com/google/googletest/issues/705.
+check_cxx_compiler_flag(
+  -fno-delete-null-pointer-checks HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
+if (HAVE_FNO_DELETE_NULL_POINTER_CHECKS)
+  target_compile_options(test-main PUBLIC -fno-delete-null-pointer-checks)
+endif ()
+
+# Use less strict pedantic flags for the tests because GMock doesn't compile
+# cleanly with -pedantic and -std=c++98.
+if (CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
+    #set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -Wno-long-long -Wno-variadic-macros)
+endif ()
+
+function(add_fmt_executable name)
+  add_executable(${name} ${ARGN})
+  if (MINGW)
+    target_link_libraries(${name} -static-libgcc -static-libstdc++)
+  endif ()
+endfunction()
+
+# Adds a test.
+# Usage: add_fmt_test(name srcs...)
+function(add_fmt_test name)
+  cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY" "" "" ${ARGN})
+
+  set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS})
+  set(libs test-main)
+  if (ADD_FMT_TEST_HEADER_ONLY)
+    set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc)
+    set(libs gtest fmt-header-only)
+    if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+      set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
+    endif ()
+  endif ()
+  add_fmt_executable(${name} ${sources})
+  target_link_libraries(${name} ${libs})
+
+  # Define if certain C++ features can be used.
+  if (FMT_PEDANTIC)
+    target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+  endif ()
+  if (FMT_WERROR)
+    target_compile_options(${name} PRIVATE ${WERROR_FLAG})
+  endif ()
+  add_test(NAME ${name} COMMAND ${name})
+endfunction()
+
+add_fmt_test(args-test)
+add_fmt_test(assert-test)
+add_fmt_test(chrono-test)
+add_fmt_test(color-test)
+add_fmt_test(core-test)
+add_fmt_test(gtest-extra-test)
+add_fmt_test(format-test mock-allocator.h)
+if (MSVC)
+  target_compile_options(format-test PRIVATE /bigobj)
+endif ()
+if (NOT (MSVC AND BUILD_SHARED_LIBS))
+  add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
+endif ()
+add_fmt_test(locale-test)
+add_fmt_test(ostream-test)
+add_fmt_test(compile-test)
+add_fmt_test(printf-test)
+add_fmt_test(ranges-test)
+add_fmt_test(scan-test)
+
+if (NOT MSVC)
+  # FMT_ENFORCE_COMPILE_STRING is not supported under MSVC due to compiler bugs.
+  add_fmt_test(enforce-checks-test)
+  target_compile_definitions(enforce-checks-test PRIVATE
+                             -DFMT_ENFORCE_COMPILE_STRING)
+endif ()
+
+if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
+  foreach (flag_var
+			 CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+			 CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+	if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
+	  set(MSVC_STATIC_RUNTIME ON)
+	  break()
+	endif()
+  endforeach()
+endif()
+
+if (NOT MSVC_STATIC_RUNTIME)
+  add_fmt_executable(posix-mock-test
+    posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
+  target_include_directories(
+    posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
+  target_link_libraries(posix-mock-test gtest)
+  if (FMT_PEDANTIC)
+    target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+  endif ()
+  if (HAVE_STRTOD_L)
+    target_compile_definitions(posix-mock-test PRIVATE FMT_LOCALE)
+  endif ()
+  add_test(NAME posix-mock-test COMMAND posix-mock-test)
+  add_fmt_test(os-test)
+endif ()
+
+message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
+
+if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
+  # MSVC fails to compile GMock when C++17 is enabled.
+  if (FMT_HAS_VARIANT AND NOT MSVC)
+    add_fmt_test(std-format-test)
+    set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17)
+  endif ()
+
+  # Test that the library can be compiled with exceptions disabled.
+  # -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822.
+  if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+    check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
+  endif ()
+  if (HAVE_FNO_EXCEPTIONS_FLAG)
+    add_library(noexception-test ../src/format.cc)
+    target_include_directories(
+      noexception-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
+    target_compile_options(noexception-test PRIVATE -fno-exceptions)
+    if (FMT_PEDANTIC)
+      target_compile_options(noexception-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+    endif ()
+  endif ()
+
+  # Test that the library compiles without locale.
+  add_library(nolocale-test ../src/format.cc)
+  target_include_directories(
+    nolocale-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
+  target_compile_definitions(
+    nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
+
+  add_test(compile-error-test ${CMAKE_CTEST_COMMAND}
+    --build-and-test
+    "${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
+    "${CMAKE_CURRENT_BINARY_DIR}/compile-error-test"
+    --build-generator ${CMAKE_GENERATOR}
+    --build-makeprogram ${CMAKE_MAKE_PROGRAM}
+    --build-options
+    "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
+    "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
+    "-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}"
+    "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
+    "-DSUPPORTS_USER_DEFINED_LITERALS=${SUPPORTS_USER_DEFINED_LITERALS}")
+endif ()
+
+# These tests are disabled on Windows because they take too long.
+if (FMT_PEDANTIC AND NOT WIN32)
+  # Test if the targets are found from the build directory.
+  add_test(find-package-test ${CMAKE_CTEST_COMMAND}
+    -C ${CMAKE_BUILD_TYPE}
+    --build-and-test
+    "${CMAKE_CURRENT_SOURCE_DIR}/find-package-test"
+    "${CMAKE_CURRENT_BINARY_DIR}/find-package-test"
+    --build-generator ${CMAKE_GENERATOR}
+    --build-makeprogram ${CMAKE_MAKE_PROGRAM}
+    --build-options
+    "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
+    "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
+    "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
+    "-DFMT_DIR=${PROJECT_BINARY_DIR}"
+    "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
+    "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
+
+  # Test if the targets are found when add_subdirectory is used.
+  add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND}
+    -C ${CMAKE_BUILD_TYPE}
+    --build-and-test
+    "${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test"
+    "${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test"
+    --build-generator ${CMAKE_GENERATOR}
+    --build-makeprogram ${CMAKE_MAKE_PROGRAM}
+    --build-options
+    "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
+    "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
+    "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
+    "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
+endif ()
+
+# Activate optional CUDA tests if CUDA is found. For version selection see
+# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
+if (FMT_CUDA_TEST)
+  if (${CMAKE_VERSION} VERSION_LESS 3.15)
+    find_package(CUDA 9.0)
+  else ()
+    include(CheckLanguage)
+    check_language(CUDA)
+    if (CMAKE_CUDA_COMPILER)
+      enable_language(CUDA OPTIONAL)
+      set(CUDA_FOUND TRUE)
+    endif ()
+  endif ()
+
+  if (CUDA_FOUND)
+    add_subdirectory(cuda-test)
+    add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
+  endif ()
+endif ()
diff --git a/libs/fmt/test/add-subdirectory-test/CMakeLists.txt b/libs/fmt/test/add-subdirectory-test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9cc4b0e97fe8d967be4cf4826d48d20706c90a1a
--- /dev/null
+++ b/libs/fmt/test/add-subdirectory-test/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.1...3.18)
+
+project(fmt-test)
+
+add_subdirectory(../.. fmt)
+
+add_executable(library-test "main.cc")
+target_link_libraries(library-test fmt::fmt)
+target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+target_include_directories(library-test PUBLIC SYSTEM .)
+
+if (TARGET fmt::fmt-header-only)
+  add_executable(header-only-test "main.cc")
+  target_link_libraries(header-only-test fmt::fmt-header-only)
+  target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+  target_include_directories(header-only-test PUBLIC SYSTEM .)
+endif ()
diff --git a/libs/fmt/test/add-subdirectory-test/main.cc b/libs/fmt/test/add-subdirectory-test/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f39f377cc7c6a173ae9b1e9aa29e99452f71b7b6
--- /dev/null
+++ b/libs/fmt/test/add-subdirectory-test/main.cc
@@ -0,0 +1,6 @@
+#include "fmt/format.h"
+
+int main(int argc, char** argv) {
+  for(int i = 0; i < argc; ++i)
+    fmt::print("{}: {}\n", i, argv[i]);
+}
diff --git a/libs/fmt/test/args-test.cc b/libs/fmt/test/args-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..a7421bc5299d092c2c49d5f4794aa4eb067dacd6
--- /dev/null
+++ b/libs/fmt/test/args-test.cc
@@ -0,0 +1,173 @@
+// Formatting library for C++ - dynamic argument store tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/args.h"
+
+#include "gtest/gtest.h"
+
+TEST(args_test, basic) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  store.push_back(42);
+  store.push_back("abc1");
+  store.push_back(1.5f);
+  EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store));
+}
+
+TEST(args_test, strings_and_refs) {
+  // Unfortunately the tests are compiled with old ABI so strings use COW.
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  char str[] = "1234567890";
+  store.push_back(str);
+  store.push_back(std::cref(str));
+  store.push_back(fmt::string_view{str});
+  str[0] = 'X';
+
+  auto result = fmt::vformat("{} and {} and {}", store);
+  EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
+}
+
+struct custom_type {
+  int i = 0;
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<custom_type> {
+  auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
+    return format_to(ctx.out(), "cust={}", p.i);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(args_test, custom_format) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  auto c = custom_type();
+  store.push_back(c);
+  ++c.i;
+  store.push_back(c);
+  ++c.i;
+  store.push_back(std::cref(c));
+  ++c.i;
+  auto result = fmt::vformat("{} and {} and {}", store);
+  EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
+}
+
+struct to_stringable {
+  friend fmt::string_view to_string_view(to_stringable) { return {}; }
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<to_stringable> {
+  auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  auto format(to_stringable, format_context& ctx) -> decltype(ctx.out()) {
+    return ctx.out();
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(args_test, to_string_and_formatter) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  auto s = to_stringable();
+  store.push_back(s);
+  store.push_back(std::cref(s));
+  fmt::vformat("", store);
+}
+
+TEST(args_test, named_int) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  store.push_back(fmt::arg("a1", 42));
+  EXPECT_EQ("42", fmt::vformat("{a1}", store));
+}
+
+TEST(args_test, named_strings) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  char str[] = "1234567890";
+  store.push_back(fmt::arg("a1", str));
+  store.push_back(fmt::arg("a2", std::cref(str)));
+  str[0] = 'X';
+  EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store));
+}
+
+TEST(args_test, named_arg_by_ref) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  char band[] = "Rolling Stones";
+  store.push_back(fmt::arg("band", std::cref(band)));
+  band[9] = 'c';  // Changing band affects the output.
+  EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones");
+}
+
+TEST(args_test, named_custom_format) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  auto c = custom_type();
+  store.push_back(fmt::arg("c1", c));
+  ++c.i;
+  store.push_back(fmt::arg("c2", c));
+  ++c.i;
+  store.push_back(fmt::arg("c_ref", std::cref(c)));
+  ++c.i;
+  auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
+  EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
+}
+
+TEST(args_test, clear) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  store.push_back(42);
+
+  auto result = fmt::vformat("{}", store);
+  EXPECT_EQ("42", result);
+
+  store.push_back(43);
+  result = fmt::vformat("{} and {}", store);
+  EXPECT_EQ("42 and 43", result);
+
+  store.clear();
+  store.push_back(44);
+  result = fmt::vformat("{}", store);
+  EXPECT_EQ("44", result);
+}
+
+TEST(args_test, reserve) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  store.reserve(2, 1);
+  store.push_back(1.5f);
+  store.push_back(fmt::arg("a1", 42));
+  auto result = fmt::vformat("{a1} and {}", store);
+  EXPECT_EQ("42 and 1.5", result);
+}
+
+struct copy_throwable {
+  copy_throwable() {}
+  copy_throwable(const copy_throwable&) { throw "deal with it"; }
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<copy_throwable> {
+  auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+  auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) {
+    return ctx.out();
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(args_test, throw_on_copy) {
+  auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
+  store.push_back(std::string("foo"));
+  try {
+    store.push_back(copy_throwable());
+  } catch (...) {
+  }
+  EXPECT_EQ(fmt::vformat("{}", store), "foo");
+}
diff --git a/libs/fmt/test/assert-test.cc b/libs/fmt/test/assert-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c74e617e68f35024919149826a011916a827cd46
--- /dev/null
+++ b/libs/fmt/test/assert-test.cc
@@ -0,0 +1,31 @@
+// Formatting library for C++ - FMT_ASSERT test
+//
+// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks
+// which are slow on some platforms. In other tests FMT_ASSERT is made to throw
+// an exception which is much faster and easier to check.
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/core.h"
+#include "gtest/gtest.h"
+
+TEST(assert_test, fail) {
+#if GTEST_HAS_DEATH_TEST
+  EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
+#else
+  fmt::print("warning: death tests are not supported\n");
+#endif
+}
+
+TEST(assert_test, dangling_else) {
+  bool test_condition = false;
+  bool executed_else = false;
+  if (test_condition)
+    FMT_ASSERT(true, "");
+  else
+    executed_else = true;
+  EXPECT_TRUE(executed_else);
+}
diff --git a/libs/fmt/test/chrono-test.cc b/libs/fmt/test/chrono-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d03e92d3f58f0808b0097a58d85b87805c6cd8df
--- /dev/null
+++ b/libs/fmt/test/chrono-test.cc
@@ -0,0 +1,369 @@
+// Formatting library for C++ - time formatting tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/chrono.h"
+
+#include "gtest-extra.h"  // EXPECT_THROW_MSG
+
+auto make_tm() -> std::tm {
+  auto time = std::tm();
+  time.tm_mday = 1;
+  return time;
+}
+
+auto make_hour(int h) -> std::tm {
+  auto time = make_tm();
+  time.tm_hour = h;
+  return time;
+}
+
+auto make_minute(int m) -> std::tm {
+  auto time = make_tm();
+  time.tm_min = m;
+  return time;
+}
+
+auto make_second(int s) -> std::tm {
+  auto time = make_tm();
+  time.tm_sec = s;
+  return time;
+}
+
+TEST(chrono_test, format_tm) {
+  auto tm = std::tm();
+  tm.tm_year = 116;
+  tm.tm_mon = 3;
+  tm.tm_mday = 25;
+  tm.tm_hour = 11;
+  tm.tm_min = 22;
+  tm.tm_sec = 33;
+  EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
+            "The date is 2016-04-25 11:22:33.");
+  EXPECT_EQ(fmt::format(L"The date is {:%Y-%m-%d %H:%M:%S}.", tm),
+            L"The date is 2016-04-25 11:22:33.");
+}
+
+TEST(chrono_test, grow_buffer) {
+  auto s = std::string("{:");
+  for (int i = 0; i < 30; ++i) s += "%c";
+  s += "}\n";
+  auto t = std::time(nullptr);
+  fmt::format(s, *std::localtime(&t));
+}
+
+TEST(chrono_test, format_to_empty_container) {
+  auto time = std::tm();
+  time.tm_sec = 42;
+  auto s = std::string();
+  fmt::format_to(std::back_inserter(s), "{:%S}", time);
+  EXPECT_EQ(s, "42");
+}
+
+TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
+
+auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
+  return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
+         lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
+         lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
+         lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
+         lhs.tm_isdst == rhs.tm_isdst;
+}
+
+TEST(chrono_test, localtime) {
+  auto t = std::time(nullptr);
+  auto tm = *std::localtime(&t);
+  EXPECT_TRUE(equal(tm, fmt::localtime(t)));
+}
+
+TEST(chrono_test, gmtime) {
+  auto t = std::time(nullptr);
+  auto tm = *std::gmtime(&t);
+  EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
+}
+
+template <typename TimePoint> auto strftime(TimePoint tp) -> std::string {
+  auto t = std::chrono::system_clock::to_time_t(tp);
+  auto tm = *std::localtime(&t);
+  char output[256] = {};
+  std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm);
+  return output;
+}
+
+TEST(chrono_test, time_point) {
+  auto t1 = std::chrono::system_clock::now();
+  EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
+  using time_point =
+      std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
+  auto t2 = time_point(std::chrono::seconds(42));
+  EXPECT_EQ(strftime(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
+}
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+
+TEST(chrono_test, format_default) {
+  EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
+  EXPECT_EQ("42as",
+            fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
+  EXPECT_EQ("42fs",
+            fmt::format("{}", std::chrono::duration<int, std::femto>(42)));
+  EXPECT_EQ("42ps",
+            fmt::format("{}", std::chrono::duration<int, std::pico>(42)));
+  EXPECT_EQ("42ns", fmt::format("{}", std::chrono::nanoseconds(42)));
+  EXPECT_EQ("42µs", fmt::format("{}", std::chrono::microseconds(42)));
+  EXPECT_EQ("42ms", fmt::format("{}", std::chrono::milliseconds(42)));
+  EXPECT_EQ("42cs",
+            fmt::format("{}", std::chrono::duration<int, std::centi>(42)));
+  EXPECT_EQ("42ds",
+            fmt::format("{}", std::chrono::duration<int, std::deci>(42)));
+  EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
+  EXPECT_EQ("42das",
+            fmt::format("{}", std::chrono::duration<int, std::deca>(42)));
+  EXPECT_EQ("42hs",
+            fmt::format("{}", std::chrono::duration<int, std::hecto>(42)));
+  EXPECT_EQ("42ks",
+            fmt::format("{}", std::chrono::duration<int, std::kilo>(42)));
+  EXPECT_EQ("42Ms",
+            fmt::format("{}", std::chrono::duration<int, std::mega>(42)));
+  EXPECT_EQ("42Gs",
+            fmt::format("{}", std::chrono::duration<int, std::giga>(42)));
+  EXPECT_EQ("42Ts",
+            fmt::format("{}", std::chrono::duration<int, std::tera>(42)));
+  EXPECT_EQ("42Ps",
+            fmt::format("{}", std::chrono::duration<int, std::peta>(42)));
+  EXPECT_EQ("42Es",
+            fmt::format("{}", std::chrono::duration<int, std::exa>(42)));
+  EXPECT_EQ("42m", fmt::format("{}", std::chrono::minutes(42)));
+  EXPECT_EQ("42h", fmt::format("{}", std::chrono::hours(42)));
+  EXPECT_EQ(
+      "42[15]s",
+      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
+  EXPECT_EQ(
+      "42[15/4]s",
+      fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
+}
+
+TEST(chrono_test, format_wide) {
+  EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
+}
+
+TEST(chrono_test, align) {
+  auto s = std::chrono::seconds(42);
+  EXPECT_EQ("42s  ", fmt::format("{:5}", s));
+  EXPECT_EQ("42s  ", fmt::format("{:{}}", s, 5));
+  EXPECT_EQ("  42s", fmt::format("{:>5}", s));
+  EXPECT_EQ("**42s**", fmt::format("{:*^7}", s));
+  EXPECT_EQ("03:25:45    ",
+            fmt::format("{:12%H:%M:%S}", std::chrono::seconds(12345)));
+  EXPECT_EQ("    03:25:45",
+            fmt::format("{:>12%H:%M:%S}", std::chrono::seconds(12345)));
+  EXPECT_EQ("~~03:25:45~~",
+            fmt::format("{:~^12%H:%M:%S}", std::chrono::seconds(12345)));
+  EXPECT_EQ("03:25:45    ",
+            fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
+}
+
+TEST(chrono_test, format_specs) {
+  EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
+  EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
+  EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
+  EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(0)));
+  EXPECT_EQ("00", fmt::format("{:%S}", std::chrono::seconds(60)));
+  EXPECT_EQ("42", fmt::format("{:%S}", std::chrono::seconds(42)));
+  EXPECT_EQ("01.234", fmt::format("{:%S}", std::chrono::milliseconds(1234)));
+  EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(0)));
+  EXPECT_EQ("00", fmt::format("{:%M}", std::chrono::minutes(60)));
+  EXPECT_EQ("42", fmt::format("{:%M}", std::chrono::minutes(42)));
+  EXPECT_EQ("01", fmt::format("{:%M}", std::chrono::seconds(61)));
+  EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(0)));
+  EXPECT_EQ("00", fmt::format("{:%H}", std::chrono::hours(24)));
+  EXPECT_EQ("14", fmt::format("{:%H}", std::chrono::hours(14)));
+  EXPECT_EQ("01", fmt::format("{:%H}", std::chrono::minutes(61)));
+  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(0)));
+  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(12)));
+  EXPECT_EQ("12", fmt::format("{:%I}", std::chrono::hours(24)));
+  EXPECT_EQ("04", fmt::format("{:%I}", std::chrono::hours(4)));
+  EXPECT_EQ("02", fmt::format("{:%I}", std::chrono::hours(14)));
+  EXPECT_EQ("03:25:45",
+            fmt::format("{:%H:%M:%S}", std::chrono::seconds(12345)));
+  EXPECT_EQ("03:25", fmt::format("{:%R}", std::chrono::seconds(12345)));
+  EXPECT_EQ("03:25:45", fmt::format("{:%T}", std::chrono::seconds(12345)));
+  EXPECT_EQ("12345", fmt::format("{:%Q}", std::chrono::seconds(12345)));
+  EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
+}
+
+TEST(chrono_test, invalid_specs) {
+  auto sec = std::chrono::seconds(0);
+  EXPECT_THROW_MSG(fmt::format(+"{:%a}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%A}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%c}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%x}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%Ex}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%X}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%EX}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%D}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%F}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%Ec}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%w}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%u}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%b}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%B}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%z}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%Z}", sec), fmt::format_error, "no date");
+  EXPECT_THROW_MSG(fmt::format(+"{:%Eq}", sec), fmt::format_error,
+                   "invalid format");
+  EXPECT_THROW_MSG(fmt::format(+"{:%Oq}", sec), fmt::format_error,
+                   "invalid format");
+}
+
+auto format_tm(const std::tm& time, fmt::string_view spec,
+               const std::locale& loc) -> std::string {
+  auto& facet = std::use_facet<std::time_put<char>>(loc);
+  std::ostringstream os;
+  os.imbue(loc);
+  facet.put(os, os, ' ', &time, spec.begin(), spec.end());
+  return os.str();
+}
+
+#  define EXPECT_TIME(spec, time, duration)                 \
+    {                                                       \
+      auto jp_loc = std::locale("ja_JP.utf8");              \
+      EXPECT_EQ(format_tm(time, spec, jp_loc),              \
+                fmt::format(loc, "{:" spec "}", duration)); \
+    }
+
+TEST(chrono_test, locale) {
+  auto loc_name = "ja_JP.utf8";
+  bool has_locale = false;
+  auto loc = std::locale();
+  try {
+    loc = std::locale(loc_name);
+    has_locale = true;
+  } catch (const std::runtime_error&) {
+  }
+  if (!has_locale) {
+    fmt::print("{} locale is missing.\n", loc_name);
+    return;
+  }
+  EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
+  EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
+  EXPECT_TIME("%OM", make_minute(42), std::chrono::minutes(42));
+  EXPECT_TIME("%OS", make_second(42), std::chrono::seconds(42));
+  auto time = make_tm();
+  time.tm_hour = 3;
+  time.tm_min = 25;
+  time.tm_sec = 45;
+  auto sec = std::chrono::seconds(12345);
+  EXPECT_TIME("%r", time, sec);
+  EXPECT_TIME("%p", time, sec);
+}
+
+using dms = std::chrono::duration<double, std::milli>;
+
+TEST(chrono_test, format_default_fp) {
+  typedef std::chrono::duration<float> fs;
+  EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
+  typedef std::chrono::duration<float, std::milli> fms;
+  EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234)));
+  typedef std::chrono::duration<double> ds;
+  EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234)));
+  EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
+}
+
+TEST(chrono_test, format_precision) {
+  EXPECT_THROW_MSG(fmt::format(+"{:.2}", std::chrono::seconds(42)),
+                   fmt::format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
+  EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
+}
+
+TEST(chrono_test, format_full_specs) {
+  EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
+  EXPECT_EQ("  1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
+  EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
+  EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8));
+  EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3));
+  EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
+}
+
+TEST(chrono_test, format_simple_q) {
+  typedef std::chrono::duration<float> fs;
+  EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
+  typedef std::chrono::duration<float, std::milli> fms;
+  EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", fms(1.234)));
+  typedef std::chrono::duration<double> ds;
+  EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", ds(1.234)));
+  EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
+}
+
+TEST(chrono_test, format_precision_q) {
+  EXPECT_THROW_MSG(fmt::format(+"{:.2%Q %q}", std::chrono::seconds(42)),
+                   fmt::format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
+  EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
+}
+
+TEST(chrono_test, format_full_specs_q) {
+  EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
+  EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
+  EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
+  EXPECT_EQ(" 1.23 ms ", fmt::format("{0:^{2}.{1}%Q %q}", dms(1.234), 2, 9));
+  EXPECT_EQ("=1.234 ms=", fmt::format("{:=^{}.{}%Q %q}", dms(1.234), 10, 3));
+  EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
+}
+
+TEST(chrono_test, invalid_width_id) {
+  EXPECT_THROW(fmt::format(+"{:{o}", std::chrono::seconds(0)),
+               fmt::format_error);
+}
+
+TEST(chrono_test, invalid_colons) {
+  EXPECT_THROW(fmt::format(+"{0}=:{0::", std::chrono::seconds(0)),
+               fmt::format_error);
+}
+
+TEST(chrono_test, negative_durations) {
+  EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
+  EXPECT_EQ("-03:25:45",
+            fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
+  EXPECT_EQ("-00:01",
+            fmt::format("{:%M:%S}", std::chrono::duration<double>(-1)));
+  EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(-12345)));
+  EXPECT_EQ("-00.127",
+            fmt::format("{:%S}",
+                        std::chrono::duration<signed char, std::milli>{-127}));
+  auto min = std::numeric_limits<int>::min();
+  EXPECT_EQ(fmt::format("{}", min),
+            fmt::format("{:%Q}", std::chrono::duration<int>(min)));
+}
+
+TEST(chrono_test, special_durations) {
+  EXPECT_EQ(
+      "40.",
+      fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
+  auto nan = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_EQ(
+      "nan nan nan nan nan:nan nan",
+      fmt::format("{:%I %H %M %S %R %r}", std::chrono::duration<double>(nan)));
+  fmt::format("{:%S}",
+              std::chrono::duration<float, std::atto>(1.79400457e+31f));
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::exa>(1)),
+            "1Es");
+  EXPECT_EQ(fmt::format("{}", std::chrono::duration<float, std::atto>(1)),
+            "1as");
+  EXPECT_EQ(fmt::format("{:%R}", std::chrono::duration<char, std::mega>{2}),
+            "03:33");
+  EXPECT_EQ(fmt::format("{:%T}", std::chrono::duration<char, std::mega>{2}),
+            "03:33:20");
+}
+
+TEST(chrono_test, unsigned_duration) {
+  EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
+}
+
+#endif  // FMT_STATIC_THOUSANDS_SEPARATOR
diff --git a/libs/fmt/test/color-test.cc b/libs/fmt/test/color-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9a38765afa781fa38519c8f5d01e0fd1cb86be07
--- /dev/null
+++ b/libs/fmt/test/color-test.cc
@@ -0,0 +1,62 @@
+// Formatting library for C++ - color tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/color.h"
+
+#include <iterator>  // std::back_inserter
+
+#include "gtest-extra.h"  // EXPECT_WRITE
+
+TEST(color_test, format) {
+  EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
+            "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
+            L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
+            "\x1b[38;2;000;000;255mblue\x1b[0m");
+  EXPECT_EQ(
+      fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
+      "\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"),
+            "\x1b[3mitalic\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"),
+            "\x1b[4munderline\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"),
+            "\x1b[9mstrikethrough\x1b[0m");
+  EXPECT_EQ(
+      fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
+      "\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
+            "\x1b[1mbold error\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue log"),
+            "\x1b[38;2;000;000;255mblue log\x1b[0m");
+  EXPECT_EQ(fmt::format(fmt::text_style(), "hi"), "hi");
+  EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "tred"),
+            "\x1b[31mtred\x1b[0m");
+  EXPECT_EQ(fmt::format(bg(fmt::terminal_color::cyan), "tcyan"),
+            "\x1b[46mtcyan\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::terminal_color::bright_green), "tbgreen"),
+            "\x1b[92mtbgreen\x1b[0m");
+  EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
+            "\x1b[105mtbmagenta\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
+            "\x1b[31mfoo\x1b[0m");
+}
+
+TEST(color_test, format_to) {
+  auto out = std::string();
+  fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)),
+                 "rgb(255,20,30){}{}{}", 1, 2, 3);
+  EXPECT_EQ(fmt::to_string(out),
+            "\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m");
+}
+
+TEST(color_test, print) {
+  EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
+               "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
+}
diff --git a/libs/fmt/test/compile-error-test/CMakeLists.txt b/libs/fmt/test/compile-error-test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8202f279434e8d48ad9c3d3e9a6d0c6bf7a665c0
--- /dev/null
+++ b/libs/fmt/test/compile-error-test/CMakeLists.txt
@@ -0,0 +1,79 @@
+# Test if compile errors are produced where necessary.
+
+cmake_minimum_required(VERSION 3.1...3.18)
+
+include(CheckCXXSourceCompiles)
+include(CheckCXXCompilerFlag)
+
+set(CMAKE_REQUIRED_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
+set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG} ${PEDANTIC_COMPILE_FLAGS})
+
+function (generate_source result fragment)
+  set(${result} "
+  #define FMT_HEADER_ONLY 1
+  #include \"fmt/format.h\"
+  int main() {
+    ${fragment}
+  }
+  " PARENT_SCOPE)
+endfunction ()
+
+function (expect_compile code)
+  generate_source(source "${code}")
+  check_cxx_source_compiles("${source}" compiles)
+  if (NOT compiles)
+    set(error_msg "Compile error for: ${code}")
+  endif ()
+  # Unset the CMake cache variable compiles. Otherwise the compile test will
+  # just use cached information next time it runs.
+  unset(compiles CACHE)
+  if (error_msg)
+    message(FATAL_ERROR ${error_msg})
+  endif ()
+endfunction ()
+
+function (expect_compile_error code)
+  generate_source(source "${code}")
+  check_cxx_source_compiles("${source}" compiles)
+  if (compiles)
+    set(error_msg "No compile error for: ${code}")
+  endif ()
+  # Unset the CMake cache variable compiles. Otherwise the compile test will
+  # just use cached information next time it runs.
+  unset(compiles CACHE)
+  if (error_msg)
+    message(FATAL_ERROR ${error_msg})
+  endif ()
+endfunction ()
+
+# check if the source file skeleton compiles
+expect_compile("")
+
+# Formatting a wide character with a narrow format string is forbidden.
+expect_compile_error("fmt::format(\"{}\", L'a');")
+
+# Formatting a wide string with a narrow format string is forbidden.
+expect_compile_error("fmt::format(\"{}\", L\"foo\");")
+
+# Formatting a narrow string with a wide format string is forbidden because
+# mixing UTF-8 with UTF-16/32 can result in an invalid output.
+expect_compile_error("fmt::format(L\"{}\", \"foo\");")
+
+# Formatting a wide string with a narrow format string is forbidden.
+expect_compile_error("
+  struct S {
+    operator std::string() const { return std::string(); }
+  };
+  fmt::format(\"{}\", S());
+")
+
+# Make sure that compiler features detected in the header
+# match the features detected in CMake.
+if (SUPPORTS_USER_DEFINED_LITERALS)
+  set(supports_udl 1)
+else ()
+  set(supports_udl 0)
+endif ()
+expect_compile("#if FMT_USE_USER_DEFINED_LITERALS != ${supports_udl}
+                # error
+                #endif")
diff --git a/libs/fmt/test/compile-test.cc b/libs/fmt/test/compile-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..71eddc920e4f5a23591a8a16d448ba3e1847b1ff
--- /dev/null
+++ b/libs/fmt/test/compile-test.cc
@@ -0,0 +1,334 @@
+// Formatting library for C++ - formatting library tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/compile.h"
+
+#include <type_traits>
+
+#include "fmt/chrono.h"
+#include "gmock/gmock.h"
+
+TEST(iterator_test, counting_iterator) {
+  auto it = fmt::detail::counting_iterator();
+  auto prev = it++;
+  EXPECT_EQ(prev.count(), 0);
+  EXPECT_EQ(it.count(), 1);
+  EXPECT_EQ((it + 41).count(), 42);
+}
+
+TEST(iterator_test, truncating_iterator) {
+  char* p = nullptr;
+  auto it = fmt::detail::truncating_iterator<char*>(p, 3);
+  auto prev = it++;
+  EXPECT_EQ(prev.base(), p);
+  EXPECT_EQ(it.base(), p + 1);
+}
+
+TEST(iterator_test, truncating_iterator_default_construct) {
+  auto it = fmt::detail::truncating_iterator<char*>();
+  EXPECT_EQ(nullptr, it.base());
+  EXPECT_EQ(std::size_t{0}, it.count());
+}
+
+#ifdef __cpp_lib_ranges
+TEST(iterator_test, truncating_iterator_is_output_iterator) {
+  static_assert(
+      std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
+}
+#endif
+
+TEST(iterator_test, truncating_back_inserter) {
+  auto buffer = std::string();
+  auto bi = std::back_inserter(buffer);
+  auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
+  *it++ = '4';
+  *it++ = '2';
+  *it++ = '1';
+  EXPECT_EQ(buffer.size(), 2);
+  EXPECT_EQ(buffer, "42");
+}
+
+struct test_formattable {};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<test_formattable> : formatter<const char*> {
+  template <typename FormatContext>
+  auto format(test_formattable, FormatContext& ctx) -> decltype(ctx.out()) {
+    return formatter<const char*>::format("foo", ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(compile_test, compile_fallback) {
+  // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
+  // not available.
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
+}
+
+#ifdef __cpp_if_constexpr
+TEST(compile_test, format_default) {
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ull));
+  EXPECT_EQ("true", fmt::format(FMT_COMPILE("{}"), true));
+  EXPECT_EQ("x", fmt::format(FMT_COMPILE("{}"), 'x'));
+  EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
+  EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
+  EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
+  EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
+#  ifdef __cpp_lib_byte
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), std::byte{42}));
+#  endif
+}
+
+TEST(compile_test, format_wide_string) {
+  EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
+}
+
+TEST(compile_test, format_specs) {
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
+  EXPECT_EQ("1.2 ms ",
+            fmt::format(FMT_COMPILE("{:7.1%Q %q}"),
+                        std::chrono::duration<double, std::milli>(1.234)));
+}
+
+TEST(compile_test, dynamic_format_specs) {
+  EXPECT_EQ("foo  ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5));
+  EXPECT_EQ("  3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2));
+  EXPECT_EQ(
+      "=1.234ms=",
+      fmt::format(FMT_COMPILE("{:=^{}.{}}"),
+                  std::chrono::duration<double, std::milli>(1.234), 9, 3));
+}
+
+TEST(compile_test, manual_ordering) {
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42));
+  EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42));
+  EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43));
+  EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41));
+  EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43));
+  EXPECT_EQ("  41   43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4));
+  EXPECT_EQ("42 1.2 ms ",
+            fmt::format(FMT_COMPILE("{0} {1:7.1%Q %q}"), 42,
+                        std::chrono::duration<double, std::milli>(1.234)));
+  EXPECT_EQ(
+      "true 42 42 foo 0x1234 foo",
+      fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
+                  "foo", reinterpret_cast<void*>(0x1234), test_formattable()));
+  EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
+}
+
+TEST(compile_test, named) {
+  auto runtime_named_field_compiled =
+      fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}"));
+  static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
+                               fmt::detail::runtime_named_field<char>>);
+
+  EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
+  EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
+                                 fmt::arg("arg", 43)));
+
+  EXPECT_EQ("foobar",
+            fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
+                        fmt::arg("a1", "bar")));
+  EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{}{a1}"), fmt::arg("a0", "foo"),
+                                  fmt::arg("a1", "bar")));
+  EXPECT_EQ("foofoo", fmt::format(FMT_COMPILE("{a0}{}"), fmt::arg("a0", "foo"),
+                                  fmt::arg("a1", "bar")));
+  EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{0}{a1}"), fmt::arg("a0", "foo"),
+                                  fmt::arg("a1", "bar")));
+  EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{a0}{1}"), fmt::arg("a0", "foo"),
+                                  fmt::arg("a1", "bar")));
+
+  EXPECT_EQ("foobar",
+            fmt::format(FMT_COMPILE("{}{a1}"), "foo", fmt::arg("a1", "bar")));
+  EXPECT_EQ("foobar",
+            fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a1", "bar"),
+                        fmt::arg("a2", "baz"), fmt::arg("a0", "foo")));
+  EXPECT_EQ(" bar foo ",
+            fmt::format(FMT_COMPILE(" {foo} {bar} "), fmt::arg("foo", "bar"),
+                        fmt::arg("bar", "foo")));
+
+  EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
+               fmt::format_error);
+
+#  if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+  using namespace fmt::literals;
+  auto statically_named_field_compiled =
+      fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}"));
+  static_assert(std::is_same_v<decltype(statically_named_field_compiled),
+                               fmt::detail::field<char, int, 0>>);
+
+  EXPECT_EQ("41 43",
+            fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43));
+  EXPECT_EQ("41 43",
+            fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41));
+#  endif
+}
+
+TEST(compile_test, format_to) {
+  char buf[8];
+  auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
+  *end = '\0';
+  EXPECT_STREQ("42", buf);
+  end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42);
+  *end = '\0';
+  EXPECT_STREQ("2a", buf);
+}
+
+TEST(compile_test, format_to_n) {
+  constexpr auto buffer_size = 8;
+  char buffer[buffer_size];
+  auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42);
+  *res.out = '\0';
+  EXPECT_STREQ("42", buffer);
+  res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42);
+  *res.out = '\0';
+  EXPECT_STREQ("2a", buffer);
+}
+
+TEST(compile_test, formatted_size) {
+  EXPECT_EQ(2, fmt::formatted_size(FMT_COMPILE("{0}"), 42));
+  EXPECT_EQ(5, fmt::formatted_size(FMT_COMPILE("{0:<4.2f}"), 42.0));
+}
+
+TEST(compile_test, text_and_arg) {
+  EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
+  EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
+}
+
+TEST(compile_test, unknown_format_fallback) {
+  EXPECT_EQ(" 42 ",
+            fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
+
+  std::vector<char> v;
+  fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
+                 fmt::arg("name", 42));
+  EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
+
+  char buffer[4];
+  auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
+                                 fmt::arg("name", 42));
+  EXPECT_EQ(5u, result.size);
+  EXPECT_EQ(buffer + 4, result.out);
+  EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4));
+}
+
+TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
+
+struct to_stringable {
+  friend fmt::string_view to_string_view(to_stringable) { return {}; }
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<to_stringable> {
+  auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  auto format(const to_stringable&, FormatContext& ctx) -> decltype(ctx.out()) {
+    return ctx.out();
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(compile_test, to_string_and_formatter) {
+  fmt::format(FMT_COMPILE("{}"), to_stringable());
+}
+#endif
+
+#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
+TEST(compile_test, compile_format_string_literal) {
+  using namespace fmt::literals;
+  EXPECT_EQ("", fmt::format(""_cf));
+  EXPECT_EQ("42", fmt::format("{}"_cf, 42));
+  EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
+}
+#endif
+
+#if __cplusplus >= 202002L || \
+    (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
+template <size_t max_string_length, typename Char = char> struct test_string {
+  template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
+    return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
+  }
+  Char buffer[max_string_length]{};
+};
+
+template <size_t max_string_length, typename Char = char, typename... Args>
+consteval auto test_format(auto format, const Args&... args) {
+  test_string<max_string_length, Char> string{};
+  fmt::format_to(string.buffer, format, args...);
+  return string;
+}
+
+TEST(compile_time_formatting_test, bool) {
+  EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true));
+  EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false));
+  EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true));
+  EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true));
+}
+
+TEST(compile_time_formatting_test, integer) {
+  EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42));
+  EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420));
+  EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42));
+  EXPECT_EQ("42 42",
+            test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42}));
+
+  EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42));
+  EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42));
+  EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42));
+
+  EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42));
+
+  EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42));
+  EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42));
+  EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42));
+  EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042));
+  EXPECT_EQ("0x4a", test_format<5>(FMT_COMPILE("{:#x}"), 0x4a));
+  EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
+
+  EXPECT_EQ("   42", test_format<6>(FMT_COMPILE("{:5}"), 42));
+  EXPECT_EQ("   42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
+  EXPECT_EQ("   42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
+
+  EXPECT_EQ("42  ", test_format<5>(FMT_COMPILE("{:<4}"), 42));
+  EXPECT_EQ("  42", test_format<5>(FMT_COMPILE("{:>4}"), 42));
+  EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42));
+  EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42));
+}
+
+TEST(compile_time_formatting_test, char) {
+  EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c'));
+
+  EXPECT_EQ("c  ", test_format<4>(FMT_COMPILE("{:3}"), 'c'));
+  EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c'));
+}
+
+TEST(compile_time_formatting_test, string) {
+  EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42"));
+  EXPECT_EQ("The answer is 42",
+            test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42"));
+
+  EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc"));
+  EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡"));
+}
+
+TEST(compile_time_formatting_test, combination) {
+  EXPECT_EQ("420, true, answer",
+            test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer"));
+
+  EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4));
+}
+
+TEST(compile_time_formatting_test, multibyte_fill) {
+  EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
+}
+#endif
diff --git a/libs/fmt/test/core-test.cc b/libs/fmt/test/core-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..76b20a3e242cf3f1e7efab9108fa10c27dff6b67
--- /dev/null
+++ b/libs/fmt/test/core-test.cc
@@ -0,0 +1,649 @@
+// Formatting library for C++ - core tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+// clang-format off
+#include "test-assert.h"
+// clang-format on
+
+#include "fmt/core.h"
+
+#include <algorithm>    // std::copy_n
+#include <climits>      // INT_MAX
+#include <cstring>      // std::strlen
+#include <functional>   // std::equal_to
+#include <iterator>     // std::back_insert_iterator
+#include <limits>       // std::numeric_limits
+#include <string>       // std::string
+#include <type_traits>  // std::is_same
+
+#include "gmock/gmock.h"
+
+#if defined(FMT_COMPILE_TIME_CHECKS) && FMT_COMPILE_TIME_CHECKS
+#  include "fmt/format.h"
+#endif
+
+using fmt::string_view;
+using fmt::detail::buffer;
+
+using testing::_;
+using testing::Invoke;
+using testing::Return;
+
+TEST(string_view_test, value_type) {
+  static_assert(std::is_same<string_view::value_type, char>::value, "");
+}
+
+TEST(string_view_test, ctor) {
+  EXPECT_STREQ("abc", fmt::string_view("abc").data());
+  EXPECT_EQ(3u, fmt::string_view("abc").size());
+
+  EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data());
+  EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size());
+}
+
+TEST(string_view_test, length) {
+  // Test that string_view::size() returns string length, not buffer size.
+  char str[100] = "some string";
+  EXPECT_EQ(std::strlen(str), string_view(str).size());
+  EXPECT_LT(std::strlen(str), sizeof(str));
+}
+
+// Check string_view's comparison operator.
+template <template <typename> class Op> void check_op() {
+  const char* inputs[] = {"foo", "fop", "fo"};
+  size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
+  for (size_t i = 0; i < num_inputs; ++i) {
+    for (size_t j = 0; j < num_inputs; ++j) {
+      string_view lhs(inputs[i]), rhs(inputs[j]);
+      EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
+    }
+  }
+}
+
+TEST(string_view_test, compare) {
+  EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
+  EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
+  EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
+  EXPECT_GT(string_view("foo").compare(string_view("fo")), 0);
+  EXPECT_LT(string_view("fo").compare(string_view("foo")), 0);
+  check_op<std::equal_to>();
+  check_op<std::not_equal_to>();
+  check_op<std::less>();
+  check_op<std::less_equal>();
+  check_op<std::greater>();
+  check_op<std::greater_equal>();
+}
+
+namespace test_ns {
+template <typename Char> class test_string {
+ private:
+  std::basic_string<Char> s_;
+
+ public:
+  test_string(const Char* s) : s_(s) {}
+  const Char* data() const { return s_.data(); }
+  size_t length() const { return s_.size(); }
+  operator const Char*() const { return s_.c_str(); }
+};
+
+template <typename Char>
+fmt::basic_string_view<Char> to_string_view(const test_string<Char>& s) {
+  return {s.data(), s.length()};
+}
+
+struct non_string {};
+}  // namespace test_ns
+
+template <typename T> class is_string_test : public testing::Test {};
+
+using string_char_types = testing::Types<char, wchar_t, char16_t, char32_t>;
+TYPED_TEST_SUITE(is_string_test, string_char_types);
+
+template <typename Char>
+struct derived_from_string_view : fmt::basic_string_view<Char> {};
+
+TYPED_TEST(is_string_test, is_string) {
+  EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value);
+  EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value);
+  EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value);
+  EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value);
+  EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value);
+  EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value);
+  EXPECT_TRUE(
+      fmt::detail::is_string<derived_from_string_view<TypeParam>>::value);
+  using fmt_string_view = fmt::detail::std_string_view<TypeParam>;
+  EXPECT_TRUE(std::is_empty<fmt_string_view>::value !=
+              fmt::detail::is_string<fmt_string_view>::value);
+  EXPECT_TRUE(fmt::detail::is_string<test_ns::test_string<TypeParam>>::value);
+  EXPECT_FALSE(fmt::detail::is_string<test_ns::non_string>::value);
+}
+
+TEST(core_test, is_output_iterator) {
+  EXPECT_TRUE((fmt::detail::is_output_iterator<char*, char>::value));
+  EXPECT_FALSE((fmt::detail::is_output_iterator<const char*, char>::value));
+  EXPECT_FALSE((fmt::detail::is_output_iterator<std::string, char>::value));
+  EXPECT_TRUE(
+      (fmt::detail::is_output_iterator<std::back_insert_iterator<std::string>,
+                                       char>::value));
+  EXPECT_TRUE(
+      (fmt::detail::is_output_iterator<std::string::iterator, char>::value));
+  EXPECT_FALSE((fmt::detail::is_output_iterator<std::string::const_iterator,
+                                                char>::value));
+}
+
+TEST(core_test, buffer_appender) {
+  // back_insert_iterator is not default-constructible before C++20, so
+  // buffer_appender can only be default-constructible when back_insert_iterator
+  // is.
+  static_assert(
+      std::is_default_constructible<
+          std::back_insert_iterator<fmt::detail::buffer<char>>>::value ==
+          std::is_default_constructible<
+              fmt::detail::buffer_appender<char>>::value,
+      "");
+
+#ifdef __cpp_lib_ranges
+  static_assert(std::output_iterator<fmt::detail::buffer_appender<char>, char>);
+#endif
+}
+
+#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
+TEST(buffer_test, noncopyable) {
+  EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value);
+#  if !FMT_MSC_VER
+  // std::is_copy_assignable is broken in MSVC2013.
+  EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
+#  endif
+}
+
+TEST(buffer_test, nonmoveable) {
+  EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value);
+#  if !FMT_MSC_VER
+  // std::is_move_assignable is broken in MSVC2013.
+  EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
+#  endif
+}
+#endif
+
+TEST(buffer_test, indestructible) {
+  static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
+                "buffer's destructor is protected");
+}
+
+template <typename T> struct mock_buffer final : buffer<T> {
+  MOCK_METHOD1(do_grow, size_t(size_t capacity));
+
+  void grow(size_t capacity) { this->set(this->data(), do_grow(capacity)); }
+
+  mock_buffer(T* data = nullptr, size_t buf_capacity = 0) {
+    this->set(data, buf_capacity);
+    ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
+      return capacity;
+    }));
+  }
+};
+
+TEST(buffer_test, ctor) {
+  {
+    mock_buffer<int> buffer;
+    EXPECT_EQ(nullptr, buffer.data());
+    EXPECT_EQ(static_cast<size_t>(0), buffer.size());
+    EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
+  }
+  {
+    int dummy;
+    mock_buffer<int> buffer(&dummy);
+    EXPECT_EQ(&dummy, &buffer[0]);
+    EXPECT_EQ(static_cast<size_t>(0), buffer.size());
+    EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
+  }
+  {
+    int dummy;
+    size_t capacity = std::numeric_limits<size_t>::max();
+    mock_buffer<int> buffer(&dummy, capacity);
+    EXPECT_EQ(&dummy, &buffer[0]);
+    EXPECT_EQ(static_cast<size_t>(0), buffer.size());
+    EXPECT_EQ(capacity, buffer.capacity());
+  }
+}
+
+TEST(buffer_test, access) {
+  char data[10];
+  mock_buffer<char> buffer(data, sizeof(data));
+  buffer[0] = 11;
+  EXPECT_EQ(11, buffer[0]);
+  buffer[3] = 42;
+  EXPECT_EQ(42, *(&buffer[0] + 3));
+  const fmt::detail::buffer<char>& const_buffer = buffer;
+  EXPECT_EQ(42, const_buffer[3]);
+}
+
+TEST(buffer_test, try_resize) {
+  char data[123];
+  mock_buffer<char> buffer(data, sizeof(data));
+  buffer[10] = 42;
+  EXPECT_EQ(42, buffer[10]);
+  buffer.try_resize(20);
+  EXPECT_EQ(20u, buffer.size());
+  EXPECT_EQ(123u, buffer.capacity());
+  EXPECT_EQ(42, buffer[10]);
+  buffer.try_resize(5);
+  EXPECT_EQ(5u, buffer.size());
+  EXPECT_EQ(123u, buffer.capacity());
+  EXPECT_EQ(42, buffer[10]);
+  // Check if try_resize calls grow.
+  EXPECT_CALL(buffer, do_grow(124));
+  buffer.try_resize(124);
+  EXPECT_CALL(buffer, do_grow(200));
+  buffer.try_resize(200);
+}
+
+TEST(buffer_test, try_resize_partial) {
+  char data[10];
+  mock_buffer<char> buffer(data, sizeof(data));
+  EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
+  buffer.try_resize(20);
+  EXPECT_EQ(buffer.capacity(), 15);
+  EXPECT_EQ(buffer.size(), 15);
+}
+
+TEST(buffer_test, clear) {
+  mock_buffer<char> buffer;
+  EXPECT_CALL(buffer, do_grow(20));
+  buffer.try_resize(20);
+  buffer.try_resize(0);
+  EXPECT_EQ(static_cast<size_t>(0), buffer.size());
+  EXPECT_EQ(20u, buffer.capacity());
+}
+
+TEST(buffer_test, append) {
+  char data[15];
+  mock_buffer<char> buffer(data, 10);
+  auto test = "test";
+  buffer.append(test, test + 5);
+  EXPECT_STREQ(test, &buffer[0]);
+  EXPECT_EQ(5u, buffer.size());
+  buffer.try_resize(10);
+  EXPECT_CALL(buffer, do_grow(12));
+  buffer.append(test, test + 2);
+  EXPECT_EQ('t', buffer[10]);
+  EXPECT_EQ('e', buffer[11]);
+  EXPECT_EQ(12u, buffer.size());
+}
+
+TEST(buffer_test, append_partial) {
+  char data[10];
+  mock_buffer<char> buffer(data, sizeof(data));
+  testing::InSequence seq;
+  EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
+  EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
+    EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
+    buffer.clear();
+    return 10;
+  }));
+  auto test = "0123456789abcde";
+  buffer.append(test, test + 15);
+}
+
+TEST(buffer_test, append_allocates_enough_storage) {
+  char data[19];
+  mock_buffer<char> buffer(data, 10);
+  auto test = "abcdefgh";
+  buffer.try_resize(10);
+  EXPECT_CALL(buffer, do_grow(19));
+  buffer.append(test, test + 9);
+}
+
+struct custom_context {
+  using char_type = char;
+  using parse_context_type = fmt::format_parse_context;
+
+  bool called = false;
+
+  template <typename T> struct formatter_type {
+    auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
+      return ctx.begin();
+    }
+
+    const char* format(const T&, custom_context& ctx) {
+      ctx.called = true;
+      return nullptr;
+    }
+  };
+
+  void advance_to(const char*) {}
+};
+
+struct test_struct {};
+
+FMT_BEGIN_NAMESPACE
+template <typename Char> struct formatter<test_struct, Char> {
+  auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  auto format(test_struct, format_context& ctx) -> decltype(ctx.out()) {
+    auto test = string_view("test");
+    return std::copy_n(test.data(), test.size(), ctx.out());
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(arg_test, format_args) {
+  auto args = fmt::format_args();
+  EXPECT_FALSE(args.get(1));
+}
+
+TEST(arg_test, make_value_with_custom_context) {
+  auto t = test_struct();
+  fmt::detail::value<custom_context> arg(
+      fmt::detail::arg_mapper<custom_context>().map(t));
+  auto ctx = custom_context();
+  auto parse_ctx = fmt::format_parse_context("");
+  arg.custom.format(&t, parse_ctx, ctx);
+  EXPECT_TRUE(ctx.called);
+}
+
+// Use a unique result type to make sure that there are no undesirable
+// conversions.
+struct test_result {};
+
+template <typename T> struct mock_visitor {
+  template <typename U> struct result { using type = test_result; };
+
+  mock_visitor() {
+    ON_CALL(*this, visit(_)).WillByDefault(Return(test_result()));
+  }
+
+  MOCK_METHOD1_T(visit, test_result(T value));
+  MOCK_METHOD0_T(unexpected, void());
+
+  test_result operator()(T value) { return visit(value); }
+
+  template <typename U> test_result operator()(U) {
+    unexpected();
+    return test_result();
+  }
+};
+
+template <typename T> struct visit_type { using type = T; };
+
+#define VISIT_TYPE(type_, visit_type_) \
+  template <> struct visit_type<type_> { using type = visit_type_; }
+
+VISIT_TYPE(signed char, int);
+VISIT_TYPE(unsigned char, unsigned);
+VISIT_TYPE(short, int);
+VISIT_TYPE(unsigned short, unsigned);
+
+#if LONG_MAX == INT_MAX
+VISIT_TYPE(long, int);
+VISIT_TYPE(unsigned long, unsigned);
+#else
+VISIT_TYPE(long, long long);
+VISIT_TYPE(unsigned long, unsigned long long);
+#endif
+
+#define CHECK_ARG(Char, expected, value)                                  \
+  {                                                                       \
+    testing::StrictMock<mock_visitor<decltype(expected)>> visitor;        \
+    EXPECT_CALL(visitor, visit(expected));                                \
+    using iterator = std::back_insert_iterator<buffer<Char>>;             \
+    fmt::visit_format_arg(                                                \
+        visitor,                                                          \
+        fmt::detail::make_arg<fmt::basic_format_context<iterator, Char>>( \
+            value));                                                      \
+  }
+
+#define CHECK_ARG_SIMPLE(value)                             \
+  {                                                         \
+    using value_type = decltype(value);                     \
+    typename visit_type<value_type>::type expected = value; \
+    CHECK_ARG(char, expected, value)                        \
+    CHECK_ARG(wchar_t, expected, value)                     \
+  }
+
+template <typename T> class numeric_arg_test : public testing::Test {};
+
+using types =
+    testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
+                   unsigned, long, unsigned long, long long, unsigned long long,
+                   float, double, long double>;
+TYPED_TEST_SUITE(numeric_arg_test, types);
+
+template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
+T test_value() {
+  return static_cast<T>(42);
+}
+
+template <typename T,
+          fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
+T test_value() {
+  return static_cast<T>(4.2);
+}
+
+TYPED_TEST(numeric_arg_test, make_and_visit) {
+  CHECK_ARG_SIMPLE(test_value<TypeParam>());
+  CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min());
+  CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
+}
+
+TEST(arg_test, char_arg) {
+  CHECK_ARG(char, 'a', 'a');
+  CHECK_ARG(wchar_t, L'a', 'a');
+  CHECK_ARG(wchar_t, L'a', L'a');
+}
+
+TEST(arg_test, string_arg) {
+  char str_data[] = "test";
+  char* str = str_data;
+  const char* cstr = str;
+  CHECK_ARG(char, cstr, str);
+
+  auto sv = fmt::string_view(str);
+  CHECK_ARG(char, sv, std::string(str));
+}
+
+TEST(arg_test, wstring_arg) {
+  wchar_t str_data[] = L"test";
+  wchar_t* str = str_data;
+  const wchar_t* cstr = str;
+
+  auto sv = fmt::wstring_view(str);
+  CHECK_ARG(wchar_t, cstr, str);
+  CHECK_ARG(wchar_t, cstr, cstr);
+  CHECK_ARG(wchar_t, sv, std::wstring(str));
+  CHECK_ARG(wchar_t, sv, fmt::wstring_view(str));
+}
+
+TEST(arg_test, pointer_arg) {
+  void* p = nullptr;
+  const void* cp = nullptr;
+  CHECK_ARG(char, cp, p);
+  CHECK_ARG(wchar_t, cp, p);
+  CHECK_ARG_SIMPLE(cp);
+}
+
+struct check_custom {
+  test_result operator()(
+      fmt::basic_format_arg<fmt::format_context>::handle h) const {
+    struct test_buffer final : fmt::detail::buffer<char> {
+      char data[10];
+      test_buffer() : fmt::detail::buffer<char>(data, 0, 10) {}
+      void grow(size_t) {}
+    } buffer;
+    auto parse_ctx = fmt::format_parse_context("");
+    auto ctx = fmt::format_context(fmt::detail::buffer_appender<char>(buffer),
+                                   fmt::format_args());
+    h.format(parse_ctx, ctx);
+    EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
+    return test_result();
+  }
+};
+
+TEST(arg_test, custom_arg) {
+  auto test = test_struct();
+  using visitor =
+      mock_visitor<fmt::basic_format_arg<fmt::format_context>::handle>;
+  testing::StrictMock<visitor> v;
+  EXPECT_CALL(v, visit(_)).WillOnce(Invoke(check_custom()));
+  fmt::visit_format_arg(v, fmt::detail::make_arg<fmt::format_context>(test));
+}
+
+TEST(arg_test, visit_invalid_arg) {
+  testing::StrictMock<mock_visitor<fmt::monostate>> visitor;
+  EXPECT_CALL(visitor, visit(_));
+  auto arg = fmt::basic_format_arg<fmt::format_context>();
+  fmt::visit_format_arg(visitor, arg);
+}
+
+struct enabled_formatter {};
+struct disabled_formatter {};
+struct disabled_formatter_convertible {
+  operator int() const { return 42; }
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<enabled_formatter> {
+  auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+  auto format(enabled_formatter, format_context& ctx) -> decltype(ctx.out()) {
+    return ctx.out();
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(core_test, has_formatter) {
+  using fmt::has_formatter;
+  using context = fmt::format_context;
+  static_assert(has_formatter<enabled_formatter, context>::value, "");
+  static_assert(!has_formatter<disabled_formatter, context>::value, "");
+  static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
+                "");
+}
+
+TEST(core_test, is_formattable) {
+  static_assert(fmt::is_formattable<enabled_formatter>::value, "");
+  static_assert(!fmt::is_formattable<disabled_formatter>::value, "");
+  static_assert(fmt::is_formattable<disabled_formatter_convertible>::value, "");
+}
+
+TEST(core_test, format) { EXPECT_EQ(fmt::format("{}", 42), "42"); }
+
+TEST(core_test, format_to) {
+  std::string s;
+  fmt::format_to(std::back_inserter(s), "{}", 42);
+  EXPECT_EQ(s, "42");
+}
+
+struct convertible_to_int {
+  operator int() const { return 42; }
+};
+
+struct convertible_to_c_string {
+  operator const char*() const { return "foo"; }
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<convertible_to_int> {
+  auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+  auto format(convertible_to_int, format_context& ctx) -> decltype(ctx.out()) {
+    return std::copy_n("foo", 3, ctx.out());
+  }
+};
+
+template <> struct formatter<convertible_to_c_string> {
+  FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+  auto format(convertible_to_c_string, format_context& ctx)
+      -> decltype(ctx.out()) {
+    return std::copy_n("bar", 3, ctx.out());
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(core_test, formatter_overrides_implicit_conversion) {
+  EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
+  EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
+}
+
+// Test that check is not found by ADL.
+template <typename T> void check(T);
+TEST(core_test, adl_check) {
+  EXPECT_EQ(fmt::format("{}", test_struct()), "test");
+}
+
+TEST(core_test, to_string_view_foreign_strings) {
+  using namespace test_ns;
+  EXPECT_EQ(to_string_view(test_string<char>("42")), "42");
+  fmt::detail::type type =
+      fmt::detail::mapped_type_constant<test_string<char>,
+                                        fmt::format_context>::value;
+  EXPECT_EQ(type, fmt::detail::type::string_type);
+}
+
+TEST(core_test, format_foreign_strings) {
+  using namespace test_ns;
+  EXPECT_EQ(fmt::format(test_string<char>("{}"), 42), "42");
+}
+
+struct implicitly_convertible_to_string {
+  operator std::string() const { return "foo"; }
+};
+
+struct implicitly_convertible_to_string_view {
+  operator fmt::string_view() const { return "foo"; }
+};
+
+TEST(core_test, format_implicitly_convertible_to_string_view) {
+  EXPECT_EQ("foo", fmt::format("{}", implicitly_convertible_to_string_view()));
+}
+
+// std::is_constructible is broken in MSVC until version 2015.
+#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
+struct explicitly_convertible_to_string_view {
+  explicit operator fmt::string_view() const { return "foo"; }
+};
+
+TEST(core_test, format_explicitly_convertible_to_string_view) {
+  EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view()));
+}
+
+#  ifdef FMT_USE_STRING_VIEW
+struct explicitly_convertible_to_std_string_view {
+  explicit operator std::string_view() const { return "foo"; }
+};
+
+TEST(core_test, format_explicitly_convertible_to_std_string_view) {
+  EXPECT_EQ("foo",
+            fmt::format("{}", explicitly_convertible_to_std_string_view()));
+}
+#  endif
+#endif
+
+struct convertible_to_long_long {
+  operator long long() const { return 1LL << 32; }
+};
+
+TEST(format_test, format_convertible_to_long_long) {
+  EXPECT_EQ("100000000", fmt::format("{:x}", convertible_to_long_long()));
+}
+
+struct disabled_rvalue_conversion {
+  operator const char*() const& { return "foo"; }
+  operator const char*() & { return "foo"; }
+  operator const char*() const&& = delete;
+  operator const char*() && = delete;
+};
+
+TEST(core_test, disabled_rvalue_conversion) {
+  EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
+}
diff --git a/libs/fmt/test/cuda-test/CMakeLists.txt b/libs/fmt/test/cuda-test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..be8685b897ec3d3618878b4f28ab10a53831e283
--- /dev/null
+++ b/libs/fmt/test/cuda-test/CMakeLists.txt
@@ -0,0 +1,73 @@
+# We can find some usecases which follow the guide of CMake which uses
+# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake
+# built-in functions use NVCC.
+
+# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement
+#
+# However, this requires CMake version 3.10 or higher and we can't be sure most
+# of the CUDA projects are using those.
+#
+# This test relies on `find_package(CUDA)` in the parent CMake config.
+
+# These can be updated when NVCC becomes ready for C++ 17 features
+# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
+set(CMAKE_CUDA_STANDARD 14)
+set(CMAKE_CUDA_STANDARD_REQUIRED 14)
+
+# In this test, we assume that the user is going to compile CUDA source code
+# with some libraries (fmt in this case).
+#
+# In addition to that, this test invokes both the C++ host compiler and NVCC
+# by providing another (non-CUDA) C++ source code.
+if (${CMAKE_VERSION} VERSION_LESS 3.15)
+  # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html
+  list(APPEND CUDA_NVCC_FLAGS "-std=c++14")
+  if (MSVC)
+    # This is the solution of pytorch:
+    # https://github.com/pytorch/pytorch/pull/7118
+    list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14")
+    list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus")
+    # for the reason of this -Xcompiler options, see below.
+  endif ()
+  cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
+  target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
+  if (MSVC)
+    # This part is for (non-CUDA) C++ code. MSVC can define incorrect
+    # `__cplusplus` macro. Fix for the issue is to use additional compiler flag. 
+    #
+    # See Also:
+    # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
+    # https://github.com/Microsoft/vscode-cpptools/issues/2595
+    target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-)
+  endif ()
+else()
+  # now using a "new" way of handling CUDA
+  add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
+  set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
+  target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
+  if (MSVC)
+    # with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will
+    # not set __cplusplus correctly anyway, while nvcc will ignore it.
+    # If specified for nvcc on the command line as '-std=c++14' nvcc will emit this
+    # message instead:
+    # nvcc warning : The -std=c++14 flag is not supported with the configured host
+    #                compiler. Flag will be ignored.
+    set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY
+      COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus)
+    set_property(SOURCE cpp14.cc APPEND PROPERTY
+      COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus)
+  endif()
+endif()
+
+get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD)
+message(STATUS "cuda_standard:          ${IN_USE_CUDA_STANDARD}")
+
+get_target_property(IN_USE_CUDA_STANDARD_REQUIRED
+    fmt-in-cuda-test CUDA_STANDARD_REQUIRED)
+message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}")
+
+# We don't use PUBLIC or other keyword for reasons explained in the
+# CUDA_LINK_LIBRARIES_KEYWORD section in
+# https://cmake.org/cmake/help/latest/module/FindCUDA.html
+target_link_libraries(fmt-in-cuda-test fmt::fmt)
+
diff --git a/libs/fmt/test/cuda-test/cpp14.cc b/libs/fmt/test/cuda-test/cpp14.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ad8463962f58200df35eacbc897d4b9d1c3aeed0
--- /dev/null
+++ b/libs/fmt/test/cuda-test/cpp14.cc
@@ -0,0 +1,11 @@
+#include <fmt/core.h>
+
+// The purpose of this part is to ensure NVCC's host compiler also supports
+// the standard version. See 'cuda-cpp14.cu'.
+//
+// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
+static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler");
+
+auto make_message_cpp() -> std::string {
+  return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus);
+}
diff --git a/libs/fmt/test/cuda-test/cuda-cpp14.cu b/libs/fmt/test/cuda-test/cuda-cpp14.cu
new file mode 100644
index 0000000000000000000000000000000000000000..76a9d08836f6c6a3fa1c64e28bd43cae0392fc93
--- /dev/null
+++ b/libs/fmt/test/cuda-test/cuda-cpp14.cu
@@ -0,0 +1,28 @@
+//  Direct NVCC command line example:
+//
+//  nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \
+//       -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus
+
+// Ensure that we are using the latest C++ standard for NVCC
+// The version is C++14
+//
+// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support
+// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
+static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc");
+
+#include <fmt/core.h>
+
+#include <cuda.h>
+#include <iostream>
+
+extern auto make_message_cpp() -> std::string;
+extern auto make_message_cuda() -> std::string;
+
+int main() {
+  std::cout << make_message_cuda() << std::endl;
+  std::cout << make_message_cpp() << std::endl;
+}
+
+auto make_message_cuda() -> std::string {
+  return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus);
+}
diff --git a/libs/fmt/test/enforce-checks-test.cc b/libs/fmt/test/enforce-checks-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..103f827e327959657b1e7656ab0d05243195a91b
--- /dev/null
+++ b/libs/fmt/test/enforce-checks-test.cc
@@ -0,0 +1,62 @@
+// Formatting library for C++ - formatting library tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include <iterator>
+#include <vector>
+
+#include "fmt/chrono.h"
+#include "fmt/color.h"
+#include "fmt/format.h"
+#include "fmt/locale.h"
+#include "fmt/ostream.h"
+#include "fmt/ranges.h"
+
+// Exercise the API to verify that everything we expect to can compile.
+void test_format_api() {
+  fmt::format(FMT_STRING("{}"), 42);
+  fmt::format(FMT_STRING(L"{}"), 42);
+  fmt::format(FMT_STRING("noop"));
+
+  fmt::to_string(42);
+  fmt::to_wstring(42);
+
+  std::vector<char> out;
+  fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42);
+
+  char buffer[4];
+  fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345);
+
+  wchar_t wbuffer[4];
+  fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345);
+}
+
+void test_chrono() {
+  fmt::format(FMT_STRING("{}"), std::chrono::seconds(42));
+  fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42));
+}
+
+void test_text_style() {
+  fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
+  fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
+
+  fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
+  std::string out;
+  fmt::format_to(std::back_inserter(out), ts,
+                 FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3);
+}
+
+void test_range() {
+  std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'};
+  fmt::format(FMT_STRING("{}"), hello);
+}
+
+int main() {
+  test_format_api();
+  test_chrono();
+  test_text_style();
+  test_range();
+}
diff --git a/libs/fmt/test/find-package-test/CMakeLists.txt b/libs/fmt/test/find-package-test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..93d686e64335d8f44c267fe00ddfdc808256e5be
--- /dev/null
+++ b/libs/fmt/test/find-package-test/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.1...3.18)
+
+project(fmt-test)
+
+find_package(FMT REQUIRED)
+
+add_executable(library-test main.cc)
+target_link_libraries(library-test fmt::fmt)
+target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+target_include_directories(library-test PUBLIC SYSTEM .)
+
+if (TARGET fmt::fmt-header-only)
+  add_executable(header-only-test main.cc)
+  target_link_libraries(header-only-test fmt::fmt-header-only)
+  target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
+  target_include_directories(header-only-test PUBLIC SYSTEM .)
+endif ()
diff --git a/libs/fmt/test/find-package-test/main.cc b/libs/fmt/test/find-package-test/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f39f377cc7c6a173ae9b1e9aa29e99452f71b7b6
--- /dev/null
+++ b/libs/fmt/test/find-package-test/main.cc
@@ -0,0 +1,6 @@
+#include "fmt/format.h"
+
+int main(int argc, char** argv) {
+  for(int i = 0; i < argc; ++i)
+    fmt::print("{}: {}\n", i, argv[i]);
+}
diff --git a/libs/fmt/test/format b/libs/fmt/test/format
new file mode 100644
index 0000000000000000000000000000000000000000..a6c9e7e990b159e7cab0d97962db4feb15468387
--- /dev/null
+++ b/libs/fmt/test/format
@@ -0,0 +1,856 @@
+// Formatting library for C++ - the standard API implementation
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_FORMAT_
+#define FMT_FORMAT_
+
+#include <algorithm>
+#include <cassert>
+#include <variant>
+#include "fmt/format.h"
+
+// This implementation verifies the correctness of the standard API proposed in
+// P0645 Text Formatting and is optimized for copy-pasting from the paper, not
+// for efficiency or readability. An efficient implementation should not use
+// std::variant and should store packed argument type tags separately from
+// values in basic_format_args for small number of arguments.
+
+namespace std {
+template<class T>
+constexpr bool Integral = is_integral_v<T>;
+
+template <class O>
+  using iter_difference_t = ptrdiff_t;
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.syn
+namespace std {
+  // [format.error], class format_error
+  class format_error;
+
+  // [format.formatter], formatter
+  template<class charT> class basic_format_parse_context;
+  using format_parse_context = basic_format_parse_context<char>;
+  using wformat_parse_context = basic_format_parse_context<wchar_t>;
+  
+  template<class Out, class charT> class basic_format_context;
+  using format_context = basic_format_context<
+    /* unspecified */ fmt::detail::buffer_appender<char>, char>;
+  using wformat_context = basic_format_context<
+    /* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>;
+
+  template<class T, class charT = char> struct formatter {
+    formatter() = delete;
+  };
+  
+  // [format.arguments], arguments
+  template<class Context> class basic_format_arg;
+
+  template<class Visitor, class Context>
+    /* see below */ auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg);
+
+  template<class Context, class... Args> struct format_arg_store; // exposition only
+
+  template<class Context> class basic_format_args;
+  using format_args = basic_format_args<format_context>;
+  using wformat_args = basic_format_args<wformat_context>;
+
+  template<class Out, class charT>
+    using format_args_t = basic_format_args<basic_format_context<Out, charT>>;
+
+  template<class Context = format_context, class... Args>
+    format_arg_store<Context, Args...>
+      make_format_args(const Args&... args);
+  template<class... Args>
+    format_arg_store<wformat_context, Args...>
+      make_wformat_args(const Args&... args);
+
+  // [format.functions], formatting functions
+  template<class... Args>
+    string format(string_view fmt, const Args&... args);
+  template<class... Args>
+    wstring format(wstring_view fmt, const Args&... args);
+
+  string vformat(string_view fmt, format_args args);
+  wstring vformat(wstring_view fmt, wformat_args args);
+
+  template<class Out, class... Args>
+    Out format_to(Out out, string_view fmt, const Args&... args);
+  template<class Out, class... Args>
+    Out format_to(Out out, wstring_view fmt, const Args&... args);
+
+  template<class Out>
+    Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args);
+  template<class Out>
+    Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
+
+  template<class Out>
+    struct format_to_n_result {
+      Out out;
+      iter_difference_t<Out> size;
+    };
+  
+  template<class Out, class... Args>
+    format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
+                                        string_view fmt, const Args&... args);
+  template<class Out, class... Args>
+    format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
+                                        wstring_view fmt, const Args&... args);
+
+  template<class... Args>
+    size_t formatted_size(string_view fmt, const Args&... args);
+  template<class... Args>
+    size_t formatted_size(wstring_view fmt, const Args&... args);
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.error
+namespace std {
+  class format_error : public runtime_error {
+  public:
+    explicit format_error(const string& what_arg) : runtime_error(what_arg) {}
+    explicit format_error(const char* what_arg) : runtime_error(what_arg) {}
+  };
+}
+
+namespace std {
+namespace detail {
+struct error_handler {
+  // This function is intentionally not constexpr to give a compile-time error.
+  void on_error(const char* message) {
+    throw std::format_error(message);
+  }
+};
+}
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.parse_context
+namespace std {
+  template<class charT>
+  class basic_format_parse_context {
+  public:
+    using char_type = charT;
+    using const_iterator = typename basic_string_view<charT>::const_iterator;
+    using iterator = const_iterator;
+
+  private:
+    iterator begin_;                              // exposition only
+    iterator end_;                                // exposition only
+    enum indexing { unknown, manual, automatic }; // exposition only
+    indexing indexing_;                           // exposition only
+    size_t next_arg_id_;                          // exposition only
+    size_t num_args_;                             // exposition only
+
+  public:
+    explicit constexpr basic_format_parse_context(basic_string_view<charT> fmt,
+                                                  size_t num_args = 0) noexcept;
+    basic_format_parse_context(const basic_format_parse_context&) = delete;
+    basic_format_parse_context& operator=(const basic_format_parse_context&) = delete;
+
+    constexpr const_iterator begin() const noexcept;
+    constexpr const_iterator end() const noexcept;
+    constexpr void advance_to(const_iterator it);
+
+    constexpr size_t next_arg_id();
+    constexpr void check_arg_id(size_t id);
+
+    // Implementation detail:
+    constexpr void check_arg_id(fmt::string_view) {}
+    detail::error_handler error_handler() const { return {}; }
+    void on_error(const char* msg) { error_handler().on_error(msg); }
+  };
+}
+
+namespace std {
+template<class charT>
+/* explicit */ constexpr basic_format_parse_context<charT>::
+                   basic_format_parse_context(basic_string_view<charT> fmt,
+                                              size_t num_args) noexcept
+: begin_(fmt.begin()), end_(fmt.end()), indexing_(unknown), next_arg_id_(0), num_args_(num_args) {}
+
+template<class charT>
+constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::begin() const noexcept { return begin_; }
+
+template<class charT>
+constexpr typename basic_format_parse_context<charT>::const_iterator basic_format_parse_context<charT>::end() const noexcept { return end_; }
+
+template<class charT>
+constexpr void basic_format_parse_context<charT>::advance_to(typename basic_format_parse_context<charT>::iterator it) { begin_ = it; }
+
+template<class charT>
+constexpr size_t basic_format_parse_context<charT>::next_arg_id() {
+  if (indexing_ == manual)
+    throw format_error("manual to automatic indexing");
+  if (indexing_ == unknown)
+    indexing_ = automatic;
+  return next_arg_id_++;
+}
+
+template<class charT>
+constexpr void basic_format_parse_context<charT>::check_arg_id(size_t id) {
+  // clang doesn't support __builtin_is_constant_evaluated yet
+  //if (!(!__builtin_is_constant_evaluated() || id < num_args_))
+  //  throw format_error(invalid index is out of range");
+  if (indexing_ == automatic)
+    throw format_error("automatic to manual indexing");
+  if (indexing_ == unknown)
+    indexing_ = manual;
+}
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.context
+namespace std {
+  template<class Out, class charT>
+  class basic_format_context {
+    basic_format_args<basic_format_context> args_; // exposition only
+    Out out_;                                      // exposition only
+
+  public:
+    using iterator = Out;
+    using char_type = charT;
+    template<class T> using formatter_type = formatter<T, charT>;
+
+    basic_format_arg<basic_format_context> arg(size_t id) const;
+
+    iterator out();
+    void advance_to(iterator it);
+
+    // Implementation details:
+    using format_arg = basic_format_arg<basic_format_context>;
+    basic_format_context(Out out, basic_format_args<basic_format_context> args, fmt::detail::locale_ref)
+    : args_(args), out_(out) {}
+    detail::error_handler error_handler() const { return {}; }
+    basic_format_arg<basic_format_context> arg(fmt::basic_string_view<charT>) const {
+      return {}; // unused: named arguments are not supported yet
+    }
+    void on_error(const char* msg) { error_handler().on_error(msg); }
+  };
+}
+
+namespace std {
+template<class O, class charT>
+basic_format_arg<basic_format_context<O, charT>> basic_format_context<O, charT>::arg(size_t id) const { return args_.get(id); }
+
+template<class O, class charT>
+typename basic_format_context<O, charT>::iterator basic_format_context<O, charT>::out() { return out_; }
+
+template<class O, class charT>
+void basic_format_context<O, charT>::advance_to(typename basic_format_context<O, charT>::iterator it) { out_ = it; }
+}
+
+namespace std {
+namespace detail {
+template <typename T>
+constexpr bool is_standard_integer_v =
+    std::is_same_v<T, signed char> ||
+    std::is_same_v<T, short int> ||
+    std::is_same_v<T, int> ||
+    std::is_same_v<T, long int> ||
+    std::is_same_v<T, long long int>;
+
+template <typename T>
+constexpr bool is_standard_unsigned_integer_v =
+    std::is_same_v<T, unsigned char> ||
+    std::is_same_v<T, unsigned short int> ||
+    std::is_same_v<T, unsigned int> ||
+    std::is_same_v<T, unsigned long int> ||
+    std::is_same_v<T, unsigned long long int>;
+
+template <typename T, typename Char> struct formatter;
+}
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.arg
+namespace std {
+  template<class Context>
+  class basic_format_arg {
+  public:
+    class handle;
+
+  private:
+    using char_type = typename Context::char_type;                       // exposition only
+
+    variant<monostate, bool, char_type,
+            int, unsigned int, long long int, unsigned long long int,
+            double, long double,
+            const char_type*, basic_string_view<char_type>,
+            const void*, handle> value;                                  // exposition only
+
+    template<typename T,
+      typename = enable_if_t<
+        std::is_same_v<T, bool> ||
+        std::is_same_v<T, char_type> ||
+        (std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>) ||
+        detail::is_standard_integer_v<T> ||
+        detail::is_standard_unsigned_integer_v<T> ||
+        sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0
+    >> explicit basic_format_arg(const T& v) noexcept; // exposition only
+    explicit basic_format_arg(float n) noexcept;                         // exposition only
+    explicit basic_format_arg(double n) noexcept;                        // exposition only
+    explicit basic_format_arg(long double n) noexcept;                   // exposition only
+    explicit basic_format_arg(const char_type* s);                       // exposition only
+
+    template<class traits>
+      explicit basic_format_arg(
+        basic_string_view<char_type, traits> s) noexcept;                // exposition only
+
+    template<class traits, class Allocator>
+      explicit basic_format_arg(
+        const basic_string<char_type, traits, Allocator>& s) noexcept;   // exposition only
+
+    explicit basic_format_arg(nullptr_t) noexcept;                       // exposition only
+
+    template<class T, typename = enable_if_t<is_void_v<T>>>
+      explicit basic_format_arg(const T* p) noexcept;                    // exposition only
+
+    // Fails due to a bug in clang
+    //template<class Visitor, class Ctx>
+    //  friend auto visit_format_arg(Visitor&& vis,
+    //                                    basic_format_arg<Ctx> arg);           // exposition only
+
+    friend auto get_value(basic_format_arg arg) {
+      return arg.value;
+    }
+
+    template <typename T, typename Char> friend struct detail::formatter;
+
+    template<class Ctx, class... Args>
+      friend format_arg_store<Ctx, Args...>
+        make_format_args(const Args&... args);                           // exposition only
+
+  public:
+    basic_format_arg() noexcept;
+
+    explicit operator bool() const noexcept;
+  };
+}
+
+namespace std {
+template<class Context>
+basic_format_arg<Context>::basic_format_arg() noexcept {}
+
+template<class Context>
+template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T& v) noexcept {
+  if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, char_type>)
+    value = v;
+  else if constexpr (std::is_same_v<T, char> && std::is_same_v<char_type, wchar_t>)
+    value = static_cast<wchar_t>(v);
+  else if constexpr (detail::is_standard_integer_v<T> && sizeof(T) <= sizeof(int))
+    value = static_cast<int>(v);
+  else if constexpr (detail::is_standard_unsigned_integer_v<T> && sizeof(T) <= sizeof(unsigned))
+    value = static_cast<unsigned>(v);
+  else if constexpr (detail::is_standard_integer_v<T>)
+    value = static_cast<long long int>(v);
+  else if constexpr (detail::is_standard_unsigned_integer_v<T>)
+    value = static_cast<unsigned long long int>(v);
+  else if constexpr (sizeof(typename Context::template formatter_type<T>().format(declval<const T&>(), declval<Context&>())) != 0)
+    value = handle(v);
+}
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(float n) noexcept
+  : value(static_cast<double>(n)) {}
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(double n) noexcept
+  : value(n) {}
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(long double n) noexcept
+  : value(n) {}
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(const typename basic_format_arg<Context>::char_type* s)
+  : value(s) {
+  assert(s != nullptr);
+}
+
+template<class Context>
+template<class traits>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(basic_string_view<char_type, traits> s) noexcept
+  : value(s) {}
+
+template<class Context>
+template<class traits, class Allocator>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(
+    const basic_string<char_type, traits, Allocator>& s) noexcept
+  : value(basic_string_view<char_type>(s.data(), s.size())) {}
+
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::basic_format_arg(nullptr_t) noexcept
+  : value(static_cast<const void*>(nullptr)) {}
+
+template<class Context>
+template<class T, typename> /* explicit */ basic_format_arg<Context>::basic_format_arg(const T* p) noexcept
+  : value(p) {}
+
+template<class Context>
+/* explicit */ basic_format_arg<Context>::operator bool() const noexcept {
+    return !holds_alternative<monostate>(value);
+}
+}
+
+namespace std {
+  template<class Context>
+  class basic_format_arg<Context>::handle {
+    const void* ptr_;                                         // exposition only
+    void (*format_)(basic_format_parse_context<char_type>&,
+                    Context&, const void*);                   // exposition only
+
+    template<class T> explicit handle(const T& val) noexcept; // exposition only
+
+    friend class basic_format_arg<Context>;                   // exposition only
+
+  public:
+    void format(basic_format_parse_context<char_type>&, Context& ctx) const;
+  };
+}
+
+namespace std {
+template<class Context>
+template<class T> /* explicit */ basic_format_arg<Context>::handle::handle(const T& val) noexcept
+  : ptr_(&val), format_([](basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx, const void* ptr) {
+    typename Context::template formatter_type<T> f;
+    parse_ctx.advance_to(f.parse(parse_ctx));
+    format_ctx.advance_to(f.format(*static_cast<const T*>(ptr), format_ctx));
+  }) {}
+
+template<class Context>
+void basic_format_arg<Context>::handle::format(basic_format_parse_context<char_type>& parse_ctx, Context& format_ctx) const {
+  format_(parse_ctx, format_ctx, ptr_);
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.visit
+template<class Visitor, class Context>
+  auto visit_format_arg(Visitor&& vis, basic_format_arg<Context> arg) {
+    return visit(vis, get_value(arg));
+  }
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.store
+namespace std {
+  template<class Context, class... Args>
+  struct format_arg_store { // exposition only
+    array<basic_format_arg<Context>, sizeof...(Args)> args;
+  };
+}
+
+// https://fmt.dev/Text%20Formatting.html#format.basic_args
+namespace std {
+  template<class Context>
+  class basic_format_args {
+    size_t size_;                           // exposition only
+    const basic_format_arg<Context>* data_; // exposition only
+
+  public:
+    basic_format_args() noexcept;
+
+    template<class... Args>
+      basic_format_args(const format_arg_store<Context, Args...>& store) noexcept;
+
+    basic_format_arg<Context> get(size_t i) const noexcept;
+  };
+}
+
+namespace std {
+
+template<class Context>
+basic_format_args<Context>::basic_format_args() noexcept : size_(0) {}
+
+template<class Context>
+template<class... Args>
+  basic_format_args<Context>::basic_format_args(const format_arg_store<Context, Args...>& store) noexcept
+  : size_(sizeof...(Args)), data_(store.args.data()) {}
+
+template<class Context>
+basic_format_arg<Context> basic_format_args<Context>::get(size_t i) const noexcept {
+  return i < size_ ? data_[i] : basic_format_arg<Context>();
+}
+}
+
+namespace std {
+// https://fmt.dev/Text%20Formatting.html#format.make_args
+template<class Context /*= format_context*/, class... Args>
+  format_arg_store<Context, Args...> make_format_args(const Args&... args) {
+    return {basic_format_arg<Context>(args)...};
+  }
+
+// https://fmt.dev/Text%20Formatting.html#format.make_wargs
+template<class... Args>
+  format_arg_store<wformat_context, Args...> make_wformat_args(const Args&... args) {
+    return make_format_args<wformat_context>(args...);
+  }
+}
+
+namespace std {
+namespace detail {
+
+template <typename OutputIt, typename Char>
+class arg_formatter
+    : public fmt::detail::arg_formatter_base<OutputIt, Char, error_handler> {
+ private:
+  using char_type = Char;
+  using base = fmt::detail::arg_formatter_base<OutputIt, Char, error_handler>;
+  using format_context = std::basic_format_context<OutputIt, Char>;
+  using parse_context = basic_format_parse_context<Char>;
+
+  parse_context* parse_ctx_;
+  format_context& ctx_;
+
+ public:
+  using iterator = OutputIt;
+  using format_specs = typename base::format_specs;
+
+  /**
+    \rst
+    Constructs an argument formatter object.
+    *ctx* is a reference to the formatting context,
+    *spec* contains format specifier information for standard argument types.
+    \endrst
+   */
+  arg_formatter(format_context& ctx, parse_context* parse_ctx = nullptr, fmt::format_specs* spec = nullptr)
+      : base(ctx.out(), spec, {}), parse_ctx_(parse_ctx), ctx_(ctx) {}
+
+  using base::operator();
+
+  /** Formats an argument of a user-defined type. */
+  iterator operator()(typename std::basic_format_arg<format_context>::handle handle) {
+    handle.format(*parse_ctx_, ctx_);
+    return this->out();
+  }
+
+  iterator operator()(monostate) {
+    throw format_error("");
+  }
+};
+
+template <typename Context>
+inline fmt::detail::type get_type(basic_format_arg<Context> arg) {
+  return visit_format_arg([&] (auto val) {
+    using char_type = typename Context::char_type;
+    using T = decltype(val);
+    if (std::is_same_v<T, monostate>)
+      return fmt::detail::type::none_type;
+    if (std::is_same_v<T, bool>)
+      return fmt::detail::type::bool_type;
+    if (std::is_same_v<T, char_type>)
+      return fmt::detail::type::char_type;
+    if (std::is_same_v<T, int>)
+      return fmt::detail::type::int_type;
+    if (std::is_same_v<T, unsigned int>)
+      return fmt::detail::type::uint_type;
+    if (std::is_same_v<T, long long int>)
+      return fmt::detail::type::long_long_type;
+    if (std::is_same_v<T, unsigned long long int>)
+      return fmt::detail::type::ulong_long_type;
+    if (std::is_same_v<T, double>)
+      return fmt::detail::type::double_type;
+    if (std::is_same_v<T, long double>)
+      return fmt::detail::type::long_double_type;
+    if (std::is_same_v<T, const char_type*>)
+      return fmt::detail::type::cstring_type;
+    if (std::is_same_v<T, basic_string_view<char_type>>)
+      return fmt::detail::type::string_type;
+    if (std::is_same_v<T, const void*>)
+      return fmt::detail::type::pointer_type;
+    assert(get_value(arg).index() == 12);
+    return fmt::detail::type::custom_type;
+  }, arg);
+}
+
+template <typename Context>
+class custom_formatter {
+ private:
+  using parse_context = basic_format_parse_context<typename Context::char_type>;
+  parse_context& parse_ctx_;
+  Context& format_ctx_;
+
+ public:
+  custom_formatter(parse_context& parse_ctx, Context& ctx) : parse_ctx_(parse_ctx), format_ctx_(ctx) {}
+
+  bool operator()(typename basic_format_arg<Context>::handle h) const {
+    h.format(parse_ctx_, format_ctx_);
+    return true;
+  }
+
+  template <typename T> bool operator()(T) const { return false; }
+};
+
+template <typename ArgFormatter, typename Char, typename Context>
+struct format_handler : detail::error_handler {
+  using iterator = typename ArgFormatter::iterator;
+
+  format_handler(iterator out, basic_string_view<Char> str,
+                 basic_format_args<Context> format_args,
+                 fmt::detail::locale_ref loc)
+      : parse_ctx(str), context(out, format_args, loc) {}
+
+  void on_text(const Char* begin, const Char* end) {
+    auto size = fmt::detail::to_unsigned(end - begin);
+    auto out = context.out();
+    auto&& it = fmt::detail::reserve(out, size);
+    it = std::copy_n(begin, size, it);
+    context.advance_to(out);
+  }
+
+  int on_arg_id() { return parse_ctx.next_arg_id(); }
+  int on_arg_id(unsigned id) { return parse_ctx.check_arg_id(id), id; }
+  int on_arg_id(fmt::basic_string_view<Char>) { return 0; }
+
+  void on_replacement_field(int id, const Char* p) {
+    auto arg = context.arg(id);
+    parse_ctx.advance_to(parse_ctx.begin() + (p  - &*parse_ctx.begin()));
+    custom_formatter<Context> f(parse_ctx, context);
+    if (!visit_format_arg(f, arg))
+      context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx), arg));
+  }
+
+  const Char* on_format_specs(int id, const Char* begin, const Char* end) {
+    auto arg = context.arg(id);
+    parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin()));
+    custom_formatter<Context> f(parse_ctx, context);
+    if (visit_format_arg(f, arg)) return &*parse_ctx.begin();
+    fmt::basic_format_specs<Char> specs;
+    using fmt::detail::specs_handler;
+    using parse_context = basic_format_parse_context<Char>;
+    fmt::detail::specs_checker<specs_handler<parse_context, Context>> handler(
+        specs_handler<parse_context, Context>(specs, parse_ctx, context), get_type(arg));
+    begin = parse_format_specs(begin, end, handler);
+    if (begin == end || *begin != '}') on_error("missing '}' in format string");
+    parse_ctx.advance_to(parse_ctx.begin() + (begin - &*parse_ctx.begin()));
+    context.advance_to(visit_format_arg(ArgFormatter(context, &parse_ctx, &specs), arg));
+    return begin;
+  }
+
+  basic_format_parse_context<Char> parse_ctx;
+  Context context;
+};
+
+template <typename T, typename Char>
+struct formatter {
+  // Parses format specifiers stopping either at the end of the range or at the
+  // terminating '}'.
+  template <typename ParseContext>
+  FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext& ctx) {
+    namespace detail = fmt::detail;
+    typedef detail::dynamic_specs_handler<ParseContext> handler_type;
+    auto type = detail::mapped_type_constant<T, fmt::buffer_context<Char>>::value;
+    detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+                                                  type);
+    auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+    auto type_spec = specs_.type;
+    auto eh = ctx.error_handler();
+    switch (type) {
+    case detail::type::none_type:
+      FMT_ASSERT(false, "invalid argument type");
+      break;
+    case detail::type::int_type:
+    case detail::type::uint_type:
+    case detail::type::long_long_type:
+    case detail::type::ulong_long_type:
+    case detail::type::bool_type:
+      handle_int_type_spec(type_spec,
+                           detail::int_type_checker<decltype(eh)>(eh));
+      break;
+    case detail::type::char_type:
+      handle_char_specs(
+          &specs_, detail::char_specs_checker<decltype(eh)>(type_spec, eh));
+      break;
+    case detail::type::double_type:
+    case detail::type::long_double_type:
+      detail::parse_float_type_spec(specs_, eh);
+      break;
+    case detail::type::cstring_type:
+      detail::handle_cstring_type_spec(
+          type_spec, detail::cstring_type_checker<decltype(eh)>(eh));
+      break;
+    case detail::type::string_type:
+      detail::check_string_type_spec(type_spec, eh);
+      break;
+    case detail::type::pointer_type:
+      detail::check_pointer_type_spec(type_spec, eh);
+      break;
+    case detail::type::custom_type:
+      // Custom format specifiers should be checked in parse functions of
+      // formatter specializations.
+      break;
+    }
+    return it;
+  }
+
+  template <typename FormatContext>
+  auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
+    fmt::detail::handle_dynamic_spec<fmt::detail::width_checker>(
+        specs_.width, specs_.width_ref, ctx);
+    fmt::detail::handle_dynamic_spec<fmt::detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+    using af = arg_formatter<typename FormatContext::iterator,
+                             typename FormatContext::char_type>;
+    return visit_format_arg(af(ctx, nullptr, &specs_),
+                            basic_format_arg<FormatContext>(val));
+  }
+
+ private:
+  fmt::detail::dynamic_format_specs<Char> specs_;
+};
+}  // namespace detail
+
+// https://fmt.dev/Text%20Formatting.html#format.functions
+template<class... Args>
+  string format(string_view fmt, const Args&... args) {
+    return vformat(fmt, make_format_args(args...));
+  }
+
+template<class... Args>
+  wstring format(wstring_view fmt, const Args&... args) {
+    return vformat(fmt, make_wformat_args(args...));
+  }
+
+string vformat(string_view fmt, format_args args) {
+  fmt::memory_buffer mbuf;
+  fmt::detail::buffer<char>& buf = mbuf;
+  using af = detail::arg_formatter<fmt::format_context::iterator, char>;
+  detail::format_handler<af, char, format_context>
+    h(fmt::detail::buffer_appender<char>(buf), fmt, args, {});
+  fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
+  return to_string(mbuf);
+}
+
+wstring vformat(wstring_view fmt, wformat_args args);
+
+template<class Out, class... Args>
+  Out format_to(Out out, string_view fmt, const Args&... args) {
+    using context = basic_format_context<Out, decltype(fmt)::value_type>;
+    return vformat_to(out, fmt, make_format_args<context>(args...));
+  }
+
+template<class Out, class... Args>
+  Out format_to(Out out, wstring_view fmt, const Args&... args) {
+    using context = basic_format_context<Out, decltype(fmt)::value_type>;
+    return vformat_to(out, fmt, make_format_args<context>(args...));
+  }
+
+template<class Out>
+  Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args) {
+    using af = detail::arg_formatter<Out, char>;
+    detail::format_handler<af, char, basic_format_context<Out, char>>
+      h(out, fmt, args, {});
+    fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
+    return h.context.out();
+  }
+
+template<class Out>
+  Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
+
+template<class Out, class... Args>
+  format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
+                                      string_view fmt, const Args&... args);
+template<class Out, class... Args>
+  format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
+                                      wstring_view fmt, const Args&... args);
+
+template<class... Args>
+  size_t formatted_size(string_view fmt, const Args&... args);
+template<class... Args>
+  size_t formatted_size(wstring_view fmt, const Args&... args);
+
+#define charT char
+
+template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {};
+
+template<> struct formatter<char, wchar_t>;
+
+template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {};
+
+template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {};
+
+template<size_t N> struct formatter<const charT[N], charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template<class traits, class Allocator>
+  struct formatter<basic_string<charT, traits, Allocator>, charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template<class traits>
+  struct formatter<basic_string_view<charT, traits>, charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {};
+
+template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<short, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<int, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<long, charT>
+      : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {};
+template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {};
+template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned long, charT>
+      : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {};
+template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {};
+
+template <> struct formatter<float, charT> : detail::formatter<double, charT> {};
+template <> struct formatter<double, charT> : detail::formatter<double, charT> {};
+template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {};
+
+#undef charT
+
+#define charT wchar_t
+
+template<> struct formatter<charT, charT> : detail::formatter<charT, charT> {};
+
+template<> struct formatter<char, wchar_t> : detail::formatter<charT, charT> {};
+
+template<> struct formatter<charT*, charT> : detail::formatter<const charT*, charT> {};
+
+template<> struct formatter<const charT*, charT> : detail::formatter<const charT*, charT> {};
+
+template<size_t N> struct formatter<const charT[N], charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template<class traits, class Allocator>
+  struct formatter<std::basic_string<charT, traits, Allocator>, charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template<class traits>
+  struct formatter<std::basic_string_view<charT, traits>, charT>
+      : detail::formatter<std::basic_string_view<charT>, charT> {};
+
+template <> struct formatter<nullptr_t, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<void*, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<const void*, charT> : detail::formatter<const void*, charT> {};
+template <> struct formatter<bool, charT> : detail::formatter<bool, charT> {};
+
+template <> struct formatter<signed char, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<short, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<int, charT> : detail::formatter<int, charT> {};
+template <> struct formatter<long, charT>
+      : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), int, long long>, charT> {};
+template <> struct formatter<long long, charT> : detail::formatter<long long, charT> {};
+template <> struct formatter<unsigned char, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned short, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned int, charT> : detail::formatter<unsigned int, charT> {};
+template <> struct formatter<unsigned long, charT>
+      : detail::formatter<std::conditional_t<sizeof(long) == sizeof(int), unsigned, unsigned long long>, charT> {};
+template <> struct formatter<unsigned long long, charT> : detail::formatter<unsigned long long, charT> {};
+
+template <> struct formatter<float, charT> : detail::formatter<double, charT> {};
+template <> struct formatter<double, charT> : detail::formatter<double, charT> {};
+template <> struct formatter<long double, charT> : detail::formatter<long double, charT> {};
+
+#undef charT
+
+  template<> struct formatter<const wchar_t, char> {
+    formatter() = delete;
+  };
+}
+
+#endif  // FMT_FORMAT_
diff --git a/libs/fmt/test/format-impl-test.cc b/libs/fmt/test/format-impl-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8094daf8e8022b96d6b8053a641f8427fe2374c7
--- /dev/null
+++ b/libs/fmt/test/format-impl-test.cc
@@ -0,0 +1,414 @@
+// Formatting library for C++ - formatting library implementation tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include <algorithm>
+#include <cstring>
+
+// clang-format off
+#include "test-assert.h"
+// clang-format on
+
+#include "fmt/format.h"
+#include "gmock/gmock.h"
+#include "util.h"
+
+using fmt::detail::bigint;
+using fmt::detail::fp;
+using fmt::detail::max_value;
+
+static_assert(!std::is_copy_constructible<bigint>::value, "");
+static_assert(!std::is_copy_assignable<bigint>::value, "");
+
+TEST(bigint_test, construct) {
+  EXPECT_EQ("", fmt::format("{}", bigint()));
+  EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
+  EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
+}
+
+TEST(bigint_test, compare) {
+  bigint n1(42);
+  bigint n2(42);
+  EXPECT_EQ(compare(n1, n2), 0);
+  n2 <<= 32;
+  EXPECT_LT(compare(n1, n2), 0);
+  bigint n3(43);
+  EXPECT_LT(compare(n1, n3), 0);
+  EXPECT_GT(compare(n3, n1), 0);
+  bigint n4(42 * 0x100000001);
+  EXPECT_LT(compare(n2, n4), 0);
+  EXPECT_GT(compare(n4, n2), 0);
+}
+
+TEST(bigint_test, add_compare) {
+  EXPECT_LT(
+      add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
+  EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
+  EXPECT_GT(add_compare(bigint(1) <<= 32, bigint(0), bigint(0xffffffff)), 0);
+  EXPECT_GT(add_compare(bigint(0), bigint(1) <<= 32, bigint(0xffffffff)), 0);
+  EXPECT_GT(add_compare(bigint(42), bigint(1), bigint(42)), 0);
+  EXPECT_GT(add_compare(bigint(0xffffffff), bigint(1), bigint(0xffffffff)), 0);
+  EXPECT_LT(add_compare(bigint(10), bigint(10), bigint(22)), 0);
+  EXPECT_LT(add_compare(bigint(0x100000010), bigint(0x100000010),
+                        bigint(0x300000010)),
+            0);
+  EXPECT_GT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000000)),
+            0);
+  EXPECT_EQ(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000001)),
+            0);
+  EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000002)),
+            0);
+  EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000003)),
+            0);
+}
+
+TEST(bigint_test, shift_left) {
+  bigint n(0x42);
+  n <<= 0;
+  EXPECT_EQ("42", fmt::format("{}", n));
+  n <<= 1;
+  EXPECT_EQ("84", fmt::format("{}", n));
+  n <<= 25;
+  EXPECT_EQ("108000000", fmt::format("{}", n));
+}
+
+TEST(bigint_test, multiply) {
+  bigint n(0x42);
+  EXPECT_THROW(n *= 0, assertion_failure);
+  n *= 1;
+  EXPECT_EQ("42", fmt::format("{}", n));
+  n *= 2;
+  EXPECT_EQ("84", fmt::format("{}", n));
+  n *= 0x12345678;
+  EXPECT_EQ("962fc95e0", fmt::format("{}", n));
+  bigint bigmax(max_value<uint32_t>());
+  bigmax *= max_value<uint32_t>();
+  EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
+  bigmax.assign(max_value<uint64_t>());
+  bigmax *= max_value<uint64_t>();
+  EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
+}
+
+TEST(bigint_test, accumulator) {
+  fmt::detail::accumulator acc;
+  EXPECT_EQ(acc.lower, 0);
+  EXPECT_EQ(acc.upper, 0);
+  acc.upper = 12;
+  acc.lower = 34;
+  EXPECT_EQ(static_cast<uint32_t>(acc), 34);
+  acc += 56;
+  EXPECT_EQ(acc.lower, 90);
+  acc += max_value<uint64_t>();
+  EXPECT_EQ(acc.upper, 13);
+  EXPECT_EQ(acc.lower, 89);
+  acc >>= 32;
+  EXPECT_EQ(acc.upper, 0);
+  EXPECT_EQ(acc.lower, 13 * 0x100000000);
+}
+
+TEST(bigint_test, square) {
+  bigint n0(0);
+  n0.square();
+  EXPECT_EQ("0", fmt::format("{}", n0));
+  bigint n1(0x100);
+  n1.square();
+  EXPECT_EQ("10000", fmt::format("{}", n1));
+  bigint n2(0xfffffffff);
+  n2.square();
+  EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2));
+  bigint n3(max_value<uint64_t>());
+  n3.square();
+  EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3));
+  bigint n4;
+  n4.assign_pow10(10);
+  EXPECT_EQ("2540be400", fmt::format("{}", n4));
+}
+
+TEST(bigint_test, divmod_assign_zero_divisor) {
+  bigint zero(0);
+  EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
+  EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
+}
+
+TEST(bigint_test, divmod_assign_self) {
+  bigint n(100);
+  EXPECT_THROW(n.divmod_assign(n), assertion_failure);
+}
+
+TEST(bigint_test, divmod_assign_unaligned) {
+  // (42 << 340) / pow(10, 100):
+  bigint n1(42);
+  n1 <<= 340;
+  bigint n2;
+  n2.assign_pow10(100);
+  int result = n1.divmod_assign(n2);
+  EXPECT_EQ(result, 9406);
+  EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96",
+            fmt::format("{}", n1));
+}
+
+TEST(bigint_test, divmod_assign) {
+  // 100 / 10:
+  bigint n1(100);
+  int result = n1.divmod_assign(bigint(10));
+  EXPECT_EQ(result, 10);
+  EXPECT_EQ("0", fmt::format("{}", n1));
+  // pow(10, 100) / (42 << 320):
+  n1.assign_pow10(100);
+  result = n1.divmod_assign(bigint(42) <<= 320);
+  EXPECT_EQ(result, 111);
+  EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96",
+            fmt::format("{}", n1));
+  // 42 / 100:
+  bigint n2(42);
+  n1.assign_pow10(2);
+  result = n2.divmod_assign(n1);
+  EXPECT_EQ(result, 0);
+  EXPECT_EQ("2a", fmt::format("{}", n2));
+}
+
+template <bool is_iec559> void run_double_tests() {
+  fmt::print("warning: double is not IEC559, skipping FP tests\n");
+}
+
+template <> void run_double_tests<true>() {
+  // Construct from double.
+  EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
+}
+
+TEST(fp_test, double_tests) {
+  run_double_tests<std::numeric_limits<double>::is_iec559>();
+}
+
+TEST(fp_test, normalize) {
+  const auto v = fp(0xbeef, 42);
+  auto normalized = normalize(v);
+  EXPECT_EQ(0xbeef000000000000, normalized.f);
+  EXPECT_EQ(-6, normalized.e);
+}
+
+TEST(fp_test, multiply) {
+  auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
+  EXPECT_EQ(v.f, 123u * 56u);
+  EXPECT_EQ(v.e, 4 + 7 + 64);
+  v = fp(123ULL << 32, 4) * fp(567ULL << 31, 8);
+  EXPECT_EQ(v.f, (123 * 567 + 1u) / 2);
+  EXPECT_EQ(v.e, 4 + 8 + 64);
+}
+
+TEST(fp_test, get_cached_power) {
+  using limits = std::numeric_limits<double>;
+  for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
+    int dec_exp = 0;
+    auto fp = fmt::detail::get_cached_power(exp, dec_exp);
+    bigint exact, cache(fp.f);
+    if (dec_exp >= 0) {
+      exact.assign_pow10(dec_exp);
+      if (fp.e <= 0)
+        exact <<= -fp.e;
+      else
+        cache <<= fp.e;
+      exact.align(cache);
+      cache.align(exact);
+      auto exact_str = fmt::format("{}", exact);
+      auto cache_str = fmt::format("{}", cache);
+      EXPECT_EQ(exact_str.size(), cache_str.size());
+      EXPECT_EQ(exact_str.substr(0, 15), cache_str.substr(0, 15));
+      int diff = cache_str[15] - exact_str[15];
+      if (diff == 1)
+        EXPECT_GT(exact_str[16], '8');
+      else
+        EXPECT_EQ(diff, 0);
+    } else {
+      cache.assign_pow10(-dec_exp);
+      cache *= fp.f + 1;  // Inexact check.
+      exact.assign(1);
+      exact <<= -fp.e;
+      exact.align(cache);
+      auto exact_str = fmt::format("{}", exact);
+      auto cache_str = fmt::format("{}", cache);
+      EXPECT_EQ(exact_str.size(), cache_str.size());
+      EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
+    }
+  }
+}
+
+TEST(fp_test, dragonbox_max_k) {
+  using fmt::detail::dragonbox::floor_log10_pow2;
+  using float_info = fmt::detail::dragonbox::float_info<float>;
+  EXPECT_EQ(fmt::detail::const_check(float_info::max_k),
+            float_info::kappa - floor_log10_pow2(float_info::min_exponent -
+                                                 float_info::significand_bits));
+  using double_info = fmt::detail::dragonbox::float_info<double>;
+  EXPECT_EQ(
+      fmt::detail::const_check(double_info::max_k),
+      double_info::kappa - floor_log10_pow2(double_info::min_exponent -
+                                            double_info::significand_bits));
+}
+
+TEST(fp_test, get_round_direction) {
+  using fmt::detail::get_round_direction;
+  using fmt::detail::round_direction;
+  EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
+  EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0));
+  EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10));
+  EXPECT_EQ(round_direction::up, get_round_direction(100, 60, 10));
+  for (size_t i = 41; i < 60; ++i)
+    EXPECT_EQ(round_direction::unknown, get_round_direction(100, i, 10));
+  uint64_t max = max_value<uint64_t>();
+  EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
+  EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
+  EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
+  // Check that remainder + error doesn't overflow.
+  EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 2));
+  // Check that 2 * (remainder + error) doesn't overflow.
+  EXPECT_EQ(round_direction::unknown,
+            get_round_direction(max, max / 2 + 1, max / 2));
+  // Check that remainder - error doesn't overflow.
+  EXPECT_EQ(round_direction::unknown, get_round_direction(100, 40, 41));
+  // Check that 2 * (remainder - error) doesn't overflow.
+  EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
+}
+
+TEST(fp_test, fixed_handler) {
+  struct handler : fmt::detail::fixed_handler {
+    char buffer[10];
+    handler(int prec = 0) : fmt::detail::fixed_handler() {
+      buf = buffer;
+      precision = prec;
+    }
+  };
+  int exp = 0;
+  handler().on_digit('0', 100, 99, 0, exp, false);
+  EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
+               assertion_failure);
+  namespace digits = fmt::detail::digits;
+  EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error);
+  // Check that divisor - error doesn't overflow.
+  EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
+  // Check that 2 * error doesn't overflow.
+  uint64_t max = max_value<uint64_t>();
+  EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false),
+            digits::error);
+}
+
+TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
+  fmt::memory_buffer buf;
+  format_float(0.42, -1, fmt::detail::float_specs(), buf);
+}
+
+TEST(format_impl_test, strerror) {
+  char* message = nullptr;
+  char buffer[256];
+  EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0),
+                "invalid buffer");
+  EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0),
+                "invalid buffer");
+  buffer[0] = 'x';
+#if defined(_GNU_SOURCE) && !defined(__COVERITY__)
+  // Use invalid error code to make sure that safe_strerror returns an error
+  // message in the buffer rather than a pointer to a static string.
+  int error_code = -1;
+#else
+  int error_code = EDOM;
+#endif
+
+  int result = fmt::detail::safe_strerror(error_code, message = buffer, 256);
+  EXPECT_EQ(result, 0);
+  size_t message_size = std::strlen(message);
+  EXPECT_GE(255u, message_size);
+  EXPECT_EQ(get_system_error(error_code), message);
+
+  // safe_strerror never uses buffer on MinGW.
+#if !defined(__MINGW32__) && !defined(__sun)
+  result =
+      fmt::detail::safe_strerror(error_code, message = buffer, message_size);
+  EXPECT_EQ(ERANGE, result);
+  result = fmt::detail::safe_strerror(error_code, message = buffer, 1);
+  EXPECT_EQ(buffer, message);  // Message should point to buffer.
+  EXPECT_EQ(ERANGE, result);
+  EXPECT_STREQ("", message);
+#endif
+}
+
+TEST(format_impl_test, format_error_code) {
+  std::string msg = "error 42", sep = ": ";
+  {
+    fmt::memory_buffer buffer;
+    format_to(buffer, "garbage");
+    fmt::detail::format_error_code(buffer, 42, "test");
+    EXPECT_EQ("test: " + msg, to_string(buffer));
+  }
+  {
+    fmt::memory_buffer buffer;
+    auto prefix =
+        std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
+    fmt::detail::format_error_code(buffer, 42, prefix);
+    EXPECT_EQ(msg, to_string(buffer));
+  }
+  int codes[] = {42, -1};
+  for (size_t i = 0, n = sizeof(codes) / sizeof(*codes); i < n; ++i) {
+    // Test maximum buffer size.
+    msg = fmt::format("error {}", codes[i]);
+    fmt::memory_buffer buffer;
+    auto prefix =
+        std::string(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
+    fmt::detail::format_error_code(buffer, codes[i], prefix);
+    EXPECT_EQ(prefix + sep + msg, to_string(buffer));
+    size_t size = fmt::inline_buffer_size;
+    EXPECT_EQ(size, buffer.size());
+    buffer.resize(0);
+    // Test with a message that doesn't fit into the buffer.
+    prefix += 'x';
+    fmt::detail::format_error_code(buffer, codes[i], prefix);
+    EXPECT_EQ(msg, to_string(buffer));
+  }
+}
+
+TEST(format_impl_test, compute_width) {
+  EXPECT_EQ(4,
+            fmt::detail::compute_width(
+                fmt::basic_string_view<fmt::detail::char8_type>(
+                    reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
+}
+
+// Tests fmt::detail::count_digits for integer type Int.
+template <typename Int> void test_count_digits() {
+  for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i));
+  for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
+    n *= 10;
+    EXPECT_EQ(i, fmt::detail::count_digits(n - 1));
+    EXPECT_EQ(i + 1, fmt::detail::count_digits(n));
+  }
+}
+
+TEST(format_impl_test, count_digits) {
+  test_count_digits<uint32_t>();
+  test_count_digits<uint64_t>();
+}
+
+TEST(format_impl_test, write_fallback_uintptr) {
+  std::string s;
+  fmt::detail::write_ptr<char>(
+      std::back_inserter(s),
+      fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
+  EXPECT_EQ(s, "0xface");
+}
+
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+#ifdef _WIN32
+TEST(format_impl_test, write_console_signature) {
+  decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW;
+  (void)p;
+}
+#endif
diff --git a/libs/fmt/test/format-test.cc b/libs/fmt/test/format-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..992b1269def8bbc1e226825852d472bf452cbc3a
--- /dev/null
+++ b/libs/fmt/test/format-test.cc
@@ -0,0 +1,2515 @@
+// Formatting library for C++ - formatting library tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+// Check if fmt/format.h compiles with windows.h included before it.
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+// clang-format off
+#include "fmt/format.h"
+// clang-format on
+
+#include <stdint.h>     // uint32_t
+#include <climits>      // INT_MAX
+#include <cmath>        // std::signbit
+#include <cstring>      // std::strlen
+#include <iterator>     // std::back_inserter
+#include <list>         // std::list
+#include <memory>       // std::unique_ptr
+#include <type_traits>  // std::is_default_constructible
+
+#include "gtest-extra.h"
+#include "mock-allocator.h"
+#include "util.h"
+
+using fmt::basic_memory_buffer;
+using fmt::format_error;
+using fmt::memory_buffer;
+using fmt::string_view;
+using fmt::detail::max_value;
+
+using testing::Return;
+using testing::StrictMock;
+
+enum { buffer_size = 256 };
+
+struct uint32_pair {
+  uint32_t u[2];
+};
+
+TEST(util_test, bit_cast) {
+  auto s = fmt::detail::bit_cast<uint32_pair>(uint64_t{42});
+  EXPECT_EQ(fmt::detail::bit_cast<uint64_t>(s), 42ull);
+  s = fmt::detail::bit_cast<uint32_pair>(~uint64_t{0});
+  EXPECT_EQ(fmt::detail::bit_cast<uint64_t>(s), ~0ull);
+}
+
+// Increment a number in a string.
+void increment(char* s) {
+  for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
+    if (s[i] != '9') {
+      ++s[i];
+      break;
+    }
+    s[i] = '0';
+  }
+}
+
+TEST(util_test, increment) {
+  char s[10] = "123";
+  increment(s);
+  EXPECT_STREQ("124", s);
+  s[2] = '8';
+  increment(s);
+  EXPECT_STREQ("129", s);
+  increment(s);
+  EXPECT_STREQ("130", s);
+  s[1] = s[2] = '9';
+  increment(s);
+  EXPECT_STREQ("200", s);
+}
+
+TEST(util_test, parse_nonnegative_int) {
+  auto s = fmt::string_view("10000000000");
+  auto begin = s.begin(), end = s.end();
+  EXPECT_THROW_MSG(
+      parse_nonnegative_int(begin, end, fmt::detail::error_handler()),
+      fmt::format_error, "number is too big");
+  s = "2147483649";
+  begin = s.begin();
+  end = s.end();
+  EXPECT_THROW_MSG(
+      parse_nonnegative_int(begin, end, fmt::detail::error_handler()),
+      fmt::format_error, "number is too big");
+}
+
+TEST(util_test, utf8_to_utf16) {
+  auto u = fmt::detail::utf8_to_utf16("лошадка");
+  EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
+  EXPECT_EQ(7, u.size());
+  // U+10437 { DESERET SMALL LETTER YEE }
+  EXPECT_EQ(L"\xD801\xDC37", fmt::detail::utf8_to_utf16("𐐷").str());
+  EXPECT_THROW_MSG(fmt::detail::utf8_to_utf16("\xc3\x28"), std::runtime_error,
+                   "invalid utf8");
+  EXPECT_THROW_MSG(fmt::detail::utf8_to_utf16(fmt::string_view("л", 1)),
+                   std::runtime_error, "invalid utf8");
+  EXPECT_EQ(L"123456", fmt::detail::utf8_to_utf16("123456").str());
+}
+
+TEST(util_test, utf8_to_utf16_empty_string) {
+  auto s = std::string();
+  auto u = fmt::detail::utf8_to_utf16(s.c_str());
+  EXPECT_EQ(L"", u.str());
+  EXPECT_EQ(s.size(), u.size());
+}
+
+TEST(util_test, allocator_ref) {
+  using test_allocator_ref = allocator_ref<mock_allocator<int>>;
+  auto check_forwarding = [](mock_allocator<int>& alloc,
+                             test_allocator_ref& ref) {
+    int mem;
+    // Check if value_type is properly defined.
+    allocator_ref<mock_allocator<int>>::value_type* ptr = &mem;
+    // Check forwarding.
+    EXPECT_CALL(alloc, allocate(42)).WillOnce(Return(ptr));
+    ref.allocate(42);
+    EXPECT_CALL(alloc, deallocate(ptr, 42));
+    ref.deallocate(ptr, 42);
+  };
+
+  StrictMock<mock_allocator<int>> alloc;
+  auto ref = test_allocator_ref(&alloc);
+  // Check if allocator_ref forwards to the underlying allocator.
+  check_forwarding(alloc, ref);
+  test_allocator_ref ref2(ref);
+  check_forwarding(alloc, ref2);
+  test_allocator_ref ref3;
+  EXPECT_EQ(nullptr, ref3.get());
+  ref3 = ref;
+  check_forwarding(alloc, ref3);
+}
+
+TEST(util_test, format_system_error) {
+  fmt::memory_buffer message;
+  fmt::format_system_error(message, EDOM, "test");
+  EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)),
+            to_string(message));
+  message = fmt::memory_buffer();
+
+  // Check if std::allocator throws on allocating max size_t / 2 chars.
+  size_t max_size = max_value<size_t>() / 2;
+  bool throws_on_alloc = false;
+  try {
+    auto alloc = std::allocator<char>();
+    alloc.deallocate(alloc.allocate(max_size), max_size);
+  } catch (const std::bad_alloc&) {
+    throws_on_alloc = true;
+  }
+  if (!throws_on_alloc) {
+    fmt::print("warning: std::allocator allocates {} chars", max_size);
+    return;
+  }
+  fmt::format_system_error(message, EDOM, fmt::string_view(nullptr, max_size));
+  EXPECT_EQ(fmt::format("error {}", EDOM), to_string(message));
+}
+
+TEST(util_test, system_error) {
+  auto test_error = fmt::system_error(EDOM, "test");
+  EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), test_error.what());
+  EXPECT_EQ(EDOM, test_error.error_code());
+
+  auto error = fmt::system_error(0, "");
+  try {
+    throw fmt::system_error(EDOM, "test {}", "error");
+  } catch (const fmt::system_error& e) {
+    error = e;
+  }
+  fmt::memory_buffer message;
+  fmt::format_system_error(message, EDOM, "test error");
+  EXPECT_EQ(to_string(message), error.what());
+  EXPECT_EQ(EDOM, error.error_code());
+}
+
+TEST(util_test, report_system_error) {
+  fmt::memory_buffer out;
+  fmt::format_system_error(out, EDOM, "test error");
+  out.push_back('\n');
+  EXPECT_WRITE(stderr, fmt::report_system_error(EDOM, "test error"),
+               to_string(out));
+}
+
+TEST(memory_buffer_test, ctor) {
+  basic_memory_buffer<char, 123> buffer;
+  EXPECT_EQ(static_cast<size_t>(0), buffer.size());
+  EXPECT_EQ(123u, buffer.capacity());
+}
+
+using std_allocator = allocator_ref<std::allocator<char>>;
+
+TEST(memory_buffer_test, move_ctor_inline_buffer) {
+  auto check_move_buffer =
+      [](const char* str, basic_memory_buffer<char, 5, std_allocator>& buffer) {
+        std::allocator<char>* alloc = buffer.get_allocator().get();
+        basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
+        // Move shouldn't destroy the inline content of the first buffer.
+        EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
+        EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
+        EXPECT_EQ(5u, buffer2.capacity());
+        // Move should transfer allocator.
+        EXPECT_EQ(nullptr, buffer.get_allocator().get());
+        EXPECT_EQ(alloc, buffer2.get_allocator().get());
+      };
+
+  auto alloc = std::allocator<char>();
+  basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
+  const char test[] = "test";
+  buffer.append(string_view(test, 4));
+  check_move_buffer("test", buffer);
+  // Adding one more character fills the inline buffer, but doesn't cause
+  // dynamic allocation.
+  buffer.push_back('a');
+  check_move_buffer("testa", buffer);
+}
+
+TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
+  auto alloc = std::allocator<char>();
+  basic_memory_buffer<char, 4, std_allocator> buffer((std_allocator(&alloc)));
+  const char test[] = "test";
+  buffer.append(test, test + 4);
+  const char* inline_buffer_ptr = &buffer[0];
+  // Adding one more character causes the content to move from the inline to
+  // a dynamically allocated buffer.
+  buffer.push_back('a');
+  basic_memory_buffer<char, 4, std_allocator> buffer2(std::move(buffer));
+  // Move should rip the guts of the first buffer.
+  EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
+  EXPECT_EQ("testa", std::string(&buffer2[0], buffer2.size()));
+  EXPECT_GT(buffer2.capacity(), 4u);
+}
+
+void check_move_assign_buffer(const char* str,
+                              basic_memory_buffer<char, 5>& buffer) {
+  basic_memory_buffer<char, 5> buffer2;
+  buffer2 = std::move(buffer);
+  // Move shouldn't destroy the inline content of the first buffer.
+  EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
+  EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
+  EXPECT_EQ(5u, buffer2.capacity());
+}
+
+TEST(memory_buffer_test, move_assignment) {
+  basic_memory_buffer<char, 5> buffer;
+  const char test[] = "test";
+  buffer.append(test, test + 4);
+  check_move_assign_buffer("test", buffer);
+  // Adding one more character fills the inline buffer, but doesn't cause
+  // dynamic allocation.
+  buffer.push_back('a');
+  check_move_assign_buffer("testa", buffer);
+  const char* inline_buffer_ptr = &buffer[0];
+  // Adding one more character causes the content to move from the inline to
+  // a dynamically allocated buffer.
+  buffer.push_back('b');
+  basic_memory_buffer<char, 5> buffer2;
+  buffer2 = std::move(buffer);
+  // Move should rip the guts of the first buffer.
+  EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
+  EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
+  EXPECT_GT(buffer2.capacity(), 5u);
+}
+
+TEST(memory_buffer_test, grow) {
+  typedef allocator_ref<mock_allocator<int>> Allocator;
+  mock_allocator<int> alloc;
+  basic_memory_buffer<int, 10, Allocator> buffer((Allocator(&alloc)));
+  buffer.resize(7);
+  using fmt::detail::to_unsigned;
+  for (int i = 0; i < 7; ++i) buffer[to_unsigned(i)] = i * i;
+  EXPECT_EQ(10u, buffer.capacity());
+  int mem[20];
+  mem[7] = 0xdead;
+  EXPECT_CALL(alloc, allocate(20)).WillOnce(Return(mem));
+  buffer.try_reserve(20);
+  EXPECT_EQ(20u, buffer.capacity());
+  // Check if size elements have been copied
+  for (int i = 0; i < 7; ++i) EXPECT_EQ(i * i, buffer[to_unsigned(i)]);
+  // and no more than that.
+  EXPECT_EQ(0xdead, buffer[7]);
+  EXPECT_CALL(alloc, deallocate(mem, 20));
+}
+
+TEST(memory_buffer_test, allocator) {
+  using test_allocator = allocator_ref<mock_allocator<char>>;
+  basic_memory_buffer<char, 10, test_allocator> buffer;
+  EXPECT_EQ(nullptr, buffer.get_allocator().get());
+  StrictMock<mock_allocator<char>> alloc;
+  char mem;
+  {
+    basic_memory_buffer<char, 10, test_allocator> buffer2(
+        (test_allocator(&alloc)));
+    EXPECT_EQ(&alloc, buffer2.get_allocator().get());
+    size_t size = 2 * fmt::inline_buffer_size;
+    EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem));
+    buffer2.reserve(size);
+    EXPECT_CALL(alloc, deallocate(&mem, size));
+  }
+}
+
+TEST(memory_buffer_test, exception_in_deallocate) {
+  using test_allocator = allocator_ref<mock_allocator<char>>;
+  StrictMock<mock_allocator<char>> alloc;
+  basic_memory_buffer<char, 10, test_allocator> buffer(
+      (test_allocator(&alloc)));
+  size_t size = 2 * fmt::inline_buffer_size;
+  auto mem = std::vector<char>(size);
+  {
+    EXPECT_CALL(alloc, allocate(size)).WillOnce(Return(&mem[0]));
+    buffer.resize(size);
+    std::fill(&buffer[0], &buffer[0] + size, 'x');
+  }
+  auto mem2 = std::vector<char>(2 * size);
+  {
+    EXPECT_CALL(alloc, allocate(2 * size)).WillOnce(Return(&mem2[0]));
+    auto e = std::exception();
+    EXPECT_CALL(alloc, deallocate(&mem[0], size)).WillOnce(testing::Throw(e));
+    EXPECT_THROW(buffer.reserve(2 * size), std::exception);
+    EXPECT_EQ(&mem2[0], &buffer[0]);
+    // Check that the data has been copied.
+    for (size_t i = 0; i < size; ++i) EXPECT_EQ('x', buffer[i]);
+  }
+  EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size));
+}
+
+template <typename Allocator, size_t MaxSize>
+class max_size_allocator : public Allocator {
+ public:
+  using typename Allocator::value_type;
+  size_t max_size() const FMT_NOEXCEPT { return MaxSize; }
+  value_type* allocate(size_t n) {
+    if (n > max_size()) {
+      throw std::length_error("size > max_size");
+    }
+    return std::allocator_traits<Allocator>::allocate(
+        *static_cast<Allocator*>(this), n);
+  }
+  void deallocate(value_type* p, size_t n) {
+    std::allocator_traits<Allocator>::deallocate(*static_cast<Allocator*>(this),
+                                                 p, n);
+  }
+};
+
+TEST(memory_buffer_test, max_size_allocator) {
+  // 160 = 128 + 32
+  using test_allocator = max_size_allocator<std::allocator<char>, 160>;
+  basic_memory_buffer<char, 10, test_allocator> buffer;
+  buffer.resize(128);
+  // new_capacity = 128 + 128/2 = 192 > 160
+  buffer.resize(160);  // Shouldn't throw.
+}
+
+TEST(memory_buffer_test, max_size_allocator_overflow) {
+  using test_allocator = max_size_allocator<std::allocator<char>, 160>;
+  basic_memory_buffer<char, 10, test_allocator> buffer;
+  EXPECT_THROW(buffer.resize(161), std::exception);
+}
+
+TEST(format_test, escape) {
+  EXPECT_EQ("{", fmt::format("{{"));
+  EXPECT_EQ("before {", fmt::format("before {{"));
+  EXPECT_EQ("{ after", fmt::format("{{ after"));
+  EXPECT_EQ("before { after", fmt::format("before {{ after"));
+
+  EXPECT_EQ("}", fmt::format("}}"));
+  EXPECT_EQ("before }", fmt::format("before }}"));
+  EXPECT_EQ("} after", fmt::format("}} after"));
+  EXPECT_EQ("before } after", fmt::format("before }} after"));
+
+  EXPECT_EQ("{}", fmt::format("{{}}"));
+  EXPECT_EQ("{42}", fmt::format("{{{0}}}", 42));
+}
+
+TEST(format_test, unmatched_braces) {
+  EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"}"), format_error,
+                   "unmatched '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0{}"), format_error, "invalid format string");
+}
+
+TEST(format_test, no_args) { EXPECT_EQ("test", fmt::format("test")); }
+
+TEST(format_test, args_in_different_positions) {
+  EXPECT_EQ("42", fmt::format("{0}", 42));
+  EXPECT_EQ("before 42", fmt::format("before {0}", 42));
+  EXPECT_EQ("42 after", fmt::format("{0} after", 42));
+  EXPECT_EQ("before 42 after", fmt::format("before {0} after", 42));
+  EXPECT_EQ("answer = 42", fmt::format("{0} = {1}", "answer", 42));
+  EXPECT_EQ("42 is the answer", fmt::format("{1} is the {0}", "answer", 42));
+  EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad"));
+}
+
+TEST(format_test, arg_errors) {
+  EXPECT_THROW_MSG(fmt::format(+"{"), format_error, "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{?}"), format_error, "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0"), format_error, "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0}"), format_error, "argument not found");
+  EXPECT_THROW_MSG(fmt::format(+"{00}", 42), format_error,
+                   "invalid format string");
+
+  char format_str[buffer_size];
+  safe_sprintf(format_str, "{%u", INT_MAX);
+  EXPECT_THROW_MSG(fmt::format(+format_str), format_error,
+                   "invalid format string");
+  safe_sprintf(format_str, "{%u}", INT_MAX);
+  EXPECT_THROW_MSG(fmt::format(+format_str), format_error,
+                   "argument not found");
+
+  safe_sprintf(format_str, "{%u", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big");
+  safe_sprintf(format_str, "{%u}", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str), format_error, "number is too big");
+}
+
+template <int N> struct test_format {
+  template <typename... Args>
+  static std::string format(fmt::string_view format_str, const Args&... args) {
+    return test_format<N - 1>::format(format_str, N - 1, args...);
+  }
+};
+
+template <> struct test_format<0> {
+  template <typename... Args>
+  static std::string format(fmt::string_view format_str, const Args&... args) {
+    return fmt::format(format_str, args...);
+  }
+};
+
+TEST(format_test, many_args) {
+  EXPECT_EQ("19", test_format<20>::format("{19}"));
+  EXPECT_THROW_MSG(test_format<20>::format("{20}"), format_error,
+                   "argument not found");
+  EXPECT_THROW_MSG(test_format<21>::format("{21}"), format_error,
+                   "argument not found");
+  using fmt::detail::max_packed_args;
+  std::string format_str = fmt::format("{{{}}}", max_packed_args + 1);
+  EXPECT_THROW_MSG(test_format<max_packed_args>::format(format_str),
+                   format_error, "argument not found");
+}
+
+TEST(format_test, named_arg) {
+  EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
+                                 fmt::arg("A_", "A"), fmt::arg("_1", 1)));
+  EXPECT_EQ(" -42", fmt::format("{0:{width}}", -42, fmt::arg("width", 4)));
+  EXPECT_EQ("st",
+            fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
+  EXPECT_EQ("1 2", fmt::format("{} {two}", 1, fmt::arg("two", 2)));
+  EXPECT_EQ("42",
+            fmt::format("{c}", fmt::arg("a", 0), fmt::arg("b", 0),
+                        fmt::arg("c", 42), fmt::arg("d", 0), fmt::arg("e", 0),
+                        fmt::arg("f", 0), fmt::arg("g", 0), fmt::arg("h", 0),
+                        fmt::arg("i", 0), fmt::arg("j", 0), fmt::arg("k", 0),
+                        fmt::arg("l", 0), fmt::arg("m", 0), fmt::arg("n", 0),
+                        fmt::arg("o", 0), fmt::arg("p", 0)));
+  EXPECT_THROW_MSG(fmt::format(+"{a}"), format_error, "argument not found");
+  EXPECT_THROW_MSG(fmt::format(+"{a}", 42), format_error, "argument not found");
+}
+
+TEST(format_test, auto_arg_index) {
+  EXPECT_EQ("abc", fmt::format("{}{}{}", 'a', 'b', 'c'));
+  EXPECT_THROW_MSG(fmt::format(+"{0}{}", 'a', 'b'), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{}{0}", 'a', 'b'), format_error,
+                   "cannot switch from automatic to manual argument indexing");
+  EXPECT_EQ("1.2", fmt::format("{:.{}}", 1.2345, 2));
+  EXPECT_THROW_MSG(fmt::format(+"{0}:.{}", 1.2345, 2), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{:.{0}}", 1.2345, 2), format_error,
+                   "cannot switch from automatic to manual argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{}"), format_error, "argument not found");
+}
+
+TEST(format_test, empty_specs) { EXPECT_EQ("42", fmt::format("{0:}", 42)); }
+
+TEST(format_test, left_align) {
+  EXPECT_EQ("42  ", fmt::format("{0:<4}", 42));
+  EXPECT_EQ("42  ", fmt::format("{0:<4o}", 042));
+  EXPECT_EQ("42  ", fmt::format("{0:<4x}", 0x42));
+  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42));
+  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42u));
+  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42l));
+  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42ul));
+  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42ll));
+  EXPECT_EQ("42   ", fmt::format("{0:<5}", 42ull));
+  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42.0));
+  EXPECT_EQ("-42  ", fmt::format("{0:<5}", -42.0l));
+  EXPECT_EQ("c    ", fmt::format("{0:<5}", 'c'));
+  EXPECT_EQ("abc  ", fmt::format("{0:<5}", "abc"));
+  EXPECT_EQ("0xface  ", fmt::format("{0:<8}", reinterpret_cast<void*>(0xface)));
+}
+
+TEST(format_test, right_align) {
+  EXPECT_EQ("  42", fmt::format("{0:>4}", 42));
+  EXPECT_EQ("  42", fmt::format("{0:>4o}", 042));
+  EXPECT_EQ("  42", fmt::format("{0:>4x}", 0x42));
+  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42));
+  EXPECT_EQ("   42", fmt::format("{0:>5}", 42u));
+  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42l));
+  EXPECT_EQ("   42", fmt::format("{0:>5}", 42ul));
+  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42ll));
+  EXPECT_EQ("   42", fmt::format("{0:>5}", 42ull));
+  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42.0));
+  EXPECT_EQ("  -42", fmt::format("{0:>5}", -42.0l));
+  EXPECT_EQ("    c", fmt::format("{0:>5}", 'c'));
+  EXPECT_EQ("  abc", fmt::format("{0:>5}", "abc"));
+  EXPECT_EQ("  0xface", fmt::format("{0:>8}", reinterpret_cast<void*>(0xface)));
+}
+
+TEST(format_test, center_align) {
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42));
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5o}", 042));
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5x}", 0x42));
+  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42));
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42u));
+  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42l));
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42ul));
+  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42ll));
+  EXPECT_EQ(" 42  ", fmt::format("{0:^5}", 42ull));
+  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0));
+  EXPECT_EQ(" -42 ", fmt::format("{0:^5}", -42.0l));
+  EXPECT_EQ("  c  ", fmt::format("{0:^5}", 'c'));
+  EXPECT_EQ(" abc  ", fmt::format("{0:^6}", "abc"));
+  EXPECT_EQ(" 0xface ", fmt::format("{0:^8}", reinterpret_cast<void*>(0xface)));
+}
+
+TEST(format_test, fill) {
+  EXPECT_THROW_MSG(fmt::format(+"{0:{<5}", 'c'), format_error,
+                   "invalid fill character '{'");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{<5}}", 'c'), format_error,
+                   "invalid fill character '{'");
+  EXPECT_EQ("**42", fmt::format("{0:*>4}", 42));
+  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42));
+  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42u));
+  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42l));
+  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ul));
+  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42ll));
+  EXPECT_EQ("***42", fmt::format("{0:*>5}", 42ull));
+  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0));
+  EXPECT_EQ("**-42", fmt::format("{0:*>5}", -42.0l));
+  EXPECT_EQ("c****", fmt::format("{0:*<5}", 'c'));
+  EXPECT_EQ("abc**", fmt::format("{0:*<5}", "abc"));
+  EXPECT_EQ("**0xface",
+            fmt::format("{0:*>8}", reinterpret_cast<void*>(0xface)));
+  EXPECT_EQ("foo=", fmt::format("{:}=", "foo"));
+  EXPECT_EQ(std::string("\0\0\0*", 4),
+            fmt::format(string_view("{:\0>4}", 6), '*'));
+  EXPECT_EQ("жж42", fmt::format("{0:ж>4}", 42));
+  EXPECT_THROW_MSG(fmt::format(+"{:\x80\x80\x80\x80\x80>}", 0), format_error,
+                   "missing '}' in format string");
+}
+
+TEST(format_test, plus_sign) {
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42));
+  EXPECT_EQ("-42", fmt::format("{0:+}", -42));
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42));
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42u), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42l));
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ul), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42ll));
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", 42ull), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42.0));
+  EXPECT_EQ("+42", fmt::format("{0:+}", 42.0l));
+  EXPECT_THROW_MSG(fmt::format(+"{0:+", 'c'), format_error,
+                   "missing '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", 'c'), format_error,
+                   "invalid format specifier for char");
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", "abc"), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", reinterpret_cast<void*>(0x42)),
+                   format_error, "format specifier requires numeric argument");
+}
+
+TEST(format_test, minus_sign) {
+  EXPECT_EQ("42", fmt::format("{0:-}", 42));
+  EXPECT_EQ("-42", fmt::format("{0:-}", -42));
+  EXPECT_EQ("42", fmt::format("{0:-}", 42));
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42u), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("42", fmt::format("{0:-}", 42l));
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ul), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("42", fmt::format("{0:-}", 42ll));
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", 42ull), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ("42", fmt::format("{0:-}", 42.0));
+  EXPECT_EQ("42", fmt::format("{0:-}", 42.0l));
+  EXPECT_THROW_MSG(fmt::format(+"{0:-", 'c'), format_error,
+                   "missing '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", 'c'), format_error,
+                   "invalid format specifier for char");
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", "abc"), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", reinterpret_cast<void*>(0x42)),
+                   format_error, "format specifier requires numeric argument");
+}
+
+TEST(format_test, space_sign) {
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42));
+  EXPECT_EQ("-42", fmt::format("{0: }", -42));
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42));
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", 42u), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42l));
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ul), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42ll));
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", 42ull), format_error,
+                   "format specifier requires signed argument");
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42.0));
+  EXPECT_EQ(" 42", fmt::format("{0: }", 42.0l));
+  EXPECT_THROW_MSG(fmt::format(+"{0: ", 'c'), format_error,
+                   "missing '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", 'c'), format_error,
+                   "invalid format specifier for char");
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", "abc"), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", reinterpret_cast<void*>(0x42)),
+                   format_error, "format specifier requires numeric argument");
+}
+
+TEST(format_test, sign_not_truncated) {
+  wchar_t format_str[] = {
+      L'{', L':',
+      '+' | static_cast<wchar_t>(1 << fmt::detail::num_bits<char>()), L'}', 0};
+  EXPECT_THROW(fmt::format(format_str, 42), format_error);
+}
+
+TEST(format_test, hash_flag) {
+  EXPECT_EQ("42", fmt::format("{0:#}", 42));
+  EXPECT_EQ("-42", fmt::format("{0:#}", -42));
+  EXPECT_EQ("0b101010", fmt::format("{0:#b}", 42));
+  EXPECT_EQ("0B101010", fmt::format("{0:#B}", 42));
+  EXPECT_EQ("-0b101010", fmt::format("{0:#b}", -42));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42));
+  EXPECT_EQ("0X42", fmt::format("{0:#X}", 0x42));
+  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42));
+  EXPECT_EQ("0", fmt::format("{0:#o}", 0));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042));
+  EXPECT_EQ("-042", fmt::format("{0:#o}", -042));
+  EXPECT_EQ("42", fmt::format("{0:#}", 42u));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42u));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042u));
+
+  EXPECT_EQ("-42", fmt::format("{0:#}", -42l));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42l));
+  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42l));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042l));
+  EXPECT_EQ("-042", fmt::format("{0:#o}", -042l));
+  EXPECT_EQ("42", fmt::format("{0:#}", 42ul));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ul));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042ul));
+
+  EXPECT_EQ("-42", fmt::format("{0:#}", -42ll));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ll));
+  EXPECT_EQ("-0x42", fmt::format("{0:#x}", -0x42ll));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042ll));
+  EXPECT_EQ("-042", fmt::format("{0:#o}", -042ll));
+  EXPECT_EQ("42", fmt::format("{0:#}", 42ull));
+  EXPECT_EQ("0x42", fmt::format("{0:#x}", 0x42ull));
+  EXPECT_EQ("042", fmt::format("{0:#o}", 042ull));
+
+  EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0));
+  EXPECT_EQ("-42.0", fmt::format("{0:#}", -42.0l));
+  EXPECT_EQ("4.e+01", fmt::format("{:#.0e}", 42.0));
+  EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.01));
+  EXPECT_EQ("0.50", fmt::format("{:#.2g}", 0.5));
+  EXPECT_EQ("0.", fmt::format("{:#.0f}", 0.5));
+  EXPECT_THROW_MSG(fmt::format(+"{0:#", 'c'), format_error,
+                   "missing '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:#}", 'c'), format_error,
+                   "invalid format specifier for char");
+  EXPECT_THROW_MSG(fmt::format(+"{0:#}", "abc"), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:#}", reinterpret_cast<void*>(0x42)),
+                   format_error, "format specifier requires numeric argument");
+}
+
+TEST(format_test, zero_flag) {
+  EXPECT_EQ("42", fmt::format("{0:0}", 42));
+  EXPECT_EQ("-0042", fmt::format("{0:05}", -42));
+  EXPECT_EQ("00042", fmt::format("{0:05}", 42u));
+  EXPECT_EQ("-0042", fmt::format("{0:05}", -42l));
+  EXPECT_EQ("00042", fmt::format("{0:05}", 42ul));
+  EXPECT_EQ("-0042", fmt::format("{0:05}", -42ll));
+  EXPECT_EQ("00042", fmt::format("{0:05}", 42ull));
+  EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0));
+  EXPECT_EQ("-000042", fmt::format("{0:07}", -42.0l));
+  EXPECT_THROW_MSG(fmt::format(+"{0:0", 'c'), format_error,
+                   "missing '}' in format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:05}", 'c'), format_error,
+                   "invalid format specifier for char");
+  EXPECT_THROW_MSG(fmt::format(+"{0:05}", "abc"), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:05}", reinterpret_cast<void*>(0x42)),
+                   format_error, "format specifier requires numeric argument");
+}
+
+TEST(format_test, width) {
+  char format_str[buffer_size];
+  safe_sprintf(format_str, "{0:%u", UINT_MAX);
+  increment(format_str + 3);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  size_t size = std::strlen(format_str);
+  format_str[size] = '}';
+  format_str[size + 1] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+
+  safe_sprintf(format_str, "{0:%u", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  safe_sprintf(format_str, "{0:%u}", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  EXPECT_EQ(" -42", fmt::format("{0:4}", -42));
+  EXPECT_EQ("   42", fmt::format("{0:5}", 42u));
+  EXPECT_EQ("   -42", fmt::format("{0:6}", -42l));
+  EXPECT_EQ("     42", fmt::format("{0:7}", 42ul));
+  EXPECT_EQ("   -42", fmt::format("{0:6}", -42ll));
+  EXPECT_EQ("     42", fmt::format("{0:7}", 42ull));
+  EXPECT_EQ("   -1.23", fmt::format("{0:8}", -1.23));
+  EXPECT_EQ("    -1.23", fmt::format("{0:9}", -1.23l));
+  EXPECT_EQ("    0xcafe",
+            fmt::format("{0:10}", reinterpret_cast<void*>(0xcafe)));
+  EXPECT_EQ("x          ", fmt::format("{0:11}", 'x'));
+  EXPECT_EQ("str         ", fmt::format("{0:12}", "str"));
+  EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
+  EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
+  EXPECT_EQ(fmt::format("{:#6}", 42.0), "  42.0");
+  EXPECT_EQ(fmt::format("{:6c}", static_cast<int>('x')), "x     ");
+  EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), "000000");
+}
+
+TEST(format_test, runtime_width) {
+  char format_str[buffer_size];
+  safe_sprintf(format_str, "{0:{%u", UINT_MAX);
+  increment(format_str + 4);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  size_t size = std::strlen(format_str);
+  format_str[size] = '}';
+  format_str[size + 1] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  format_str[size + 1] = '}';
+  format_str[size + 2] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:{", 0), format_error,
+                   "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{}", 0), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{?}}", 0), format_error,
+                   "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0), format_error,
+                   "argument not found");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:{0:}}", 0), format_error,
+                   "invalid format string");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1), format_error,
+                   "negative width");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1u)), format_error,
+                   "number is too big");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, -1l), format_error,
+                   "negative width");
+  if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
+    long value = INT_MAX;
+    EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (value + 1)), format_error,
+                     "number is too big");
+  }
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, (INT_MAX + 1ul)), format_error,
+                   "number is too big");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, '0'), format_error,
+                   "width is not integer");
+  EXPECT_THROW_MSG(fmt::format(+"{0:{1}}", 0, 0.0), format_error,
+                   "width is not integer");
+
+  EXPECT_EQ(" -42", fmt::format("{0:{1}}", -42, 4));
+  EXPECT_EQ("   42", fmt::format("{0:{1}}", 42u, 5));
+  EXPECT_EQ("   -42", fmt::format("{0:{1}}", -42l, 6));
+  EXPECT_EQ("     42", fmt::format("{0:{1}}", 42ul, 7));
+  EXPECT_EQ("   -42", fmt::format("{0:{1}}", -42ll, 6));
+  EXPECT_EQ("     42", fmt::format("{0:{1}}", 42ull, 7));
+  EXPECT_EQ("   -1.23", fmt::format("{0:{1}}", -1.23, 8));
+  EXPECT_EQ("    -1.23", fmt::format("{0:{1}}", -1.23l, 9));
+  EXPECT_EQ("    0xcafe",
+            fmt::format("{0:{1}}", reinterpret_cast<void*>(0xcafe), 10));
+  EXPECT_EQ("x          ", fmt::format("{0:{1}}", 'x', 11));
+  EXPECT_EQ("str         ", fmt::format("{0:{1}}", "str", 12));
+}
+
+TEST(format_test, precision) {
+  char format_str[buffer_size];
+  safe_sprintf(format_str, "{0:.%u", UINT_MAX);
+  increment(format_str + 4);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  size_t size = std::strlen(format_str);
+  format_str[size] = '}';
+  format_str[size + 1] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+
+  safe_sprintf(format_str, "{0:.%u", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  safe_sprintf(format_str, "{0:.%u}", INT_MAX + 1u);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.", 0), format_error,
+                   "missing precision specifier");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.}", 0), format_error,
+                   "missing precision specifier");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2", 0), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42u), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42u), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42l), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42l), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ul), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ul), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ll), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ll), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", 42ull), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", 42ull), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:3.0}", 'x'), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345));
+  EXPECT_EQ("1.2", fmt::format("{0:.2}", 1.2345l));
+  EXPECT_EQ("1.2e+56", fmt::format("{:.2}", 1.234e56));
+  EXPECT_EQ("1.1", fmt::format("{0:.3}", 1.1));
+  EXPECT_EQ("1e+00", fmt::format("{:.0e}", 1.0L));
+  EXPECT_EQ("  0.0e+00", fmt::format("{:9.1e}", 0.0));
+  EXPECT_EQ(
+      fmt::format("{:.494}", 4.9406564584124654E-324),
+      "4.9406564584124654417656879286822137236505980261432476442558568250067550"
+      "727020875186529983636163599237979656469544571773092665671035593979639877"
+      "479601078187812630071319031140452784581716784898210368871863605699873072"
+      "305000638740915356498438731247339727316961514003171538539807412623856559"
+      "117102665855668676818703956031062493194527159149245532930545654440112748"
+      "012970999954193198940908041656332452475714786901472678015935523861155013"
+      "480352649347201937902681071074917033322268447533357208324319361e-324");
+
+  std::string outputs[] = {
+      "-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000P+127",
+      "-0XA.0FF1FFF38E4F0000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000P+124"};
+  EXPECT_THAT(outputs,
+              testing::Contains(fmt::format("{:.838A}", -2.14001164E+38)));
+
+  EXPECT_EQ("123.", fmt::format("{:#.0f}", 123.0));
+  EXPECT_EQ("1.23", fmt::format("{:.02f}", 1.234));
+  EXPECT_EQ("0.001", fmt::format("{:.1g}", 0.001));
+  EXPECT_EQ("1019666400", fmt::format("{}", 1019666432.0f));
+  EXPECT_EQ("1e+01", fmt::format("{:.0e}", 9.5));
+  EXPECT_EQ("1.0e-34", fmt::format("{:.1e}", 1e-34));
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2}", reinterpret_cast<void*>(0xcafe)),
+                   format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.2f}", reinterpret_cast<void*>(0xcafe)),
+                   format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{:.{}e}", 42.0, fmt::detail::max_value<int>()),
+                   format_error, "number is too big");
+
+  EXPECT_EQ("st", fmt::format("{0:.2}", "str"));
+}
+
+TEST(format_test, runtime_precision) {
+  char format_str[buffer_size];
+  safe_sprintf(format_str, "{0:.{%u", UINT_MAX);
+  increment(format_str + 5);
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  size_t size = std::strlen(format_str);
+  format_str[size] = '}';
+  format_str[size + 1] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+  format_str[size + 1] = '}';
+  format_str[size + 2] = 0;
+  EXPECT_THROW_MSG(fmt::format(+format_str, 0), format_error,
+                   "number is too big");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{", 0), format_error,
+                   "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{}", 0), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{?}}", 0), format_error,
+                   "invalid format string");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}", 0, 0), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0), format_error,
+                   "argument not found");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{0:}}", 0), format_error,
+                   "invalid format string");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1), format_error,
+                   "negative precision");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1u)), format_error,
+                   "number is too big");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, -1l), format_error,
+                   "negative precision");
+  if (fmt::detail::const_check(sizeof(long) > sizeof(int))) {
+    long value = INT_MAX;
+    EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (value + 1)), format_error,
+                     "number is too big");
+  }
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, (INT_MAX + 1ul)), format_error,
+                   "number is too big");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, '0'), format_error,
+                   "precision is not integer");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 0, 0.0), format_error,
+                   "precision is not integer");
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42u, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42u, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42l, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42l, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ul, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ul, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ll, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ll, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", 42ull, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}f}", 42ull, 2), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(fmt::format(+"{0:3.{1}}", 'x', 0), format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_EQ("1.2", fmt::format("{0:.{1}}", 1.2345, 2));
+  EXPECT_EQ("1.2", fmt::format("{1:.{0}}", 2, 1.2345l));
+
+  EXPECT_THROW_MSG(fmt::format(+"{0:.{1}}", reinterpret_cast<void*>(0xcafe), 2),
+                   format_error,
+                   "precision not allowed for this argument type");
+  EXPECT_THROW_MSG(
+      fmt::format(+"{0:.{1}f}", reinterpret_cast<void*>(0xcafe), 2),
+      format_error, "precision not allowed for this argument type");
+
+  EXPECT_EQ("st", fmt::format("{0:.{1}}", "str", 2));
+}
+
+TEST(format_test, format_bool) {
+  EXPECT_EQ("true", fmt::format("{}", true));
+  EXPECT_EQ("false", fmt::format("{}", false));
+  EXPECT_EQ("1", fmt::format("{:d}", true));
+  EXPECT_EQ("true ", fmt::format("{:5}", true));
+  EXPECT_EQ(L"true", fmt::format(L"{}", true));
+  EXPECT_EQ("true", fmt::format("{:s}", true));
+  EXPECT_EQ("false", fmt::format("{:s}", false));
+  EXPECT_EQ("false ", fmt::format("{:6s}", false));
+}
+
+TEST(format_test, format_short) {
+  short s = 42;
+  EXPECT_EQ("42", fmt::format("{0:d}", s));
+  unsigned short us = 42;
+  EXPECT_EQ("42", fmt::format("{0:d}", us));
+}
+
+template <typename T>
+void check_unknown_types(const T& value, const char* types, const char*) {
+  char format_str[buffer_size];
+  const char* special = ".0123456789L}";
+  for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
+    char c = static_cast<char>(i);
+    if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;
+    safe_sprintf(format_str, "{0:10%c}", c);
+    const char* message = "invalid type specifier";
+    EXPECT_THROW_MSG(fmt::format(+format_str, value), format_error, message)
+        << format_str << " " << message;
+  }
+}
+
+TEST(format_test, format_int) {
+  EXPECT_THROW_MSG(fmt::format(+"{0:v", 42), format_error,
+                   "missing '}' in format string");
+  check_unknown_types(42, "bBdoxXnLc", "integer");
+  EXPECT_EQ("x", fmt::format("{:c}", static_cast<int>('x')));
+}
+
+TEST(format_test, format_bin) {
+  EXPECT_EQ("0", fmt::format("{0:b}", 0));
+  EXPECT_EQ("101010", fmt::format("{0:b}", 42));
+  EXPECT_EQ("101010", fmt::format("{0:b}", 42u));
+  EXPECT_EQ("-101010", fmt::format("{0:b}", -42));
+  EXPECT_EQ("11000000111001", fmt::format("{0:b}", 12345));
+  EXPECT_EQ("10010001101000101011001111000", fmt::format("{0:b}", 0x12345678));
+  EXPECT_EQ("10010000101010111100110111101111",
+            fmt::format("{0:b}", 0x90ABCDEF));
+  EXPECT_EQ("11111111111111111111111111111111",
+            fmt::format("{0:b}", max_value<uint32_t>()));
+}
+
+#if FMT_USE_INT128
+constexpr auto int128_max = static_cast<__int128_t>(
+    (static_cast<__uint128_t>(1) << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1);
+constexpr auto int128_min = -int128_max - 1;
+
+constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
+#endif
+
+TEST(format_test, format_dec) {
+  EXPECT_EQ("0", fmt::format("{0}", 0));
+  EXPECT_EQ("42", fmt::format("{0}", 42));
+  EXPECT_EQ("42", fmt::format("{0:d}", 42));
+  EXPECT_EQ("42", fmt::format("{0}", 42u));
+  EXPECT_EQ("-42", fmt::format("{0}", -42));
+  EXPECT_EQ("12345", fmt::format("{0}", 12345));
+  EXPECT_EQ("67890", fmt::format("{0}", 67890));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", fmt::format("{0}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", fmt::format("{0}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("9223372036854775808",
+            fmt::format("{0}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-9223372036854775809",
+            fmt::format("{0}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("18446744073709551616",
+            fmt::format("{0}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("170141183460469231731687303715884105727",
+            fmt::format("{0}", int128_max));
+  EXPECT_EQ("-170141183460469231731687303715884105728",
+            fmt::format("{0}", int128_min));
+  EXPECT_EQ("340282366920938463463374607431768211455",
+            fmt::format("{0}", uint128_max));
+#endif
+
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "%d", INT_MIN);
+  EXPECT_EQ(buffer, fmt::format("{0}", INT_MIN));
+  safe_sprintf(buffer, "%d", INT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0}", INT_MAX));
+  safe_sprintf(buffer, "%u", UINT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0}", UINT_MAX));
+  safe_sprintf(buffer, "%ld", 0 - static_cast<unsigned long>(LONG_MIN));
+  EXPECT_EQ(buffer, fmt::format("{0}", LONG_MIN));
+  safe_sprintf(buffer, "%ld", LONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0}", LONG_MAX));
+  safe_sprintf(buffer, "%lu", ULONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0}", ULONG_MAX));
+}
+
+TEST(format_test, format_hex) {
+  EXPECT_EQ("0", fmt::format("{0:x}", 0));
+  EXPECT_EQ("42", fmt::format("{0:x}", 0x42));
+  EXPECT_EQ("42", fmt::format("{0:x}", 0x42u));
+  EXPECT_EQ("-42", fmt::format("{0:x}", -0x42));
+  EXPECT_EQ("12345678", fmt::format("{0:x}", 0x12345678));
+  EXPECT_EQ("90abcdef", fmt::format("{0:x}", 0x90abcdef));
+  EXPECT_EQ("12345678", fmt::format("{0:X}", 0x12345678));
+  EXPECT_EQ("90ABCDEF", fmt::format("{0:X}", 0x90ABCDEF));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", fmt::format("{0:x}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("8000000000000000",
+            fmt::format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-8000000000000001",
+            fmt::format("{0:x}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("10000000000000000",
+            fmt::format("{0:x}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("7fffffffffffffffffffffffffffffff",
+            fmt::format("{0:x}", int128_max));
+  EXPECT_EQ("-80000000000000000000000000000000",
+            fmt::format("{0:x}", int128_min));
+  EXPECT_EQ("ffffffffffffffffffffffffffffffff",
+            fmt::format("{0:x}", uint128_max));
+#endif
+
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "-%x", 0 - static_cast<unsigned>(INT_MIN));
+  EXPECT_EQ(buffer, fmt::format("{0:x}", INT_MIN));
+  safe_sprintf(buffer, "%x", INT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:x}", INT_MAX));
+  safe_sprintf(buffer, "%x", UINT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:x}", UINT_MAX));
+  safe_sprintf(buffer, "-%lx", 0 - static_cast<unsigned long>(LONG_MIN));
+  EXPECT_EQ(buffer, fmt::format("{0:x}", LONG_MIN));
+  safe_sprintf(buffer, "%lx", LONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:x}", LONG_MAX));
+  safe_sprintf(buffer, "%lx", ULONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:x}", ULONG_MAX));
+}
+
+TEST(format_test, format_oct) {
+  EXPECT_EQ("0", fmt::format("{0:o}", 0));
+  EXPECT_EQ("42", fmt::format("{0:o}", 042));
+  EXPECT_EQ("42", fmt::format("{0:o}", 042u));
+  EXPECT_EQ("-42", fmt::format("{0:o}", -042));
+  EXPECT_EQ("12345670", fmt::format("{0:o}", 012345670));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", fmt::format("{0:o}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("1000000000000000000000",
+            fmt::format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-1000000000000000000001",
+            fmt::format("{0:o}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("2000000000000000000000",
+            fmt::format("{0:o}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("1777777777777777777777777777777777777777777",
+            fmt::format("{0:o}", int128_max));
+  EXPECT_EQ("-2000000000000000000000000000000000000000000",
+            fmt::format("{0:o}", int128_min));
+  EXPECT_EQ("3777777777777777777777777777777777777777777",
+            fmt::format("{0:o}", uint128_max));
+#endif
+
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "-%o", 0 - static_cast<unsigned>(INT_MIN));
+  EXPECT_EQ(buffer, fmt::format("{0:o}", INT_MIN));
+  safe_sprintf(buffer, "%o", INT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:o}", INT_MAX));
+  safe_sprintf(buffer, "%o", UINT_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:o}", UINT_MAX));
+  safe_sprintf(buffer, "-%lo", 0 - static_cast<unsigned long>(LONG_MIN));
+  EXPECT_EQ(buffer, fmt::format("{0:o}", LONG_MIN));
+  safe_sprintf(buffer, "%lo", LONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:o}", LONG_MAX));
+  safe_sprintf(buffer, "%lo", ULONG_MAX);
+  EXPECT_EQ(buffer, fmt::format("{0:o}", ULONG_MAX));
+}
+
+TEST(format_test, format_int_locale) {
+  EXPECT_EQ("1234", fmt::format("{:L}", 1234));
+}
+
+TEST(format_test, format_float) {
+  EXPECT_EQ("0", fmt::format("{}", 0.0f));
+  EXPECT_EQ("392.500000", fmt::format("{0:f}", 392.5f));
+}
+
+TEST(format_test, format_double) {
+  EXPECT_EQ("0", fmt::format("{}", 0.0));
+  check_unknown_types(1.2, "eEfFgGaAnL%", "double");
+  EXPECT_EQ("0", fmt::format("{:}", 0.0));
+  EXPECT_EQ("0.000000", fmt::format("{:f}", 0.0));
+  EXPECT_EQ("0", fmt::format("{:g}", 0.0));
+  EXPECT_EQ("392.65", fmt::format("{:}", 392.65));
+  EXPECT_EQ("392.65", fmt::format("{:g}", 392.65));
+  EXPECT_EQ("392.65", fmt::format("{:G}", 392.65));
+  EXPECT_EQ("4.9014e+06", fmt::format("{:g}", 4.9014e6));
+  EXPECT_EQ("392.650000", fmt::format("{:f}", 392.65));
+  EXPECT_EQ("392.650000", fmt::format("{:F}", 392.65));
+  EXPECT_EQ("42", fmt::format("{:L}", 42.0));
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "%e", 392.65);
+  EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65));
+  safe_sprintf(buffer, "%E", 392.65);
+  EXPECT_EQ(buffer, fmt::format("{0:E}", 392.65));
+  EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.65));
+  safe_sprintf(buffer, "%a", -42.0);
+  EXPECT_EQ(buffer, fmt::format("{:a}", -42.0));
+  safe_sprintf(buffer, "%A", -42.0);
+  EXPECT_EQ(buffer, fmt::format("{:A}", -42.0));
+  EXPECT_EQ("9223372036854775808.000000",
+            fmt::format("{:f}", 9223372036854775807.0));
+}
+
+TEST(format_test, precision_rounding) {
+  EXPECT_EQ("0", fmt::format("{:.0f}", 0.0));
+  EXPECT_EQ("0", fmt::format("{:.0f}", 0.01));
+  EXPECT_EQ("0", fmt::format("{:.0f}", 0.1));
+  EXPECT_EQ("0.000", fmt::format("{:.3f}", 0.00049));
+  EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.0005));
+  EXPECT_EQ("0.001", fmt::format("{:.3f}", 0.00149));
+  EXPECT_EQ("0.002", fmt::format("{:.3f}", 0.0015));
+  EXPECT_EQ("1.000", fmt::format("{:.3f}", 0.9999));
+  EXPECT_EQ("0.00123", fmt::format("{:.3}", 0.00123));
+  EXPECT_EQ("0.1", fmt::format("{:.16g}", 0.1));
+  EXPECT_EQ("1", fmt::format("{:.0}", 1.0));
+  EXPECT_EQ("225.51575035152063720",
+            fmt::format("{:.17f}", 225.51575035152064));
+  EXPECT_EQ("-761519619559038.2", fmt::format("{:.1f}", -761519619559038.2));
+  EXPECT_EQ("1.9156918820264798e-56",
+            fmt::format("{}", 1.9156918820264798e-56));
+  EXPECT_EQ("0.0000", fmt::format("{:.4f}", 7.2809479766055470e-15));
+
+  // Trigger a rounding error in Grisu by a specially chosen number.
+  EXPECT_EQ("3788512123356.985352", fmt::format("{:f}", 3788512123356.985352));
+}
+
+TEST(format_test, prettify_float) {
+  EXPECT_EQ("0.0001", fmt::format("{}", 1e-4));
+  EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
+  EXPECT_EQ("1000000000000000", fmt::format("{}", 1e15));
+  EXPECT_EQ("1e+16", fmt::format("{}", 1e16));
+  EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
+  EXPECT_EQ("10000000000", fmt::format("{}", 1e10));
+  EXPECT_EQ("100000000000", fmt::format("{}", 1e11));
+  EXPECT_EQ("12340000000", fmt::format("{}", 1234e7));
+  EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
+  EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
+  EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
+  EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
+  EXPECT_EQ("1.3563156e-19", fmt::format("{}", 1.35631564e-19f));
+}
+
+TEST(format_test, format_nan) {
+  double nan = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_EQ("nan", fmt::format("{}", nan));
+  EXPECT_EQ("+nan", fmt::format("{:+}", nan));
+  if (std::signbit(-nan))
+    EXPECT_EQ("-nan", fmt::format("{}", -nan));
+  else
+    fmt::print("Warning: compiler doesn't handle negative NaN correctly");
+  EXPECT_EQ(" nan", fmt::format("{: }", nan));
+  EXPECT_EQ("NAN", fmt::format("{:F}", nan));
+  EXPECT_EQ("nan    ", fmt::format("{:<7}", nan));
+  EXPECT_EQ("  nan  ", fmt::format("{:^7}", nan));
+  EXPECT_EQ("    nan", fmt::format("{:>7}", nan));
+}
+
+TEST(format_test, format_infinity) {
+  double inf = std::numeric_limits<double>::infinity();
+  EXPECT_EQ("inf", fmt::format("{}", inf));
+  EXPECT_EQ("+inf", fmt::format("{:+}", inf));
+  EXPECT_EQ("-inf", fmt::format("{}", -inf));
+  EXPECT_EQ(" inf", fmt::format("{: }", inf));
+  EXPECT_EQ("INF", fmt::format("{:F}", inf));
+  EXPECT_EQ("inf    ", fmt::format("{:<7}", inf));
+  EXPECT_EQ("  inf  ", fmt::format("{:^7}", inf));
+  EXPECT_EQ("    inf", fmt::format("{:>7}", inf));
+}
+
+TEST(format_test, format_long_double) {
+  EXPECT_EQ("0", fmt::format("{0:}", 0.0l));
+  EXPECT_EQ("0.000000", fmt::format("{0:f}", 0.0l));
+  EXPECT_EQ("392.65", fmt::format("{0:}", 392.65l));
+  EXPECT_EQ("392.65", fmt::format("{0:g}", 392.65l));
+  EXPECT_EQ("392.65", fmt::format("{0:G}", 392.65l));
+  EXPECT_EQ("392.650000", fmt::format("{0:f}", 392.65l));
+  EXPECT_EQ("392.650000", fmt::format("{0:F}", 392.65l));
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "%Le", 392.65l);
+  EXPECT_EQ(buffer, fmt::format("{0:e}", 392.65l));
+  EXPECT_EQ("+0000392.6", fmt::format("{0:+010.4g}", 392.64l));
+  safe_sprintf(buffer, "%La", 3.31l);
+  EXPECT_EQ(buffer, fmt::format("{:a}", 3.31l));
+}
+
+TEST(format_test, format_char) {
+  const char types[] = "cbBdoxX";
+  check_unknown_types('a', types, "char");
+  EXPECT_EQ("a", fmt::format("{0}", 'a'));
+  EXPECT_EQ("z", fmt::format("{0:c}", 'z'));
+  EXPECT_EQ(L"a", fmt::format(L"{0}", 'a'));
+  int n = 'x';
+  for (const char* type = types + 1; *type; ++type) {
+    std::string format_str = fmt::format("{{:{}}}", *type);
+    EXPECT_EQ(fmt::format(format_str, n), fmt::format(format_str, 'x'))
+        << format_str;
+  }
+  EXPECT_EQ(fmt::format("{:02X}", n), fmt::format("{:02X}", 'x'));
+}
+
+TEST(format_test, format_volatile_char) {
+  volatile char c = 'x';
+  EXPECT_EQ("x", fmt::format("{}", c));
+}
+
+TEST(format_test, format_unsigned_char) {
+  EXPECT_EQ("42", fmt::format("{}", static_cast<unsigned char>(42)));
+  EXPECT_EQ("42", fmt::format("{}", static_cast<uint8_t>(42)));
+}
+
+TEST(format_test, format_wchar) {
+  EXPECT_EQ(L"a", fmt::format(L"{0}", L'a'));
+  // This shouldn't compile:
+  // format("{}", L'a');
+}
+
+TEST(format_test, format_cstring) {
+  check_unknown_types("test", "sp", "string");
+  EXPECT_EQ("test", fmt::format("{0}", "test"));
+  EXPECT_EQ("test", fmt::format("{0:s}", "test"));
+  char nonconst[] = "nonconst";
+  EXPECT_EQ("nonconst", fmt::format("{0}", nonconst));
+  EXPECT_THROW_MSG(fmt::format(+"{0}", static_cast<const char*>(nullptr)),
+                   format_error, "string pointer is null");
+}
+
+TEST(format_test, format_schar_string) {
+  signed char str[] = "test";
+  EXPECT_EQ("test", fmt::format("{0:s}", str));
+  const signed char* const_str = str;
+  EXPECT_EQ("test", fmt::format("{0:s}", const_str));
+}
+
+TEST(format_test, format_uchar_string) {
+  unsigned char str[] = "test";
+  EXPECT_EQ("test", fmt::format("{0:s}", str));
+  const unsigned char* const_str = str;
+  EXPECT_EQ("test", fmt::format("{0:s}", const_str));
+  unsigned char* ptr = str;
+  EXPECT_EQ("test", fmt::format("{0:s}", ptr));
+}
+
+void function_pointer_test(int, double, std::string) {}
+
+TEST(format_test, format_pointer) {
+  check_unknown_types(reinterpret_cast<void*>(0x1234), "p", "pointer");
+  EXPECT_EQ("0x0", fmt::format("{0}", static_cast<void*>(nullptr)));
+  EXPECT_EQ("0x1234", fmt::format("{0}", reinterpret_cast<void*>(0x1234)));
+  EXPECT_EQ("0x1234", fmt::format("{0:p}", reinterpret_cast<void*>(0x1234)));
+  EXPECT_EQ("0x" + std::string(sizeof(void*) * CHAR_BIT / 4, 'f'),
+            fmt::format("{0}", reinterpret_cast<void*>(~uintptr_t())));
+  EXPECT_EQ("0x1234",
+            fmt::format("{}", fmt::ptr(reinterpret_cast<int*>(0x1234))));
+  std::unique_ptr<int> up(new int(1));
+  EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())),
+            fmt::format("{}", fmt::ptr(up)));
+  std::shared_ptr<int> sp(new int(1));
+  EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
+            fmt::format("{}", fmt::ptr(sp)));
+  EXPECT_EQ(fmt::format("{}", fmt::detail::bit_cast<const void*>(
+                                  &function_pointer_test)),
+            fmt::format("{}", fmt::ptr(function_pointer_test)));
+  EXPECT_EQ("0x0", fmt::format("{}", nullptr));
+}
+
+TEST(format_test, format_string) {
+  EXPECT_EQ("test", fmt::format("{0}", std::string("test")));
+}
+
+TEST(format_test, format_string_view) {
+  EXPECT_EQ("test", fmt::format("{}", string_view("test")));
+  EXPECT_EQ("", fmt::format("{}", string_view()));
+}
+
+#ifdef FMT_USE_STRING_VIEW
+struct string_viewable {};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<string_viewable> : formatter<std::string_view> {
+  auto format(string_viewable, format_context& ctx) -> decltype(ctx.out()) {
+    return formatter<std::string_view>::format("foo", ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(format_test, format_std_string_view) {
+  EXPECT_EQ("test", fmt::format("{}", std::string_view("test")));
+  EXPECT_EQ("foo", fmt::format("{}", string_viewable()));
+}
+
+struct explicitly_convertible_to_std_string_view {
+  explicit operator std::string_view() const { return "foo"; }
+};
+
+template <>
+struct fmt::formatter<explicitly_convertible_to_std_string_view>
+    : formatter<std::string_view> {
+  auto format(explicitly_convertible_to_std_string_view v, format_context& ctx)
+      -> decltype(ctx.out()) {
+    return format_to(ctx.out(), "'{}'", std::string_view(v));
+  }
+};
+
+TEST(format_test, format_explicitly_convertible_to_std_string_view) {
+  EXPECT_EQ("'foo'",
+            fmt::format("{}", explicitly_convertible_to_std_string_view()));
+}
+#endif
+
+// std::is_constructible is broken in MSVC until version 2015.
+#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
+struct explicitly_convertible_to_wstring_view {
+  explicit operator fmt::wstring_view() const { return L"foo"; }
+};
+
+TEST(format_test, format_explicitly_convertible_to_wstring_view) {
+  EXPECT_EQ(L"foo",
+            fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
+}
+#endif
+
+namespace fake_qt {
+class QString {
+ public:
+  QString(const wchar_t* s) : s_(s) {}
+  const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); }
+  int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); }
+
+ private:
+  std::wstring s_;
+};
+
+fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
+  return {s.utf16(), static_cast<size_t>(s.size())};
+}
+}  // namespace fake_qt
+
+TEST(format_test, format_foreign_strings) {
+  using fake_qt::QString;
+  EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
+  EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
+}
+
+class Answer {};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<date> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    auto it = ctx.begin();
+    if (it != ctx.end() && *it == 'd') ++it;
+    return it;
+  }
+
+  auto format(const date& d, format_context& ctx) -> decltype(ctx.out()) {
+    format_to(ctx.out(), "{}-{}-{}", d.year(), d.month(), d.day());
+    return ctx.out();
+  }
+};
+
+template <> struct formatter<Answer> : formatter<int> {
+  template <typename FormatContext>
+  auto format(Answer, FormatContext& ctx) -> decltype(ctx.out()) {
+    return formatter<int>::format(42, ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(format_test, format_custom) {
+  EXPECT_THROW_MSG(fmt::format(+"{:s}", date(2012, 12, 9)), format_error,
+                   "unknown format specifier");
+  EXPECT_EQ("42", fmt::format("{0}", Answer()));
+  EXPECT_EQ("0042", fmt::format("{:04}", Answer()));
+}
+
+TEST(format_test, format_to_custom) {
+  char buf[10] = {};
+  auto end =
+      &*fmt::format_to(fmt::detail::make_checked(buf, 10), "{}", Answer());
+  EXPECT_EQ(end, buf + 2);
+  EXPECT_STREQ(buf, "42");
+}
+
+TEST(format_test, wide_format_string) {
+  EXPECT_EQ(L"42", fmt::format(L"{}", 42));
+  EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
+  EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
+  EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
+  EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error);
+}
+
+TEST(format_test, format_string_from_speed_test) {
+  EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%",
+            fmt::format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%", 1.234, 42,
+                        3.13, "str", reinterpret_cast<void*>(1000), 'X'));
+}
+
+TEST(format_test, format_examples) {
+  std::string message = fmt::format("The answer is {}", 42);
+  EXPECT_EQ("The answer is 42", message);
+
+  EXPECT_EQ("42", fmt::format("{}", 42));
+  EXPECT_EQ("42", fmt::format(std::string("{}"), 42));
+
+  memory_buffer out;
+  format_to(out, "The answer is {}.", 42);
+  EXPECT_EQ("The answer is 42.", to_string(out));
+
+  const char* filename = "nonexistent";
+  FILE* ftest = safe_fopen(filename, "r");
+  if (ftest) fclose(ftest);
+  int error_code = errno;
+  EXPECT_TRUE(ftest == nullptr);
+  EXPECT_SYSTEM_ERROR(
+      {
+        FILE* f = safe_fopen(filename, "r");
+        if (!f)
+          throw fmt::system_error(errno, "Cannot open file '{}'", filename);
+        fclose(f);
+      },
+      error_code, "Cannot open file 'nonexistent'");
+
+  EXPECT_EQ("First, thou shalt count to three",
+            fmt::format("First, thou shalt count to {0}", "three"));
+  EXPECT_EQ("Bring me a shrubbery", fmt::format("Bring me a {}", "shrubbery"));
+  EXPECT_EQ("From 1 to 3", fmt::format("From {} to {}", 1, 3));
+
+  char buffer[buffer_size];
+  safe_sprintf(buffer, "%03.2f", -1.2);
+  EXPECT_EQ(buffer, fmt::format("{:03.2f}", -1.2));
+
+  EXPECT_EQ("a, b, c", fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'));
+  EXPECT_EQ("a, b, c", fmt::format("{}, {}, {}", 'a', 'b', 'c'));
+  EXPECT_EQ("c, b, a", fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'));
+  EXPECT_EQ("abracadabra", fmt::format("{0}{1}{0}", "abra", "cad"));
+
+  EXPECT_EQ("left aligned                  ",
+            fmt::format("{:<30}", "left aligned"));
+  EXPECT_EQ("                 right aligned",
+            fmt::format("{:>30}", "right aligned"));
+  EXPECT_EQ("           centered           ",
+            fmt::format("{:^30}", "centered"));
+  EXPECT_EQ("***********centered***********",
+            fmt::format("{:*^30}", "centered"));
+
+  EXPECT_EQ("+3.140000; -3.140000", fmt::format("{:+f}; {:+f}", 3.14, -3.14));
+  EXPECT_EQ(" 3.140000; -3.140000", fmt::format("{: f}; {: f}", 3.14, -3.14));
+  EXPECT_EQ("3.140000; -3.140000", fmt::format("{:-f}; {:-f}", 3.14, -3.14));
+
+  EXPECT_EQ("int: 42;  hex: 2a;  oct: 52",
+            fmt::format("int: {0:d};  hex: {0:x};  oct: {0:o}", 42));
+  EXPECT_EQ("int: 42;  hex: 0x2a;  oct: 052",
+            fmt::format("int: {0:d};  hex: {0:#x};  oct: {0:#o}", 42));
+
+  EXPECT_EQ("The answer is 42", fmt::format("The answer is {}", 42));
+  EXPECT_THROW_MSG(fmt::format(+"The answer is {:d}", "forty-two"),
+                   format_error, "invalid type specifier");
+
+  EXPECT_EQ(L"Cyrillic letter \x42e",
+            fmt::format(L"Cyrillic letter {}", L'\x42e'));
+
+  EXPECT_WRITE(
+      stdout, fmt::print("{}", std::numeric_limits<double>::infinity()), "inf");
+}
+
+TEST(format_test, print) {
+  EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!");
+  EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
+               "Don't panic!");
+  // Check that the wide print overload compiles.
+  if (fmt::detail::const_check(false)) fmt::print(L"test");
+}
+
+TEST(format_test, variadic) {
+  EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1));
+  EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
+}
+
+TEST(format_test, dynamic) {
+  using ctx = fmt::format_context;
+  auto args = std::vector<fmt::basic_format_arg<ctx>>();
+  args.emplace_back(fmt::detail::make_arg<ctx>(42));
+  args.emplace_back(fmt::detail::make_arg<ctx>("abc1"));
+  args.emplace_back(fmt::detail::make_arg<ctx>(1.5f));
+
+  std::string result = fmt::vformat(
+      "{} and {} and {}",
+      fmt::format_args(args.data(), static_cast<int>(args.size())));
+
+  EXPECT_EQ("42 and abc1 and 1.5", result);
+}
+
+TEST(format_test, bytes) {
+  auto s = fmt::format("{:10}", fmt::bytes("ёжик"));
+  EXPECT_EQ("ёжик  ", s);
+  EXPECT_EQ(10, s.size());
+}
+
+enum test_enum { foo, bar };
+
+TEST(format_test, join) {
+  using fmt::join;
+  int v1[3] = {1, 2, 3};
+  auto v2 = std::vector<float>();
+  v2.push_back(1.2f);
+  v2.push_back(3.4f);
+  void* v3[2] = {&v1[0], &v1[1]};
+
+  EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, v1 + 3, ", ")));
+  EXPECT_EQ("(1)", fmt::format("({})", join(v1, v1 + 1, ", ")));
+  EXPECT_EQ("()", fmt::format("({})", join(v1, v1, ", ")));
+  EXPECT_EQ("(001, 002, 003)", fmt::format("({:03})", join(v1, v1 + 3, ", ")));
+  EXPECT_EQ("(+01.20, +03.40)",
+            fmt::format("({:+06.2f})", join(v2.begin(), v2.end(), ", ")));
+
+  EXPECT_EQ(L"(1, 2, 3)", fmt::format(L"({})", join(v1, v1 + 3, L", ")));
+  EXPECT_EQ("1, 2, 3", fmt::format("{0:{1}}", join(v1, v1 + 3, ", "), 1));
+
+  EXPECT_EQ(fmt::format("{}, {}", v3[0], v3[1]),
+            fmt::format("{}", join(v3, v3 + 2, ", ")));
+
+  EXPECT_EQ("(1, 2, 3)", fmt::format("({})", join(v1, ", ")));
+  EXPECT_EQ("(+01.20, +03.40)", fmt::format("({:+06.2f})", join(v2, ", ")));
+
+  auto v4 = std::vector<test_enum>{foo, bar, foo};
+  EXPECT_EQ("0 1 0", fmt::format("{}", join(v4, " ")));
+}
+
+#ifdef __cpp_lib_byte
+TEST(format_test, join_bytes) {
+  auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
+  EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, ", ")));
+}
+#endif
+
+std::string vformat_message(int id, const char* format, fmt::format_args args) {
+  fmt::memory_buffer buffer;
+  format_to(buffer, "[{}] ", id);
+  vformat_to(buffer, format, args);
+  return to_string(buffer);
+}
+
+template <typename... Args>
+std::string format_message(int id, const char* format, const Args&... args) {
+  auto va = fmt::make_format_args(args...);
+  return vformat_message(id, format, va);
+}
+
+TEST(format_test, format_message_example) {
+  EXPECT_EQ("[42] something happened",
+            format_message(42, "{} happened", "something"));
+}
+
+template <typename... Args>
+void print_error(const char* file, int line, const char* format,
+                 const Args&... args) {
+  fmt::print("{}: {}: ", file, line);
+  fmt::print(format, args...);
+}
+
+TEST(format_test, unpacked_args) {
+  EXPECT_EQ("0123456789abcdefg",
+            fmt::format("{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}", 0, 1, 2, 3, 4, 5,
+                        6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
+}
+
+struct string_like {};
+fmt::string_view to_string_view(string_like) { return "foo"; }
+
+constexpr char with_null[3] = {'{', '}', '\0'};
+constexpr char no_null[2] = {'{', '}'};
+static FMT_CONSTEXPR_DECL const char static_with_null[3] = {'{', '}', '\0'};
+static FMT_CONSTEXPR_DECL const wchar_t static_with_null_wide[3] = {'{', '}',
+                                                                    '\0'};
+static FMT_CONSTEXPR_DECL const char static_no_null[2] = {'{', '}'};
+static FMT_CONSTEXPR_DECL const wchar_t static_no_null_wide[2] = {'{', '}'};
+
+TEST(format_test, compile_time_string) {
+  EXPECT_EQ("foo", fmt::format(FMT_STRING("foo")));
+  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
+  EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
+  EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
+
+  (void)static_with_null;
+  (void)static_with_null_wide;
+  (void)static_no_null;
+  (void)static_no_null_wide;
+#ifndef _MSC_VER
+  EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42));
+  EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_with_null_wide), 42));
+  EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42));
+  EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_no_null_wide), 42));
+#endif
+
+  (void)with_null;
+  (void)no_null;
+#if __cplusplus >= 201703L
+  EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42));
+  EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42));
+#endif
+#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
+  EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
+  EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
+#endif
+}
+
+TEST(format_test, custom_format_compile_time_string) {
+  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer()));
+  auto answer = Answer();
+  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer));
+  char buf[10] = {};
+  fmt::format_to(buf, FMT_STRING("{}"), answer);
+  const Answer const_answer = Answer();
+  EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer));
+}
+
+#if FMT_USE_USER_DEFINED_LITERALS
+// Passing user-defined literals directly to EXPECT_EQ causes problems
+// with macro argument stringification (#) on some versions of GCC.
+// Workaround: Assing the UDL result to a variable before the macro.
+
+using namespace fmt::literals;
+
+TEST(format_test, format_udl) {
+  auto udl_format = "{}c{}"_format("ab", 1);
+  EXPECT_EQ(fmt::format("{}c{}", "ab", 1), udl_format);
+  auto udl_format_w = L"{}c{}"_format(L"ab", 1);
+  EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), udl_format_w);
+}
+
+TEST(format_test, named_arg_udl) {
+  auto udl_a = fmt::format("{first}{second}{first}{third}", "first"_a = "abra",
+                           "second"_a = "cad", "third"_a = 99);
+  EXPECT_EQ(
+      fmt::format("{first}{second}{first}{third}", fmt::arg("first", "abra"),
+                  fmt::arg("second", "cad"), fmt::arg("third", 99)),
+      udl_a);
+  auto udl_a_w =
+      fmt::format(L"{first}{second}{first}{third}", L"first"_a = L"abra",
+                  L"second"_a = L"cad", L"third"_a = 99);
+  EXPECT_EQ(
+      fmt::format(L"{first}{second}{first}{third}", fmt::arg(L"first", L"abra"),
+                  fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
+      udl_a_w);
+}
+
+TEST(format_test, udl_template) {
+  EXPECT_EQ("foo", "foo"_format());
+  EXPECT_EQ("        42", "{0:10}"_format(42));
+}
+
+TEST(format_test, udl_pass_user_defined_object_as_lvalue) {
+  EXPECT_EQ("2015-10-21", "{}"_format(date(2015, 10, 21)));
+}
+#endif  // FMT_USE_USER_DEFINED_LITERALS
+
+TEST(format_test, enum) { EXPECT_EQ("0", fmt::format("{}", foo)); }
+
+TEST(format_test, formatter_not_specialized) {
+  static_assert(!fmt::has_formatter<fmt::formatter<test_enum>,
+                                    fmt::format_context>::value,
+                "");
+}
+
+#if FMT_HAS_FEATURE(cxx_strong_enums)
+enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
+
+TEST(format_test, strong_enum) {
+  EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value));
+}
+#endif
+
+TEST(format_test, non_null_terminated_format_string) {
+  EXPECT_EQ("42", fmt::format(string_view("{}foo", 2), 42));
+}
+
+struct variant {
+  enum { int_type, string_type } type;
+  explicit variant(int) : type(int_type) {}
+  explicit variant(const char*) : type(string_type) {}
+};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<variant> : dynamic_formatter<> {
+  auto format(variant value, format_context& ctx) -> decltype(ctx.out()) {
+    if (value.type == variant::int_type)
+      return dynamic_formatter<>::format(42, ctx);
+    return dynamic_formatter<>::format("foo", ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(format_test, dynamic_formatter) {
+  auto num = variant(42);
+  auto str = variant("foo");
+  EXPECT_EQ("42", fmt::format("{:d}", num));
+  EXPECT_EQ("foo", fmt::format("{:s}", str));
+  EXPECT_EQ(" 42 foo ", fmt::format("{:{}} {:{}}", num, 3, str, 4));
+  EXPECT_THROW_MSG(fmt::format(+"{0:{}}", num), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{:{0}}", num), format_error,
+                   "cannot switch from automatic to manual argument indexing");
+  EXPECT_THROW_MSG(fmt::format(+"{:+}", str), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{:-}", str), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{: }", str), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{:#}", str), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{:0}", str), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{:.2}", num), format_error,
+                   "precision not allowed for this argument type");
+}
+
+namespace adl_test {
+namespace fmt {
+namespace detail {
+struct foo {};
+template <typename, typename OutputIt> void write(OutputIt, foo) = delete;
+}  // namespace detail
+}  // namespace fmt
+}  // namespace adl_test
+
+FMT_BEGIN_NAMESPACE
+template <>
+struct formatter<adl_test::fmt::detail::foo> : formatter<std::string> {
+  template <typename FormatContext>
+  auto format(adl_test::fmt::detail::foo, FormatContext& ctx)
+      -> decltype(ctx.out()) {
+    return formatter<std::string>::format("foo", ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(format_test, to_string) {
+  EXPECT_EQ("42", fmt::to_string(42));
+  EXPECT_EQ("0x1234", fmt::to_string(reinterpret_cast<void*>(0x1234)));
+  EXPECT_EQ("foo", fmt::to_string(adl_test::fmt::detail::foo()));
+
+  enum test_enum2 : unsigned char { test_value };
+  EXPECT_EQ("0", fmt::to_string(test_value));
+}
+
+TEST(format_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
+
+TEST(format_test, output_iterators) {
+  std::list<char> out;
+  fmt::format_to(std::back_inserter(out), "{}", 42);
+  EXPECT_EQ("42", std::string(out.begin(), out.end()));
+  std::stringstream s;
+  fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
+  EXPECT_EQ("42", s.str());
+}
+
+TEST(format_test, formatted_size) {
+  EXPECT_EQ(2u, fmt::formatted_size("{}", 42));
+}
+
+TEST(format_test, format_to_no_args) {
+  std::string s;
+  fmt::format_to(std::back_inserter(s), "test");
+  EXPECT_EQ("test", s);
+}
+
+TEST(format_test, format_to) {
+  std::string s;
+  fmt::format_to(std::back_inserter(s), "part{0}", 1);
+  EXPECT_EQ("part1", s);
+  fmt::format_to(std::back_inserter(s), "part{0}", 2);
+  EXPECT_EQ("part1part2", s);
+}
+
+TEST(format_test, format_to_wide) {
+  std::vector<wchar_t> buf;
+  fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
+  EXPECT_STREQ(buf.data(), L"42");
+}
+
+TEST(format_test, format_to_memory_buffer) {
+  fmt::basic_memory_buffer<char, 100> buffer;
+  fmt::format_to(buffer, "{}", "foo");
+  EXPECT_EQ("foo", to_string(buffer));
+  fmt::wmemory_buffer wbuffer;
+  fmt::format_to(wbuffer, L"{}", L"foo");
+  EXPECT_EQ(L"foo", to_string(wbuffer));
+}
+
+TEST(format_test, format_to_vector) {
+  std::vector<char> v;
+  fmt::format_to(std::back_inserter(v), "{}", "foo");
+  EXPECT_EQ(string_view(v.data(), v.size()), "foo");
+}
+
+struct nongrowing_container {
+  using value_type = char;
+  void push_back(char) { throw std::runtime_error("can't take it any more"); }
+};
+
+TEST(format_test, format_to_propagates_exceptions) {
+  auto c = nongrowing_container();
+  EXPECT_THROW(fmt::format_to(std::back_inserter(c), "{}", 42),
+               std::runtime_error);
+}
+
+TEST(format_test, format_to_n) {
+  char buffer[4];
+  buffer[3] = 'x';
+  auto result = fmt::format_to_n(buffer, 3, "{}", 12345);
+  EXPECT_EQ(5u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ("123x", fmt::string_view(buffer, 4));
+
+  result = fmt::format_to_n(buffer, 3, "{:s}", "foobar");
+  EXPECT_EQ(6u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ("foox", fmt::string_view(buffer, 4));
+
+  buffer[0] = 'x';
+  buffer[1] = 'x';
+  buffer[2] = 'x';
+  result = fmt::format_to_n(buffer, 3, "{}", 'A');
+  EXPECT_EQ(1u, result.size);
+  EXPECT_EQ(buffer + 1, result.out);
+  EXPECT_EQ("Axxx", fmt::string_view(buffer, 4));
+
+  result = fmt::format_to_n(buffer, 3, "{}{} ", 'B', 'C');
+  EXPECT_EQ(3u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ("BC x", fmt::string_view(buffer, 4));
+
+  result = fmt::format_to_n(buffer, 4, "{}", "ABCDE");
+  EXPECT_EQ(5u, result.size);
+  EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
+
+  buffer[3] = 'x';
+  result = fmt::format_to_n(buffer, 3, "{}", std::string(1000, '*'));
+  EXPECT_EQ(1000u, result.size);
+  EXPECT_EQ("***x", fmt::string_view(buffer, 4));
+}
+
+TEST(format_test, wide_format_to_n) {
+  wchar_t buffer[4];
+  buffer[3] = L'x';
+  auto result = fmt::format_to_n(buffer, 3, L"{}", 12345);
+  EXPECT_EQ(5u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ(L"123x", fmt::wstring_view(buffer, 4));
+  buffer[0] = L'x';
+  buffer[1] = L'x';
+  buffer[2] = L'x';
+  result = fmt::format_to_n(buffer, 3, L"{}", L'A');
+  EXPECT_EQ(1u, result.size);
+  EXPECT_EQ(buffer + 1, result.out);
+  EXPECT_EQ(L"Axxx", fmt::wstring_view(buffer, 4));
+  result = fmt::format_to_n(buffer, 3, L"{}{} ", L'B', L'C');
+  EXPECT_EQ(3u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
+}
+
+struct test_output_iterator {
+  char* data;
+
+  using iterator_category = std::output_iterator_tag;
+  using value_type = void;
+  using difference_type = void;
+  using pointer = void;
+  using reference = void;
+
+  test_output_iterator& operator++() {
+    ++data;
+    return *this;
+  }
+  test_output_iterator operator++(int) {
+    auto tmp = *this;
+    ++data;
+    return tmp;
+  }
+  char& operator*() { return *data; }
+};
+
+TEST(format_test, format_to_n_output_iterator) {
+  char buf[10] = {};
+  fmt::format_to_n(test_output_iterator{buf}, 10, "{}", 42);
+  EXPECT_STREQ(buf, "42");
+}
+
+#if FMT_USE_CONSTEXPR
+enum class arg_id_result { none, empty, index, name, error };
+struct test_arg_id_handler {
+  arg_id_result res = arg_id_result::none;
+  int index = 0;
+  string_view name;
+
+  FMT_CONSTEXPR void operator()() { res = arg_id_result::empty; }
+
+  FMT_CONSTEXPR void operator()(int i) {
+    res = arg_id_result::index;
+    index = i;
+  }
+
+  FMT_CONSTEXPR void operator()(string_view n) {
+    res = arg_id_result::name;
+    name = n;
+  }
+
+  FMT_CONSTEXPR void on_error(const char*) { res = arg_id_result::error; }
+};
+
+template <size_t N>
+FMT_CONSTEXPR test_arg_id_handler parse_arg_id(const char (&s)[N]) {
+  test_arg_id_handler h;
+  fmt::detail::parse_arg_id(s, s + N, h);
+  return h;
+}
+
+TEST(format_test, constexpr_parse_arg_id) {
+  static_assert(parse_arg_id(":").res == arg_id_result::empty, "");
+  static_assert(parse_arg_id("}").res == arg_id_result::empty, "");
+  static_assert(parse_arg_id("42:").res == arg_id_result::index, "");
+  static_assert(parse_arg_id("42:").index == 42, "");
+  static_assert(parse_arg_id("foo:").res == arg_id_result::name, "");
+  static_assert(parse_arg_id("foo:").name.size() == 3, "");
+  static_assert(parse_arg_id("!").res == arg_id_result::error, "");
+}
+
+struct test_format_specs_handler {
+  enum result { none, plus, minus, space, hash, zero, loc, error };
+  result res = none;
+
+  fmt::align_t alignment = fmt::align::none;
+  char fill = 0;
+  int width = 0;
+  fmt::detail::arg_ref<char> width_ref;
+  int precision = 0;
+  fmt::detail::arg_ref<char> precision_ref;
+  char type = 0;
+
+  // Workaround for MSVC2017 bug that results in "expression did not evaluate
+  // to a constant" with compiler-generated copy ctor.
+  FMT_CONSTEXPR test_format_specs_handler() {}
+  FMT_CONSTEXPR test_format_specs_handler(
+      const test_format_specs_handler& other)
+      : res(other.res),
+        alignment(other.alignment),
+        fill(other.fill),
+        width(other.width),
+        width_ref(other.width_ref),
+        precision(other.precision),
+        precision_ref(other.precision_ref),
+        type(other.type) {}
+
+  FMT_CONSTEXPR void on_align(fmt::align_t a) { alignment = a; }
+  FMT_CONSTEXPR void on_fill(fmt::string_view f) { fill = f[0]; }
+  FMT_CONSTEXPR void on_plus() { res = plus; }
+  FMT_CONSTEXPR void on_minus() { res = minus; }
+  FMT_CONSTEXPR void on_space() { res = space; }
+  FMT_CONSTEXPR void on_hash() { res = hash; }
+  FMT_CONSTEXPR void on_zero() { res = zero; }
+  FMT_CONSTEXPR void on_localized() { res = loc; }
+
+  FMT_CONSTEXPR void on_width(int w) { width = w; }
+  FMT_CONSTEXPR void on_dynamic_width(fmt::detail::auto_id) {}
+  FMT_CONSTEXPR void on_dynamic_width(int index) { width_ref = index; }
+  FMT_CONSTEXPR void on_dynamic_width(string_view) {}
+
+  FMT_CONSTEXPR void on_precision(int p) { precision = p; }
+  FMT_CONSTEXPR void on_dynamic_precision(fmt::detail::auto_id) {}
+  FMT_CONSTEXPR void on_dynamic_precision(int index) { precision_ref = index; }
+  FMT_CONSTEXPR void on_dynamic_precision(string_view) {}
+
+  FMT_CONSTEXPR void end_precision() {}
+  FMT_CONSTEXPR void on_type(char t) { type = t; }
+  FMT_CONSTEXPR void on_error(const char*) { res = error; }
+};
+
+template <size_t N>
+FMT_CONSTEXPR test_format_specs_handler parse_test_specs(const char (&s)[N]) {
+  auto h = test_format_specs_handler();
+  fmt::detail::parse_format_specs(s, s + N, h);
+  return h;
+}
+
+TEST(format_test, constexpr_parse_format_specs) {
+  using handler = test_format_specs_handler;
+  static_assert(parse_test_specs("<").alignment == fmt::align::left, "");
+  static_assert(parse_test_specs("*^").fill == '*', "");
+  static_assert(parse_test_specs("+").res == handler::plus, "");
+  static_assert(parse_test_specs("-").res == handler::minus, "");
+  static_assert(parse_test_specs(" ").res == handler::space, "");
+  static_assert(parse_test_specs("#").res == handler::hash, "");
+  static_assert(parse_test_specs("0").res == handler::zero, "");
+  static_assert(parse_test_specs("L").res == handler::loc, "");
+  static_assert(parse_test_specs("42").width == 42, "");
+  static_assert(parse_test_specs("{42}").width_ref.val.index == 42, "");
+  static_assert(parse_test_specs(".42").precision == 42, "");
+  static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
+  static_assert(parse_test_specs("d").type == 'd', "");
+  static_assert(parse_test_specs("{<").res == handler::error, "");
+}
+
+struct test_parse_context {
+  typedef char char_type;
+
+  FMT_CONSTEXPR int next_arg_id() { return 11; }
+  template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
+
+  FMT_CONSTEXPR const char* begin() { return nullptr; }
+  FMT_CONSTEXPR const char* end() { return nullptr; }
+
+  void on_error(const char*) {}
+};
+
+struct test_context {
+  using char_type = char;
+  using format_arg = fmt::basic_format_arg<test_context>;
+  using parse_context_type = fmt::format_parse_context;
+
+  template <typename T> struct formatter_type {
+    using type = fmt::formatter<T, char_type>;
+  };
+
+  template <typename Id>
+  FMT_CONSTEXPR fmt::basic_format_arg<test_context> arg(Id id) {
+    return fmt::detail::make_arg<test_context>(id);
+  }
+
+  void on_error(const char*) {}
+
+  FMT_CONSTEXPR test_context error_handler() { return *this; }
+};
+
+template <size_t N>
+FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) {
+  auto specs = fmt::format_specs();
+  auto parse_ctx = test_parse_context();
+  auto ctx = test_context();
+  fmt::detail::specs_handler<test_parse_context, test_context> h(
+      specs, parse_ctx, ctx);
+  parse_format_specs(s, s + N, h);
+  return specs;
+}
+
+TEST(format_test, constexpr_specs_handler) {
+  static_assert(parse_specs("<").align == fmt::align::left, "");
+  static_assert(parse_specs("*^").fill[0] == '*', "");
+  static_assert(parse_specs("+").sign == fmt::sign::plus, "");
+  static_assert(parse_specs("-").sign == fmt::sign::minus, "");
+  static_assert(parse_specs(" ").sign == fmt::sign::space, "");
+  static_assert(parse_specs("#").alt, "");
+  static_assert(parse_specs("0").align == fmt::align::numeric, "");
+  static_assert(parse_specs("42").width == 42, "");
+  static_assert(parse_specs("{}").width == 11, "");
+  static_assert(parse_specs("{22}").width == 22, "");
+  static_assert(parse_specs(".42").precision == 42, "");
+  static_assert(parse_specs(".{}").precision == 11, "");
+  static_assert(parse_specs(".{22}").precision == 22, "");
+  static_assert(parse_specs("d").type == 'd', "");
+}
+
+template <size_t N>
+FMT_CONSTEXPR fmt::detail::dynamic_format_specs<char> parse_dynamic_specs(
+    const char (&s)[N]) {
+  auto specs = fmt::detail::dynamic_format_specs<char>();
+  auto ctx = test_parse_context();
+  auto h = fmt::detail::dynamic_specs_handler<test_parse_context>(specs, ctx);
+  parse_format_specs(s, s + N, h);
+  return specs;
+}
+
+TEST(format_test, constexpr_dynamic_specs_handler) {
+  static_assert(parse_dynamic_specs("<").align == fmt::align::left, "");
+  static_assert(parse_dynamic_specs("*^").fill[0] == '*', "");
+  static_assert(parse_dynamic_specs("+").sign == fmt::sign::plus, "");
+  static_assert(parse_dynamic_specs("-").sign == fmt::sign::minus, "");
+  static_assert(parse_dynamic_specs(" ").sign == fmt::sign::space, "");
+  static_assert(parse_dynamic_specs("#").alt, "");
+  static_assert(parse_dynamic_specs("0").align == fmt::align::numeric, "");
+  static_assert(parse_dynamic_specs("42").width == 42, "");
+  static_assert(parse_dynamic_specs("{}").width_ref.val.index == 11, "");
+  static_assert(parse_dynamic_specs("{42}").width_ref.val.index == 42, "");
+  static_assert(parse_dynamic_specs(".42").precision == 42, "");
+  static_assert(parse_dynamic_specs(".{}").precision_ref.val.index == 11, "");
+  static_assert(parse_dynamic_specs(".{42}").precision_ref.val.index == 42, "");
+  static_assert(parse_dynamic_specs("d").type == 'd', "");
+}
+
+template <size_t N>
+FMT_CONSTEXPR test_format_specs_handler check_specs(const char (&s)[N]) {
+  fmt::detail::specs_checker<test_format_specs_handler> checker(
+      test_format_specs_handler(), fmt::detail::type::double_type);
+  parse_format_specs(s, s + N, checker);
+  return checker;
+}
+
+TEST(format_test, constexpr_specs_checker) {
+  using handler = test_format_specs_handler;
+  static_assert(check_specs("<").alignment == fmt::align::left, "");
+  static_assert(check_specs("*^").fill == '*', "");
+  static_assert(check_specs("+").res == handler::plus, "");
+  static_assert(check_specs("-").res == handler::minus, "");
+  static_assert(check_specs(" ").res == handler::space, "");
+  static_assert(check_specs("#").res == handler::hash, "");
+  static_assert(check_specs("0").res == handler::zero, "");
+  static_assert(check_specs("42").width == 42, "");
+  static_assert(check_specs("{42}").width_ref.val.index == 42, "");
+  static_assert(check_specs(".42").precision == 42, "");
+  static_assert(check_specs(".{42}").precision_ref.val.index == 42, "");
+  static_assert(check_specs("d").type == 'd', "");
+  static_assert(check_specs("{<").res == handler::error, "");
+}
+
+struct test_format_string_handler {
+  FMT_CONSTEXPR void on_text(const char*, const char*) {}
+
+  FMT_CONSTEXPR int on_arg_id() { return 0; }
+
+  template <typename T> FMT_CONSTEXPR int on_arg_id(T) { return 0; }
+
+  FMT_CONSTEXPR void on_replacement_field(int, const char*) {}
+
+  FMT_CONSTEXPR const char* on_format_specs(int, const char* begin,
+                                            const char*) {
+    return begin;
+  }
+
+  FMT_CONSTEXPR void on_error(const char*) { error = true; }
+
+  bool error = false;
+};
+
+template <size_t N> FMT_CONSTEXPR bool parse_string(const char (&s)[N]) {
+  auto h = test_format_string_handler();
+  fmt::detail::parse_format_string<true>(fmt::string_view(s, N - 1), h);
+  return !h.error;
+}
+
+TEST(format_test, constexpr_parse_format_string) {
+  static_assert(parse_string("foo"), "");
+  static_assert(!parse_string("}"), "");
+  static_assert(parse_string("{}"), "");
+  static_assert(parse_string("{42}"), "");
+  static_assert(parse_string("{foo}"), "");
+  static_assert(parse_string("{:}"), "");
+}
+
+struct test_error_handler {
+  const char*& error;
+
+  FMT_CONSTEXPR test_error_handler(const char*& err) : error(err) {}
+
+  FMT_CONSTEXPR test_error_handler(const test_error_handler& other)
+      : error(other.error) {}
+
+  FMT_CONSTEXPR void on_error(const char* message) {
+    if (!error) error = message;
+  }
+};
+
+FMT_CONSTEXPR size_t len(const char* s) {
+  size_t len = 0;
+  while (*s++) ++len;
+  return len;
+}
+
+FMT_CONSTEXPR bool equal(const char* s1, const char* s2) {
+  if (!s1 || !s2) return s1 == s2;
+  while (*s1 && *s1 == *s2) {
+    ++s1;
+    ++s2;
+  }
+  return *s1 == *s2;
+}
+
+template <typename... Args>
+FMT_CONSTEXPR bool test_error(const char* fmt, const char* expected_error) {
+  const char* actual_error = nullptr;
+  auto s = string_view(fmt, len(fmt));
+  auto checker =
+      fmt::detail::format_string_checker<char, test_error_handler, Args...>(
+          s, test_error_handler(actual_error));
+  fmt::detail::parse_format_string<true>(s, checker);
+  return equal(actual_error, expected_error);
+}
+
+#  define EXPECT_ERROR_NOARGS(fmt, error) \
+    static_assert(test_error(fmt, error), "")
+#  define EXPECT_ERROR(fmt, error, ...) \
+    static_assert(test_error<__VA_ARGS__>(fmt, error), "")
+
+TEST(format_test, format_string_errors) {
+  EXPECT_ERROR_NOARGS("foo", nullptr);
+  EXPECT_ERROR_NOARGS("}", "unmatched '}' in format string");
+  EXPECT_ERROR("{0:s", "unknown format specifier", date);
+#  if !FMT_MSC_VER || FMT_MSC_VER >= 1916
+  // This causes an detail compiler error in MSVC2017.
+  EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
+  EXPECT_ERROR("{:10000000000}", "number is too big", int);
+  EXPECT_ERROR("{:.10000000000}", "number is too big", int);
+  EXPECT_ERROR_NOARGS("{:x}", "argument not found");
+  EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
+               const char*);
+  EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
+               const char*);
+  EXPECT_ERROR("{:#}", "format specifier requires numeric argument",
+               const char*);
+  EXPECT_ERROR("{: }", "format specifier requires numeric argument",
+               const char*);
+  EXPECT_ERROR("{:0}", "format specifier requires numeric argument",
+               const char*);
+  EXPECT_ERROR("{:+}", "format specifier requires signed argument", unsigned);
+  EXPECT_ERROR("{:-}", "format specifier requires signed argument", unsigned);
+  EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned);
+  EXPECT_ERROR("{:{}}", "argument not found", int);
+  EXPECT_ERROR("{:.{}}", "argument not found", double);
+  EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int);
+  EXPECT_ERROR("{:s}", "invalid type specifier", int);
+  EXPECT_ERROR("{:s}", "invalid type specifier", char);
+  EXPECT_ERROR("{:+}", "invalid format specifier for char", char);
+  EXPECT_ERROR("{:s}", "invalid type specifier", double);
+  EXPECT_ERROR("{:d}", "invalid type specifier", const char*);
+  EXPECT_ERROR("{:d}", "invalid type specifier", std::string);
+  EXPECT_ERROR("{:s}", "invalid type specifier", void*);
+#  else
+  fmt::print("warning: constexpr is broken in this version of MSVC\n");
+#  endif
+  EXPECT_ERROR("{foo", "compile-time checks don't support named arguments",
+               int);
+  EXPECT_ERROR_NOARGS("{10000000000}", "number is too big");
+  EXPECT_ERROR_NOARGS("{0x}", "invalid format string");
+  EXPECT_ERROR_NOARGS("{-}", "invalid format string");
+  EXPECT_ERROR("{:{0x}}", "invalid format string", int);
+  EXPECT_ERROR("{:{-}}", "invalid format string", int);
+  EXPECT_ERROR("{:.{0x}}", "invalid format string", int);
+  EXPECT_ERROR("{:.{-}}", "invalid format string", int);
+  EXPECT_ERROR("{:.x}", "missing precision specifier", int);
+  EXPECT_ERROR_NOARGS("{}", "argument not found");
+  EXPECT_ERROR("{1}", "argument not found", int);
+  EXPECT_ERROR("{1}{}",
+               "cannot switch from manual to automatic argument indexing", int,
+               int);
+  EXPECT_ERROR("{}{1}",
+               "cannot switch from automatic to manual argument indexing", int,
+               int);
+}
+
+TEST(format_test, vformat_to) {
+  using context = fmt::format_context;
+  fmt::basic_format_arg<context> arg = fmt::detail::make_arg<context>(42);
+  auto args = fmt::basic_format_args<context>(&arg, 1);
+  auto s = std::string();
+  fmt::vformat_to(std::back_inserter(s), "{}", args);
+  EXPECT_EQ("42", s);
+  s.clear();
+  fmt::vformat_to(std::back_inserter(s), FMT_STRING("{}"), args);
+  EXPECT_EQ("42", s);
+
+  using wcontext = fmt::wformat_context;
+  fmt::basic_format_arg<wcontext> warg = fmt::detail::make_arg<wcontext>(42);
+  auto wargs = fmt::basic_format_args<wcontext>(&warg, 1);
+  auto w = std::wstring();
+  fmt::vformat_to(std::back_inserter(w), L"{}", wargs);
+  EXPECT_EQ(L"42", w);
+  w.clear();
+  fmt::vformat_to(std::back_inserter(w), FMT_STRING(L"{}"), wargs);
+  EXPECT_EQ(L"42", w);
+}
+
+template <typename T> static std::string fmt_to_string(const T& t) {
+  return fmt::format(FMT_STRING("{}"), t);
+}
+
+TEST(format_test, fmt_string_in_template) {
+  EXPECT_EQ(fmt_to_string(1), "1");
+  EXPECT_EQ(fmt_to_string(0), "0");
+}
+
+#endif  // FMT_USE_CONSTEXPR
+
+TEST(format_test, char_traits_is_not_ambiguous) {
+  // Test that we don't inject detail names into the std namespace.
+  using namespace std;
+  auto c = char_traits<char>::char_type();
+  (void)c;
+#if __cplusplus >= 201103L
+  auto s = std::string();
+  auto lval = begin(s);
+  (void)lval;
+#endif
+}
+
+#if __cplusplus > 201103L
+struct custom_char {
+  int value;
+  custom_char() = default;
+
+  template <typename T>
+  constexpr custom_char(T val) : value(static_cast<int>(val)) {}
+
+  operator int() const { return value; }
+};
+
+int to_ascii(custom_char c) { return c; }
+
+FMT_BEGIN_NAMESPACE
+template <> struct is_char<custom_char> : std::true_type {};
+FMT_END_NAMESPACE
+
+TEST(format_test, format_custom_char) {
+  const custom_char format[] = {'{', '}', 0};
+  auto result = fmt::format(format, custom_char('x'));
+  EXPECT_EQ(result.size(), 1);
+  EXPECT_EQ(result[0], custom_char('x'));
+}
+#endif
+
+// Convert a char8_t string to std::string. Otherwise GTest will insist on
+// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
+template <typename S> std::string from_u8str(const S& str) {
+  return std::string(str.begin(), str.end());
+}
+
+TEST(format_test, format_utf8_precision) {
+  using str_type = std::basic_string<fmt::detail::char8_type>;
+  auto format =
+      str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}"));
+  auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>(
+      u8"caf\u00e9s"));  // cafés
+  auto result = fmt::format(format, str);
+  EXPECT_EQ(fmt::detail::compute_width(result), 4);
+  EXPECT_EQ(result.size(), 5);
+  EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
+}
+
+struct check_back_appender {};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<check_back_appender> {
+  auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename Context>
+  auto format(check_back_appender, Context& ctx) -> decltype(ctx.out()) {
+    auto out = ctx.out();
+    static_assert(std::is_same<decltype(++out), decltype(out)&>::value,
+                  "needs to satisfy weakly_incrementable");
+    *out = 'y';
+    return ++out;
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(format_test, back_insert_slicing) {
+  EXPECT_EQ(fmt::format("{}", check_back_appender{}), "y");
+}
+
+template <typename Char, typename T> bool check_enabled_formatter() {
+  static_assert(std::is_default_constructible<fmt::formatter<T, Char>>::value,
+                "");
+  return true;
+}
+
+template <typename Char, typename... T> void check_enabled_formatters() {
+  auto dummy = {check_enabled_formatter<Char, T>()...};
+  (void)dummy;
+}
+
+TEST(format_test, test_formatters_enabled) {
+  check_enabled_formatters<char, bool, char, signed char, unsigned char, short,
+                           unsigned short, int, unsigned, long, unsigned long,
+                           long long, unsigned long long, float, double,
+                           long double, void*, const void*, char*, const char*,
+                           std::string, std::nullptr_t>();
+  check_enabled_formatters<wchar_t, bool, wchar_t, signed char, unsigned char,
+                           short, unsigned short, int, unsigned, long,
+                           unsigned long, long long, unsigned long long, float,
+                           double, long double, void*, const void*, wchar_t*,
+                           const wchar_t*, std::wstring, std::nullptr_t>();
+}
+
+TEST(format_int_test, data) {
+  fmt::format_int format_int(42);
+  EXPECT_EQ("42", std::string(format_int.data(), format_int.size()));
+}
+
+TEST(format_int_test, format_int) {
+  EXPECT_EQ("42", fmt::format_int(42).str());
+  EXPECT_EQ(2u, fmt::format_int(42).size());
+  EXPECT_EQ("-42", fmt::format_int(-42).str());
+  EXPECT_EQ(3u, fmt::format_int(-42).size());
+  EXPECT_EQ("42", fmt::format_int(42ul).str());
+  EXPECT_EQ("-42", fmt::format_int(-42l).str());
+  EXPECT_EQ("42", fmt::format_int(42ull).str());
+  EXPECT_EQ("-42", fmt::format_int(-42ll).str());
+  std::ostringstream os;
+  os << max_value<int64_t>();
+  EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
+}
diff --git a/libs/fmt/test/fuzzing/.gitignore b/libs/fmt/test/fuzzing/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..ea4104026128ae8a2930bfbc01a91d33d3b6f920
--- /dev/null
+++ b/libs/fmt/test/fuzzing/.gitignore
@@ -0,0 +1,3 @@
+# ignore artifacts from the build.sh script
+build-*/
+
diff --git a/libs/fmt/test/fuzzing/CMakeLists.txt b/libs/fmt/test/fuzzing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2f716d8365065fb17f2c9296bf149a91d410e1d4
--- /dev/null
+++ b/libs/fmt/test/fuzzing/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (c) 2019, Paul Dreik
+# License: see LICENSE.rst in the fmt root directory
+
+# Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
+# (Note that libFuzzer can also reproduce, just pass it the files.)
+option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
+
+# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
+# the fuzz targets, otherwise the CMake configuration step fails.
+set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
+
+# Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
+# through the fuzzers.
+function(add_fuzzer source)
+  get_filename_component(basename ${source} NAME_WE)
+  set(name ${basename}-fuzzer)
+  add_executable(${name} ${source} fuzzer-common.h)
+  if (FMT_FUZZ_LINKMAIN)
+    target_sources(${name} PRIVATE main.cc)
+  endif ()
+  target_link_libraries(${name} PRIVATE fmt)
+  if (FMT_FUZZ_LDFLAGS)
+    target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
+  endif ()
+  target_compile_features(${name} PRIVATE cxx_generic_lambdas)
+endfunction()
+
+foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc)
+  add_fuzzer(${source})
+endforeach ()
diff --git a/libs/fmt/test/fuzzing/README.md b/libs/fmt/test/fuzzing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bb3d0e04f19bed04852e7154f56857d466064646
--- /dev/null
+++ b/libs/fmt/test/fuzzing/README.md
@@ -0,0 +1,25 @@
+# Running the fuzzers locally
+
+There is a [helper script](build.sh) to build the fuzzers, which has only been
+tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on
+Windows (using clang>=8) or on Mac, but the script will probably not work out of
+the box.
+
+Something along
+```sh
+mkdir build
+cd build
+export CXX=clang++
+export CXXFLAGS="-fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
+cmake .. -DFMT_SAFE_DURATION_CAST=On -DFMT_FUZZ=On -DFMT_FUZZ_LINKMAIN=Off -DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+cmake --build .
+```
+should work to build the fuzzers for all platforms which clang supports.
+
+Execute a fuzzer with for instance
+```sh
+cd build
+export UBSAN_OPTIONS=halt_on_error=1
+mkdir out_chrono
+bin/fuzzer_chrono_duration out_chrono
+```
diff --git a/libs/fmt/test/fuzzing/build.sh b/libs/fmt/test/fuzzing/build.sh
new file mode 100644
index 0000000000000000000000000000000000000000..28c506332e5e9093aa65c92f8432de40713a82c4
--- /dev/null
+++ b/libs/fmt/test/fuzzing/build.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Creates fuzzer builds of various kinds
+# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
+# - libFuzzer build (you will need clang)
+# - afl build (you will need afl)
+#
+#
+# Copyright (c) 2019 Paul Dreik
+#
+# For the license information refer to format.h.
+
+set -e
+me=$(basename $0)
+root=$(readlink -f "$(dirname "$0")/../..")
+
+
+echo $me: root=$root
+
+here=$(pwd)
+
+CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
+CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
+
+# For performance analysis of the fuzzers.
+builddir=$here/build-fuzzers-perfanalysis
+mkdir -p $builddir
+cd $builddir
+CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL -g" cmake \
+$CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=On \
+-DCMAKE_BUILD_TYPE=Release
+
+cmake --build $builddir
+
+# Builds the fuzzers as oss-fuzz does.
+builddir=$here/build-fuzzers-ossfuzz
+mkdir -p $builddir
+cd $builddir
+CXX="clang++" \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+
+cmake --build $builddir
+
+
+# Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
+builddir=$here/build-fuzzers-libfuzzer
+mkdir -p $builddir
+cd $builddir
+CXX="clang++" \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,address,undefined" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
+
+cmake --build $builddir
+
+# Builds a fast fuzzer for making coverage fast.
+builddir=$here/build-fuzzers-fast
+mkdir -p $builddir
+cd $builddir
+CXX="clang++" \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link -O3" cmake \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=Off \
+-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer" \
+ -DCMAKE_BUILD_TYPE=Release
+
+cmake --build $builddir
+
+
+# Builds fuzzers for local fuzzing with afl.
+builddir=$here/build-fuzzers-afl
+mkdir -p $builddir
+cd $builddir
+CXX="afl-g++" \
+CXXFLAGS="$CXXFLAGSALL -fsanitize=address,undefined" \
+cmake $CMAKEFLAGSALL \
+-DFMT_FUZZ_LINKMAIN=On
+
+cmake --build $builddir
+
+
+echo $me: all good
+
diff --git a/libs/fmt/test/fuzzing/chrono-duration.cc b/libs/fmt/test/fuzzing/chrono-duration.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fdad9894b6d866bde7bc8ad62f9bce31488b6e79
--- /dev/null
+++ b/libs/fmt/test/fuzzing/chrono-duration.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <cstdint>
+#include <fmt/chrono.h>
+
+#include "fuzzer-common.h"
+
+template <typename Period, typename Rep>
+void invoke_inner(fmt::string_view format_str, Rep rep) {
+  auto value = std::chrono::duration<Rep, Period>(rep);
+  try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+    std::string message = fmt::format(format_str, value);
+#else
+    fmt::memory_buffer buf;
+    fmt::format_to(buf, format_str, value);
+#endif
+  } catch (std::exception&) {
+  }
+}
+
+// Rep is a duration's representation type.
+template <typename Rep>
+void invoke_outer(const uint8_t* data, size_t size, int period) {
+  // Always use a fixed location of the data.
+  static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
+  if (size <= fixed_size + 1) return;
+
+  const Rep rep = assign_from_buf<Rep>(data);
+  data += fixed_size;
+  size -= fixed_size;
+
+  // data is already allocated separately in libFuzzer so reading past the end 
+  // will most likely be detected anyway.
+  const auto format_str = fmt::string_view(as_chars(data), size);
+
+  // yocto, zepto, zetta and yotta are not handled.
+  switch (period) {
+  case 1:
+    invoke_inner<std::atto>(format_str, rep);
+    break;
+  case 2:
+    invoke_inner<std::femto>(format_str, rep);
+    break;
+  case 3:
+    invoke_inner<std::pico>(format_str, rep);
+    break;
+  case 4:
+    invoke_inner<std::nano>(format_str, rep);
+    break;
+  case 5:
+    invoke_inner<std::micro>(format_str, rep);
+    break;
+  case 6:
+    invoke_inner<std::milli>(format_str, rep);
+    break;
+  case 7:
+    invoke_inner<std::centi>(format_str, rep);
+    break;
+  case 8:
+    invoke_inner<std::deci>(format_str, rep);
+    break;
+  case 9:
+    invoke_inner<std::deca>(format_str, rep);
+    break;
+  case 10:
+    invoke_inner<std::kilo>(format_str, rep);
+    break;
+  case 11:
+    invoke_inner<std::mega>(format_str, rep);
+    break;
+  case 12:
+    invoke_inner<std::giga>(format_str, rep);
+    break;
+  case 13:
+    invoke_inner<std::tera>(format_str, rep);
+    break;
+  case 14:
+    invoke_inner<std::peta>(format_str, rep);
+    break;
+  case 15:
+    invoke_inner<std::exa>(format_str, rep);
+    break;
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size <= 4)  return 0;
+
+  const auto representation = data[0];
+  const auto period = data[1];
+  data += 2;
+  size -= 2;
+
+  switch (representation) {
+  case 1:
+    invoke_outer<char>(data, size, period);
+    break;
+  case 2:
+    invoke_outer<signed char>(data, size, period);
+    break;
+  case 3:
+    invoke_outer<unsigned char>(data, size, period);
+    break;
+  case 4:
+    invoke_outer<short>(data, size, period);
+    break;
+  case 5:
+    invoke_outer<unsigned short>(data, size, period);
+    break;
+  case 6:
+    invoke_outer<int>(data, size, period);
+    break;
+  case 7:
+    invoke_outer<unsigned int>(data, size, period);
+    break;
+  case 8:
+    invoke_outer<long>(data, size, period);
+    break;
+  case 9:
+    invoke_outer<unsigned long>(data, size, period);
+    break;
+  case 10:
+    invoke_outer<float>(data, size, period);
+    break;
+  case 11:
+    invoke_outer<double>(data, size, period);
+    break;
+  case 12:
+    invoke_outer<long double>(data, size, period);
+    break;
+  }
+  return 0;
+}
diff --git a/libs/fmt/test/fuzzing/float.cc b/libs/fmt/test/fuzzing/float.cc
new file mode 100644
index 0000000000000000000000000000000000000000..073e4bcd60361a67e19ae54a267d1fc9f7db3bab
--- /dev/null
+++ b/libs/fmt/test/fuzzing/float.cc
@@ -0,0 +1,39 @@
+// A fuzzer for floating-point formatter.
+// For the license information refer to format.h.
+
+#include <cstdint>
+#include <cstdlib>
+#include <stdexcept>
+#include <limits>
+#include <fmt/format.h>
+
+#include "fuzzer-common.h"
+
+void check_round_trip(fmt::string_view format_str, double value) {
+  auto buffer = fmt::memory_buffer();
+  fmt::format_to(buffer, format_str, value);
+
+  if (std::isnan(value)) {
+    auto nan = std::signbit(value) ? "-nan" : "nan";
+    if (fmt::string_view(buffer.data(), buffer.size()) != nan)
+      throw std::runtime_error("round trip failure");
+    return;
+  }
+
+  buffer.push_back('\0');
+  char* ptr = nullptr;
+  if (std::strtod(buffer.data(), &ptr) != value)
+    throw std::runtime_error("round trip failure");
+  if (ptr + 1 != buffer.end())
+    throw std::runtime_error("unparsed output");
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
+    return 0;
+  check_round_trip("{}", assign_from_buf<double>(data));
+  // A larger than necessary precision is used to trigger the fallback
+  // formatter.
+  check_round_trip("{:.50g}", assign_from_buf<double>(data));
+  return 0;
+}
diff --git a/libs/fmt/test/fuzzing/fuzzer-common.h b/libs/fmt/test/fuzzing/fuzzer-common.h
new file mode 100644
index 0000000000000000000000000000000000000000..635a5d996191a7590e93cd4c802de0d3c9098ae8
--- /dev/null
+++ b/libs/fmt/test/fuzzing/fuzzer-common.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#ifndef FUZZER_COMMON_H
+#define FUZZER_COMMON_H
+
+#include <cstdint>      // std::uint8_t
+#include <cstring>      // memcpy
+#include <vector>
+
+#include <fmt/core.h>
+
+// One can format to either a string, or a buffer. The latter is faster, but
+// one may be interested in formatting to a string instead to verify it works
+// as intended. To avoid a combinatoric explosion, select this at compile time
+// instead of dynamically from the fuzz data.
+#define FMT_FUZZ_FORMAT_TO_STRING 0
+
+// If {fmt} is given a buffer that is separately allocated, chances that address
+// sanitizer detects out of bound reads is much higher. However, it slows down
+// the fuzzing.
+#define FMT_FUZZ_SEPARATE_ALLOCATION 1
+
+// The size of the largest possible type in use.
+// To let the the fuzzer mutation be efficient at cross pollinating between
+// different types, use a fixed size format. The same bit pattern, interpreted
+// as another type, is likely interesting.
+constexpr auto fixed_size = 16;
+
+// Casts data to a char pointer.
+template <typename T> inline const char* as_chars(const T* data) {
+  return reinterpret_cast<const char*>(data);
+}
+
+// Casts data to a byte pointer.
+template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
+  return reinterpret_cast<const std::uint8_t*>(data);
+}
+
+// Blits bytes from data to form an (assumed trivially constructible) object
+// of type Item.
+template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
+  auto item = Item();
+  std::memcpy(&item, data, sizeof(Item));
+  return item;
+}
+
+// Reads a boolean value by looking at the first byte from data.
+template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
+  return *data != 0;
+}
+
+struct data_to_string {
+#if FMT_FUZZ_SEPARATE_ALLOCATION
+  std::vector<char> buffer;
+
+  data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
+      : buffer(size + (add_terminator ? 1 : 0)) {
+    std::memcpy(buffer.data(), data, size);
+  }
+
+  fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
+#else
+  fmt::string_view sv;
+
+  data_to_string(const uint8_t* data, size_t size, bool = false)
+      : str(as_chars(data), size) {}
+
+  fmt::string_view get() const { return sv; }
+#endif
+
+  const char* data() const { return get().data(); }
+};
+
+#endif  // FUZZER_COMMON_H
diff --git a/libs/fmt/test/fuzzing/main.cc b/libs/fmt/test/fuzzing/main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8f8c719b7263ec5328f9277cf53efd0156f51ef7
--- /dev/null
+++ b/libs/fmt/test/fuzzing/main.cc
@@ -0,0 +1,22 @@
+#include <cassert>
+#include <fstream>
+#include <vector>
+
+#include "fuzzer-common.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
+
+int main(int argc, char** argv) {
+  for (int i = 1; i < argc; ++i) {
+    std::ifstream in(argv[i]);
+    assert(in);
+    in.seekg(0, std::ios_base::end);
+    const auto size = in.tellg();
+    assert(size >= 0);
+    in.seekg(0, std::ios_base::beg);
+    std::vector<char> buf(static_cast<size_t>(size));
+    in.read(buf.data(), size);
+    assert(in.gcount() == size);
+    LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
+  }
+}
diff --git a/libs/fmt/test/fuzzing/named-arg.cc b/libs/fmt/test/fuzzing/named-arg.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ffd8e903b8c13ea3bbf25abda5746e1bff6230d1
--- /dev/null
+++ b/libs/fmt/test/fuzzing/named-arg.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <cstdint>
+#include <type_traits>
+#include <vector>
+#include <fmt/chrono.h>
+
+#include "fuzzer-common.h"
+
+template <typename T>
+void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
+  static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
+  if (size <= fixed_size) return;
+  const T value = assign_from_buf<T>(data);
+  data += fixed_size;
+  size -= fixed_size;
+
+  if (arg_name_size <= 0 || arg_name_size >= size) return;
+  data_to_string arg_name(data, arg_name_size, true);
+  data += arg_name_size;
+  size -= arg_name_size;
+
+  data_to_string format_str(data, size);
+  try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+    std::string message =
+      fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
+#else
+    fmt::memory_buffer out;
+    fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value));
+#endif
+  } catch (std::exception&) {
+  }
+}
+
+// For dynamic dispatching to an explicit instantiation.
+template <typename Callback> void invoke(int type, Callback callback) {
+  switch (type) {
+  case 0:
+    callback(bool());
+    break;
+  case 1:
+    callback(char());
+    break;
+  case 2:
+    using sc = signed char;
+    callback(sc());
+    break;
+  case 3:
+    using uc = unsigned char;
+    callback(uc());
+    break;
+  case 4:
+    callback(short());
+    break;
+  case 5:
+    using us = unsigned short;
+    callback(us());
+    break;
+  case 6:
+    callback(int());
+    break;
+  case 7:
+    callback(unsigned());
+    break;
+  case 8:
+    callback(long());
+    break;
+  case 9:
+    using ul = unsigned long;
+    callback(ul());
+    break;
+  case 10:
+    callback(float());
+    break;
+  case 11:
+    callback(double());
+    break;
+  case 12:
+    using LD = long double;
+    callback(LD());
+    break;
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size <= 3) return 0;
+
+  // Switch types depending on the first byte of the input.
+  const auto type = data[0] & 0x0F;
+  const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
+  data++;
+  size--;
+
+  invoke(type, [=](auto arg) {
+    invoke_fmt<decltype(arg)>(data, size, arg_name_size);
+  });
+  return 0;
+}
diff --git a/libs/fmt/test/fuzzing/one-arg.cc b/libs/fmt/test/fuzzing/one-arg.cc
new file mode 100644
index 0000000000000000000000000000000000000000..df1734324380e82ca6eb89550041cdc29f8c2ed3
--- /dev/null
+++ b/libs/fmt/test/fuzzing/one-arg.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <cstdint>
+#include <exception>
+#include <fmt/chrono.h>
+
+#include "fuzzer-common.h"
+
+template <typename T, typename Repr>
+const T* from_repr(const Repr& r) { return &r; }
+
+template <>
+const std::tm* from_repr<std::tm>(const std::time_t& t) {
+  return std::localtime(&t);
+}
+
+template <typename T, typename Repr = T>
+void invoke_fmt(const uint8_t* data, size_t size) {
+  static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
+  if (size <= fixed_size) return;
+  auto repr = assign_from_buf<Repr>(data);
+  const T* value = from_repr<T>(repr);
+  if (!value) return;
+  data += fixed_size;
+  size -= fixed_size;
+  data_to_string format_str(data, size);
+  try {
+#if FMT_FUZZ_FORMAT_TO_STRING
+    std::string message = fmt::format(format_str.get(), *value);
+#else
+    fmt::memory_buffer message;
+    fmt::format_to(message, format_str.get(), *value);
+#endif
+  } catch (std::exception&) {
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size <= 3) return 0;
+
+  const auto first = data[0];
+  data++;
+  size--;
+
+  switch (first) {
+  case 0:
+    invoke_fmt<bool>(data, size);
+    break;
+  case 1:
+    invoke_fmt<char>(data, size);
+    break;
+  case 2:
+    invoke_fmt<unsigned char>(data, size);
+    break;
+  case 3:
+    invoke_fmt<signed char>(data, size);
+    break;
+  case 4:
+    invoke_fmt<short>(data, size);
+    break;
+  case 5:
+    invoke_fmt<unsigned short>(data, size);
+    break;
+  case 6:
+    invoke_fmt<int>(data, size);
+    break;
+  case 7:
+    invoke_fmt<unsigned int>(data, size);
+    break;
+  case 8:
+    invoke_fmt<long>(data, size);
+    break;
+  case 9:
+    invoke_fmt<unsigned long>(data, size);
+    break;
+  case 10:
+    invoke_fmt<float>(data, size);
+    break;
+  case 11:
+    invoke_fmt<double>(data, size);
+    break;
+  case 12:
+    invoke_fmt<long double>(data, size);
+    break;
+  case 13:
+    invoke_fmt<std::tm, std::time_t>(data, size);
+    break;
+  }
+  return 0;
+}
diff --git a/libs/fmt/test/fuzzing/two-args.cc b/libs/fmt/test/fuzzing/two-args.cc
new file mode 100644
index 0000000000000000000000000000000000000000..4d7d34531779a88741db59dea55e2996cea6a4ab
--- /dev/null
+++ b/libs/fmt/test/fuzzing/two-args.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2019, Paul Dreik
+// For the license information refer to format.h.
+
+#include <cstdint>
+#include <exception>
+#include <string>
+#include <fmt/format.h>
+
+#include "fuzzer-common.h"
+
+template <typename Item1, typename Item2>
+void invoke_fmt(const uint8_t* data, size_t size) {
+  static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
+  static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
+  if (size <= fixed_size + fixed_size) return;
+
+  const Item1 item1 = assign_from_buf<Item1>(data);
+  data += fixed_size;
+  size -= fixed_size;
+
+  const Item2 item2 = assign_from_buf<Item2>(data);
+  data += fixed_size;
+  size -= fixed_size;
+
+  auto format_str = fmt::string_view(as_chars(data), size);
+#if FMT_FUZZ_FORMAT_TO_STRING
+  std::string message = fmt::format(format_str, item1, item2);
+#else
+  fmt::memory_buffer message;
+  fmt::format_to(message, format_str, item1, item2);
+#endif
+}
+
+// For dynamic dispatching to an explicit instantiation.
+template <typename Callback> void invoke(int index, Callback callback) {
+  switch (index) {
+  case 0:
+    callback(bool());
+    break;
+  case 1:
+    callback(char());
+    break;
+  case 2:
+    using sc = signed char;
+    callback(sc());
+    break;
+  case 3:
+    using uc = unsigned char;
+    callback(uc());
+    break;
+  case 4:
+    callback(short());
+    break;
+  case 5:
+    using us = unsigned short;
+    callback(us());
+    break;
+  case 6:
+    callback(int());
+    break;
+  case 7:
+    callback(unsigned());
+    break;
+  case 8:
+    callback(long());
+    break;
+  case 9:
+    using ul = unsigned long;
+    callback(ul());
+    break;
+  case 10:
+    callback(float());
+    break;
+  case 11:
+    callback(double());
+    break;
+  case 12:
+    using LD = long double;
+    callback(LD());
+    break;
+  case 13:
+    using ptr = void*;
+    callback(ptr());
+    break;
+  }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size <= 3) return 0;
+
+  // Switch types depending on the first byte of the input.
+  const auto type1 = data[0] & 0x0F;
+  const auto type2 = (data[0] & 0xF0) >> 4;
+  data++;
+  size--;
+  try {
+    invoke(type1, [=](auto param1) {
+      invoke(type2, [=](auto param2) {
+        invoke_fmt<decltype(param1), decltype(param2)>(data, size);
+      });
+    });
+  } catch (std::exception&) {
+  }
+  return 0;
+}
diff --git a/libs/fmt/test/gtest-extra-test.cc b/libs/fmt/test/gtest-extra-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..46be75b8602f7ed7aa24ed885434456599e7f44d
--- /dev/null
+++ b/libs/fmt/test/gtest-extra-test.cc
@@ -0,0 +1,433 @@
+// Formatting library for C++ - tests of custom Google Test assertions
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "gtest-extra.h"
+
+#include <gtest/gtest-spi.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <stdexcept>
+
+#include "fmt/os.h"
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+#  include <crtdbg.h>  // for _CrtSetReportMode
+#endif                 // _WIN32
+
+#include "util.h"
+
+namespace {
+
+// Tests that assertion macros evaluate their arguments exactly once.
+class SingleEvaluationTest : public ::testing::Test {
+ protected:
+  SingleEvaluationTest() {
+    p_ = s_;
+    a_ = 0;
+    b_ = 0;
+  }
+
+  static const char* const s_;
+  static const char* p_;
+
+  static int a_;
+  static int b_;
+};
+
+const char* const SingleEvaluationTest::s_ = "01234";
+const char* SingleEvaluationTest::p_;
+int SingleEvaluationTest::a_;
+int SingleEvaluationTest::b_;
+
+void do_nothing() {}
+
+FMT_NORETURN void throw_exception() { throw std::runtime_error("test"); }
+
+FMT_NORETURN void throw_system_error() {
+  throw fmt::system_error(EDOM, "test");
+}
+
+// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
+// exactly once.
+TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234");
+  EXPECT_EQ(s_ + 1, p_);
+}
+
+// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument
+// exactly once.
+TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) {
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++),
+                          "01234");
+  EXPECT_EQ(s_ + 1, p_);
+}
+
+// Tests that assertion arguments are evaluated exactly once.
+TEST_F(SingleEvaluationTest, ExceptionTests) {
+  // successful EXPECT_THROW_MSG
+  EXPECT_THROW_MSG(
+      {  // NOLINT
+        a_++;
+        throw_exception();
+      },
+      std::exception, (b_++, "test"));
+  EXPECT_EQ(1, a_);
+  EXPECT_EQ(1, b_);
+
+  // failed EXPECT_THROW_MSG, throws different type
+  EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(
+                              {  // NOLINT
+                                a_++;
+                                throw_exception();
+                              },
+                              std::logic_error, (b_++, "test")),
+                          "throws a different type");
+  EXPECT_EQ(2, a_);
+  EXPECT_EQ(2, b_);
+
+  // failed EXPECT_THROW_MSG, throws an exception with different message
+  EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(
+                              {  // NOLINT
+                                a_++;
+                                throw_exception();
+                              },
+                              std::exception, (b_++, "other")),
+                          "throws an exception with a different message");
+  EXPECT_EQ(3, a_);
+  EXPECT_EQ(3, b_);
+
+  // failed EXPECT_THROW_MSG, throws nothing
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(a_++, std::exception, (b_++, "test")), "throws nothing");
+  EXPECT_EQ(4, a_);
+  EXPECT_EQ(4, b_);
+}
+
+TEST_F(SingleEvaluationTest, SystemErrorTests) {
+  // successful EXPECT_SYSTEM_ERROR
+  EXPECT_SYSTEM_ERROR(
+      {  // NOLINT
+        a_++;
+        throw_system_error();
+      },
+      EDOM, (b_++, "test"));
+  EXPECT_EQ(1, a_);
+  EXPECT_EQ(1, b_);
+
+  // failed EXPECT_SYSTEM_ERROR, throws different type
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(
+                              {  // NOLINT
+                                a_++;
+                                throw_exception();
+                              },
+                              EDOM, (b_++, "test")),
+                          "throws a different type");
+  EXPECT_EQ(2, a_);
+  EXPECT_EQ(2, b_);
+
+  // failed EXPECT_SYSTEM_ERROR, throws an exception with different message
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(
+                              {  // NOLINT
+                                a_++;
+                                throw_system_error();
+                              },
+                              EDOM, (b_++, "other")),
+                          "throws an exception with a different message");
+  EXPECT_EQ(3, a_);
+  EXPECT_EQ(3, b_);
+
+  // failed EXPECT_SYSTEM_ERROR, throws nothing
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(a_++, EDOM, (b_++, "test")),
+                          "throws nothing");
+  EXPECT_EQ(4, a_);
+  EXPECT_EQ(4, b_);
+}
+
+#if FMT_USE_FCNTL
+// Tests that when EXPECT_WRITE fails, it evaluates its message argument
+// exactly once.
+TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
+                          "01234");
+  EXPECT_EQ(s_ + 1, p_);
+}
+
+// Tests that assertion arguments are evaluated exactly once.
+TEST_F(SingleEvaluationTest, WriteTests) {
+  // successful EXPECT_WRITE
+  EXPECT_WRITE(
+      stdout,
+      {  // NOLINT
+        a_++;
+        std::printf("test");
+      },
+      (b_++, "test"));
+  EXPECT_EQ(1, a_);
+  EXPECT_EQ(1, b_);
+
+  // failed EXPECT_WRITE
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(
+                              stdout,
+                              {  // NOLINT
+                                a_++;
+                                std::printf("test");
+                              },
+                              (b_++, "other")),
+                          "Actual: test");
+  EXPECT_EQ(2, a_);
+  EXPECT_EQ(2, b_);
+}
+
+// Tests EXPECT_WRITE.
+TEST(ExpectTest, EXPECT_WRITE) {
+  EXPECT_WRITE(stdout, do_nothing(), "");
+  EXPECT_WRITE(stdout, std::printf("test"), "test");
+  EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"),
+                          "Expected: this\n"
+                          "  Actual: that");
+}
+
+TEST(StreamingAssertionsTest, EXPECT_WRITE) {
+  EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
+                              << "expected failure",
+                          "expected failure");
+}
+#endif  // FMT_USE_FCNTL
+
+// Tests that the compiler will not complain about unreachable code in the
+// EXPECT_THROW_MSG macro.
+TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
+  int n = 0;
+  using std::runtime_error;
+  EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
+  EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(n++, runtime_error, ""), "");
+  EXPECT_NONFATAL_FAILURE(EXPECT_THROW_MSG(throw 1, runtime_error, ""), "");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(throw runtime_error("a"), runtime_error, "b"), "");
+}
+
+// Tests that the compiler will not complain about unreachable code in the
+// EXPECT_SYSTEM_ERROR macro.
+TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
+  int n = 0;
+  EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
+  EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw 1, EDOM, ""), "");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "aaa"), EDOM, "bbb"),
+      "");
+}
+
+TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
+  if (::testing::internal::AlwaysFalse())
+    EXPECT_THROW_MSG(do_nothing(), std::exception, "");
+
+  if (::testing::internal::AlwaysTrue())
+    EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
+  else
+    do_nothing();
+}
+
+TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
+  if (::testing::internal::AlwaysFalse())
+    EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "");
+
+  if (::testing::internal::AlwaysTrue())
+    EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
+  else
+    do_nothing();
+}
+
+TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
+  if (::testing::internal::AlwaysFalse())
+    EXPECT_WRITE(stdout, std::printf("x"), "x");
+
+  if (::testing::internal::AlwaysTrue())
+    EXPECT_WRITE(stdout, std::printf("x"), "x");
+  else
+    do_nothing();
+}
+
+// Tests EXPECT_THROW_MSG.
+TEST(ExpectTest, EXPECT_THROW_MSG) {
+  EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"),
+      "Expected: throw_exception() throws an exception of "
+      "type std::logic_error.\n  Actual: it throws a different type.");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(do_nothing(), std::exception, "test"),
+      "Expected: do_nothing() throws an exception of type std::exception.\n"
+      "  Actual: it throws nothing.");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(throw_exception(), std::exception, "other"),
+      "throw_exception() throws an exception with a different message.\n"
+      "Expected: other\n"
+      "  Actual: test");
+}
+
+// Tests EXPECT_SYSTEM_ERROR.
+TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
+  EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
+      "Expected: throw_exception() throws an exception of "
+      "type fmt::system_error.\n  Actual: it throws a different type.");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
+      "Expected: do_nothing() throws an exception of type fmt::system_error.\n"
+      "  Actual: it throws nothing.");
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
+      fmt::format(
+          "throw_system_error() throws an exception with a different message.\n"
+          "Expected: {}\n"
+          "  Actual: {}",
+          format_system_error(EDOM, "other"),
+          format_system_error(EDOM, "test")));
+}
+
+TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
+  EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
+      << "unexpected failure";
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_THROW_MSG(throw_exception(), std::exception, "other")
+          << "expected failure",
+      "expected failure");
+}
+
+TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
+  EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test")
+      << "unexpected failure";
+  EXPECT_NONFATAL_FAILURE(
+      EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other")
+          << "expected failure",
+      "expected failure");
+}
+
+TEST(UtilTest, FormatSystemError) {
+  fmt::memory_buffer out;
+  fmt::format_system_error(out, EDOM, "test message");
+  EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
+}
+
+#if FMT_USE_FCNTL
+
+using fmt::buffered_file;
+using fmt::error_code;
+using fmt::file;
+
+TEST(ErrorCodeTest, Ctor) {
+  EXPECT_EQ(error_code().get(), 0);
+  EXPECT_EQ(error_code(42).get(), 42);
+}
+
+TEST(OutputRedirectTest, ScopedRedirect) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  {
+    buffered_file file(write_end.fdopen("w"));
+    std::fprintf(file.get(), "[[[");
+    {
+      output_redirect redir(file.get());
+      std::fprintf(file.get(), "censored");
+    }
+    std::fprintf(file.get(), "]]]");
+  }
+  EXPECT_READ(read_end, "[[[]]]");
+}
+
+// Test that output_redirect handles errors in flush correctly.
+TEST(OutputRedirectTest, FlushErrorInCtor) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  int write_fd = write_end.descriptor();
+  file write_copy = write_end.dup(write_fd);
+  buffered_file f = write_end.fdopen("w");
+  // Put a character in a file buffer.
+  EXPECT_EQ('x', fputc('x', f.get()));
+  FMT_POSIX(close(write_fd));
+  std::unique_ptr<output_redirect> redir{nullptr};
+  EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new output_redirect(f.get())), EBADF,
+                               "cannot flush stream");
+  redir.reset(nullptr);
+  write_copy.dup2(write_fd);  // "undo" close or dtor will fail
+}
+
+TEST(OutputRedirectTest, DupErrorInCtor) {
+  buffered_file f = open_buffered_file();
+  int fd = (f.fileno)();
+  file copy = file::dup(fd);
+  FMT_POSIX(close(fd));
+  std::unique_ptr<output_redirect> redir{nullptr};
+  EXPECT_SYSTEM_ERROR_NOASSERT(
+      redir.reset(new output_redirect(f.get())), EBADF,
+      fmt::format("cannot duplicate file descriptor {}", fd));
+  copy.dup2(fd);  // "undo" close or dtor will fail
+}
+
+TEST(OutputRedirectTest, RestoreAndRead) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  buffered_file file(write_end.fdopen("w"));
+  std::fprintf(file.get(), "[[[");
+  output_redirect redir(file.get());
+  std::fprintf(file.get(), "censored");
+  EXPECT_EQ("censored", redir.restore_and_read());
+  EXPECT_EQ("", redir.restore_and_read());
+  std::fprintf(file.get(), "]]]");
+  file = buffered_file();
+  EXPECT_READ(read_end, "[[[]]]");
+}
+
+// Test that OutputRedirect handles errors in flush correctly.
+TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  int write_fd = write_end.descriptor();
+  file write_copy = write_end.dup(write_fd);
+  buffered_file f = write_end.fdopen("w");
+  output_redirect redir(f.get());
+  // Put a character in a file buffer.
+  EXPECT_EQ('x', fputc('x', f.get()));
+  FMT_POSIX(close(write_fd));
+  EXPECT_SYSTEM_ERROR_NOASSERT(redir.restore_and_read(), EBADF,
+                               "cannot flush stream");
+  write_copy.dup2(write_fd);  // "undo" close or dtor will fail
+}
+
+TEST(OutputRedirectTest, ErrorInDtor) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  int write_fd = write_end.descriptor();
+  file write_copy = write_end.dup(write_fd);
+  buffered_file f = write_end.fdopen("w");
+  std::unique_ptr<output_redirect> redir(new output_redirect(f.get()));
+  // Put a character in a file buffer.
+  EXPECT_EQ('x', fputc('x', f.get()));
+  EXPECT_WRITE(
+      stderr,
+      {
+        // The close function must be called inside EXPECT_WRITE,
+        // otherwise the system may recycle closed file descriptor when
+        // redirecting the output in EXPECT_STDERR and the second close
+        // will break output redirection.
+        FMT_POSIX(close(write_fd));
+        SUPPRESS_ASSERT(redir.reset(nullptr));
+      },
+      format_system_error(EBADF, "cannot flush stream"));
+  write_copy.dup2(write_fd);  // "undo" close or dtor of buffered_file will fail
+}
+
+#endif  // FMT_USE_FILE_DESCRIPTORS
+
+}  // namespace
diff --git a/libs/fmt/test/gtest-extra.cc b/libs/fmt/test/gtest-extra.cc
new file mode 100644
index 0000000000000000000000000000000000000000..d71b951e19c31cd3c3448682e81da40e1b48da5b
--- /dev/null
+++ b/libs/fmt/test/gtest-extra.cc
@@ -0,0 +1,87 @@
+// Formatting library for C++ - custom Google Test assertions
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "gtest-extra.h"
+
+#if FMT_USE_FCNTL
+
+using fmt::file;
+
+output_redirect::output_redirect(FILE* f) : file_(f) {
+  flush();
+  int fd = FMT_POSIX(fileno(f));
+  // Create a file object referring to the original file.
+  original_ = file::dup(fd);
+  // Create a pipe.
+  file write_end;
+  file::pipe(read_end_, write_end);
+  // Connect the passed FILE object to the write end of the pipe.
+  write_end.dup2(fd);
+}
+
+output_redirect::~output_redirect() FMT_NOEXCEPT {
+  try {
+    restore();
+  } catch (const std::exception& e) {
+    std::fputs(e.what(), stderr);
+  }
+}
+
+void output_redirect::flush() {
+#  if EOF != -1
+#    error "FMT_RETRY assumes return value of -1 indicating failure"
+#  endif
+  int result = 0;
+  FMT_RETRY(result, fflush(file_));
+  if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
+}
+
+void output_redirect::restore() {
+  if (original_.descriptor() == -1) return;  // Already restored.
+  flush();
+  // Restore the original file.
+  original_.dup2(FMT_POSIX(fileno(file_)));
+  original_.close();
+}
+
+std::string output_redirect::restore_and_read() {
+  // Restore output.
+  restore();
+
+  // Read everything from the pipe.
+  std::string content;
+  if (read_end_.descriptor() == -1) return content;  // Already read.
+  enum { BUFFER_SIZE = 4096 };
+  char buffer[BUFFER_SIZE];
+  size_t count = 0;
+  do {
+    count = read_end_.read(buffer, BUFFER_SIZE);
+    content.append(buffer, count);
+  } while (count != 0);
+  read_end_.close();
+  return content;
+}
+
+std::string read(file& f, size_t count) {
+  std::string buffer(count, '\0');
+  size_t n = 0, offset = 0;
+  do {
+    n = f.read(&buffer[offset], count - offset);
+    // We can't read more than size_t bytes since count has type size_t.
+    offset += n;
+  } while (offset < count && n != 0);
+  buffer.resize(offset);
+  return buffer;
+}
+
+#endif  // FMT_USE_FCNTL
+
+std::string format_system_error(int error_code, fmt::string_view message) {
+  fmt::memory_buffer out;
+  format_system_error(out, error_code, message);
+  return to_string(out);
+}
diff --git a/libs/fmt/test/gtest-extra.h b/libs/fmt/test/gtest-extra.h
new file mode 100644
index 0000000000000000000000000000000000000000..84a6ee676b38ca46a5d0882227309afed3978fe2
--- /dev/null
+++ b/libs/fmt/test/gtest-extra.h
@@ -0,0 +1,160 @@
+// Formatting library for C++ - custom Google Test assertions
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_GTEST_EXTRA_H_
+#define FMT_GTEST_EXTRA_H_
+
+#include <stdlib.h>  // _invalid_parameter_handler
+
+#include <string>
+
+#include "fmt/os.h"
+#include "gmock/gmock.h"
+
+#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                                \
+  if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) {   \
+    std::string gtest_expected_message = expected_message;                     \
+    bool gtest_caught_expected = false;                                        \
+    try {                                                                      \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);               \
+    } catch (expected_exception const& e) {                                    \
+      if (gtest_expected_message != e.what()) {                                \
+        gtest_ar << #statement                                                 \
+            " throws an exception with a different message.\n"                 \
+                 << "Expected: " << gtest_expected_message << "\n"             \
+                 << "  Actual: " << e.what();                                  \
+        goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);            \
+      }                                                                        \
+      gtest_caught_expected = true;                                            \
+    } catch (...) {                                                            \
+      gtest_ar << "Expected: " #statement                                      \
+                  " throws an exception of type " #expected_exception          \
+                  ".\n  Actual: it throws a different type.";                  \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);              \
+    }                                                                          \
+    if (!gtest_caught_expected) {                                              \
+      gtest_ar << "Expected: " #statement                                      \
+                  " throws an exception of type " #expected_exception          \
+                  ".\n  Actual: it throws nothing.";                           \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);              \
+    }                                                                          \
+  } else                                                                       \
+    GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__)                      \
+        : fail(gtest_ar.failure_message())
+
+// Tests that the statement throws the expected exception and the exception's
+// what() method returns expected message.
+#define EXPECT_THROW_MSG(statement, expected_exception, expected_message) \
+  FMT_TEST_THROW_(statement, expected_exception, expected_message,        \
+                  GTEST_NONFATAL_FAILURE_)
+
+std::string format_system_error(int error_code, fmt::string_view message);
+
+#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
+  EXPECT_THROW_MSG(statement, fmt::system_error,            \
+                   format_system_error(error_code, message))
+
+#if FMT_USE_FCNTL
+
+// Captures file output by redirecting it to a pipe.
+// The output it can handle is limited by the pipe capacity.
+class output_redirect {
+ private:
+  FILE* file_;
+  fmt::file original_;  // Original file passed to redirector.
+  fmt::file read_end_;  // Read end of the pipe where the output is redirected.
+
+  void flush();
+  void restore();
+
+ public:
+  explicit output_redirect(FILE* file);
+  ~output_redirect() FMT_NOEXCEPT;
+
+  output_redirect(const output_redirect&) = delete;
+  void operator=(const output_redirect&) = delete;
+
+  // Restores the original file, reads output from the pipe into a string
+  // and returns it.
+  std::string restore_and_read();
+};
+
+#  define FMT_TEST_WRITE_(statement, expected_output, file, fail)              \
+    GTEST_AMBIGUOUS_ELSE_BLOCKER_                                              \
+    if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
+      std::string gtest_expected_output = expected_output;                     \
+      output_redirect gtest_redir(file);                                       \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);               \
+      std::string gtest_output = gtest_redir.restore_and_read();               \
+      if (gtest_output != gtest_expected_output) {                             \
+        gtest_ar << #statement " produces different output.\n"                 \
+                 << "Expected: " << gtest_expected_output << "\n"              \
+                 << "  Actual: " << gtest_output;                              \
+        goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);            \
+      }                                                                        \
+    } else                                                                     \
+      GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__)                    \
+          : fail(gtest_ar.failure_message())
+
+// Tests that the statement writes the expected output to file.
+#  define EXPECT_WRITE(file, statement, expected_output) \
+    FMT_TEST_WRITE_(statement, expected_output, file, GTEST_NONFATAL_FAILURE_)
+
+#  ifdef _MSC_VER
+
+// Suppresses Windows assertions on invalid file descriptors, making
+// POSIX functions return proper error codes instead of crashing on Windows.
+class suppress_assert {
+ private:
+  _invalid_parameter_handler original_handler_;
+  int original_report_mode_;
+
+  static void handle_invalid_parameter(const wchar_t*, const wchar_t*,
+                                       const wchar_t*, unsigned, uintptr_t) {}
+
+ public:
+  suppress_assert()
+      : original_handler_(
+            _set_invalid_parameter_handler(handle_invalid_parameter)),
+        original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {}
+  ~suppress_assert() {
+    _set_invalid_parameter_handler(original_handler_);
+    _CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
+  }
+};
+
+#    define SUPPRESS_ASSERT(statement) \
+      {                                \
+        suppress_assert sa;            \
+        statement;                     \
+      }
+#  else
+#    define SUPPRESS_ASSERT(statement) statement
+#  endif  // _MSC_VER
+
+#  define EXPECT_SYSTEM_ERROR_NOASSERT(statement, error_code, message) \
+    EXPECT_SYSTEM_ERROR(SUPPRESS_ASSERT(statement), error_code, message)
+
+// Attempts to read count characters from a file.
+std::string read(fmt::file& f, size_t count);
+
+#  define EXPECT_READ(file, expected_content) \
+    EXPECT_EQ(expected_content,               \
+              read(file, fmt::string_view(expected_content).size()))
+
+#else
+#  define EXPECT_WRITE(file, statement, expected_output) \
+    do {                                                 \
+      (void)(file);                                      \
+      (void)(statement);                                 \
+      (void)(expected_output);                           \
+      SUCCEED();                                         \
+    } while (false)
+#endif  // FMT_USE_FCNTL
+
+#endif  // FMT_GTEST_EXTRA_H_
diff --git a/libs/fmt/test/gtest/.clang-format b/libs/fmt/test/gtest/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..ec09b9b6bb1af079f4918169ee0255d8558ad400
--- /dev/null
+++ b/libs/fmt/test/gtest/.clang-format
@@ -0,0 +1,3 @@
+# Disable clang-format here
+DisableFormat: true
+SortIncludes: Never
diff --git a/libs/fmt/test/gtest/CMakeLists.txt b/libs/fmt/test/gtest/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1282d62afac659e12c65d96d8fd119c167aac827
--- /dev/null
+++ b/libs/fmt/test/gtest/CMakeLists.txt
@@ -0,0 +1,31 @@
+#------------------------------------------------------------------------------
+# Build the google test library
+
+# We compile Google Test ourselves instead of using pre-compiled libraries.
+# See the Google Test FAQ "Why is it not recommended to install a
+# pre-compiled copy of Google Test (for example, into /usr/local)?"
+# at http://code.google.com/p/googletest/wiki/FAQ for more details.
+add_library(gtest STATIC
+  gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
+target_compile_definitions(gtest PUBLIC GTEST_HAS_STD_WSTRING=1)
+target_include_directories(gtest SYSTEM PUBLIC .)
+
+find_package(Threads)
+if (Threads_FOUND)
+  target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT})
+else ()
+  target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
+endif ()
+
+if (MSVC)
+  # Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
+  target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
+  if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    # Disable MSVC warnings of POSIX functions.
+    target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
+  endif ()
+endif ()
+
+# Silence MSVC tr1 deprecation warning in gmock.
+target_compile_definitions(gtest
+  PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
diff --git a/libs/fmt/test/gtest/gmock-gtest-all.cc b/libs/fmt/test/gtest/gmock-gtest-all.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b67316c3b9941a2975fb5684af0ace71c4e611c7
--- /dev/null
+++ b/libs/fmt/test/gtest/gmock-gtest-all.cc
@@ -0,0 +1,14434 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Google C++ Testing and Mocking Framework (Google Test)
+//
+// Sometimes it's desirable to build Google Test by compiling a single file.
+// This file serves this purpose.
+
+// This line ensures that gtest.h can be compiled on its own, even
+// when it's fused.
+#include "gtest/gtest.h"
+
+// The following lines pull in the real gtest *.cc files.
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Utilities for testing Google Test itself and code that uses Google Test
+// (e.g. frameworks built on top of Google Test).
+
+// GOOGLETEST_CM0004 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// This helper class can be used to mock out Google Test failure reporting
+// so that we can test Google Test or code that builds on Google Test.
+//
+// An object of this class appends a TestPartResult object to the
+// TestPartResultArray object given in the constructor whenever a Google Test
+// failure is reported. It can either intercept only failures that are
+// generated in the same thread that created this object or it can intercept
+// all generated failures. The scope of this mock object can be controlled with
+// the second argument to the two arguments constructor.
+class GTEST_API_ ScopedFakeTestPartResultReporter
+    : public TestPartResultReporterInterface {
+ public:
+  // The two possible mocking modes of this object.
+  enum InterceptMode {
+    INTERCEPT_ONLY_CURRENT_THREAD,  // Intercepts only thread local failures.
+    INTERCEPT_ALL_THREADS           // Intercepts all failures.
+  };
+
+  // The c'tor sets this object as the test part result reporter used
+  // by Google Test.  The 'result' parameter specifies where to report the
+  // results. This reporter will only catch failures generated in the current
+  // thread. DEPRECATED
+  explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
+
+  // Same as above, but you can choose the interception scope of this object.
+  ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
+                                   TestPartResultArray* result);
+
+  // The d'tor restores the previous test part result reporter.
+  ~ScopedFakeTestPartResultReporter() override;
+
+  // Appends the TestPartResult object to the TestPartResultArray
+  // received in the constructor.
+  //
+  // This method is from the TestPartResultReporterInterface
+  // interface.
+  void ReportTestPartResult(const TestPartResult& result) override;
+
+ private:
+  void Init();
+
+  const InterceptMode intercept_mode_;
+  TestPartResultReporterInterface* old_reporter_;
+  TestPartResultArray* const result_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter);
+};
+
+namespace internal {
+
+// A helper class for implementing EXPECT_FATAL_FAILURE() and
+// EXPECT_NONFATAL_FAILURE().  Its destructor verifies that the given
+// TestPartResultArray contains exactly one failure that has the given
+// type and contains the given substring.  If that's not the case, a
+// non-fatal failure will be generated.
+class GTEST_API_ SingleFailureChecker {
+ public:
+  // The constructor remembers the arguments.
+  SingleFailureChecker(const TestPartResultArray* results,
+                       TestPartResult::Type type, const std::string& substr);
+  ~SingleFailureChecker();
+ private:
+  const TestPartResultArray* const results_;
+  const TestPartResult::Type type_;
+  const std::string substr_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
+};
+
+}  // namespace internal
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+// A set of macros for testing Google Test assertions or code that's expected
+// to generate Google Test fatal failures.  It verifies that the given
+// statement will cause exactly one fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+//   - 'statement' cannot reference local non-static variables or
+//     non-static members of the current object.
+//   - 'statement' cannot return a value.
+//   - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works.  The AcceptsMacroThatExpandsToUnprotectedComma test in
+// gtest_unittest.cc will fail to compile if we do that.
+#define EXPECT_FATAL_FAILURE(statement, substr) \
+  do { \
+    class GTestExpectFatalFailureHelper {\
+     public:\
+      static void Execute() { statement; }\
+    };\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+      GTestExpectFatalFailureHelper::Execute();\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+  do { \
+    class GTestExpectFatalFailureHelper {\
+     public:\
+      static void Execute() { statement; }\
+    };\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ALL_THREADS, &gtest_failures);\
+      GTestExpectFatalFailureHelper::Execute();\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+// A macro for testing Google Test assertions or code that's expected to
+// generate Google Test non-fatal failures.  It asserts that the given
+// statement will cause exactly one non-fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// 'statement' is allowed to reference local variables and members of
+// the current object.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+//   - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works.  If we do that, the code won't compile when the user gives
+// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
+// expands to code containing an unprotected comma.  The
+// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
+// catches that.
+//
+// For the same reason, we have to write
+//   if (::testing::internal::AlwaysTrue()) { statement; }
+// instead of
+//   GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
+// to avoid an MSVC warning on unreachable code.
+#define EXPECT_NONFATAL_FAILURE(statement, substr) \
+  do {\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+        (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+      if (::testing::internal::AlwaysTrue()) { statement; }\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+  do {\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+        (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
+          &gtest_failures);\
+      if (::testing::internal::AlwaysTrue()) { statement; }\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <algorithm>
+#include <chrono>  // NOLINT
+#include <cmath>
+#include <cstdint>
+#include <iomanip>
+#include <limits>
+#include <list>
+#include <map>
+#include <ostream>  // NOLINT
+#include <sstream>
+#include <vector>
+
+#if GTEST_OS_LINUX
+
+# include <fcntl.h>  // NOLINT
+# include <limits.h>  // NOLINT
+# include <sched.h>  // NOLINT
+// Declares vsnprintf().  This header is not available on Windows.
+# include <strings.h>  // NOLINT
+# include <sys/mman.h>  // NOLINT
+# include <sys/time.h>  // NOLINT
+# include <unistd.h>  // NOLINT
+# include <string>
+
+#elif GTEST_OS_ZOS
+# include <sys/time.h>  // NOLINT
+
+// On z/OS we additionally need strings.h for strcasecmp.
+# include <strings.h>  // NOLINT
+
+#elif GTEST_OS_WINDOWS_MOBILE  // We are on Windows CE.
+
+# include <windows.h>  // NOLINT
+# undef min
+
+#elif GTEST_OS_WINDOWS  // We are on Windows proper.
+
+# include <windows.h>  // NOLINT
+# undef min
+
+#ifdef _MSC_VER
+# include <crtdbg.h>  // NOLINT
+#endif
+
+# include <io.h>  // NOLINT
+# include <sys/timeb.h>  // NOLINT
+# include <sys/types.h>  // NOLINT
+# include <sys/stat.h>  // NOLINT
+
+# if GTEST_OS_WINDOWS_MINGW
+#  include <sys/time.h>  // NOLINT
+# endif  // GTEST_OS_WINDOWS_MINGW
+
+#else
+
+// cpplint thinks that the header is already included, so we want to
+// silence it.
+# include <sys/time.h>  // NOLINT
+# include <unistd.h>  // NOLINT
+
+#endif  // GTEST_OS_LINUX
+
+#if GTEST_HAS_EXCEPTIONS
+# include <stdexcept>
+#endif
+
+#if GTEST_CAN_STREAM_RESULTS_
+# include <arpa/inet.h>  // NOLINT
+# include <netdb.h>  // NOLINT
+# include <sys/socket.h>  // NOLINT
+# include <sys/types.h>  // NOLINT
+#endif
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Utility functions and classes used by the Google C++ testing framework.//
+// This file contains purely Google Test's internal implementation.  Please
+// DO NOT #INCLUDE IT IN A USER PROGRAM.
+
+#ifndef GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_
+#define GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_
+
+#ifndef _WIN32_WCE
+# include <errno.h>
+#endif  // !_WIN32_WCE
+#include <stddef.h>
+#include <stdlib.h>  // For strtoll/_strtoul64/malloc/free.
+#include <string.h>  // For memmove.
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+
+#if GTEST_CAN_STREAM_RESULTS_
+# include <arpa/inet.h>  // NOLINT
+# include <netdb.h>  // NOLINT
+#endif
+
+#if GTEST_OS_WINDOWS
+# include <windows.h>  // NOLINT
+#endif  // GTEST_OS_WINDOWS
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// Declares the flags.
+//
+// We don't want the users to modify this flag in the code, but want
+// Google Test's own unit tests to be able to access it. Therefore we
+// declare it here as opposed to in gtest.h.
+GTEST_DECLARE_bool_(death_test_use_fork);
+
+namespace internal {
+
+// The value of GetTestTypeId() as seen from within the Google Test
+// library.  This is solely for testing GetTestTypeId().
+GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest;
+
+// Names of the flags (needed for parsing Google Test flags).
+const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests";
+const char kBreakOnFailureFlag[] = "break_on_failure";
+const char kCatchExceptionsFlag[] = "catch_exceptions";
+const char kColorFlag[] = "color";
+const char kFailFast[] = "fail_fast";
+const char kFilterFlag[] = "filter";
+const char kListTestsFlag[] = "list_tests";
+const char kOutputFlag[] = "output";
+const char kBriefFlag[] = "brief";
+const char kPrintTimeFlag[] = "print_time";
+const char kPrintUTF8Flag[] = "print_utf8";
+const char kRandomSeedFlag[] = "random_seed";
+const char kRepeatFlag[] = "repeat";
+const char kShuffleFlag[] = "shuffle";
+const char kStackTraceDepthFlag[] = "stack_trace_depth";
+const char kStreamResultToFlag[] = "stream_result_to";
+const char kThrowOnFailureFlag[] = "throw_on_failure";
+const char kFlagfileFlag[] = "flagfile";
+
+// A valid random seed must be in [1, kMaxRandomSeed].
+const int kMaxRandomSeed = 99999;
+
+// g_help_flag is true if and only if the --help flag or an equivalent form
+// is specified on the command line.
+GTEST_API_ extern bool g_help_flag;
+
+// Returns the current time in milliseconds.
+GTEST_API_ TimeInMillis GetTimeInMillis();
+
+// Returns true if and only if Google Test should use colors in the output.
+GTEST_API_ bool ShouldUseColor(bool stdout_is_tty);
+
+// Formats the given time in milliseconds as seconds.
+GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms);
+
+// Converts the given time in milliseconds to a date string in the ISO 8601
+// format, without the timezone information.  N.B.: due to the use the
+// non-reentrant localtime() function, this function is not thread safe.  Do
+// not use it in any code that can be called from multiple threads.
+GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms);
+
+// Parses a string for an Int32 flag, in the form of "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+GTEST_API_ bool ParseInt32Flag(
+    const char* str, const char* flag, int32_t* value);
+
+// Returns a random seed in range [1, kMaxRandomSeed] based on the
+// given --gtest_random_seed flag value.
+inline int GetRandomSeedFromFlag(int32_t random_seed_flag) {
+  const unsigned int raw_seed = (random_seed_flag == 0) ?
+      static_cast<unsigned int>(GetTimeInMillis()) :
+      static_cast<unsigned int>(random_seed_flag);
+
+  // Normalizes the actual seed to range [1, kMaxRandomSeed] such that
+  // it's easy to type.
+  const int normalized_seed =
+      static_cast<int>((raw_seed - 1U) %
+                       static_cast<unsigned int>(kMaxRandomSeed)) + 1;
+  return normalized_seed;
+}
+
+// Returns the first valid random seed after 'seed'.  The behavior is
+// undefined if 'seed' is invalid.  The seed after kMaxRandomSeed is
+// considered to be 1.
+inline int GetNextRandomSeed(int seed) {
+  GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed)
+      << "Invalid random seed " << seed << " - must be in [1, "
+      << kMaxRandomSeed << "].";
+  const int next_seed = seed + 1;
+  return (next_seed > kMaxRandomSeed) ? 1 : next_seed;
+}
+
+// This class saves the values of all Google Test flags in its c'tor, and
+// restores them in its d'tor.
+class GTestFlagSaver {
+ public:
+  // The c'tor.
+  GTestFlagSaver() {
+    also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests);
+    break_on_failure_ = GTEST_FLAG(break_on_failure);
+    catch_exceptions_ = GTEST_FLAG(catch_exceptions);
+    color_ = GTEST_FLAG(color);
+    death_test_style_ = GTEST_FLAG(death_test_style);
+    death_test_use_fork_ = GTEST_FLAG(death_test_use_fork);
+    fail_fast_ = GTEST_FLAG(fail_fast);
+    filter_ = GTEST_FLAG(filter);
+    internal_run_death_test_ = GTEST_FLAG(internal_run_death_test);
+    list_tests_ = GTEST_FLAG(list_tests);
+    output_ = GTEST_FLAG(output);
+    brief_ = GTEST_FLAG(brief);
+    print_time_ = GTEST_FLAG(print_time);
+    print_utf8_ = GTEST_FLAG(print_utf8);
+    random_seed_ = GTEST_FLAG(random_seed);
+    repeat_ = GTEST_FLAG(repeat);
+    shuffle_ = GTEST_FLAG(shuffle);
+    stack_trace_depth_ = GTEST_FLAG(stack_trace_depth);
+    stream_result_to_ = GTEST_FLAG(stream_result_to);
+    throw_on_failure_ = GTEST_FLAG(throw_on_failure);
+  }
+
+  // The d'tor is not virtual.  DO NOT INHERIT FROM THIS CLASS.
+  ~GTestFlagSaver() {
+    GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_;
+    GTEST_FLAG(break_on_failure) = break_on_failure_;
+    GTEST_FLAG(catch_exceptions) = catch_exceptions_;
+    GTEST_FLAG(color) = color_;
+    GTEST_FLAG(death_test_style) = death_test_style_;
+    GTEST_FLAG(death_test_use_fork) = death_test_use_fork_;
+    GTEST_FLAG(filter) = filter_;
+    GTEST_FLAG(fail_fast) = fail_fast_;
+    GTEST_FLAG(internal_run_death_test) = internal_run_death_test_;
+    GTEST_FLAG(list_tests) = list_tests_;
+    GTEST_FLAG(output) = output_;
+    GTEST_FLAG(brief) = brief_;
+    GTEST_FLAG(print_time) = print_time_;
+    GTEST_FLAG(print_utf8) = print_utf8_;
+    GTEST_FLAG(random_seed) = random_seed_;
+    GTEST_FLAG(repeat) = repeat_;
+    GTEST_FLAG(shuffle) = shuffle_;
+    GTEST_FLAG(stack_trace_depth) = stack_trace_depth_;
+    GTEST_FLAG(stream_result_to) = stream_result_to_;
+    GTEST_FLAG(throw_on_failure) = throw_on_failure_;
+  }
+
+ private:
+  // Fields for saving the original values of flags.
+  bool also_run_disabled_tests_;
+  bool break_on_failure_;
+  bool catch_exceptions_;
+  std::string color_;
+  std::string death_test_style_;
+  bool death_test_use_fork_;
+  bool fail_fast_;
+  std::string filter_;
+  std::string internal_run_death_test_;
+  bool list_tests_;
+  std::string output_;
+  bool brief_;
+  bool print_time_;
+  bool print_utf8_;
+  int32_t random_seed_;
+  int32_t repeat_;
+  bool shuffle_;
+  int32_t stack_trace_depth_;
+  std::string stream_result_to_;
+  bool throw_on_failure_;
+} GTEST_ATTRIBUTE_UNUSED_;
+
+// Converts a Unicode code point to a narrow string in UTF-8 encoding.
+// code_point parameter is of type UInt32 because wchar_t may not be
+// wide enough to contain a code point.
+// If the code_point is not a valid Unicode code point
+// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
+// to "(Invalid Unicode 0xXXXXXXXX)".
+GTEST_API_ std::string CodePointToUtf8(uint32_t code_point);
+
+// Converts a wide string to a narrow string in UTF-8 encoding.
+// The wide string is assumed to have the following encoding:
+//   UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin)
+//   UTF-32 if sizeof(wchar_t) == 4 (on Linux)
+// Parameter str points to a null-terminated wide string.
+// Parameter num_chars may additionally limit the number
+// of wchar_t characters processed. -1 is used when the entire string
+// should be processed.
+// If the string contains code points that are not valid Unicode code points
+// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output
+// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding
+// and contains invalid UTF-16 surrogate pairs, values in those pairs
+// will be encoded as individual Unicode characters from Basic Normal Plane.
+GTEST_API_ std::string WideStringToUtf8(const wchar_t* str, int num_chars);
+
+// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file
+// if the variable is present. If a file already exists at this location, this
+// function will write over it. If the variable is present, but the file cannot
+// be created, prints an error and exits.
+void WriteToShardStatusFileIfNeeded();
+
+// Checks whether sharding is enabled by examining the relevant
+// environment variable values. If the variables are present,
+// but inconsistent (e.g., shard_index >= total_shards), prints
+// an error and exits. If in_subprocess_for_death_test, sharding is
+// disabled because it must only be applied to the original test
+// process. Otherwise, we could filter out death tests we intended to execute.
+GTEST_API_ bool ShouldShard(const char* total_shards_str,
+                            const char* shard_index_str,
+                            bool in_subprocess_for_death_test);
+
+// Parses the environment variable var as a 32-bit integer. If it is unset,
+// returns default_val. If it is not a 32-bit integer, prints an error and
+// and aborts.
+GTEST_API_ int32_t Int32FromEnvOrDie(const char* env_var, int32_t default_val);
+
+// Given the total number of shards, the shard index, and the test id,
+// returns true if and only if the test should be run on this shard. The test id
+// is some arbitrary but unique non-negative integer assigned to each test
+// method. Assumes that 0 <= shard_index < total_shards.
+GTEST_API_ bool ShouldRunTestOnShard(
+    int total_shards, int shard_index, int test_id);
+
+// STL container utilities.
+
+// Returns the number of elements in the given container that satisfy
+// the given predicate.
+template <class Container, typename Predicate>
+inline int CountIf(const Container& c, Predicate predicate) {
+  // Implemented as an explicit loop since std::count_if() in libCstd on
+  // Solaris has a non-standard signature.
+  int count = 0;
+  for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) {
+    if (predicate(*it))
+      ++count;
+  }
+  return count;
+}
+
+// Applies a function/functor to each element in the container.
+template <class Container, typename Functor>
+void ForEach(const Container& c, Functor functor) {
+  std::for_each(c.begin(), c.end(), functor);
+}
+
+// Returns the i-th element of the vector, or default_value if i is not
+// in range [0, v.size()).
+template <typename E>
+inline E GetElementOr(const std::vector<E>& v, int i, E default_value) {
+  return (i < 0 || i >= static_cast<int>(v.size())) ? default_value
+                                                    : v[static_cast<size_t>(i)];
+}
+
+// Performs an in-place shuffle of a range of the vector's elements.
+// 'begin' and 'end' are element indices as an STL-style range;
+// i.e. [begin, end) are shuffled, where 'end' == size() means to
+// shuffle to the end of the vector.
+template <typename E>
+void ShuffleRange(internal::Random* random, int begin, int end,
+                  std::vector<E>* v) {
+  const int size = static_cast<int>(v->size());
+  GTEST_CHECK_(0 <= begin && begin <= size)
+      << "Invalid shuffle range start " << begin << ": must be in range [0, "
+      << size << "].";
+  GTEST_CHECK_(begin <= end && end <= size)
+      << "Invalid shuffle range finish " << end << ": must be in range ["
+      << begin << ", " << size << "].";
+
+  // Fisher-Yates shuffle, from
+  // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
+  for (int range_width = end - begin; range_width >= 2; range_width--) {
+    const int last_in_range = begin + range_width - 1;
+    const int selected =
+        begin +
+        static_cast<int>(random->Generate(static_cast<uint32_t>(range_width)));
+    std::swap((*v)[static_cast<size_t>(selected)],
+              (*v)[static_cast<size_t>(last_in_range)]);
+  }
+}
+
+// Performs an in-place shuffle of the vector's elements.
+template <typename E>
+inline void Shuffle(internal::Random* random, std::vector<E>* v) {
+  ShuffleRange(random, 0, static_cast<int>(v->size()), v);
+}
+
+// A function for deleting an object.  Handy for being used as a
+// functor.
+template <typename T>
+static void Delete(T* x) {
+  delete x;
+}
+
+// A predicate that checks the key of a TestProperty against a known key.
+//
+// TestPropertyKeyIs is copyable.
+class TestPropertyKeyIs {
+ public:
+  // Constructor.
+  //
+  // TestPropertyKeyIs has NO default constructor.
+  explicit TestPropertyKeyIs(const std::string& key) : key_(key) {}
+
+  // Returns true if and only if the test name of test property matches on key_.
+  bool operator()(const TestProperty& test_property) const {
+    return test_property.key() == key_;
+  }
+
+ private:
+  std::string key_;
+};
+
+// Class UnitTestOptions.
+//
+// This class contains functions for processing options the user
+// specifies when running the tests.  It has only static members.
+//
+// In most cases, the user can specify an option using either an
+// environment variable or a command line flag.  E.g. you can set the
+// test filter using either GTEST_FILTER or --gtest_filter.  If both
+// the variable and the flag are present, the latter overrides the
+// former.
+class GTEST_API_ UnitTestOptions {
+ public:
+  // Functions for processing the gtest_output flag.
+
+  // Returns the output format, or "" for normal printed output.
+  static std::string GetOutputFormat();
+
+  // Returns the absolute path of the requested output file, or the
+  // default (test_detail.xml in the original working directory) if
+  // none was explicitly specified.
+  static std::string GetAbsolutePathToOutputFile();
+
+  // Functions for processing the gtest_filter flag.
+
+  // Returns true if and only if the user-specified filter matches the test
+  // suite name and the test name.
+  static bool FilterMatchesTest(const std::string& test_suite_name,
+                                const std::string& test_name);
+
+#if GTEST_OS_WINDOWS
+  // Function for supporting the gtest_catch_exception flag.
+
+  // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the
+  // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise.
+  // This function is useful as an __except condition.
+  static int GTestShouldProcessSEH(DWORD exception_code);
+#endif  // GTEST_OS_WINDOWS
+
+  // Returns true if "name" matches the ':' separated list of glob-style
+  // filters in "filter".
+  static bool MatchesFilter(const std::string& name, const char* filter);
+};
+
+// Returns the current application's name, removing directory path if that
+// is present.  Used by UnitTestOptions::GetOutputFile.
+GTEST_API_ FilePath GetCurrentExecutableName();
+
+// The role interface for getting the OS stack trace as a string.
+class OsStackTraceGetterInterface {
+ public:
+  OsStackTraceGetterInterface() {}
+  virtual ~OsStackTraceGetterInterface() {}
+
+  // Returns the current OS stack trace as an std::string.  Parameters:
+  //
+  //   max_depth  - the maximum number of stack frames to be included
+  //                in the trace.
+  //   skip_count - the number of top frames to be skipped; doesn't count
+  //                against max_depth.
+  virtual std::string CurrentStackTrace(int max_depth, int skip_count) = 0;
+
+  // UponLeavingGTest() should be called immediately before Google Test calls
+  // user code. It saves some information about the current stack that
+  // CurrentStackTrace() will use to find and hide Google Test stack frames.
+  virtual void UponLeavingGTest() = 0;
+
+  // This string is inserted in place of stack frames that are part of
+  // Google Test's implementation.
+  static const char* const kElidedFramesMarker;
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface);
+};
+
+// A working implementation of the OsStackTraceGetterInterface interface.
+class OsStackTraceGetter : public OsStackTraceGetterInterface {
+ public:
+  OsStackTraceGetter() {}
+
+  std::string CurrentStackTrace(int max_depth, int skip_count) override;
+  void UponLeavingGTest() override;
+
+ private:
+#if GTEST_HAS_ABSL
+  Mutex mutex_;  // Protects all internal state.
+
+  // We save the stack frame below the frame that calls user code.
+  // We do this because the address of the frame immediately below
+  // the user code changes between the call to UponLeavingGTest()
+  // and any calls to the stack trace code from within the user code.
+  void* caller_frame_ = nullptr;
+#endif  // GTEST_HAS_ABSL
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter);
+};
+
+// Information about a Google Test trace point.
+struct TraceInfo {
+  const char* file;
+  int line;
+  std::string message;
+};
+
+// This is the default global test part result reporter used in UnitTestImpl.
+// This class should only be used by UnitTestImpl.
+class DefaultGlobalTestPartResultReporter
+  : public TestPartResultReporterInterface {
+ public:
+  explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test);
+  // Implements the TestPartResultReporterInterface. Reports the test part
+  // result in the current test.
+  void ReportTestPartResult(const TestPartResult& result) override;
+
+ private:
+  UnitTestImpl* const unit_test_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter);
+};
+
+// This is the default per thread test part result reporter used in
+// UnitTestImpl. This class should only be used by UnitTestImpl.
+class DefaultPerThreadTestPartResultReporter
+    : public TestPartResultReporterInterface {
+ public:
+  explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test);
+  // Implements the TestPartResultReporterInterface. The implementation just
+  // delegates to the current global test part result reporter of *unit_test_.
+  void ReportTestPartResult(const TestPartResult& result) override;
+
+ private:
+  UnitTestImpl* const unit_test_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter);
+};
+
+// The private implementation of the UnitTest class.  We don't protect
+// the methods under a mutex, as this class is not accessible by a
+// user and the UnitTest class that delegates work to this class does
+// proper locking.
+class GTEST_API_ UnitTestImpl {
+ public:
+  explicit UnitTestImpl(UnitTest* parent);
+  virtual ~UnitTestImpl();
+
+  // There are two different ways to register your own TestPartResultReporter.
+  // You can register your own repoter to listen either only for test results
+  // from the current thread or for results from all threads.
+  // By default, each per-thread test result repoter just passes a new
+  // TestPartResult to the global test result reporter, which registers the
+  // test part result for the currently running test.
+
+  // Returns the global test part result reporter.
+  TestPartResultReporterInterface* GetGlobalTestPartResultReporter();
+
+  // Sets the global test part result reporter.
+  void SetGlobalTestPartResultReporter(
+      TestPartResultReporterInterface* reporter);
+
+  // Returns the test part result reporter for the current thread.
+  TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread();
+
+  // Sets the test part result reporter for the current thread.
+  void SetTestPartResultReporterForCurrentThread(
+      TestPartResultReporterInterface* reporter);
+
+  // Gets the number of successful test suites.
+  int successful_test_suite_count() const;
+
+  // Gets the number of failed test suites.
+  int failed_test_suite_count() const;
+
+  // Gets the number of all test suites.
+  int total_test_suite_count() const;
+
+  // Gets the number of all test suites that contain at least one test
+  // that should run.
+  int test_suite_to_run_count() const;
+
+  // Gets the number of successful tests.
+  int successful_test_count() const;
+
+  // Gets the number of skipped tests.
+  int skipped_test_count() const;
+
+  // Gets the number of failed tests.
+  int failed_test_count() const;
+
+  // Gets the number of disabled tests that will be reported in the XML report.
+  int reportable_disabled_test_count() const;
+
+  // Gets the number of disabled tests.
+  int disabled_test_count() const;
+
+  // Gets the number of tests to be printed in the XML report.
+  int reportable_test_count() const;
+
+  // Gets the number of all tests.
+  int total_test_count() const;
+
+  // Gets the number of tests that should run.
+  int test_to_run_count() const;
+
+  // Gets the time of the test program start, in ms from the start of the
+  // UNIX epoch.
+  TimeInMillis start_timestamp() const { return start_timestamp_; }
+
+  // Gets the elapsed time, in milliseconds.
+  TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+  // Returns true if and only if the unit test passed (i.e. all test suites
+  // passed).
+  bool Passed() const { return !Failed(); }
+
+  // Returns true if and only if the unit test failed (i.e. some test suite
+  // failed or something outside of all tests failed).
+  bool Failed() const {
+    return failed_test_suite_count() > 0 || ad_hoc_test_result()->Failed();
+  }
+
+  // Gets the i-th test suite among all the test suites. i can range from 0 to
+  // total_test_suite_count() - 1. If i is not in that range, returns NULL.
+  const TestSuite* GetTestSuite(int i) const {
+    const int index = GetElementOr(test_suite_indices_, i, -1);
+    return index < 0 ? nullptr : test_suites_[static_cast<size_t>(i)];
+  }
+
+  //  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  const TestCase* GetTestCase(int i) const { return GetTestSuite(i); }
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Gets the i-th test suite among all the test suites. i can range from 0 to
+  // total_test_suite_count() - 1. If i is not in that range, returns NULL.
+  TestSuite* GetMutableSuiteCase(int i) {
+    const int index = GetElementOr(test_suite_indices_, i, -1);
+    return index < 0 ? nullptr : test_suites_[static_cast<size_t>(index)];
+  }
+
+  // Provides access to the event listener list.
+  TestEventListeners* listeners() { return &listeners_; }
+
+  // Returns the TestResult for the test that's currently running, or
+  // the TestResult for the ad hoc test if no test is running.
+  TestResult* current_test_result();
+
+  // Returns the TestResult for the ad hoc test.
+  const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; }
+
+  // Sets the OS stack trace getter.
+  //
+  // Does nothing if the input and the current OS stack trace getter
+  // are the same; otherwise, deletes the old getter and makes the
+  // input the current getter.
+  void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter);
+
+  // Returns the current OS stack trace getter if it is not NULL;
+  // otherwise, creates an OsStackTraceGetter, makes it the current
+  // getter, and returns it.
+  OsStackTraceGetterInterface* os_stack_trace_getter();
+
+  // Returns the current OS stack trace as an std::string.
+  //
+  // The maximum number of stack frames to be included is specified by
+  // the gtest_stack_trace_depth flag.  The skip_count parameter
+  // specifies the number of top frames to be skipped, which doesn't
+  // count against the number of frames to be included.
+  //
+  // For example, if Foo() calls Bar(), which in turn calls
+  // CurrentOsStackTraceExceptTop(1), Foo() will be included in the
+  // trace but Bar() and CurrentOsStackTraceExceptTop() won't.
+  std::string CurrentOsStackTraceExceptTop(int skip_count) GTEST_NO_INLINE_;
+
+  // Finds and returns a TestSuite with the given name.  If one doesn't
+  // exist, creates one and returns it.
+  //
+  // Arguments:
+  //
+  //   test_suite_name: name of the test suite
+  //   type_param:      the name of the test's type parameter, or NULL if
+  //                    this is not a typed or a type-parameterized test.
+  //   set_up_tc:       pointer to the function that sets up the test suite
+  //   tear_down_tc:    pointer to the function that tears down the test suite
+  TestSuite* GetTestSuite(const char* test_suite_name, const char* type_param,
+                          internal::SetUpTestSuiteFunc set_up_tc,
+                          internal::TearDownTestSuiteFunc tear_down_tc);
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  TestCase* GetTestCase(const char* test_case_name, const char* type_param,
+                        internal::SetUpTestSuiteFunc set_up_tc,
+                        internal::TearDownTestSuiteFunc tear_down_tc) {
+    return GetTestSuite(test_case_name, type_param, set_up_tc, tear_down_tc);
+  }
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Adds a TestInfo to the unit test.
+  //
+  // Arguments:
+  //
+  //   set_up_tc:    pointer to the function that sets up the test suite
+  //   tear_down_tc: pointer to the function that tears down the test suite
+  //   test_info:    the TestInfo object
+  void AddTestInfo(internal::SetUpTestSuiteFunc set_up_tc,
+                   internal::TearDownTestSuiteFunc tear_down_tc,
+                   TestInfo* test_info) {
+#if GTEST_HAS_DEATH_TEST
+    // In order to support thread-safe death tests, we need to
+    // remember the original working directory when the test program
+    // was first invoked.  We cannot do this in RUN_ALL_TESTS(), as
+    // the user may have changed the current directory before calling
+    // RUN_ALL_TESTS().  Therefore we capture the current directory in
+    // AddTestInfo(), which is called to register a TEST or TEST_F
+    // before main() is reached.
+    if (original_working_dir_.IsEmpty()) {
+      original_working_dir_.Set(FilePath::GetCurrentDir());
+      GTEST_CHECK_(!original_working_dir_.IsEmpty())
+          << "Failed to get the current working directory.";
+    }
+#endif  // GTEST_HAS_DEATH_TEST
+
+    GetTestSuite(test_info->test_suite_name(), test_info->type_param(),
+                 set_up_tc, tear_down_tc)
+        ->AddTestInfo(test_info);
+  }
+
+  // Returns ParameterizedTestSuiteRegistry object used to keep track of
+  // value-parameterized tests and instantiate and register them.
+  internal::ParameterizedTestSuiteRegistry& parameterized_test_registry() {
+    return parameterized_test_registry_;
+  }
+
+  std::set<std::string>* ignored_parameterized_test_suites() {
+    return &ignored_parameterized_test_suites_;
+  }
+
+  // Returns TypeParameterizedTestSuiteRegistry object used to keep track of
+  // type-parameterized tests and instantiations of them.
+  internal::TypeParameterizedTestSuiteRegistry&
+  type_parameterized_test_registry() {
+    return type_parameterized_test_registry_;
+  }
+
+  // Sets the TestSuite object for the test that's currently running.
+  void set_current_test_suite(TestSuite* a_current_test_suite) {
+    current_test_suite_ = a_current_test_suite;
+  }
+
+  // Sets the TestInfo object for the test that's currently running.  If
+  // current_test_info is NULL, the assertion results will be stored in
+  // ad_hoc_test_result_.
+  void set_current_test_info(TestInfo* a_current_test_info) {
+    current_test_info_ = a_current_test_info;
+  }
+
+  // Registers all parameterized tests defined using TEST_P and
+  // INSTANTIATE_TEST_SUITE_P, creating regular tests for each test/parameter
+  // combination. This method can be called more then once; it has guards
+  // protecting from registering the tests more then once.  If
+  // value-parameterized tests are disabled, RegisterParameterizedTests is
+  // present but does nothing.
+  void RegisterParameterizedTests();
+
+  // Runs all tests in this UnitTest object, prints the result, and
+  // returns true if all tests are successful.  If any exception is
+  // thrown during a test, this test is considered to be failed, but
+  // the rest of the tests will still be run.
+  bool RunAllTests();
+
+  // Clears the results of all tests, except the ad hoc tests.
+  void ClearNonAdHocTestResult() {
+    ForEach(test_suites_, TestSuite::ClearTestSuiteResult);
+  }
+
+  // Clears the results of ad-hoc test assertions.
+  void ClearAdHocTestResult() {
+    ad_hoc_test_result_.Clear();
+  }
+
+  // Adds a TestProperty to the current TestResult object when invoked in a
+  // context of a test or a test suite, or to the global property set. If the
+  // result already contains a property with the same key, the value will be
+  // updated.
+  void RecordProperty(const TestProperty& test_property);
+
+  enum ReactionToSharding {
+    HONOR_SHARDING_PROTOCOL,
+    IGNORE_SHARDING_PROTOCOL
+  };
+
+  // Matches the full name of each test against the user-specified
+  // filter to decide whether the test should run, then records the
+  // result in each TestSuite and TestInfo object.
+  // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests
+  // based on sharding variables in the environment.
+  // Returns the number of tests that should run.
+  int FilterTests(ReactionToSharding shard_tests);
+
+  // Prints the names of the tests matching the user-specified filter flag.
+  void ListTestsMatchingFilter();
+
+  const TestSuite* current_test_suite() const { return current_test_suite_; }
+  TestInfo* current_test_info() { return current_test_info_; }
+  const TestInfo* current_test_info() const { return current_test_info_; }
+
+  // Returns the vector of environments that need to be set-up/torn-down
+  // before/after the tests are run.
+  std::vector<Environment*>& environments() { return environments_; }
+
+  // Getters for the per-thread Google Test trace stack.
+  std::vector<TraceInfo>& gtest_trace_stack() {
+    return *(gtest_trace_stack_.pointer());
+  }
+  const std::vector<TraceInfo>& gtest_trace_stack() const {
+    return gtest_trace_stack_.get();
+  }
+
+#if GTEST_HAS_DEATH_TEST
+  void InitDeathTestSubprocessControlInfo() {
+    internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag());
+  }
+  // Returns a pointer to the parsed --gtest_internal_run_death_test
+  // flag, or NULL if that flag was not specified.
+  // This information is useful only in a death test child process.
+  // Must not be called before a call to InitGoogleTest.
+  const InternalRunDeathTestFlag* internal_run_death_test_flag() const {
+    return internal_run_death_test_flag_.get();
+  }
+
+  // Returns a pointer to the current death test factory.
+  internal::DeathTestFactory* death_test_factory() {
+    return death_test_factory_.get();
+  }
+
+  void SuppressTestEventsIfInSubprocess();
+
+  friend class ReplaceDeathTestFactory;
+#endif  // GTEST_HAS_DEATH_TEST
+
+  // Initializes the event listener performing XML output as specified by
+  // UnitTestOptions. Must not be called before InitGoogleTest.
+  void ConfigureXmlOutput();
+
+#if GTEST_CAN_STREAM_RESULTS_
+  // Initializes the event listener for streaming test results to a socket.
+  // Must not be called before InitGoogleTest.
+  void ConfigureStreamingOutput();
+#endif
+
+  // Performs initialization dependent upon flag values obtained in
+  // ParseGoogleTestFlagsOnly.  Is called from InitGoogleTest after the call to
+  // ParseGoogleTestFlagsOnly.  In case a user neglects to call InitGoogleTest
+  // this function is also called from RunAllTests.  Since this function can be
+  // called more than once, it has to be idempotent.
+  void PostFlagParsingInit();
+
+  // Gets the random seed used at the start of the current test iteration.
+  int random_seed() const { return random_seed_; }
+
+  // Gets the random number generator.
+  internal::Random* random() { return &random_; }
+
+  // Shuffles all test suites, and the tests within each test suite,
+  // making sure that death tests are still run first.
+  void ShuffleTests();
+
+  // Restores the test suites and tests to their order before the first shuffle.
+  void UnshuffleTests();
+
+  // Returns the value of GTEST_FLAG(catch_exceptions) at the moment
+  // UnitTest::Run() starts.
+  bool catch_exceptions() const { return catch_exceptions_; }
+
+ private:
+  friend class ::testing::UnitTest;
+
+  // Used by UnitTest::Run() to capture the state of
+  // GTEST_FLAG(catch_exceptions) at the moment it starts.
+  void set_catch_exceptions(bool value) { catch_exceptions_ = value; }
+
+  // The UnitTest object that owns this implementation object.
+  UnitTest* const parent_;
+
+  // The working directory when the first TEST() or TEST_F() was
+  // executed.
+  internal::FilePath original_working_dir_;
+
+  // The default test part result reporters.
+  DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_;
+  DefaultPerThreadTestPartResultReporter
+      default_per_thread_test_part_result_reporter_;
+
+  // Points to (but doesn't own) the global test part result reporter.
+  TestPartResultReporterInterface* global_test_part_result_repoter_;
+
+  // Protects read and write access to global_test_part_result_reporter_.
+  internal::Mutex global_test_part_result_reporter_mutex_;
+
+  // Points to (but doesn't own) the per-thread test part result reporter.
+  internal::ThreadLocal<TestPartResultReporterInterface*>
+      per_thread_test_part_result_reporter_;
+
+  // The vector of environments that need to be set-up/torn-down
+  // before/after the tests are run.
+  std::vector<Environment*> environments_;
+
+  // The vector of TestSuites in their original order.  It owns the
+  // elements in the vector.
+  std::vector<TestSuite*> test_suites_;
+
+  // Provides a level of indirection for the test suite list to allow
+  // easy shuffling and restoring the test suite order.  The i-th
+  // element of this vector is the index of the i-th test suite in the
+  // shuffled order.
+  std::vector<int> test_suite_indices_;
+
+  // ParameterizedTestRegistry object used to register value-parameterized
+  // tests.
+  internal::ParameterizedTestSuiteRegistry parameterized_test_registry_;
+  internal::TypeParameterizedTestSuiteRegistry
+      type_parameterized_test_registry_;
+
+  // The set holding the name of parameterized
+  // test suites that may go uninstantiated.
+  std::set<std::string> ignored_parameterized_test_suites_;
+
+  // Indicates whether RegisterParameterizedTests() has been called already.
+  bool parameterized_tests_registered_;
+
+  // Index of the last death test suite registered.  Initially -1.
+  int last_death_test_suite_;
+
+  // This points to the TestSuite for the currently running test.  It
+  // changes as Google Test goes through one test suite after another.
+  // When no test is running, this is set to NULL and Google Test
+  // stores assertion results in ad_hoc_test_result_.  Initially NULL.
+  TestSuite* current_test_suite_;
+
+  // This points to the TestInfo for the currently running test.  It
+  // changes as Google Test goes through one test after another.  When
+  // no test is running, this is set to NULL and Google Test stores
+  // assertion results in ad_hoc_test_result_.  Initially NULL.
+  TestInfo* current_test_info_;
+
+  // Normally, a user only writes assertions inside a TEST or TEST_F,
+  // or inside a function called by a TEST or TEST_F.  Since Google
+  // Test keeps track of which test is current running, it can
+  // associate such an assertion with the test it belongs to.
+  //
+  // If an assertion is encountered when no TEST or TEST_F is running,
+  // Google Test attributes the assertion result to an imaginary "ad hoc"
+  // test, and records the result in ad_hoc_test_result_.
+  TestResult ad_hoc_test_result_;
+
+  // The list of event listeners that can be used to track events inside
+  // Google Test.
+  TestEventListeners listeners_;
+
+  // The OS stack trace getter.  Will be deleted when the UnitTest
+  // object is destructed.  By default, an OsStackTraceGetter is used,
+  // but the user can set this field to use a custom getter if that is
+  // desired.
+  OsStackTraceGetterInterface* os_stack_trace_getter_;
+
+  // True if and only if PostFlagParsingInit() has been called.
+  bool post_flag_parse_init_performed_;
+
+  // The random number seed used at the beginning of the test run.
+  int random_seed_;
+
+  // Our random number generator.
+  internal::Random random_;
+
+  // The time of the test program start, in ms from the start of the
+  // UNIX epoch.
+  TimeInMillis start_timestamp_;
+
+  // How long the test took to run, in milliseconds.
+  TimeInMillis elapsed_time_;
+
+#if GTEST_HAS_DEATH_TEST
+  // The decomposed components of the gtest_internal_run_death_test flag,
+  // parsed when RUN_ALL_TESTS is called.
+  std::unique_ptr<InternalRunDeathTestFlag> internal_run_death_test_flag_;
+  std::unique_ptr<internal::DeathTestFactory> death_test_factory_;
+#endif  // GTEST_HAS_DEATH_TEST
+
+  // A per-thread stack of traces created by the SCOPED_TRACE() macro.
+  internal::ThreadLocal<std::vector<TraceInfo> > gtest_trace_stack_;
+
+  // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests()
+  // starts.
+  bool catch_exceptions_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl);
+};  // class UnitTestImpl
+
+// Convenience function for accessing the global UnitTest
+// implementation object.
+inline UnitTestImpl* GetUnitTestImpl() {
+  return UnitTest::GetInstance()->impl();
+}
+
+#if GTEST_USES_SIMPLE_RE
+
+// Internal helper functions for implementing the simple regular
+// expression matcher.
+GTEST_API_ bool IsInSet(char ch, const char* str);
+GTEST_API_ bool IsAsciiDigit(char ch);
+GTEST_API_ bool IsAsciiPunct(char ch);
+GTEST_API_ bool IsRepeat(char ch);
+GTEST_API_ bool IsAsciiWhiteSpace(char ch);
+GTEST_API_ bool IsAsciiWordChar(char ch);
+GTEST_API_ bool IsValidEscape(char ch);
+GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch);
+GTEST_API_ bool ValidateRegex(const char* regex);
+GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str);
+GTEST_API_ bool MatchRepetitionAndRegexAtHead(
+    bool escaped, char ch, char repeat, const char* regex, const char* str);
+GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str);
+
+#endif  // GTEST_USES_SIMPLE_RE
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test.
+GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv);
+GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv);
+
+#if GTEST_HAS_DEATH_TEST
+
+// Returns the message describing the last system error, regardless of the
+// platform.
+GTEST_API_ std::string GetLastErrnoDescription();
+
+// Attempts to parse a string into a positive integer pointed to by the
+// number parameter.  Returns true if that is possible.
+// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use
+// it here.
+template <typename Integer>
+bool ParseNaturalNumber(const ::std::string& str, Integer* number) {
+  // Fail fast if the given string does not begin with a digit;
+  // this bypasses strtoXXX's "optional leading whitespace and plus
+  // or minus sign" semantics, which are undesirable here.
+  if (str.empty() || !IsDigit(str[0])) {
+    return false;
+  }
+  errno = 0;
+
+  char* end;
+  // BiggestConvertible is the largest integer type that system-provided
+  // string-to-number conversion routines can return.
+  using BiggestConvertible = unsigned long long;  // NOLINT
+
+  const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10);  // NOLINT
+  const bool parse_success = *end == '\0' && errno == 0;
+
+  GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed));
+
+  const Integer result = static_cast<Integer>(parsed);
+  if (parse_success && static_cast<BiggestConvertible>(result) == parsed) {
+    *number = result;
+    return true;
+  }
+  return false;
+}
+#endif  // GTEST_HAS_DEATH_TEST
+
+// TestResult contains some private methods that should be hidden from
+// Google Test user but are required for testing. This class allow our tests
+// to access them.
+//
+// This class is supplied only for the purpose of testing Google Test's own
+// constructs. Do not use it in user tests, either directly or indirectly.
+class TestResultAccessor {
+ public:
+  static void RecordProperty(TestResult* test_result,
+                             const std::string& xml_element,
+                             const TestProperty& property) {
+    test_result->RecordProperty(xml_element, property);
+  }
+
+  static void ClearTestPartResults(TestResult* test_result) {
+    test_result->ClearTestPartResults();
+  }
+
+  static const std::vector<testing::TestPartResult>& test_part_results(
+      const TestResult& test_result) {
+    return test_result.test_part_results();
+  }
+};
+
+#if GTEST_CAN_STREAM_RESULTS_
+
+// Streams test results to the given port on the given host machine.
+class StreamingListener : public EmptyTestEventListener {
+ public:
+  // Abstract base class for writing strings to a socket.
+  class AbstractSocketWriter {
+   public:
+    virtual ~AbstractSocketWriter() {}
+
+    // Sends a string to the socket.
+    virtual void Send(const std::string& message) = 0;
+
+    // Closes the socket.
+    virtual void CloseConnection() {}
+
+    // Sends a string and a newline to the socket.
+    void SendLn(const std::string& message) { Send(message + "\n"); }
+  };
+
+  // Concrete class for actually writing strings to a socket.
+  class SocketWriter : public AbstractSocketWriter {
+   public:
+    SocketWriter(const std::string& host, const std::string& port)
+        : sockfd_(-1), host_name_(host), port_num_(port) {
+      MakeConnection();
+    }
+
+    ~SocketWriter() override {
+      if (sockfd_ != -1)
+        CloseConnection();
+    }
+
+    // Sends a string to the socket.
+    void Send(const std::string& message) override {
+      GTEST_CHECK_(sockfd_ != -1)
+          << "Send() can be called only when there is a connection.";
+
+      const auto len = static_cast<size_t>(message.length());
+      if (write(sockfd_, message.c_str(), len) != static_cast<ssize_t>(len)) {
+        GTEST_LOG_(WARNING)
+            << "stream_result_to: failed to stream to "
+            << host_name_ << ":" << port_num_;
+      }
+    }
+
+   private:
+    // Creates a client socket and connects to the server.
+    void MakeConnection();
+
+    // Closes the socket.
+    void CloseConnection() override {
+      GTEST_CHECK_(sockfd_ != -1)
+          << "CloseConnection() can be called only when there is a connection.";
+
+      close(sockfd_);
+      sockfd_ = -1;
+    }
+
+    int sockfd_;  // socket file descriptor
+    const std::string host_name_;
+    const std::string port_num_;
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(SocketWriter);
+  };  // class SocketWriter
+
+  // Escapes '=', '&', '%', and '\n' characters in str as "%xx".
+  static std::string UrlEncode(const char* str);
+
+  StreamingListener(const std::string& host, const std::string& port)
+      : socket_writer_(new SocketWriter(host, port)) {
+    Start();
+  }
+
+  explicit StreamingListener(AbstractSocketWriter* socket_writer)
+      : socket_writer_(socket_writer) { Start(); }
+
+  void OnTestProgramStart(const UnitTest& /* unit_test */) override {
+    SendLn("event=TestProgramStart");
+  }
+
+  void OnTestProgramEnd(const UnitTest& unit_test) override {
+    // Note that Google Test current only report elapsed time for each
+    // test iteration, not for the entire test program.
+    SendLn("event=TestProgramEnd&passed=" + FormatBool(unit_test.Passed()));
+
+    // Notify the streaming server to stop.
+    socket_writer_->CloseConnection();
+  }
+
+  void OnTestIterationStart(const UnitTest& /* unit_test */,
+                            int iteration) override {
+    SendLn("event=TestIterationStart&iteration=" +
+           StreamableToString(iteration));
+  }
+
+  void OnTestIterationEnd(const UnitTest& unit_test,
+                          int /* iteration */) override {
+    SendLn("event=TestIterationEnd&passed=" +
+           FormatBool(unit_test.Passed()) + "&elapsed_time=" +
+           StreamableToString(unit_test.elapsed_time()) + "ms");
+  }
+
+  // Note that "event=TestCaseStart" is a wire format and has to remain
+  // "case" for compatibility
+  void OnTestCaseStart(const TestCase& test_case) override {
+    SendLn(std::string("event=TestCaseStart&name=") + test_case.name());
+  }
+
+  // Note that "event=TestCaseEnd" is a wire format and has to remain
+  // "case" for compatibility
+  void OnTestCaseEnd(const TestCase& test_case) override {
+    SendLn("event=TestCaseEnd&passed=" + FormatBool(test_case.Passed()) +
+           "&elapsed_time=" + StreamableToString(test_case.elapsed_time()) +
+           "ms");
+  }
+
+  void OnTestStart(const TestInfo& test_info) override {
+    SendLn(std::string("event=TestStart&name=") + test_info.name());
+  }
+
+  void OnTestEnd(const TestInfo& test_info) override {
+    SendLn("event=TestEnd&passed=" +
+           FormatBool((test_info.result())->Passed()) +
+           "&elapsed_time=" +
+           StreamableToString((test_info.result())->elapsed_time()) + "ms");
+  }
+
+  void OnTestPartResult(const TestPartResult& test_part_result) override {
+    const char* file_name = test_part_result.file_name();
+    if (file_name == nullptr) file_name = "";
+    SendLn("event=TestPartResult&file=" + UrlEncode(file_name) +
+           "&line=" + StreamableToString(test_part_result.line_number()) +
+           "&message=" + UrlEncode(test_part_result.message()));
+  }
+
+ private:
+  // Sends the given message and a newline to the socket.
+  void SendLn(const std::string& message) { socket_writer_->SendLn(message); }
+
+  // Called at the start of streaming to notify the receiver what
+  // protocol we are using.
+  void Start() { SendLn("gtest_streaming_protocol_version=1.0"); }
+
+  std::string FormatBool(bool value) { return value ? "1" : "0"; }
+
+  const std::unique_ptr<AbstractSocketWriter> socket_writer_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener);
+};  // class StreamingListener
+
+#endif  // GTEST_CAN_STREAM_RESULTS_
+
+}  // namespace internal
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLETEST_SRC_GTEST_INTERNAL_INL_H_
+
+#if GTEST_OS_WINDOWS
+# define vsnprintf _vsnprintf
+#endif  // GTEST_OS_WINDOWS
+
+#if GTEST_OS_MAC
+#ifndef GTEST_OS_IOS
+#include <crt_externs.h>
+#endif
+#endif
+
+#if GTEST_HAS_ABSL
+#include "absl/debugging/failure_signal_handler.h"
+#include "absl/debugging/stacktrace.h"
+#include "absl/debugging/symbolize.h"
+#include "absl/strings/str_cat.h"
+#endif  // GTEST_HAS_ABSL
+
+namespace testing {
+
+using internal::CountIf;
+using internal::ForEach;
+using internal::GetElementOr;
+using internal::Shuffle;
+
+// Constants.
+
+// A test whose test suite name or test name matches this filter is
+// disabled and not run.
+static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*";
+
+// A test suite whose name matches this filter is considered a death
+// test suite and will be run before test suites whose name doesn't
+// match this filter.
+static const char kDeathTestSuiteFilter[] = "*DeathTest:*DeathTest/*";
+
+// A test filter that matches everything.
+static const char kUniversalFilter[] = "*";
+
+// The default output format.
+static const char kDefaultOutputFormat[] = "xml";
+// The default output file.
+static const char kDefaultOutputFile[] = "test_detail";
+
+// The environment variable name for the test shard index.
+static const char kTestShardIndex[] = "GTEST_SHARD_INDEX";
+// The environment variable name for the total number of test shards.
+static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS";
+// The environment variable name for the test shard status file.
+static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE";
+
+namespace internal {
+
+// The text used in failure messages to indicate the start of the
+// stack trace.
+const char kStackTraceMarker[] = "\nStack trace:\n";
+
+// g_help_flag is true if and only if the --help flag or an equivalent form
+// is specified on the command line.
+bool g_help_flag = false;
+
+// Utilty function to Open File for Writing
+static FILE* OpenFileForWriting(const std::string& output_file) {
+  FILE* fileout = nullptr;
+  FilePath output_file_path(output_file);
+  FilePath output_dir(output_file_path.RemoveFileName());
+
+  if (output_dir.CreateDirectoriesRecursively()) {
+    fileout = posix::FOpen(output_file.c_str(), "w");
+  }
+  if (fileout == nullptr) {
+    GTEST_LOG_(FATAL) << "Unable to open file \"" << output_file << "\"";
+  }
+  return fileout;
+}
+
+}  // namespace internal
+
+// Bazel passes in the argument to '--test_filter' via the TESTBRIDGE_TEST_ONLY
+// environment variable.
+static const char* GetDefaultFilter() {
+  const char* const testbridge_test_only =
+      internal::posix::GetEnv("TESTBRIDGE_TEST_ONLY");
+  if (testbridge_test_only != nullptr) {
+    return testbridge_test_only;
+  }
+  return kUniversalFilter;
+}
+
+// Bazel passes in the argument to '--test_runner_fail_fast' via the
+// TESTBRIDGE_TEST_RUNNER_FAIL_FAST environment variable.
+static bool GetDefaultFailFast() {
+  const char* const testbridge_test_runner_fail_fast =
+      internal::posix::GetEnv("TESTBRIDGE_TEST_RUNNER_FAIL_FAST");
+  if (testbridge_test_runner_fail_fast != nullptr) {
+    return strcmp(testbridge_test_runner_fail_fast, "1") == 0;
+  }
+  return false;
+}
+
+GTEST_DEFINE_bool_(
+    fail_fast, internal::BoolFromGTestEnv("fail_fast", GetDefaultFailFast()),
+    "True if and only if a test failure should stop further test execution.");
+
+GTEST_DEFINE_bool_(
+    also_run_disabled_tests,
+    internal::BoolFromGTestEnv("also_run_disabled_tests", false),
+    "Run disabled tests too, in addition to the tests normally being run.");
+
+GTEST_DEFINE_bool_(
+    break_on_failure, internal::BoolFromGTestEnv("break_on_failure", false),
+    "True if and only if a failed assertion should be a debugger "
+    "break-point.");
+
+GTEST_DEFINE_bool_(catch_exceptions,
+                   internal::BoolFromGTestEnv("catch_exceptions", true),
+                   "True if and only if " GTEST_NAME_
+                   " should catch exceptions and treat them as test failures.");
+
+GTEST_DEFINE_string_(
+    color,
+    internal::StringFromGTestEnv("color", "auto"),
+    "Whether to use colors in the output.  Valid values: yes, no, "
+    "and auto.  'auto' means to use colors if the output is "
+    "being sent to a terminal and the TERM environment variable "
+    "is set to a terminal type that supports colors.");
+
+GTEST_DEFINE_string_(
+    filter,
+    internal::StringFromGTestEnv("filter", GetDefaultFilter()),
+    "A colon-separated list of glob (not regex) patterns "
+    "for filtering the tests to run, optionally followed by a "
+    "'-' and a : separated list of negative patterns (tests to "
+    "exclude).  A test is run if it matches one of the positive "
+    "patterns and does not match any of the negative patterns.");
+
+GTEST_DEFINE_bool_(
+    install_failure_signal_handler,
+    internal::BoolFromGTestEnv("install_failure_signal_handler", false),
+    "If true and supported on the current platform, " GTEST_NAME_ " should "
+    "install a signal handler that dumps debugging information when fatal "
+    "signals are raised.");
+
+GTEST_DEFINE_bool_(list_tests, false,
+                   "List all tests without running them.");
+
+// The net priority order after flag processing is thus:
+//   --gtest_output command line flag
+//   GTEST_OUTPUT environment variable
+//   XML_OUTPUT_FILE environment variable
+//   ''
+GTEST_DEFINE_string_(
+    output,
+    internal::StringFromGTestEnv("output",
+      internal::OutputFlagAlsoCheckEnvVar().c_str()),
+    "A format (defaults to \"xml\" but can be specified to be \"json\"), "
+    "optionally followed by a colon and an output file name or directory. "
+    "A directory is indicated by a trailing pathname separator. "
+    "Examples: \"xml:filename.xml\", \"xml::directoryname/\". "
+    "If a directory is specified, output files will be created "
+    "within that directory, with file-names based on the test "
+    "executable's name and, if necessary, made unique by adding "
+    "digits.");
+
+GTEST_DEFINE_bool_(
+    brief, internal::BoolFromGTestEnv("brief", false),
+    "True if only test failures should be displayed in text output.");
+
+GTEST_DEFINE_bool_(print_time, internal::BoolFromGTestEnv("print_time", true),
+                   "True if and only if " GTEST_NAME_
+                   " should display elapsed time in text output.");
+
+GTEST_DEFINE_bool_(print_utf8, internal::BoolFromGTestEnv("print_utf8", true),
+                   "True if and only if " GTEST_NAME_
+                   " prints UTF8 characters as text.");
+
+GTEST_DEFINE_int32_(
+    random_seed,
+    internal::Int32FromGTestEnv("random_seed", 0),
+    "Random number seed to use when shuffling test orders.  Must be in range "
+    "[1, 99999], or 0 to use a seed based on the current time.");
+
+GTEST_DEFINE_int32_(
+    repeat,
+    internal::Int32FromGTestEnv("repeat", 1),
+    "How many times to repeat each test.  Specify a negative number "
+    "for repeating forever.  Useful for shaking out flaky tests.");
+
+GTEST_DEFINE_bool_(show_internal_stack_frames, false,
+                   "True if and only if " GTEST_NAME_
+                   " should include internal stack frames when "
+                   "printing test failure stack traces.");
+
+GTEST_DEFINE_bool_(shuffle, internal::BoolFromGTestEnv("shuffle", false),
+                   "True if and only if " GTEST_NAME_
+                   " should randomize tests' order on every run.");
+
+GTEST_DEFINE_int32_(
+    stack_trace_depth,
+    internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth),
+    "The maximum number of stack frames to print when an "
+    "assertion fails.  The valid range is 0 through 100, inclusive.");
+
+GTEST_DEFINE_string_(
+    stream_result_to,
+    internal::StringFromGTestEnv("stream_result_to", ""),
+    "This flag specifies the host name and the port number on which to stream "
+    "test results. Example: \"localhost:555\". The flag is effective only on "
+    "Linux.");
+
+GTEST_DEFINE_bool_(
+    throw_on_failure,
+    internal::BoolFromGTestEnv("throw_on_failure", false),
+    "When this flag is specified, a failed assertion will throw an exception "
+    "if exceptions are enabled or exit the program with a non-zero code "
+    "otherwise. For use with an external test framework.");
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+GTEST_DEFINE_string_(
+    flagfile,
+    internal::StringFromGTestEnv("flagfile", ""),
+    "This flag specifies the flagfile to read command-line flags from.");
+#endif  // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+namespace internal {
+
+// Generates a random number from [0, range), using a Linear
+// Congruential Generator (LCG).  Crashes if 'range' is 0 or greater
+// than kMaxRange.
+uint32_t Random::Generate(uint32_t range) {
+  // These constants are the same as are used in glibc's rand(3).
+  // Use wider types than necessary to prevent unsigned overflow diagnostics.
+  state_ = static_cast<uint32_t>(1103515245ULL*state_ + 12345U) % kMaxRange;
+
+  GTEST_CHECK_(range > 0)
+      << "Cannot generate a number in the range [0, 0).";
+  GTEST_CHECK_(range <= kMaxRange)
+      << "Generation of a number in [0, " << range << ") was requested, "
+      << "but this can only generate numbers in [0, " << kMaxRange << ").";
+
+  // Converting via modulus introduces a bit of downward bias, but
+  // it's simple, and a linear congruential generator isn't too good
+  // to begin with.
+  return state_ % range;
+}
+
+// GTestIsInitialized() returns true if and only if the user has initialized
+// Google Test.  Useful for catching the user mistake of not initializing
+// Google Test before calling RUN_ALL_TESTS().
+static bool GTestIsInitialized() { return GetArgvs().size() > 0; }
+
+// Iterates over a vector of TestSuites, keeping a running sum of the
+// results of calling a given int-returning method on each.
+// Returns the sum.
+static int SumOverTestSuiteList(const std::vector<TestSuite*>& case_list,
+                                int (TestSuite::*method)() const) {
+  int sum = 0;
+  for (size_t i = 0; i < case_list.size(); i++) {
+    sum += (case_list[i]->*method)();
+  }
+  return sum;
+}
+
+// Returns true if and only if the test suite passed.
+static bool TestSuitePassed(const TestSuite* test_suite) {
+  return test_suite->should_run() && test_suite->Passed();
+}
+
+// Returns true if and only if the test suite failed.
+static bool TestSuiteFailed(const TestSuite* test_suite) {
+  return test_suite->should_run() && test_suite->Failed();
+}
+
+// Returns true if and only if test_suite contains at least one test that
+// should run.
+static bool ShouldRunTestSuite(const TestSuite* test_suite) {
+  return test_suite->should_run();
+}
+
+// AssertHelper constructor.
+AssertHelper::AssertHelper(TestPartResult::Type type,
+                           const char* file,
+                           int line,
+                           const char* message)
+    : data_(new AssertHelperData(type, file, line, message)) {
+}
+
+AssertHelper::~AssertHelper() {
+  delete data_;
+}
+
+// Message assignment, for assertion streaming support.
+void AssertHelper::operator=(const Message& message) const {
+  UnitTest::GetInstance()->
+    AddTestPartResult(data_->type, data_->file, data_->line,
+                      AppendUserMessage(data_->message, message),
+                      UnitTest::GetInstance()->impl()
+                      ->CurrentOsStackTraceExceptTop(1)
+                      // Skips the stack frame for this function itself.
+                      );  // NOLINT
+}
+
+namespace {
+
+// When TEST_P is found without a matching INSTANTIATE_TEST_SUITE_P
+// to creates test cases for it, a syntetic test case is
+// inserted to report ether an error or a log message.
+//
+// This configuration bit will likely be removed at some point.
+constexpr bool kErrorOnUninstantiatedParameterizedTest = true;
+constexpr bool kErrorOnUninstantiatedTypeParameterizedTest = true;
+
+// A test that fails at a given file/line location with a given message.
+class FailureTest : public Test {
+ public:
+  explicit FailureTest(const CodeLocation& loc, std::string error_message,
+                       bool as_error)
+      : loc_(loc),
+        error_message_(std::move(error_message)),
+        as_error_(as_error) {}
+
+  void TestBody() override {
+    if (as_error_) {
+      AssertHelper(TestPartResult::kNonFatalFailure, loc_.file.c_str(),
+                   loc_.line, "") = Message() << error_message_;
+    } else {
+      std::cout << error_message_ << std::endl;
+    }
+  }
+
+ private:
+  const CodeLocation loc_;
+  const std::string error_message_;
+  const bool as_error_;
+};
+
+
+}  // namespace
+
+std::set<std::string>* GetIgnoredParameterizedTestSuites() {
+  return UnitTest::GetInstance()->impl()->ignored_parameterized_test_suites();
+}
+
+// Add a given test_suit to the list of them allow to go un-instantiated.
+MarkAsIgnored::MarkAsIgnored(const char* test_suite) {
+  GetIgnoredParameterizedTestSuites()->insert(test_suite);
+}
+
+// If this parameterized test suite has no instantiations (and that
+// has not been marked as okay), emit a test case reporting that.
+void InsertSyntheticTestCase(const std::string& name, CodeLocation location,
+                             bool has_test_p) {
+  const auto& ignored = *GetIgnoredParameterizedTestSuites();
+  if (ignored.find(name) != ignored.end()) return;
+
+  const char kMissingInstantiation[] =  //
+      " is defined via TEST_P, but never instantiated. None of the test cases "
+      "will run. Either no INSTANTIATE_TEST_SUITE_P is provided or the only "
+      "ones provided expand to nothing."
+      "\n\n"
+      "Ideally, TEST_P definitions should only ever be included as part of "
+      "binaries that intend to use them. (As opposed to, for example, being "
+      "placed in a library that may be linked in to get other utilities.)";
+
+  const char kMissingTestCase[] =  //
+      " is instantiated via INSTANTIATE_TEST_SUITE_P, but no tests are "
+      "defined via TEST_P . No test cases will run."
+      "\n\n"
+      "Ideally, INSTANTIATE_TEST_SUITE_P should only ever be invoked from "
+      "code that always depend on code that provides TEST_P. Failing to do "
+      "so is often an indication of dead code, e.g. the last TEST_P was "
+      "removed but the rest got left behind.";
+
+  std::string message =
+      "Parameterized test suite " + name +
+      (has_test_p ? kMissingInstantiation : kMissingTestCase) +
+      "\n\n"
+      "To suppress this error for this test suite, insert the following line "
+      "(in a non-header) in the namespace it is defined in:"
+      "\n\n"
+      "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" + name + ");";
+
+  std::string full_name = "UninstantiatedParameterizedTestSuite<" + name + ">";
+  RegisterTest(  //
+      "GoogleTestVerification", full_name.c_str(),
+      nullptr,  // No type parameter.
+      nullptr,  // No value parameter.
+      location.file.c_str(), location.line, [message, location] {
+        return new FailureTest(location, message,
+                               kErrorOnUninstantiatedParameterizedTest);
+      });
+}
+
+void RegisterTypeParameterizedTestSuite(const char* test_suite_name,
+                                        CodeLocation code_location) {
+  GetUnitTestImpl()->type_parameterized_test_registry().RegisterTestSuite(
+      test_suite_name, code_location);
+}
+
+void RegisterTypeParameterizedTestSuiteInstantiation(const char* case_name) {
+  GetUnitTestImpl()
+      ->type_parameterized_test_registry()
+      .RegisterInstantiation(case_name);
+}
+
+void TypeParameterizedTestSuiteRegistry::RegisterTestSuite(
+    const char* test_suite_name, CodeLocation code_location) {
+  suites_.emplace(std::string(test_suite_name),
+                 TypeParameterizedTestSuiteInfo(code_location));
+}
+
+void TypeParameterizedTestSuiteRegistry::RegisterInstantiation(
+        const char* test_suite_name) {
+  auto it = suites_.find(std::string(test_suite_name));
+  if (it != suites_.end()) {
+    it->second.instantiated = true;
+  } else {
+    GTEST_LOG_(ERROR) << "Unknown type parameterized test suit '"
+                      << test_suite_name << "'";
+  }
+}
+
+void TypeParameterizedTestSuiteRegistry::CheckForInstantiations() {
+  const auto& ignored = *GetIgnoredParameterizedTestSuites();
+  for (const auto& testcase : suites_) {
+    if (testcase.second.instantiated) continue;
+    if (ignored.find(testcase.first) != ignored.end()) continue;
+
+    std::string message =
+        "Type parameterized test suite " + testcase.first +
+        " is defined via REGISTER_TYPED_TEST_SUITE_P, but never instantiated "
+        "via INSTANTIATE_TYPED_TEST_SUITE_P. None of the test cases will run."
+        "\n\n"
+        "Ideally, TYPED_TEST_P definitions should only ever be included as "
+        "part of binaries that intend to use them. (As opposed to, for "
+        "example, being placed in a library that may be linked in to get other "
+        "utilities.)"
+        "\n\n"
+        "To suppress this error for this test suite, insert the following line "
+        "(in a non-header) in the namespace it is defined in:"
+        "\n\n"
+        "GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(" +
+        testcase.first + ");";
+
+    std::string full_name =
+        "UninstantiatedTypeParameterizedTestSuite<" + testcase.first + ">";
+    RegisterTest(  //
+        "GoogleTestVerification", full_name.c_str(),
+        nullptr,  // No type parameter.
+        nullptr,  // No value parameter.
+        testcase.second.code_location.file.c_str(),
+        testcase.second.code_location.line, [message, testcase] {
+          return new FailureTest(testcase.second.code_location, message,
+                                 kErrorOnUninstantiatedTypeParameterizedTest);
+        });
+  }
+}
+
+// A copy of all command line arguments.  Set by InitGoogleTest().
+static ::std::vector<std::string> g_argvs;
+
+::std::vector<std::string> GetArgvs() {
+#if defined(GTEST_CUSTOM_GET_ARGVS_)
+  // GTEST_CUSTOM_GET_ARGVS_() may return a container of std::string or
+  // ::string. This code converts it to the appropriate type.
+  const auto& custom = GTEST_CUSTOM_GET_ARGVS_();
+  return ::std::vector<std::string>(custom.begin(), custom.end());
+#else   // defined(GTEST_CUSTOM_GET_ARGVS_)
+  return g_argvs;
+#endif  // defined(GTEST_CUSTOM_GET_ARGVS_)
+}
+
+// Returns the current application's name, removing directory path if that
+// is present.
+FilePath GetCurrentExecutableName() {
+  FilePath result;
+
+#if GTEST_OS_WINDOWS || GTEST_OS_OS2
+  result.Set(FilePath(GetArgvs()[0]).RemoveExtension("exe"));
+#else
+  result.Set(FilePath(GetArgvs()[0]));
+#endif  // GTEST_OS_WINDOWS
+
+  return result.RemoveDirectoryName();
+}
+
+// Functions for processing the gtest_output flag.
+
+// Returns the output format, or "" for normal printed output.
+std::string UnitTestOptions::GetOutputFormat() {
+  const char* const gtest_output_flag = GTEST_FLAG(output).c_str();
+  const char* const colon = strchr(gtest_output_flag, ':');
+  return (colon == nullptr)
+             ? std::string(gtest_output_flag)
+             : std::string(gtest_output_flag,
+                           static_cast<size_t>(colon - gtest_output_flag));
+}
+
+// Returns the name of the requested output file, or the default if none
+// was explicitly specified.
+std::string UnitTestOptions::GetAbsolutePathToOutputFile() {
+  const char* const gtest_output_flag = GTEST_FLAG(output).c_str();
+
+  std::string format = GetOutputFormat();
+  if (format.empty())
+    format = std::string(kDefaultOutputFormat);
+
+  const char* const colon = strchr(gtest_output_flag, ':');
+  if (colon == nullptr)
+    return internal::FilePath::MakeFileName(
+        internal::FilePath(
+            UnitTest::GetInstance()->original_working_dir()),
+        internal::FilePath(kDefaultOutputFile), 0,
+        format.c_str()).string();
+
+  internal::FilePath output_name(colon + 1);
+  if (!output_name.IsAbsolutePath())
+    output_name = internal::FilePath::ConcatPaths(
+        internal::FilePath(UnitTest::GetInstance()->original_working_dir()),
+        internal::FilePath(colon + 1));
+
+  if (!output_name.IsDirectory())
+    return output_name.string();
+
+  internal::FilePath result(internal::FilePath::GenerateUniqueFileName(
+      output_name, internal::GetCurrentExecutableName(),
+      GetOutputFormat().c_str()));
+  return result.string();
+}
+
+// Returns true if and only if the wildcard pattern matches the string. Each
+// pattern consists of regular characters, single-character wildcards (?), and
+// multi-character wildcards (*).
+//
+// This function implements a linear-time string globbing algorithm based on
+// https://research.swtch.com/glob.
+static bool PatternMatchesString(const std::string& name_str,
+                                 const char* pattern, const char* pattern_end) {
+  const char* name = name_str.c_str();
+  const char* const name_begin = name;
+  const char* const name_end = name + name_str.size();
+
+  const char* pattern_next = pattern;
+  const char* name_next = name;
+
+  while (pattern < pattern_end || name < name_end) {
+    if (pattern < pattern_end) {
+      switch (*pattern) {
+        default:  // Match an ordinary character.
+          if (name < name_end && *name == *pattern) {
+            ++pattern;
+            ++name;
+            continue;
+          }
+          break;
+        case '?':  // Match any single character.
+          if (name < name_end) {
+            ++pattern;
+            ++name;
+            continue;
+          }
+          break;
+        case '*':
+          // Match zero or more characters. Start by skipping over the wildcard
+          // and matching zero characters from name. If that fails, restart and
+          // match one more character than the last attempt.
+          pattern_next = pattern;
+          name_next = name + 1;
+          ++pattern;
+          continue;
+      }
+    }
+    // Failed to match a character. Restart if possible.
+    if (name_begin < name_next && name_next <= name_end) {
+      pattern = pattern_next;
+      name = name_next;
+      continue;
+    }
+    return false;
+  }
+  return true;
+}
+
+bool UnitTestOptions::MatchesFilter(const std::string& name_str,
+                                    const char* filter) {
+  // The filter is a list of patterns separated by colons (:).
+  const char* pattern = filter;
+  while (true) {
+    // Find the bounds of this pattern.
+    const char* const next_sep = strchr(pattern, ':');
+    const char* const pattern_end =
+        next_sep != nullptr ? next_sep : pattern + strlen(pattern);
+
+    // Check if this pattern matches name_str.
+    if (PatternMatchesString(name_str, pattern, pattern_end)) {
+      return true;
+    }
+
+    // Give up on this pattern. However, if we found a pattern separator (:),
+    // advance to the next pattern (skipping over the separator) and restart.
+    if (next_sep == nullptr) {
+      return false;
+    }
+    pattern = next_sep + 1;
+  }
+  return true;
+}
+
+// Returns true if and only if the user-specified filter matches the test
+// suite name and the test name.
+bool UnitTestOptions::FilterMatchesTest(const std::string& test_suite_name,
+                                        const std::string& test_name) {
+  const std::string& full_name = test_suite_name + "." + test_name.c_str();
+
+  // Split --gtest_filter at '-', if there is one, to separate into
+  // positive filter and negative filter portions
+  const char* const p = GTEST_FLAG(filter).c_str();
+  const char* const dash = strchr(p, '-');
+  std::string positive;
+  std::string negative;
+  if (dash == nullptr) {
+    positive = GTEST_FLAG(filter).c_str();  // Whole string is a positive filter
+    negative = "";
+  } else {
+    positive = std::string(p, dash);   // Everything up to the dash
+    negative = std::string(dash + 1);  // Everything after the dash
+    if (positive.empty()) {
+      // Treat '-test1' as the same as '*-test1'
+      positive = kUniversalFilter;
+    }
+  }
+
+  // A filter is a colon-separated list of patterns.  It matches a
+  // test if any pattern in it matches the test.
+  return (MatchesFilter(full_name, positive.c_str()) &&
+          !MatchesFilter(full_name, negative.c_str()));
+}
+
+#if GTEST_HAS_SEH
+// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the
+// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise.
+// This function is useful as an __except condition.
+int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) {
+  // Google Test should handle a SEH exception if:
+  //   1. the user wants it to, AND
+  //   2. this is not a breakpoint exception, AND
+  //   3. this is not a C++ exception (VC++ implements them via SEH,
+  //      apparently).
+  //
+  // SEH exception code for C++ exceptions.
+  // (see http://support.microsoft.com/kb/185294 for more information).
+  const DWORD kCxxExceptionCode = 0xe06d7363;
+
+  bool should_handle = true;
+
+  if (!GTEST_FLAG(catch_exceptions))
+    should_handle = false;
+  else if (exception_code == EXCEPTION_BREAKPOINT)
+    should_handle = false;
+  else if (exception_code == kCxxExceptionCode)
+    should_handle = false;
+
+  return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
+}
+#endif  // GTEST_HAS_SEH
+
+}  // namespace internal
+
+// The c'tor sets this object as the test part result reporter used by
+// Google Test.  The 'result' parameter specifies where to report the
+// results. Intercepts only failures from the current thread.
+ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter(
+    TestPartResultArray* result)
+    : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD),
+      result_(result) {
+  Init();
+}
+
+// The c'tor sets this object as the test part result reporter used by
+// Google Test.  The 'result' parameter specifies where to report the
+// results.
+ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter(
+    InterceptMode intercept_mode, TestPartResultArray* result)
+    : intercept_mode_(intercept_mode),
+      result_(result) {
+  Init();
+}
+
+void ScopedFakeTestPartResultReporter::Init() {
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  if (intercept_mode_ == INTERCEPT_ALL_THREADS) {
+    old_reporter_ = impl->GetGlobalTestPartResultReporter();
+    impl->SetGlobalTestPartResultReporter(this);
+  } else {
+    old_reporter_ = impl->GetTestPartResultReporterForCurrentThread();
+    impl->SetTestPartResultReporterForCurrentThread(this);
+  }
+}
+
+// The d'tor restores the test part result reporter used by Google Test
+// before.
+ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() {
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  if (intercept_mode_ == INTERCEPT_ALL_THREADS) {
+    impl->SetGlobalTestPartResultReporter(old_reporter_);
+  } else {
+    impl->SetTestPartResultReporterForCurrentThread(old_reporter_);
+  }
+}
+
+// Increments the test part result count and remembers the result.
+// This method is from the TestPartResultReporterInterface interface.
+void ScopedFakeTestPartResultReporter::ReportTestPartResult(
+    const TestPartResult& result) {
+  result_->Append(result);
+}
+
+namespace internal {
+
+// Returns the type ID of ::testing::Test.  We should always call this
+// instead of GetTypeId< ::testing::Test>() to get the type ID of
+// testing::Test.  This is to work around a suspected linker bug when
+// using Google Test as a framework on Mac OS X.  The bug causes
+// GetTypeId< ::testing::Test>() to return different values depending
+// on whether the call is from the Google Test framework itself or
+// from user test code.  GetTestTypeId() is guaranteed to always
+// return the same value, as it always calls GetTypeId<>() from the
+// gtest.cc, which is within the Google Test framework.
+TypeId GetTestTypeId() {
+  return GetTypeId<Test>();
+}
+
+// The value of GetTestTypeId() as seen from within the Google Test
+// library.  This is solely for testing GetTestTypeId().
+extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId();
+
+// This predicate-formatter checks that 'results' contains a test part
+// failure of the given type and that the failure message contains the
+// given substring.
+static AssertionResult HasOneFailure(const char* /* results_expr */,
+                                     const char* /* type_expr */,
+                                     const char* /* substr_expr */,
+                                     const TestPartResultArray& results,
+                                     TestPartResult::Type type,
+                                     const std::string& substr) {
+  const std::string expected(type == TestPartResult::kFatalFailure ?
+                        "1 fatal failure" :
+                        "1 non-fatal failure");
+  Message msg;
+  if (results.size() != 1) {
+    msg << "Expected: " << expected << "\n"
+        << "  Actual: " << results.size() << " failures";
+    for (int i = 0; i < results.size(); i++) {
+      msg << "\n" << results.GetTestPartResult(i);
+    }
+    return AssertionFailure() << msg;
+  }
+
+  const TestPartResult& r = results.GetTestPartResult(0);
+  if (r.type() != type) {
+    return AssertionFailure() << "Expected: " << expected << "\n"
+                              << "  Actual:\n"
+                              << r;
+  }
+
+  if (strstr(r.message(), substr.c_str()) == nullptr) {
+    return AssertionFailure() << "Expected: " << expected << " containing \""
+                              << substr << "\"\n"
+                              << "  Actual:\n"
+                              << r;
+  }
+
+  return AssertionSuccess();
+}
+
+// The constructor of SingleFailureChecker remembers where to look up
+// test part results, what type of failure we expect, and what
+// substring the failure message should contain.
+SingleFailureChecker::SingleFailureChecker(const TestPartResultArray* results,
+                                           TestPartResult::Type type,
+                                           const std::string& substr)
+    : results_(results), type_(type), substr_(substr) {}
+
+// The destructor of SingleFailureChecker verifies that the given
+// TestPartResultArray contains exactly one failure that has the given
+// type and contains the given substring.  If that's not the case, a
+// non-fatal failure will be generated.
+SingleFailureChecker::~SingleFailureChecker() {
+  EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_);
+}
+
+DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter(
+    UnitTestImpl* unit_test) : unit_test_(unit_test) {}
+
+void DefaultGlobalTestPartResultReporter::ReportTestPartResult(
+    const TestPartResult& result) {
+  unit_test_->current_test_result()->AddTestPartResult(result);
+  unit_test_->listeners()->repeater()->OnTestPartResult(result);
+}
+
+DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter(
+    UnitTestImpl* unit_test) : unit_test_(unit_test) {}
+
+void DefaultPerThreadTestPartResultReporter::ReportTestPartResult(
+    const TestPartResult& result) {
+  unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result);
+}
+
+// Returns the global test part result reporter.
+TestPartResultReporterInterface*
+UnitTestImpl::GetGlobalTestPartResultReporter() {
+  internal::MutexLock lock(&global_test_part_result_reporter_mutex_);
+  return global_test_part_result_repoter_;
+}
+
+// Sets the global test part result reporter.
+void UnitTestImpl::SetGlobalTestPartResultReporter(
+    TestPartResultReporterInterface* reporter) {
+  internal::MutexLock lock(&global_test_part_result_reporter_mutex_);
+  global_test_part_result_repoter_ = reporter;
+}
+
+// Returns the test part result reporter for the current thread.
+TestPartResultReporterInterface*
+UnitTestImpl::GetTestPartResultReporterForCurrentThread() {
+  return per_thread_test_part_result_reporter_.get();
+}
+
+// Sets the test part result reporter for the current thread.
+void UnitTestImpl::SetTestPartResultReporterForCurrentThread(
+    TestPartResultReporterInterface* reporter) {
+  per_thread_test_part_result_reporter_.set(reporter);
+}
+
+// Gets the number of successful test suites.
+int UnitTestImpl::successful_test_suite_count() const {
+  return CountIf(test_suites_, TestSuitePassed);
+}
+
+// Gets the number of failed test suites.
+int UnitTestImpl::failed_test_suite_count() const {
+  return CountIf(test_suites_, TestSuiteFailed);
+}
+
+// Gets the number of all test suites.
+int UnitTestImpl::total_test_suite_count() const {
+  return static_cast<int>(test_suites_.size());
+}
+
+// Gets the number of all test suites that contain at least one test
+// that should run.
+int UnitTestImpl::test_suite_to_run_count() const {
+  return CountIf(test_suites_, ShouldRunTestSuite);
+}
+
+// Gets the number of successful tests.
+int UnitTestImpl::successful_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::successful_test_count);
+}
+
+// Gets the number of skipped tests.
+int UnitTestImpl::skipped_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::skipped_test_count);
+}
+
+// Gets the number of failed tests.
+int UnitTestImpl::failed_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::failed_test_count);
+}
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int UnitTestImpl::reportable_disabled_test_count() const {
+  return SumOverTestSuiteList(test_suites_,
+                              &TestSuite::reportable_disabled_test_count);
+}
+
+// Gets the number of disabled tests.
+int UnitTestImpl::disabled_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::disabled_test_count);
+}
+
+// Gets the number of tests to be printed in the XML report.
+int UnitTestImpl::reportable_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::reportable_test_count);
+}
+
+// Gets the number of all tests.
+int UnitTestImpl::total_test_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::total_test_count);
+}
+
+// Gets the number of tests that should run.
+int UnitTestImpl::test_to_run_count() const {
+  return SumOverTestSuiteList(test_suites_, &TestSuite::test_to_run_count);
+}
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag.  The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// CurrentOsStackTraceExceptTop(1), Foo() will be included in the
+// trace but Bar() and CurrentOsStackTraceExceptTop() won't.
+std::string UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) {
+  return os_stack_trace_getter()->CurrentStackTrace(
+      static_cast<int>(GTEST_FLAG(stack_trace_depth)),
+      skip_count + 1
+      // Skips the user-specified number of frames plus this function
+      // itself.
+      );  // NOLINT
+}
+
+// A helper class for measuring elapsed times.
+class Timer {
+ public:
+  Timer() : start_(std::chrono::steady_clock::now()) {}
+
+  // Return time elapsed in milliseconds since the timer was created.
+  TimeInMillis Elapsed() {
+    return std::chrono::duration_cast<std::chrono::milliseconds>(
+               std::chrono::steady_clock::now() - start_)
+        .count();
+  }
+
+ private:
+  std::chrono::steady_clock::time_point start_;
+};
+
+// Returns a timestamp as milliseconds since the epoch. Note this time may jump
+// around subject to adjustments by the system, to measure elapsed time use
+// Timer instead.
+TimeInMillis GetTimeInMillis() {
+  return std::chrono::duration_cast<std::chrono::milliseconds>(
+             std::chrono::system_clock::now() -
+             std::chrono::system_clock::from_time_t(0))
+      .count();
+}
+
+// Utilities
+
+// class String.
+
+#if GTEST_OS_WINDOWS_MOBILE
+// Creates a UTF-16 wide string from the given ANSI string, allocating
+// memory using new. The caller is responsible for deleting the return
+// value using delete[]. Returns the wide string, or NULL if the
+// input is NULL.
+LPCWSTR String::AnsiToUtf16(const char* ansi) {
+  if (!ansi) return nullptr;
+  const int length = strlen(ansi);
+  const int unicode_length =
+      MultiByteToWideChar(CP_ACP, 0, ansi, length, nullptr, 0);
+  WCHAR* unicode = new WCHAR[unicode_length + 1];
+  MultiByteToWideChar(CP_ACP, 0, ansi, length,
+                      unicode, unicode_length);
+  unicode[unicode_length] = 0;
+  return unicode;
+}
+
+// Creates an ANSI string from the given wide string, allocating
+// memory using new. The caller is responsible for deleting the return
+// value using delete[]. Returns the ANSI string, or NULL if the
+// input is NULL.
+const char* String::Utf16ToAnsi(LPCWSTR utf16_str)  {
+  if (!utf16_str) return nullptr;
+  const int ansi_length = WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, nullptr,
+                                              0, nullptr, nullptr);
+  char* ansi = new char[ansi_length + 1];
+  WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, ansi, ansi_length, nullptr,
+                      nullptr);
+  ansi[ansi_length] = 0;
+  return ansi;
+}
+
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+// Compares two C strings.  Returns true if and only if they have the same
+// content.
+//
+// Unlike strcmp(), this function can handle NULL argument(s).  A NULL
+// C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::CStringEquals(const char * lhs, const char * rhs) {
+  if (lhs == nullptr) return rhs == nullptr;
+
+  if (rhs == nullptr) return false;
+
+  return strcmp(lhs, rhs) == 0;
+}
+
+#if GTEST_HAS_STD_WSTRING
+
+// Converts an array of wide chars to a narrow string using the UTF-8
+// encoding, and streams the result to the given Message object.
+static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length,
+                                     Message* msg) {
+  for (size_t i = 0; i != length; ) {  // NOLINT
+    if (wstr[i] != L'\0') {
+      *msg << WideStringToUtf8(wstr + i, static_cast<int>(length - i));
+      while (i != length && wstr[i] != L'\0')
+        i++;
+    } else {
+      *msg << '\0';
+      i++;
+    }
+  }
+}
+
+#endif  // GTEST_HAS_STD_WSTRING
+
+void SplitString(const ::std::string& str, char delimiter,
+                 ::std::vector< ::std::string>* dest) {
+  ::std::vector< ::std::string> parsed;
+  ::std::string::size_type pos = 0;
+  while (::testing::internal::AlwaysTrue()) {
+    const ::std::string::size_type colon = str.find(delimiter, pos);
+    if (colon == ::std::string::npos) {
+      parsed.push_back(str.substr(pos));
+      break;
+    } else {
+      parsed.push_back(str.substr(pos, colon - pos));
+      pos = colon + 1;
+    }
+  }
+  dest->swap(parsed);
+}
+
+}  // namespace internal
+
+// Constructs an empty Message.
+// We allocate the stringstream separately because otherwise each use of
+// ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's
+// stack frame leading to huge stack frames in some cases; gcc does not reuse
+// the stack space.
+Message::Message() : ss_(new ::std::stringstream) {
+  // By default, we want there to be enough precision when printing
+  // a double to a Message.
+  *ss_ << std::setprecision(std::numeric_limits<double>::digits10 + 2);
+}
+
+// These two overloads allow streaming a wide C string to a Message
+// using the UTF-8 encoding.
+Message& Message::operator <<(const wchar_t* wide_c_str) {
+  return *this << internal::String::ShowWideCString(wide_c_str);
+}
+Message& Message::operator <<(wchar_t* wide_c_str) {
+  return *this << internal::String::ShowWideCString(wide_c_str);
+}
+
+#if GTEST_HAS_STD_WSTRING
+// Converts the given wide string to a narrow string using the UTF-8
+// encoding, and streams the result to this Message object.
+Message& Message::operator <<(const ::std::wstring& wstr) {
+  internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this);
+  return *this;
+}
+#endif  // GTEST_HAS_STD_WSTRING
+
+// Gets the text streamed to this object so far as an std::string.
+// Each '\0' character in the buffer is replaced with "\\0".
+std::string Message::GetString() const {
+  return internal::StringStreamToString(ss_.get());
+}
+
+// AssertionResult constructors.
+// Used in EXPECT_TRUE/FALSE(assertion_result).
+AssertionResult::AssertionResult(const AssertionResult& other)
+    : success_(other.success_),
+      message_(other.message_.get() != nullptr
+                   ? new ::std::string(*other.message_)
+                   : static_cast< ::std::string*>(nullptr)) {}
+
+// Swaps two AssertionResults.
+void AssertionResult::swap(AssertionResult& other) {
+  using std::swap;
+  swap(success_, other.success_);
+  swap(message_, other.message_);
+}
+
+// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
+AssertionResult AssertionResult::operator!() const {
+  AssertionResult negation(!success_);
+  if (message_.get() != nullptr) negation << *message_;
+  return negation;
+}
+
+// Makes a successful assertion result.
+AssertionResult AssertionSuccess() {
+  return AssertionResult(true);
+}
+
+// Makes a failed assertion result.
+AssertionResult AssertionFailure() {
+  return AssertionResult(false);
+}
+
+// Makes a failed assertion result with the given failure message.
+// Deprecated; use AssertionFailure() << message.
+AssertionResult AssertionFailure(const Message& message) {
+  return AssertionFailure() << message;
+}
+
+namespace internal {
+
+namespace edit_distance {
+std::vector<EditType> CalculateOptimalEdits(const std::vector<size_t>& left,
+                                            const std::vector<size_t>& right) {
+  std::vector<std::vector<double> > costs(
+      left.size() + 1, std::vector<double>(right.size() + 1));
+  std::vector<std::vector<EditType> > best_move(
+      left.size() + 1, std::vector<EditType>(right.size() + 1));
+
+  // Populate for empty right.
+  for (size_t l_i = 0; l_i < costs.size(); ++l_i) {
+    costs[l_i][0] = static_cast<double>(l_i);
+    best_move[l_i][0] = kRemove;
+  }
+  // Populate for empty left.
+  for (size_t r_i = 1; r_i < costs[0].size(); ++r_i) {
+    costs[0][r_i] = static_cast<double>(r_i);
+    best_move[0][r_i] = kAdd;
+  }
+
+  for (size_t l_i = 0; l_i < left.size(); ++l_i) {
+    for (size_t r_i = 0; r_i < right.size(); ++r_i) {
+      if (left[l_i] == right[r_i]) {
+        // Found a match. Consume it.
+        costs[l_i + 1][r_i + 1] = costs[l_i][r_i];
+        best_move[l_i + 1][r_i + 1] = kMatch;
+        continue;
+      }
+
+      const double add = costs[l_i + 1][r_i];
+      const double remove = costs[l_i][r_i + 1];
+      const double replace = costs[l_i][r_i];
+      if (add < remove && add < replace) {
+        costs[l_i + 1][r_i + 1] = add + 1;
+        best_move[l_i + 1][r_i + 1] = kAdd;
+      } else if (remove < add && remove < replace) {
+        costs[l_i + 1][r_i + 1] = remove + 1;
+        best_move[l_i + 1][r_i + 1] = kRemove;
+      } else {
+        // We make replace a little more expensive than add/remove to lower
+        // their priority.
+        costs[l_i + 1][r_i + 1] = replace + 1.00001;
+        best_move[l_i + 1][r_i + 1] = kReplace;
+      }
+    }
+  }
+
+  // Reconstruct the best path. We do it in reverse order.
+  std::vector<EditType> best_path;
+  for (size_t l_i = left.size(), r_i = right.size(); l_i > 0 || r_i > 0;) {
+    EditType move = best_move[l_i][r_i];
+    best_path.push_back(move);
+    l_i -= move != kAdd;
+    r_i -= move != kRemove;
+  }
+  std::reverse(best_path.begin(), best_path.end());
+  return best_path;
+}
+
+namespace {
+
+// Helper class to convert string into ids with deduplication.
+class InternalStrings {
+ public:
+  size_t GetId(const std::string& str) {
+    IdMap::iterator it = ids_.find(str);
+    if (it != ids_.end()) return it->second;
+    size_t id = ids_.size();
+    return ids_[str] = id;
+  }
+
+ private:
+  typedef std::map<std::string, size_t> IdMap;
+  IdMap ids_;
+};
+
+}  // namespace
+
+std::vector<EditType> CalculateOptimalEdits(
+    const std::vector<std::string>& left,
+    const std::vector<std::string>& right) {
+  std::vector<size_t> left_ids, right_ids;
+  {
+    InternalStrings intern_table;
+    for (size_t i = 0; i < left.size(); ++i) {
+      left_ids.push_back(intern_table.GetId(left[i]));
+    }
+    for (size_t i = 0; i < right.size(); ++i) {
+      right_ids.push_back(intern_table.GetId(right[i]));
+    }
+  }
+  return CalculateOptimalEdits(left_ids, right_ids);
+}
+
+namespace {
+
+// Helper class that holds the state for one hunk and prints it out to the
+// stream.
+// It reorders adds/removes when possible to group all removes before all
+// adds. It also adds the hunk header before printint into the stream.
+class Hunk {
+ public:
+  Hunk(size_t left_start, size_t right_start)
+      : left_start_(left_start),
+        right_start_(right_start),
+        adds_(),
+        removes_(),
+        common_() {}
+
+  void PushLine(char edit, const char* line) {
+    switch (edit) {
+      case ' ':
+        ++common_;
+        FlushEdits();
+        hunk_.push_back(std::make_pair(' ', line));
+        break;
+      case '-':
+        ++removes_;
+        hunk_removes_.push_back(std::make_pair('-', line));
+        break;
+      case '+':
+        ++adds_;
+        hunk_adds_.push_back(std::make_pair('+', line));
+        break;
+    }
+  }
+
+  void PrintTo(std::ostream* os) {
+    PrintHeader(os);
+    FlushEdits();
+    for (std::list<std::pair<char, const char*> >::const_iterator it =
+             hunk_.begin();
+         it != hunk_.end(); ++it) {
+      *os << it->first << it->second << "\n";
+    }
+  }
+
+  bool has_edits() const { return adds_ || removes_; }
+
+ private:
+  void FlushEdits() {
+    hunk_.splice(hunk_.end(), hunk_removes_);
+    hunk_.splice(hunk_.end(), hunk_adds_);
+  }
+
+  // Print a unified diff header for one hunk.
+  // The format is
+  //   "@@ -<left_start>,<left_length> +<right_start>,<right_length> @@"
+  // where the left/right parts are omitted if unnecessary.
+  void PrintHeader(std::ostream* ss) const {
+    *ss << "@@ ";
+    if (removes_) {
+      *ss << "-" << left_start_ << "," << (removes_ + common_);
+    }
+    if (removes_ && adds_) {
+      *ss << " ";
+    }
+    if (adds_) {
+      *ss << "+" << right_start_ << "," << (adds_ + common_);
+    }
+    *ss << " @@\n";
+  }
+
+  size_t left_start_, right_start_;
+  size_t adds_, removes_, common_;
+  std::list<std::pair<char, const char*> > hunk_, hunk_adds_, hunk_removes_;
+};
+
+}  // namespace
+
+// Create a list of diff hunks in Unified diff format.
+// Each hunk has a header generated by PrintHeader above plus a body with
+// lines prefixed with ' ' for no change, '-' for deletion and '+' for
+// addition.
+// 'context' represents the desired unchanged prefix/suffix around the diff.
+// If two hunks are close enough that their contexts overlap, then they are
+// joined into one hunk.
+std::string CreateUnifiedDiff(const std::vector<std::string>& left,
+                              const std::vector<std::string>& right,
+                              size_t context) {
+  const std::vector<EditType> edits = CalculateOptimalEdits(left, right);
+
+  size_t l_i = 0, r_i = 0, edit_i = 0;
+  std::stringstream ss;
+  while (edit_i < edits.size()) {
+    // Find first edit.
+    while (edit_i < edits.size() && edits[edit_i] == kMatch) {
+      ++l_i;
+      ++r_i;
+      ++edit_i;
+    }
+
+    // Find the first line to include in the hunk.
+    const size_t prefix_context = std::min(l_i, context);
+    Hunk hunk(l_i - prefix_context + 1, r_i - prefix_context + 1);
+    for (size_t i = prefix_context; i > 0; --i) {
+      hunk.PushLine(' ', left[l_i - i].c_str());
+    }
+
+    // Iterate the edits until we found enough suffix for the hunk or the input
+    // is over.
+    size_t n_suffix = 0;
+    for (; edit_i < edits.size(); ++edit_i) {
+      if (n_suffix >= context) {
+        // Continue only if the next hunk is very close.
+        auto it = edits.begin() + static_cast<int>(edit_i);
+        while (it != edits.end() && *it == kMatch) ++it;
+        if (it == edits.end() ||
+            static_cast<size_t>(it - edits.begin()) - edit_i >= context) {
+          // There is no next edit or it is too far away.
+          break;
+        }
+      }
+
+      EditType edit = edits[edit_i];
+      // Reset count when a non match is found.
+      n_suffix = edit == kMatch ? n_suffix + 1 : 0;
+
+      if (edit == kMatch || edit == kRemove || edit == kReplace) {
+        hunk.PushLine(edit == kMatch ? ' ' : '-', left[l_i].c_str());
+      }
+      if (edit == kAdd || edit == kReplace) {
+        hunk.PushLine('+', right[r_i].c_str());
+      }
+
+      // Advance indices, depending on edit type.
+      l_i += edit != kAdd;
+      r_i += edit != kRemove;
+    }
+
+    if (!hunk.has_edits()) {
+      // We are done. We don't want this hunk.
+      break;
+    }
+
+    hunk.PrintTo(&ss);
+  }
+  return ss.str();
+}
+
+}  // namespace edit_distance
+
+namespace {
+
+// The string representation of the values received in EqFailure() are already
+// escaped. Split them on escaped '\n' boundaries. Leave all other escaped
+// characters the same.
+std::vector<std::string> SplitEscapedString(const std::string& str) {
+  std::vector<std::string> lines;
+  size_t start = 0, end = str.size();
+  if (end > 2 && str[0] == '"' && str[end - 1] == '"') {
+    ++start;
+    --end;
+  }
+  bool escaped = false;
+  for (size_t i = start; i + 1 < end; ++i) {
+    if (escaped) {
+      escaped = false;
+      if (str[i] == 'n') {
+        lines.push_back(str.substr(start, i - start - 1));
+        start = i + 1;
+      }
+    } else {
+      escaped = str[i] == '\\';
+    }
+  }
+  lines.push_back(str.substr(start, end - start));
+  return lines;
+}
+
+}  // namespace
+
+// Constructs and returns the message for an equality assertion
+// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure.
+//
+// The first four parameters are the expressions used in the assertion
+// and their values, as strings.  For example, for ASSERT_EQ(foo, bar)
+// where foo is 5 and bar is 6, we have:
+//
+//   lhs_expression: "foo"
+//   rhs_expression: "bar"
+//   lhs_value:      "5"
+//   rhs_value:      "6"
+//
+// The ignoring_case parameter is true if and only if the assertion is a
+// *_STRCASEEQ*.  When it's true, the string "Ignoring case" will
+// be inserted into the message.
+AssertionResult EqFailure(const char* lhs_expression,
+                          const char* rhs_expression,
+                          const std::string& lhs_value,
+                          const std::string& rhs_value,
+                          bool ignoring_case) {
+  Message msg;
+  msg << "Expected equality of these values:";
+  msg << "\n  " << lhs_expression;
+  if (lhs_value != lhs_expression) {
+    msg << "\n    Which is: " << lhs_value;
+  }
+  msg << "\n  " << rhs_expression;
+  if (rhs_value != rhs_expression) {
+    msg << "\n    Which is: " << rhs_value;
+  }
+
+  if (ignoring_case) {
+    msg << "\nIgnoring case";
+  }
+
+  if (!lhs_value.empty() && !rhs_value.empty()) {
+    const std::vector<std::string> lhs_lines =
+        SplitEscapedString(lhs_value);
+    const std::vector<std::string> rhs_lines =
+        SplitEscapedString(rhs_value);
+    if (lhs_lines.size() > 1 || rhs_lines.size() > 1) {
+      msg << "\nWith diff:\n"
+          << edit_distance::CreateUnifiedDiff(lhs_lines, rhs_lines);
+    }
+  }
+
+  return AssertionFailure() << msg;
+}
+
+// Constructs a failure message for Boolean assertions such as EXPECT_TRUE.
+std::string GetBoolAssertionFailureMessage(
+    const AssertionResult& assertion_result,
+    const char* expression_text,
+    const char* actual_predicate_value,
+    const char* expected_predicate_value) {
+  const char* actual_message = assertion_result.message();
+  Message msg;
+  msg << "Value of: " << expression_text
+      << "\n  Actual: " << actual_predicate_value;
+  if (actual_message[0] != '\0')
+    msg << " (" << actual_message << ")";
+  msg << "\nExpected: " << expected_predicate_value;
+  return msg.GetString();
+}
+
+// Helper function for implementing ASSERT_NEAR.
+AssertionResult DoubleNearPredFormat(const char* expr1,
+                                     const char* expr2,
+                                     const char* abs_error_expr,
+                                     double val1,
+                                     double val2,
+                                     double abs_error) {
+  const double diff = fabs(val1 - val2);
+  if (diff <= abs_error) return AssertionSuccess();
+
+  // Find the value which is closest to zero.
+  const double min_abs = std::min(fabs(val1), fabs(val2));
+  // Find the distance to the next double from that value.
+  const double epsilon =
+      nextafter(min_abs, std::numeric_limits<double>::infinity()) - min_abs;
+  // Detect the case where abs_error is so small that EXPECT_NEAR is
+  // effectively the same as EXPECT_EQUAL, and give an informative error
+  // message so that the situation can be more easily understood without
+  // requiring exotic floating-point knowledge.
+  // Don't do an epsilon check if abs_error is zero because that implies
+  // that an equality check was actually intended.
+  if (!(std::isnan)(val1) && !(std::isnan)(val2) && abs_error > 0 &&
+      abs_error < epsilon) {
+    return AssertionFailure()
+           << "The difference between " << expr1 << " and " << expr2 << " is "
+           << diff << ", where\n"
+           << expr1 << " evaluates to " << val1 << ",\n"
+           << expr2 << " evaluates to " << val2 << ".\nThe abs_error parameter "
+           << abs_error_expr << " evaluates to " << abs_error
+           << " which is smaller than the minimum distance between doubles for "
+              "numbers of this magnitude which is "
+           << epsilon
+           << ", thus making this EXPECT_NEAR check equivalent to "
+              "EXPECT_EQUAL. Consider using EXPECT_DOUBLE_EQ instead.";
+  }
+  return AssertionFailure()
+      << "The difference between " << expr1 << " and " << expr2
+      << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n"
+      << expr1 << " evaluates to " << val1 << ",\n"
+      << expr2 << " evaluates to " << val2 << ", and\n"
+      << abs_error_expr << " evaluates to " << abs_error << ".";
+}
+
+
+// Helper template for implementing FloatLE() and DoubleLE().
+template <typename RawType>
+AssertionResult FloatingPointLE(const char* expr1,
+                                const char* expr2,
+                                RawType val1,
+                                RawType val2) {
+  // Returns success if val1 is less than val2,
+  if (val1 < val2) {
+    return AssertionSuccess();
+  }
+
+  // or if val1 is almost equal to val2.
+  const FloatingPoint<RawType> lhs(val1), rhs(val2);
+  if (lhs.AlmostEquals(rhs)) {
+    return AssertionSuccess();
+  }
+
+  // Note that the above two checks will both fail if either val1 or
+  // val2 is NaN, as the IEEE floating-point standard requires that
+  // any predicate involving a NaN must return false.
+
+  ::std::stringstream val1_ss;
+  val1_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+          << val1;
+
+  ::std::stringstream val2_ss;
+  val2_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+          << val2;
+
+  return AssertionFailure()
+      << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n"
+      << "  Actual: " << StringStreamToString(&val1_ss) << " vs "
+      << StringStreamToString(&val2_ss);
+}
+
+}  // namespace internal
+
+// Asserts that val1 is less than, or almost equal to, val2.  Fails
+// otherwise.  In particular, it fails if either val1 or val2 is NaN.
+AssertionResult FloatLE(const char* expr1, const char* expr2,
+                        float val1, float val2) {
+  return internal::FloatingPointLE<float>(expr1, expr2, val1, val2);
+}
+
+// Asserts that val1 is less than, or almost equal to, val2.  Fails
+// otherwise.  In particular, it fails if either val1 or val2 is NaN.
+AssertionResult DoubleLE(const char* expr1, const char* expr2,
+                         double val1, double val2) {
+  return internal::FloatingPointLE<double>(expr1, expr2, val1, val2);
+}
+
+namespace internal {
+
+// The helper function for {ASSERT|EXPECT}_STREQ.
+AssertionResult CmpHelperSTREQ(const char* lhs_expression,
+                               const char* rhs_expression,
+                               const char* lhs,
+                               const char* rhs) {
+  if (String::CStringEquals(lhs, rhs)) {
+    return AssertionSuccess();
+  }
+
+  return EqFailure(lhs_expression,
+                   rhs_expression,
+                   PrintToString(lhs),
+                   PrintToString(rhs),
+                   false);
+}
+
+// The helper function for {ASSERT|EXPECT}_STRCASEEQ.
+AssertionResult CmpHelperSTRCASEEQ(const char* lhs_expression,
+                                   const char* rhs_expression,
+                                   const char* lhs,
+                                   const char* rhs) {
+  if (String::CaseInsensitiveCStringEquals(lhs, rhs)) {
+    return AssertionSuccess();
+  }
+
+  return EqFailure(lhs_expression,
+                   rhs_expression,
+                   PrintToString(lhs),
+                   PrintToString(rhs),
+                   true);
+}
+
+// The helper function for {ASSERT|EXPECT}_STRNE.
+AssertionResult CmpHelperSTRNE(const char* s1_expression,
+                               const char* s2_expression,
+                               const char* s1,
+                               const char* s2) {
+  if (!String::CStringEquals(s1, s2)) {
+    return AssertionSuccess();
+  } else {
+    return AssertionFailure() << "Expected: (" << s1_expression << ") != ("
+                              << s2_expression << "), actual: \""
+                              << s1 << "\" vs \"" << s2 << "\"";
+  }
+}
+
+// The helper function for {ASSERT|EXPECT}_STRCASENE.
+AssertionResult CmpHelperSTRCASENE(const char* s1_expression,
+                                   const char* s2_expression,
+                                   const char* s1,
+                                   const char* s2) {
+  if (!String::CaseInsensitiveCStringEquals(s1, s2)) {
+    return AssertionSuccess();
+  } else {
+    return AssertionFailure()
+        << "Expected: (" << s1_expression << ") != ("
+        << s2_expression << ") (ignoring case), actual: \""
+        << s1 << "\" vs \"" << s2 << "\"";
+  }
+}
+
+}  // namespace internal
+
+namespace {
+
+// Helper functions for implementing IsSubString() and IsNotSubstring().
+
+// This group of overloaded functions return true if and only if needle
+// is a substring of haystack.  NULL is considered a substring of
+// itself only.
+
+bool IsSubstringPred(const char* needle, const char* haystack) {
+  if (needle == nullptr || haystack == nullptr) return needle == haystack;
+
+  return strstr(haystack, needle) != nullptr;
+}
+
+bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) {
+  if (needle == nullptr || haystack == nullptr) return needle == haystack;
+
+  return wcsstr(haystack, needle) != nullptr;
+}
+
+// StringType here can be either ::std::string or ::std::wstring.
+template <typename StringType>
+bool IsSubstringPred(const StringType& needle,
+                     const StringType& haystack) {
+  return haystack.find(needle) != StringType::npos;
+}
+
+// This function implements either IsSubstring() or IsNotSubstring(),
+// depending on the value of the expected_to_be_substring parameter.
+// StringType here can be const char*, const wchar_t*, ::std::string,
+// or ::std::wstring.
+template <typename StringType>
+AssertionResult IsSubstringImpl(
+    bool expected_to_be_substring,
+    const char* needle_expr, const char* haystack_expr,
+    const StringType& needle, const StringType& haystack) {
+  if (IsSubstringPred(needle, haystack) == expected_to_be_substring)
+    return AssertionSuccess();
+
+  const bool is_wide_string = sizeof(needle[0]) > 1;
+  const char* const begin_string_quote = is_wide_string ? "L\"" : "\"";
+  return AssertionFailure()
+      << "Value of: " << needle_expr << "\n"
+      << "  Actual: " << begin_string_quote << needle << "\"\n"
+      << "Expected: " << (expected_to_be_substring ? "" : "not ")
+      << "a substring of " << haystack_expr << "\n"
+      << "Which is: " << begin_string_quote << haystack << "\"";
+}
+
+}  // namespace
+
+// IsSubstring() and IsNotSubstring() check whether needle is a
+// substring of haystack (NULL is considered a substring of itself
+// only), and return an appropriate error message when they fail.
+
+AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const char* needle, const char* haystack) {
+  return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const wchar_t* needle, const wchar_t* haystack) {
+  return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const char* needle, const char* haystack) {
+  return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const wchar_t* needle, const wchar_t* haystack) {
+  return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::string& needle, const ::std::string& haystack) {
+  return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::string& needle, const ::std::string& haystack) {
+  return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+
+#if GTEST_HAS_STD_WSTRING
+AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::wstring& needle, const ::std::wstring& haystack) {
+  return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack);
+}
+
+AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::wstring& needle, const ::std::wstring& haystack) {
+  return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack);
+}
+#endif  // GTEST_HAS_STD_WSTRING
+
+namespace internal {
+
+#if GTEST_OS_WINDOWS
+
+namespace {
+
+// Helper function for IsHRESULT{SuccessFailure} predicates
+AssertionResult HRESULTFailureHelper(const char* expr,
+                                     const char* expected,
+                                     long hr) {  // NOLINT
+# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_TV_TITLE
+
+  // Windows CE doesn't support FormatMessage.
+  const char error_text[] = "";
+
+# else
+
+  // Looks up the human-readable system message for the HRESULT code
+  // and since we're not passing any params to FormatMessage, we don't
+  // want inserts expanded.
+  const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM |
+                       FORMAT_MESSAGE_IGNORE_INSERTS;
+  const DWORD kBufSize = 4096;
+  // Gets the system's human readable message string for this HRESULT.
+  char error_text[kBufSize] = { '\0' };
+  DWORD message_length = ::FormatMessageA(kFlags,
+                                          0,   // no source, we're asking system
+                                          static_cast<DWORD>(hr),  // the error
+                                          0,   // no line width restrictions
+                                          error_text,  // output buffer
+                                          kBufSize,    // buf size
+                                          nullptr);  // no arguments for inserts
+  // Trims tailing white space (FormatMessage leaves a trailing CR-LF)
+  for (; message_length && IsSpace(error_text[message_length - 1]);
+          --message_length) {
+    error_text[message_length - 1] = '\0';
+  }
+
+# endif  // GTEST_OS_WINDOWS_MOBILE
+
+  const std::string error_hex("0x" + String::FormatHexInt(hr));
+  return ::testing::AssertionFailure()
+      << "Expected: " << expr << " " << expected << ".\n"
+      << "  Actual: " << error_hex << " " << error_text << "\n";
+}
+
+}  // namespace
+
+AssertionResult IsHRESULTSuccess(const char* expr, long hr) {  // NOLINT
+  if (SUCCEEDED(hr)) {
+    return AssertionSuccess();
+  }
+  return HRESULTFailureHelper(expr, "succeeds", hr);
+}
+
+AssertionResult IsHRESULTFailure(const char* expr, long hr) {  // NOLINT
+  if (FAILED(hr)) {
+    return AssertionSuccess();
+  }
+  return HRESULTFailureHelper(expr, "fails", hr);
+}
+
+#endif  // GTEST_OS_WINDOWS
+
+// Utility functions for encoding Unicode text (wide strings) in
+// UTF-8.
+
+// A Unicode code-point can have up to 21 bits, and is encoded in UTF-8
+// like this:
+//
+// Code-point length   Encoding
+//   0 -  7 bits       0xxxxxxx
+//   8 - 11 bits       110xxxxx 10xxxxxx
+//  12 - 16 bits       1110xxxx 10xxxxxx 10xxxxxx
+//  17 - 21 bits       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+
+// The maximum code-point a one-byte UTF-8 sequence can represent.
+constexpr uint32_t kMaxCodePoint1 = (static_cast<uint32_t>(1) <<  7) - 1;
+
+// The maximum code-point a two-byte UTF-8 sequence can represent.
+constexpr uint32_t kMaxCodePoint2 = (static_cast<uint32_t>(1) << (5 + 6)) - 1;
+
+// The maximum code-point a three-byte UTF-8 sequence can represent.
+constexpr uint32_t kMaxCodePoint3 = (static_cast<uint32_t>(1) << (4 + 2*6)) - 1;
+
+// The maximum code-point a four-byte UTF-8 sequence can represent.
+constexpr uint32_t kMaxCodePoint4 = (static_cast<uint32_t>(1) << (3 + 3*6)) - 1;
+
+// Chops off the n lowest bits from a bit pattern.  Returns the n
+// lowest bits.  As a side effect, the original bit pattern will be
+// shifted to the right by n bits.
+inline uint32_t ChopLowBits(uint32_t* bits, int n) {
+  const uint32_t low_bits = *bits & ((static_cast<uint32_t>(1) << n) - 1);
+  *bits >>= n;
+  return low_bits;
+}
+
+// Converts a Unicode code point to a narrow string in UTF-8 encoding.
+// code_point parameter is of type uint32_t because wchar_t may not be
+// wide enough to contain a code point.
+// If the code_point is not a valid Unicode code point
+// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be converted
+// to "(Invalid Unicode 0xXXXXXXXX)".
+std::string CodePointToUtf8(uint32_t code_point) {
+  if (code_point > kMaxCodePoint4) {
+    return "(Invalid Unicode 0x" + String::FormatHexUInt32(code_point) + ")";
+  }
+
+  char str[5];  // Big enough for the largest valid code point.
+  if (code_point <= kMaxCodePoint1) {
+    str[1] = '\0';
+    str[0] = static_cast<char>(code_point);                          // 0xxxxxxx
+  } else if (code_point <= kMaxCodePoint2) {
+    str[2] = '\0';
+    str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[0] = static_cast<char>(0xC0 | code_point);                   // 110xxxxx
+  } else if (code_point <= kMaxCodePoint3) {
+    str[3] = '\0';
+    str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[0] = static_cast<char>(0xE0 | code_point);                   // 1110xxxx
+  } else {  // code_point <= kMaxCodePoint4
+    str[4] = '\0';
+    str[3] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[2] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[1] = static_cast<char>(0x80 | ChopLowBits(&code_point, 6));  // 10xxxxxx
+    str[0] = static_cast<char>(0xF0 | code_point);                   // 11110xxx
+  }
+  return str;
+}
+
+// The following two functions only make sense if the system
+// uses UTF-16 for wide string encoding. All supported systems
+// with 16 bit wchar_t (Windows, Cygwin) do use UTF-16.
+
+// Determines if the arguments constitute UTF-16 surrogate pair
+// and thus should be combined into a single Unicode code point
+// using CreateCodePointFromUtf16SurrogatePair.
+inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) {
+  return sizeof(wchar_t) == 2 &&
+      (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00;
+}
+
+// Creates a Unicode code point from UTF16 surrogate pair.
+inline uint32_t CreateCodePointFromUtf16SurrogatePair(wchar_t first,
+                                                      wchar_t second) {
+  const auto first_u = static_cast<uint32_t>(first);
+  const auto second_u = static_cast<uint32_t>(second);
+  const uint32_t mask = (1 << 10) - 1;
+  return (sizeof(wchar_t) == 2)
+             ? (((first_u & mask) << 10) | (second_u & mask)) + 0x10000
+             :
+             // This function should not be called when the condition is
+             // false, but we provide a sensible default in case it is.
+             first_u;
+}
+
+// Converts a wide string to a narrow string in UTF-8 encoding.
+// The wide string is assumed to have the following encoding:
+//   UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin)
+//   UTF-32 if sizeof(wchar_t) == 4 (on Linux)
+// Parameter str points to a null-terminated wide string.
+// Parameter num_chars may additionally limit the number
+// of wchar_t characters processed. -1 is used when the entire string
+// should be processed.
+// If the string contains code points that are not valid Unicode code points
+// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output
+// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding
+// and contains invalid UTF-16 surrogate pairs, values in those pairs
+// will be encoded as individual Unicode characters from Basic Normal Plane.
+std::string WideStringToUtf8(const wchar_t* str, int num_chars) {
+  if (num_chars == -1)
+    num_chars = static_cast<int>(wcslen(str));
+
+  ::std::stringstream stream;
+  for (int i = 0; i < num_chars; ++i) {
+    uint32_t unicode_code_point;
+
+    if (str[i] == L'\0') {
+      break;
+    } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) {
+      unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i],
+                                                                 str[i + 1]);
+      i++;
+    } else {
+      unicode_code_point = static_cast<uint32_t>(str[i]);
+    }
+
+    stream << CodePointToUtf8(unicode_code_point);
+  }
+  return StringStreamToString(&stream);
+}
+
+// Converts a wide C string to an std::string using the UTF-8 encoding.
+// NULL will be converted to "(null)".
+std::string String::ShowWideCString(const wchar_t * wide_c_str) {
+  if (wide_c_str == nullptr) return "(null)";
+
+  return internal::WideStringToUtf8(wide_c_str, -1);
+}
+
+// Compares two wide C strings.  Returns true if and only if they have the
+// same content.
+//
+// Unlike wcscmp(), this function can handle NULL argument(s).  A NULL
+// C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) {
+  if (lhs == nullptr) return rhs == nullptr;
+
+  if (rhs == nullptr) return false;
+
+  return wcscmp(lhs, rhs) == 0;
+}
+
+// Helper function for *_STREQ on wide strings.
+AssertionResult CmpHelperSTREQ(const char* lhs_expression,
+                               const char* rhs_expression,
+                               const wchar_t* lhs,
+                               const wchar_t* rhs) {
+  if (String::WideCStringEquals(lhs, rhs)) {
+    return AssertionSuccess();
+  }
+
+  return EqFailure(lhs_expression,
+                   rhs_expression,
+                   PrintToString(lhs),
+                   PrintToString(rhs),
+                   false);
+}
+
+// Helper function for *_STRNE on wide strings.
+AssertionResult CmpHelperSTRNE(const char* s1_expression,
+                               const char* s2_expression,
+                               const wchar_t* s1,
+                               const wchar_t* s2) {
+  if (!String::WideCStringEquals(s1, s2)) {
+    return AssertionSuccess();
+  }
+
+  return AssertionFailure() << "Expected: (" << s1_expression << ") != ("
+                            << s2_expression << "), actual: "
+                            << PrintToString(s1)
+                            << " vs " << PrintToString(s2);
+}
+
+// Compares two C strings, ignoring case.  Returns true if and only if they have
+// the same content.
+//
+// Unlike strcasecmp(), this function can handle NULL argument(s).  A
+// NULL C string is considered different to any non-NULL C string,
+// including the empty string.
+bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) {
+  if (lhs == nullptr) return rhs == nullptr;
+  if (rhs == nullptr) return false;
+  return posix::StrCaseCmp(lhs, rhs) == 0;
+}
+
+// Compares two wide C strings, ignoring case.  Returns true if and only if they
+// have the same content.
+//
+// Unlike wcscasecmp(), this function can handle NULL argument(s).
+// A NULL C string is considered different to any non-NULL wide C string,
+// including the empty string.
+// NB: The implementations on different platforms slightly differ.
+// On windows, this method uses _wcsicmp which compares according to LC_CTYPE
+// environment variable. On GNU platform this method uses wcscasecmp
+// which compares according to LC_CTYPE category of the current locale.
+// On MacOS X, it uses towlower, which also uses LC_CTYPE category of the
+// current locale.
+bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs,
+                                              const wchar_t* rhs) {
+  if (lhs == nullptr) return rhs == nullptr;
+
+  if (rhs == nullptr) return false;
+
+#if GTEST_OS_WINDOWS
+  return _wcsicmp(lhs, rhs) == 0;
+#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID
+  return wcscasecmp(lhs, rhs) == 0;
+#else
+  // Android, Mac OS X and Cygwin don't define wcscasecmp.
+  // Other unknown OSes may not define it either.
+  wint_t left, right;
+  do {
+    left = towlower(static_cast<wint_t>(*lhs++));
+    right = towlower(static_cast<wint_t>(*rhs++));
+  } while (left && left == right);
+  return left == right;
+#endif  // OS selector
+}
+
+// Returns true if and only if str ends with the given suffix, ignoring case.
+// Any string is considered to end with an empty suffix.
+bool String::EndsWithCaseInsensitive(
+    const std::string& str, const std::string& suffix) {
+  const size_t str_len = str.length();
+  const size_t suffix_len = suffix.length();
+  return (str_len >= suffix_len) &&
+         CaseInsensitiveCStringEquals(str.c_str() + str_len - suffix_len,
+                                      suffix.c_str());
+}
+
+// Formats an int value as "%02d".
+std::string String::FormatIntWidth2(int value) {
+  return FormatIntWidthN(value, 2);
+}
+
+// Formats an int value to given width with leading zeros.
+std::string String::FormatIntWidthN(int value, int width) {
+  std::stringstream ss;
+  ss << std::setfill('0') << std::setw(width) << value;
+  return ss.str();
+}
+
+// Formats an int value as "%X".
+std::string String::FormatHexUInt32(uint32_t value) {
+  std::stringstream ss;
+  ss << std::hex << std::uppercase << value;
+  return ss.str();
+}
+
+// Formats an int value as "%X".
+std::string String::FormatHexInt(int value) {
+  return FormatHexUInt32(static_cast<uint32_t>(value));
+}
+
+// Formats a byte as "%02X".
+std::string String::FormatByte(unsigned char value) {
+  std::stringstream ss;
+  ss << std::setfill('0') << std::setw(2) << std::hex << std::uppercase
+     << static_cast<unsigned int>(value);
+  return ss.str();
+}
+
+// Converts the buffer in a stringstream to an std::string, converting NUL
+// bytes to "\\0" along the way.
+std::string StringStreamToString(::std::stringstream* ss) {
+  const ::std::string& str = ss->str();
+  const char* const start = str.c_str();
+  const char* const end = start + str.length();
+
+  std::string result;
+  result.reserve(static_cast<size_t>(2 * (end - start)));
+  for (const char* ch = start; ch != end; ++ch) {
+    if (*ch == '\0') {
+      result += "\\0";  // Replaces NUL with "\\0";
+    } else {
+      result += *ch;
+    }
+  }
+
+  return result;
+}
+
+// Appends the user-supplied message to the Google-Test-generated message.
+std::string AppendUserMessage(const std::string& gtest_msg,
+                              const Message& user_msg) {
+  // Appends the user message if it's non-empty.
+  const std::string user_msg_string = user_msg.GetString();
+  if (user_msg_string.empty()) {
+    return gtest_msg;
+  }
+  if (gtest_msg.empty()) {
+    return user_msg_string;
+  }
+  return gtest_msg + "\n" + user_msg_string;
+}
+
+}  // namespace internal
+
+// class TestResult
+
+// Creates an empty TestResult.
+TestResult::TestResult()
+    : death_test_count_(0), start_timestamp_(0), elapsed_time_(0) {}
+
+// D'tor.
+TestResult::~TestResult() {
+}
+
+// Returns the i-th test part result among all the results. i can
+// range from 0 to total_part_count() - 1. If i is not in that range,
+// aborts the program.
+const TestPartResult& TestResult::GetTestPartResult(int i) const {
+  if (i < 0 || i >= total_part_count())
+    internal::posix::Abort();
+  return test_part_results_.at(static_cast<size_t>(i));
+}
+
+// Returns the i-th test property. i can range from 0 to
+// test_property_count() - 1. If i is not in that range, aborts the
+// program.
+const TestProperty& TestResult::GetTestProperty(int i) const {
+  if (i < 0 || i >= test_property_count())
+    internal::posix::Abort();
+  return test_properties_.at(static_cast<size_t>(i));
+}
+
+// Clears the test part results.
+void TestResult::ClearTestPartResults() {
+  test_part_results_.clear();
+}
+
+// Adds a test part result to the list.
+void TestResult::AddTestPartResult(const TestPartResult& test_part_result) {
+  test_part_results_.push_back(test_part_result);
+}
+
+// Adds a test property to the list. If a property with the same key as the
+// supplied property is already represented, the value of this test_property
+// replaces the old value for that key.
+void TestResult::RecordProperty(const std::string& xml_element,
+                                const TestProperty& test_property) {
+  if (!ValidateTestProperty(xml_element, test_property)) {
+    return;
+  }
+  internal::MutexLock lock(&test_properties_mutex_);
+  const std::vector<TestProperty>::iterator property_with_matching_key =
+      std::find_if(test_properties_.begin(), test_properties_.end(),
+                   internal::TestPropertyKeyIs(test_property.key()));
+  if (property_with_matching_key == test_properties_.end()) {
+    test_properties_.push_back(test_property);
+    return;
+  }
+  property_with_matching_key->SetValue(test_property.value());
+}
+
+// The list of reserved attributes used in the <testsuites> element of XML
+// output.
+static const char* const kReservedTestSuitesAttributes[] = {
+  "disabled",
+  "errors",
+  "failures",
+  "name",
+  "random_seed",
+  "tests",
+  "time",
+  "timestamp"
+};
+
+// The list of reserved attributes used in the <testsuite> element of XML
+// output.
+static const char* const kReservedTestSuiteAttributes[] = {
+    "disabled", "errors", "failures",  "name",
+    "tests",    "time",   "timestamp", "skipped"};
+
+// The list of reserved attributes used in the <testcase> element of XML output.
+static const char* const kReservedTestCaseAttributes[] = {
+    "classname",   "name", "status", "time",  "type_param",
+    "value_param", "file", "line"};
+
+// Use a slightly different set for allowed output to ensure existing tests can
+// still RecordProperty("result") or "RecordProperty(timestamp")
+static const char* const kReservedOutputTestCaseAttributes[] = {
+    "classname",   "name", "status", "time",   "type_param",
+    "value_param", "file", "line",   "result", "timestamp"};
+
+template <size_t kSize>
+std::vector<std::string> ArrayAsVector(const char* const (&array)[kSize]) {
+  return std::vector<std::string>(array, array + kSize);
+}
+
+static std::vector<std::string> GetReservedAttributesForElement(
+    const std::string& xml_element) {
+  if (xml_element == "testsuites") {
+    return ArrayAsVector(kReservedTestSuitesAttributes);
+  } else if (xml_element == "testsuite") {
+    return ArrayAsVector(kReservedTestSuiteAttributes);
+  } else if (xml_element == "testcase") {
+    return ArrayAsVector(kReservedTestCaseAttributes);
+  } else {
+    GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element;
+  }
+  // This code is unreachable but some compilers may not realizes that.
+  return std::vector<std::string>();
+}
+
+// TODO(jdesprez): Merge the two getReserved attributes once skip is improved
+static std::vector<std::string> GetReservedOutputAttributesForElement(
+    const std::string& xml_element) {
+  if (xml_element == "testsuites") {
+    return ArrayAsVector(kReservedTestSuitesAttributes);
+  } else if (xml_element == "testsuite") {
+    return ArrayAsVector(kReservedTestSuiteAttributes);
+  } else if (xml_element == "testcase") {
+    return ArrayAsVector(kReservedOutputTestCaseAttributes);
+  } else {
+    GTEST_CHECK_(false) << "Unrecognized xml_element provided: " << xml_element;
+  }
+  // This code is unreachable but some compilers may not realizes that.
+  return std::vector<std::string>();
+}
+
+static std::string FormatWordList(const std::vector<std::string>& words) {
+  Message word_list;
+  for (size_t i = 0; i < words.size(); ++i) {
+    if (i > 0 && words.size() > 2) {
+      word_list << ", ";
+    }
+    if (i == words.size() - 1) {
+      word_list << "and ";
+    }
+    word_list << "'" << words[i] << "'";
+  }
+  return word_list.GetString();
+}
+
+static bool ValidateTestPropertyName(
+    const std::string& property_name,
+    const std::vector<std::string>& reserved_names) {
+  if (std::find(reserved_names.begin(), reserved_names.end(), property_name) !=
+          reserved_names.end()) {
+    ADD_FAILURE() << "Reserved key used in RecordProperty(): " << property_name
+                  << " (" << FormatWordList(reserved_names)
+                  << " are reserved by " << GTEST_NAME_ << ")";
+    return false;
+  }
+  return true;
+}
+
+// Adds a failure if the key is a reserved attribute of the element named
+// xml_element.  Returns true if the property is valid.
+bool TestResult::ValidateTestProperty(const std::string& xml_element,
+                                      const TestProperty& test_property) {
+  return ValidateTestPropertyName(test_property.key(),
+                                  GetReservedAttributesForElement(xml_element));
+}
+
+// Clears the object.
+void TestResult::Clear() {
+  test_part_results_.clear();
+  test_properties_.clear();
+  death_test_count_ = 0;
+  elapsed_time_ = 0;
+}
+
+// Returns true off the test part was skipped.
+static bool TestPartSkipped(const TestPartResult& result) {
+  return result.skipped();
+}
+
+// Returns true if and only if the test was skipped.
+bool TestResult::Skipped() const {
+  return !Failed() && CountIf(test_part_results_, TestPartSkipped) > 0;
+}
+
+// Returns true if and only if the test failed.
+bool TestResult::Failed() const {
+  for (int i = 0; i < total_part_count(); ++i) {
+    if (GetTestPartResult(i).failed())
+      return true;
+  }
+  return false;
+}
+
+// Returns true if and only if the test part fatally failed.
+static bool TestPartFatallyFailed(const TestPartResult& result) {
+  return result.fatally_failed();
+}
+
+// Returns true if and only if the test fatally failed.
+bool TestResult::HasFatalFailure() const {
+  return CountIf(test_part_results_, TestPartFatallyFailed) > 0;
+}
+
+// Returns true if and only if the test part non-fatally failed.
+static bool TestPartNonfatallyFailed(const TestPartResult& result) {
+  return result.nonfatally_failed();
+}
+
+// Returns true if and only if the test has a non-fatal failure.
+bool TestResult::HasNonfatalFailure() const {
+  return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0;
+}
+
+// Gets the number of all test parts.  This is the sum of the number
+// of successful test parts and the number of failed test parts.
+int TestResult::total_part_count() const {
+  return static_cast<int>(test_part_results_.size());
+}
+
+// Returns the number of the test properties.
+int TestResult::test_property_count() const {
+  return static_cast<int>(test_properties_.size());
+}
+
+// class Test
+
+// Creates a Test object.
+
+// The c'tor saves the states of all flags.
+Test::Test()
+    : gtest_flag_saver_(new GTEST_FLAG_SAVER_) {
+}
+
+// The d'tor restores the states of all flags.  The actual work is
+// done by the d'tor of the gtest_flag_saver_ field, and thus not
+// visible here.
+Test::~Test() {
+}
+
+// Sets up the test fixture.
+//
+// A sub-class may override this.
+void Test::SetUp() {
+}
+
+// Tears down the test fixture.
+//
+// A sub-class may override this.
+void Test::TearDown() {
+}
+
+// Allows user supplied key value pairs to be recorded for later output.
+void Test::RecordProperty(const std::string& key, const std::string& value) {
+  UnitTest::GetInstance()->RecordProperty(key, value);
+}
+
+// Allows user supplied key value pairs to be recorded for later output.
+void Test::RecordProperty(const std::string& key, int value) {
+  Message value_message;
+  value_message << value;
+  RecordProperty(key, value_message.GetString().c_str());
+}
+
+namespace internal {
+
+void ReportFailureInUnknownLocation(TestPartResult::Type result_type,
+                                    const std::string& message) {
+  // This function is a friend of UnitTest and as such has access to
+  // AddTestPartResult.
+  UnitTest::GetInstance()->AddTestPartResult(
+      result_type,
+      nullptr,  // No info about the source file where the exception occurred.
+      -1,       // We have no info on which line caused the exception.
+      message,
+      "");  // No stack trace, either.
+}
+
+}  // namespace internal
+
+// Google Test requires all tests in the same test suite to use the same test
+// fixture class.  This function checks if the current test has the
+// same fixture class as the first test in the current test suite.  If
+// yes, it returns true; otherwise it generates a Google Test failure and
+// returns false.
+bool Test::HasSameFixtureClass() {
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  const TestSuite* const test_suite = impl->current_test_suite();
+
+  // Info about the first test in the current test suite.
+  const TestInfo* const first_test_info = test_suite->test_info_list()[0];
+  const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_;
+  const char* const first_test_name = first_test_info->name();
+
+  // Info about the current test.
+  const TestInfo* const this_test_info = impl->current_test_info();
+  const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_;
+  const char* const this_test_name = this_test_info->name();
+
+  if (this_fixture_id != first_fixture_id) {
+    // Is the first test defined using TEST?
+    const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId();
+    // Is this test defined using TEST?
+    const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId();
+
+    if (first_is_TEST || this_is_TEST) {
+      // Both TEST and TEST_F appear in same test suite, which is incorrect.
+      // Tell the user how to fix this.
+
+      // Gets the name of the TEST and the name of the TEST_F.  Note
+      // that first_is_TEST and this_is_TEST cannot both be true, as
+      // the fixture IDs are different for the two tests.
+      const char* const TEST_name =
+          first_is_TEST ? first_test_name : this_test_name;
+      const char* const TEST_F_name =
+          first_is_TEST ? this_test_name : first_test_name;
+
+      ADD_FAILURE()
+          << "All tests in the same test suite must use the same test fixture\n"
+          << "class, so mixing TEST_F and TEST in the same test suite is\n"
+          << "illegal.  In test suite " << this_test_info->test_suite_name()
+          << ",\n"
+          << "test " << TEST_F_name << " is defined using TEST_F but\n"
+          << "test " << TEST_name << " is defined using TEST.  You probably\n"
+          << "want to change the TEST to TEST_F or move it to another test\n"
+          << "case.";
+    } else {
+      // Two fixture classes with the same name appear in two different
+      // namespaces, which is not allowed. Tell the user how to fix this.
+      ADD_FAILURE()
+          << "All tests in the same test suite must use the same test fixture\n"
+          << "class.  However, in test suite "
+          << this_test_info->test_suite_name() << ",\n"
+          << "you defined test " << first_test_name << " and test "
+          << this_test_name << "\n"
+          << "using two different test fixture classes.  This can happen if\n"
+          << "the two classes are from different namespaces or translation\n"
+          << "units and have the same name.  You should probably rename one\n"
+          << "of the classes to put the tests into different test suites.";
+    }
+    return false;
+  }
+
+  return true;
+}
+
+#if GTEST_HAS_SEH
+
+// Adds an "exception thrown" fatal failure to the current test.  This
+// function returns its result via an output parameter pointer because VC++
+// prohibits creation of objects with destructors on stack in functions
+// using __try (see error C2712).
+static std::string* FormatSehExceptionMessage(DWORD exception_code,
+                                              const char* location) {
+  Message message;
+  message << "SEH exception with code 0x" << std::setbase(16) <<
+    exception_code << std::setbase(10) << " thrown in " << location << ".";
+
+  return new std::string(message.GetString());
+}
+
+#endif  // GTEST_HAS_SEH
+
+namespace internal {
+
+#if GTEST_HAS_EXCEPTIONS
+
+// Adds an "exception thrown" fatal failure to the current test.
+static std::string FormatCxxExceptionMessage(const char* description,
+                                             const char* location) {
+  Message message;
+  if (description != nullptr) {
+    message << "C++ exception with description \"" << description << "\"";
+  } else {
+    message << "Unknown C++ exception";
+  }
+  message << " thrown in " << location << ".";
+
+  return message.GetString();
+}
+
+static std::string PrintTestPartResultToString(
+    const TestPartResult& test_part_result);
+
+GoogleTestFailureException::GoogleTestFailureException(
+    const TestPartResult& failure)
+    : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {}
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+// We put these helper functions in the internal namespace as IBM's xlC
+// compiler rejects the code if they were declared static.
+
+// Runs the given method and handles SEH exceptions it throws, when
+// SEH is supported; returns the 0-value for type Result in case of an
+// SEH exception.  (Microsoft compilers cannot handle SEH and C++
+// exceptions in the same function.  Therefore, we provide a separate
+// wrapper function for handling SEH exceptions.)
+template <class T, typename Result>
+Result HandleSehExceptionsInMethodIfSupported(
+    T* object, Result (T::*method)(), const char* location) {
+#if GTEST_HAS_SEH
+  __try {
+    return (object->*method)();
+  } __except (internal::UnitTestOptions::GTestShouldProcessSEH(  // NOLINT
+      GetExceptionCode())) {
+    // We create the exception message on the heap because VC++ prohibits
+    // creation of objects with destructors on stack in functions using __try
+    // (see error C2712).
+    std::string* exception_message = FormatSehExceptionMessage(
+        GetExceptionCode(), location);
+    internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure,
+                                             *exception_message);
+    delete exception_message;
+    return static_cast<Result>(0);
+  }
+#else
+  (void)location;
+  return (object->*method)();
+#endif  // GTEST_HAS_SEH
+}
+
+// Runs the given method and catches and reports C++ and/or SEH-style
+// exceptions, if they are supported; returns the 0-value for type
+// Result in case of an SEH exception.
+template <class T, typename Result>
+Result HandleExceptionsInMethodIfSupported(
+    T* object, Result (T::*method)(), const char* location) {
+  // NOTE: The user code can affect the way in which Google Test handles
+  // exceptions by setting GTEST_FLAG(catch_exceptions), but only before
+  // RUN_ALL_TESTS() starts. It is technically possible to check the flag
+  // after the exception is caught and either report or re-throw the
+  // exception based on the flag's value:
+  //
+  // try {
+  //   // Perform the test method.
+  // } catch (...) {
+  //   if (GTEST_FLAG(catch_exceptions))
+  //     // Report the exception as failure.
+  //   else
+  //     throw;  // Re-throws the original exception.
+  // }
+  //
+  // However, the purpose of this flag is to allow the program to drop into
+  // the debugger when the exception is thrown. On most platforms, once the
+  // control enters the catch block, the exception origin information is
+  // lost and the debugger will stop the program at the point of the
+  // re-throw in this function -- instead of at the point of the original
+  // throw statement in the code under test.  For this reason, we perform
+  // the check early, sacrificing the ability to affect Google Test's
+  // exception handling in the method where the exception is thrown.
+  if (internal::GetUnitTestImpl()->catch_exceptions()) {
+#if GTEST_HAS_EXCEPTIONS
+    try {
+      return HandleSehExceptionsInMethodIfSupported(object, method, location);
+    } catch (const AssertionException&) {  // NOLINT
+      // This failure was reported already.
+    } catch (const internal::GoogleTestFailureException&) {  // NOLINT
+      // This exception type can only be thrown by a failed Google
+      // Test assertion with the intention of letting another testing
+      // framework catch it.  Therefore we just re-throw it.
+      throw;
+    } catch (const std::exception& e) {  // NOLINT
+      internal::ReportFailureInUnknownLocation(
+          TestPartResult::kFatalFailure,
+          FormatCxxExceptionMessage(e.what(), location));
+    } catch (...) {  // NOLINT
+      internal::ReportFailureInUnknownLocation(
+          TestPartResult::kFatalFailure,
+          FormatCxxExceptionMessage(nullptr, location));
+    }
+    return static_cast<Result>(0);
+#else
+    return HandleSehExceptionsInMethodIfSupported(object, method, location);
+#endif  // GTEST_HAS_EXCEPTIONS
+  } else {
+    return (object->*method)();
+  }
+}
+
+}  // namespace internal
+
+// Runs the test and updates the test result.
+void Test::Run() {
+  if (!HasSameFixtureClass()) return;
+
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  impl->os_stack_trace_getter()->UponLeavingGTest();
+  internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()");
+  // We will run the test only if SetUp() was successful and didn't call
+  // GTEST_SKIP().
+  if (!HasFatalFailure() && !IsSkipped()) {
+    impl->os_stack_trace_getter()->UponLeavingGTest();
+    internal::HandleExceptionsInMethodIfSupported(
+        this, &Test::TestBody, "the test body");
+  }
+
+  // However, we want to clean up as much as possible.  Hence we will
+  // always call TearDown(), even if SetUp() or the test body has
+  // failed.
+  impl->os_stack_trace_getter()->UponLeavingGTest();
+  internal::HandleExceptionsInMethodIfSupported(
+      this, &Test::TearDown, "TearDown()");
+}
+
+// Returns true if and only if the current test has a fatal failure.
+bool Test::HasFatalFailure() {
+  return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure();
+}
+
+// Returns true if and only if the current test has a non-fatal failure.
+bool Test::HasNonfatalFailure() {
+  return internal::GetUnitTestImpl()->current_test_result()->
+      HasNonfatalFailure();
+}
+
+// Returns true if and only if the current test was skipped.
+bool Test::IsSkipped() {
+  return internal::GetUnitTestImpl()->current_test_result()->Skipped();
+}
+
+// class TestInfo
+
+// Constructs a TestInfo object. It assumes ownership of the test factory
+// object.
+TestInfo::TestInfo(const std::string& a_test_suite_name,
+                   const std::string& a_name, const char* a_type_param,
+                   const char* a_value_param,
+                   internal::CodeLocation a_code_location,
+                   internal::TypeId fixture_class_id,
+                   internal::TestFactoryBase* factory)
+    : test_suite_name_(a_test_suite_name),
+      name_(a_name),
+      type_param_(a_type_param ? new std::string(a_type_param) : nullptr),
+      value_param_(a_value_param ? new std::string(a_value_param) : nullptr),
+      location_(a_code_location),
+      fixture_class_id_(fixture_class_id),
+      should_run_(false),
+      is_disabled_(false),
+      matches_filter_(false),
+      is_in_another_shard_(false),
+      factory_(factory),
+      result_() {}
+
+// Destructs a TestInfo object.
+TestInfo::~TestInfo() { delete factory_; }
+
+namespace internal {
+
+// Creates a new TestInfo object and registers it with Google Test;
+// returns the created object.
+//
+// Arguments:
+//
+//   test_suite_name:  name of the test suite
+//   name:             name of the test
+//   type_param:       the name of the test's type parameter, or NULL if
+//                     this is not a typed or a type-parameterized test.
+//   value_param:      text representation of the test's value parameter,
+//                     or NULL if this is not a value-parameterized test.
+//   code_location:    code location where the test is defined
+//   fixture_class_id: ID of the test fixture class
+//   set_up_tc:        pointer to the function that sets up the test suite
+//   tear_down_tc:     pointer to the function that tears down the test suite
+//   factory:          pointer to the factory that creates a test object.
+//                     The newly created TestInfo instance will assume
+//                     ownership of the factory object.
+TestInfo* MakeAndRegisterTestInfo(
+    const char* test_suite_name, const char* name, const char* type_param,
+    const char* value_param, CodeLocation code_location,
+    TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc,
+    TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory) {
+  TestInfo* const test_info =
+      new TestInfo(test_suite_name, name, type_param, value_param,
+                   code_location, fixture_class_id, factory);
+  GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info);
+  return test_info;
+}
+
+void ReportInvalidTestSuiteType(const char* test_suite_name,
+                                CodeLocation code_location) {
+  Message errors;
+  errors
+      << "Attempted redefinition of test suite " << test_suite_name << ".\n"
+      << "All tests in the same test suite must use the same test fixture\n"
+      << "class.  However, in test suite " << test_suite_name << ", you tried\n"
+      << "to define a test using a fixture class different from the one\n"
+      << "used earlier. This can happen if the two fixture classes are\n"
+      << "from different namespaces and have the same name. You should\n"
+      << "probably rename one of the classes to put the tests into different\n"
+      << "test suites.";
+
+  GTEST_LOG_(ERROR) << FormatFileLocation(code_location.file.c_str(),
+                                          code_location.line)
+                    << " " << errors.GetString();
+}
+}  // namespace internal
+
+namespace {
+
+// A predicate that checks the test name of a TestInfo against a known
+// value.
+//
+// This is used for implementation of the TestSuite class only.  We put
+// it in the anonymous namespace to prevent polluting the outer
+// namespace.
+//
+// TestNameIs is copyable.
+class TestNameIs {
+ public:
+  // Constructor.
+  //
+  // TestNameIs has NO default constructor.
+  explicit TestNameIs(const char* name)
+      : name_(name) {}
+
+  // Returns true if and only if the test name of test_info matches name_.
+  bool operator()(const TestInfo * test_info) const {
+    return test_info && test_info->name() == name_;
+  }
+
+ private:
+  std::string name_;
+};
+
+}  // namespace
+
+namespace internal {
+
+// This method expands all parameterized tests registered with macros TEST_P
+// and INSTANTIATE_TEST_SUITE_P into regular tests and registers those.
+// This will be done just once during the program runtime.
+void UnitTestImpl::RegisterParameterizedTests() {
+  if (!parameterized_tests_registered_) {
+    parameterized_test_registry_.RegisterTests();
+    type_parameterized_test_registry_.CheckForInstantiations();
+    parameterized_tests_registered_ = true;
+  }
+}
+
+}  // namespace internal
+
+// Creates the test object, runs it, records its result, and then
+// deletes it.
+void TestInfo::Run() {
+  if (!should_run_) return;
+
+  // Tells UnitTest where to store test result.
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  impl->set_current_test_info(this);
+
+  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+  // Notifies the unit test event listeners that a test is about to start.
+  repeater->OnTestStart(*this);
+
+  result_.set_start_timestamp(internal::GetTimeInMillis());
+  internal::Timer timer;
+
+  impl->os_stack_trace_getter()->UponLeavingGTest();
+
+  // Creates the test object.
+  Test* const test = internal::HandleExceptionsInMethodIfSupported(
+      factory_, &internal::TestFactoryBase::CreateTest,
+      "the test fixture's constructor");
+
+  // Runs the test if the constructor didn't generate a fatal failure or invoke
+  // GTEST_SKIP().
+  // Note that the object will not be null
+  if (!Test::HasFatalFailure() && !Test::IsSkipped()) {
+    // This doesn't throw as all user code that can throw are wrapped into
+    // exception handling code.
+    test->Run();
+  }
+
+  if (test != nullptr) {
+    // Deletes the test object.
+    impl->os_stack_trace_getter()->UponLeavingGTest();
+    internal::HandleExceptionsInMethodIfSupported(
+        test, &Test::DeleteSelf_, "the test fixture's destructor");
+  }
+
+  result_.set_elapsed_time(timer.Elapsed());
+
+  // Notifies the unit test event listener that a test has just finished.
+  repeater->OnTestEnd(*this);
+
+  // Tells UnitTest to stop associating assertion results to this
+  // test.
+  impl->set_current_test_info(nullptr);
+}
+
+// Skip and records a skipped test result for this object.
+void TestInfo::Skip() {
+  if (!should_run_) return;
+
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  impl->set_current_test_info(this);
+
+  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+  // Notifies the unit test event listeners that a test is about to start.
+  repeater->OnTestStart(*this);
+
+  const TestPartResult test_part_result =
+      TestPartResult(TestPartResult::kSkip, this->file(), this->line(), "");
+  impl->GetTestPartResultReporterForCurrentThread()->ReportTestPartResult(
+      test_part_result);
+
+  // Notifies the unit test event listener that a test has just finished.
+  repeater->OnTestEnd(*this);
+  impl->set_current_test_info(nullptr);
+}
+
+// class TestSuite
+
+// Gets the number of successful tests in this test suite.
+int TestSuite::successful_test_count() const {
+  return CountIf(test_info_list_, TestPassed);
+}
+
+// Gets the number of successful tests in this test suite.
+int TestSuite::skipped_test_count() const {
+  return CountIf(test_info_list_, TestSkipped);
+}
+
+// Gets the number of failed tests in this test suite.
+int TestSuite::failed_test_count() const {
+  return CountIf(test_info_list_, TestFailed);
+}
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int TestSuite::reportable_disabled_test_count() const {
+  return CountIf(test_info_list_, TestReportableDisabled);
+}
+
+// Gets the number of disabled tests in this test suite.
+int TestSuite::disabled_test_count() const {
+  return CountIf(test_info_list_, TestDisabled);
+}
+
+// Gets the number of tests to be printed in the XML report.
+int TestSuite::reportable_test_count() const {
+  return CountIf(test_info_list_, TestReportable);
+}
+
+// Get the number of tests in this test suite that should run.
+int TestSuite::test_to_run_count() const {
+  return CountIf(test_info_list_, ShouldRunTest);
+}
+
+// Gets the number of all tests.
+int TestSuite::total_test_count() const {
+  return static_cast<int>(test_info_list_.size());
+}
+
+// Creates a TestSuite with the given name.
+//
+// Arguments:
+//
+//   a_name:       name of the test suite
+//   a_type_param: the name of the test suite's type parameter, or NULL if
+//                 this is not a typed or a type-parameterized test suite.
+//   set_up_tc:    pointer to the function that sets up the test suite
+//   tear_down_tc: pointer to the function that tears down the test suite
+TestSuite::TestSuite(const char* a_name, const char* a_type_param,
+                     internal::SetUpTestSuiteFunc set_up_tc,
+                     internal::TearDownTestSuiteFunc tear_down_tc)
+    : name_(a_name),
+      type_param_(a_type_param ? new std::string(a_type_param) : nullptr),
+      set_up_tc_(set_up_tc),
+      tear_down_tc_(tear_down_tc),
+      should_run_(false),
+      start_timestamp_(0),
+      elapsed_time_(0) {}
+
+// Destructor of TestSuite.
+TestSuite::~TestSuite() {
+  // Deletes every Test in the collection.
+  ForEach(test_info_list_, internal::Delete<TestInfo>);
+}
+
+// Returns the i-th test among all the tests. i can range from 0 to
+// total_test_count() - 1. If i is not in that range, returns NULL.
+const TestInfo* TestSuite::GetTestInfo(int i) const {
+  const int index = GetElementOr(test_indices_, i, -1);
+  return index < 0 ? nullptr : test_info_list_[static_cast<size_t>(index)];
+}
+
+// Returns the i-th test among all the tests. i can range from 0 to
+// total_test_count() - 1. If i is not in that range, returns NULL.
+TestInfo* TestSuite::GetMutableTestInfo(int i) {
+  const int index = GetElementOr(test_indices_, i, -1);
+  return index < 0 ? nullptr : test_info_list_[static_cast<size_t>(index)];
+}
+
+// Adds a test to this test suite.  Will delete the test upon
+// destruction of the TestSuite object.
+void TestSuite::AddTestInfo(TestInfo* test_info) {
+  test_info_list_.push_back(test_info);
+  test_indices_.push_back(static_cast<int>(test_indices_.size()));
+}
+
+// Runs every test in this TestSuite.
+void TestSuite::Run() {
+  if (!should_run_) return;
+
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  impl->set_current_test_suite(this);
+
+  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+  // Call both legacy and the new API
+  repeater->OnTestSuiteStart(*this);
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  repeater->OnTestCaseStart(*this);
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  impl->os_stack_trace_getter()->UponLeavingGTest();
+  internal::HandleExceptionsInMethodIfSupported(
+      this, &TestSuite::RunSetUpTestSuite, "SetUpTestSuite()");
+
+  start_timestamp_ = internal::GetTimeInMillis();
+  internal::Timer timer;
+  for (int i = 0; i < total_test_count(); i++) {
+    GetMutableTestInfo(i)->Run();
+    if (GTEST_FLAG(fail_fast) && GetMutableTestInfo(i)->result()->Failed()) {
+      for (int j = i + 1; j < total_test_count(); j++) {
+        GetMutableTestInfo(j)->Skip();
+      }
+      break;
+    }
+  }
+  elapsed_time_ = timer.Elapsed();
+
+  impl->os_stack_trace_getter()->UponLeavingGTest();
+  internal::HandleExceptionsInMethodIfSupported(
+      this, &TestSuite::RunTearDownTestSuite, "TearDownTestSuite()");
+
+  // Call both legacy and the new API
+  repeater->OnTestSuiteEnd(*this);
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  repeater->OnTestCaseEnd(*this);
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  impl->set_current_test_suite(nullptr);
+}
+
+// Skips all tests under this TestSuite.
+void TestSuite::Skip() {
+  if (!should_run_) return;
+
+  internal::UnitTestImpl* const impl = internal::GetUnitTestImpl();
+  impl->set_current_test_suite(this);
+
+  TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater();
+
+  // Call both legacy and the new API
+  repeater->OnTestSuiteStart(*this);
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  repeater->OnTestCaseStart(*this);
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  for (int i = 0; i < total_test_count(); i++) {
+    GetMutableTestInfo(i)->Skip();
+  }
+
+  // Call both legacy and the new API
+  repeater->OnTestSuiteEnd(*this);
+  // Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  repeater->OnTestCaseEnd(*this);
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  impl->set_current_test_suite(nullptr);
+}
+
+// Clears the results of all tests in this test suite.
+void TestSuite::ClearResult() {
+  ad_hoc_test_result_.Clear();
+  ForEach(test_info_list_, TestInfo::ClearTestResult);
+}
+
+// Shuffles the tests in this test suite.
+void TestSuite::ShuffleTests(internal::Random* random) {
+  Shuffle(random, &test_indices_);
+}
+
+// Restores the test order to before the first shuffle.
+void TestSuite::UnshuffleTests() {
+  for (size_t i = 0; i < test_indices_.size(); i++) {
+    test_indices_[i] = static_cast<int>(i);
+  }
+}
+
+// Formats a countable noun.  Depending on its quantity, either the
+// singular form or the plural form is used. e.g.
+//
+// FormatCountableNoun(1, "formula", "formuli") returns "1 formula".
+// FormatCountableNoun(5, "book", "books") returns "5 books".
+static std::string FormatCountableNoun(int count,
+                                       const char * singular_form,
+                                       const char * plural_form) {
+  return internal::StreamableToString(count) + " " +
+      (count == 1 ? singular_form : plural_form);
+}
+
+// Formats the count of tests.
+static std::string FormatTestCount(int test_count) {
+  return FormatCountableNoun(test_count, "test", "tests");
+}
+
+// Formats the count of test suites.
+static std::string FormatTestSuiteCount(int test_suite_count) {
+  return FormatCountableNoun(test_suite_count, "test suite", "test suites");
+}
+
+// Converts a TestPartResult::Type enum to human-friendly string
+// representation.  Both kNonFatalFailure and kFatalFailure are translated
+// to "Failure", as the user usually doesn't care about the difference
+// between the two when viewing the test result.
+static const char * TestPartResultTypeToString(TestPartResult::Type type) {
+  switch (type) {
+    case TestPartResult::kSkip:
+      return "Skipped\n";
+    case TestPartResult::kSuccess:
+      return "Success";
+
+    case TestPartResult::kNonFatalFailure:
+    case TestPartResult::kFatalFailure:
+#ifdef _MSC_VER
+      return "error: ";
+#else
+      return "Failure\n";
+#endif
+    default:
+      return "Unknown result type";
+  }
+}
+
+namespace internal {
+namespace {
+enum class GTestColor { kDefault, kRed, kGreen, kYellow };
+}  // namespace
+
+// Prints a TestPartResult to an std::string.
+static std::string PrintTestPartResultToString(
+    const TestPartResult& test_part_result) {
+  return (Message()
+          << internal::FormatFileLocation(test_part_result.file_name(),
+                                          test_part_result.line_number())
+          << " " << TestPartResultTypeToString(test_part_result.type())
+          << test_part_result.message()).GetString();
+}
+
+// Prints a TestPartResult.
+static void PrintTestPartResult(const TestPartResult& test_part_result) {
+  const std::string& result =
+      PrintTestPartResultToString(test_part_result);
+  printf("%s\n", result.c_str());
+  fflush(stdout);
+  // If the test program runs in Visual Studio or a debugger, the
+  // following statements add the test part result message to the Output
+  // window such that the user can double-click on it to jump to the
+  // corresponding source code location; otherwise they do nothing.
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+  // We don't call OutputDebugString*() on Windows Mobile, as printing
+  // to stdout is done by OutputDebugString() there already - we don't
+  // want the same message printed twice.
+  ::OutputDebugStringA(result.c_str());
+  ::OutputDebugStringA("\n");
+#endif
+}
+
+// class PrettyUnitTestResultPrinter
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \
+    !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW
+
+// Returns the character attribute for the given color.
+static WORD GetColorAttribute(GTestColor color) {
+  switch (color) {
+    case GTestColor::kRed:
+      return FOREGROUND_RED;
+    case GTestColor::kGreen:
+      return FOREGROUND_GREEN;
+    case GTestColor::kYellow:
+      return FOREGROUND_RED | FOREGROUND_GREEN;
+    default:           return 0;
+  }
+}
+
+static int GetBitOffset(WORD color_mask) {
+  if (color_mask == 0) return 0;
+
+  int bitOffset = 0;
+  while ((color_mask & 1) == 0) {
+    color_mask >>= 1;
+    ++bitOffset;
+  }
+  return bitOffset;
+}
+
+static WORD GetNewColor(GTestColor color, WORD old_color_attrs) {
+  // Let's reuse the BG
+  static const WORD background_mask = BACKGROUND_BLUE | BACKGROUND_GREEN |
+                                      BACKGROUND_RED | BACKGROUND_INTENSITY;
+  static const WORD foreground_mask = FOREGROUND_BLUE | FOREGROUND_GREEN |
+                                      FOREGROUND_RED | FOREGROUND_INTENSITY;
+  const WORD existing_bg = old_color_attrs & background_mask;
+
+  WORD new_color =
+      GetColorAttribute(color) | existing_bg | FOREGROUND_INTENSITY;
+  static const int bg_bitOffset = GetBitOffset(background_mask);
+  static const int fg_bitOffset = GetBitOffset(foreground_mask);
+
+  if (((new_color & background_mask) >> bg_bitOffset) ==
+      ((new_color & foreground_mask) >> fg_bitOffset)) {
+    new_color ^= FOREGROUND_INTENSITY;  // invert intensity
+  }
+  return new_color;
+}
+
+#else
+
+// Returns the ANSI color code for the given color. GTestColor::kDefault is
+// an invalid input.
+static const char* GetAnsiColorCode(GTestColor color) {
+  switch (color) {
+    case GTestColor::kRed:
+      return "1";
+    case GTestColor::kGreen:
+      return "2";
+    case GTestColor::kYellow:
+      return "3";
+    default:
+      return nullptr;
+  }
+}
+
+#endif  // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+
+// Returns true if and only if Google Test should use colors in the output.
+bool ShouldUseColor(bool stdout_is_tty) {
+  const char* const gtest_color = GTEST_FLAG(color).c_str();
+
+  if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) {
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW
+    // On Windows the TERM variable is usually not set, but the
+    // console there does support colors.
+    return stdout_is_tty;
+#else
+    // On non-Windows platforms, we rely on the TERM variable.
+    const char* const term = posix::GetEnv("TERM");
+    const bool term_supports_color =
+        String::CStringEquals(term, "xterm") ||
+        String::CStringEquals(term, "xterm-color") ||
+        String::CStringEquals(term, "xterm-256color") ||
+        String::CStringEquals(term, "screen") ||
+        String::CStringEquals(term, "screen-256color") ||
+        String::CStringEquals(term, "tmux") ||
+        String::CStringEquals(term, "tmux-256color") ||
+        String::CStringEquals(term, "rxvt-unicode") ||
+        String::CStringEquals(term, "rxvt-unicode-256color") ||
+        String::CStringEquals(term, "linux") ||
+        String::CStringEquals(term, "cygwin");
+    return stdout_is_tty && term_supports_color;
+#endif  // GTEST_OS_WINDOWS
+  }
+
+  return String::CaseInsensitiveCStringEquals(gtest_color, "yes") ||
+      String::CaseInsensitiveCStringEquals(gtest_color, "true") ||
+      String::CaseInsensitiveCStringEquals(gtest_color, "t") ||
+      String::CStringEquals(gtest_color, "1");
+  // We take "yes", "true", "t", and "1" as meaning "yes".  If the
+  // value is neither one of these nor "auto", we treat it as "no" to
+  // be conservative.
+}
+
+// Helpers for printing colored strings to stdout. Note that on Windows, we
+// cannot simply emit special characters and have the terminal change colors.
+// This routine must actually emit the characters rather than return a string
+// that would be colored when printed, as can be done on Linux.
+
+GTEST_ATTRIBUTE_PRINTF_(2, 3)
+static void ColoredPrintf(GTestColor color, const char *fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS || GTEST_OS_IOS || \
+    GTEST_OS_WINDOWS_PHONE || GTEST_OS_WINDOWS_RT || defined(ESP_PLATFORM)
+  const bool use_color = AlwaysFalse();
+#else
+  static const bool in_color_mode =
+      ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0);
+  const bool use_color = in_color_mode && (color != GTestColor::kDefault);
+#endif  // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_ZOS
+
+  if (!use_color) {
+    vprintf(fmt, args);
+    va_end(args);
+    return;
+  }
+
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE && \
+    !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT && !GTEST_OS_WINDOWS_MINGW
+  const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+
+  // Gets the current text color.
+  CONSOLE_SCREEN_BUFFER_INFO buffer_info;
+  GetConsoleScreenBufferInfo(stdout_handle, &buffer_info);
+  const WORD old_color_attrs = buffer_info.wAttributes;
+  const WORD new_color = GetNewColor(color, old_color_attrs);
+
+  // We need to flush the stream buffers into the console before each
+  // SetConsoleTextAttribute call lest it affect the text that is already
+  // printed but has not yet reached the console.
+  fflush(stdout);
+  SetConsoleTextAttribute(stdout_handle, new_color);
+
+  vprintf(fmt, args);
+
+  fflush(stdout);
+  // Restores the text color.
+  SetConsoleTextAttribute(stdout_handle, old_color_attrs);
+#else
+  printf("\033[0;3%sm", GetAnsiColorCode(color));
+  vprintf(fmt, args);
+  printf("\033[m");  // Resets the terminal to default.
+#endif  // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE
+  va_end(args);
+}
+
+// Text printed in Google Test's text output and --gtest_list_tests
+// output to label the type parameter and value parameter for a test.
+static const char kTypeParamLabel[] = "TypeParam";
+static const char kValueParamLabel[] = "GetParam()";
+
+static void PrintFullTestCommentIfPresent(const TestInfo& test_info) {
+  const char* const type_param = test_info.type_param();
+  const char* const value_param = test_info.value_param();
+
+  if (type_param != nullptr || value_param != nullptr) {
+    printf(", where ");
+    if (type_param != nullptr) {
+      printf("%s = %s", kTypeParamLabel, type_param);
+      if (value_param != nullptr) printf(" and ");
+    }
+    if (value_param != nullptr) {
+      printf("%s = %s", kValueParamLabel, value_param);
+    }
+  }
+}
+
+// This class implements the TestEventListener interface.
+//
+// Class PrettyUnitTestResultPrinter is copyable.
+class PrettyUnitTestResultPrinter : public TestEventListener {
+ public:
+  PrettyUnitTestResultPrinter() {}
+  static void PrintTestName(const char* test_suite, const char* test) {
+    printf("%s.%s", test_suite, test);
+  }
+
+  // The following methods override what's in the TestEventListener class.
+  void OnTestProgramStart(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationStart(const UnitTest& unit_test, int iteration) override;
+  void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override;
+  void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {}
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseStart(const TestCase& test_case) override;
+#else
+  void OnTestSuiteStart(const TestSuite& test_suite) override;
+#endif  // OnTestCaseStart
+
+  void OnTestStart(const TestInfo& test_info) override;
+
+  void OnTestPartResult(const TestPartResult& result) override;
+  void OnTestEnd(const TestInfo& test_info) override;
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseEnd(const TestCase& test_case) override;
+#else
+  void OnTestSuiteEnd(const TestSuite& test_suite) override;
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override;
+  void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override;
+  void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {}
+
+ private:
+  static void PrintFailedTests(const UnitTest& unit_test);
+  static void PrintFailedTestSuites(const UnitTest& unit_test);
+  static void PrintSkippedTests(const UnitTest& unit_test);
+};
+
+  // Fired before each iteration of tests starts.
+void PrettyUnitTestResultPrinter::OnTestIterationStart(
+    const UnitTest& unit_test, int iteration) {
+  if (GTEST_FLAG(repeat) != 1)
+    printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1);
+
+  const char* const filter = GTEST_FLAG(filter).c_str();
+
+  // Prints the filter if it's not *.  This reminds the user that some
+  // tests may be skipped.
+  if (!String::CStringEquals(filter, kUniversalFilter)) {
+    ColoredPrintf(GTestColor::kYellow, "Note: %s filter = %s\n", GTEST_NAME_,
+                  filter);
+  }
+
+  if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) {
+    const int32_t shard_index = Int32FromEnvOrDie(kTestShardIndex, -1);
+    ColoredPrintf(GTestColor::kYellow, "Note: This is test shard %d of %s.\n",
+                  static_cast<int>(shard_index) + 1,
+                  internal::posix::GetEnv(kTestTotalShards));
+  }
+
+  if (GTEST_FLAG(shuffle)) {
+    ColoredPrintf(GTestColor::kYellow,
+                  "Note: Randomizing tests' orders with a seed of %d .\n",
+                  unit_test.random_seed());
+  }
+
+  ColoredPrintf(GTestColor::kGreen, "[==========] ");
+  printf("Running %s from %s.\n",
+         FormatTestCount(unit_test.test_to_run_count()).c_str(),
+         FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str());
+  fflush(stdout);
+}
+
+void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart(
+    const UnitTest& /*unit_test*/) {
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("Global test environment set-up.\n");
+  fflush(stdout);
+}
+
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) {
+  const std::string counts =
+      FormatCountableNoun(test_case.test_to_run_count(), "test", "tests");
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("%s from %s", counts.c_str(), test_case.name());
+  if (test_case.type_param() == nullptr) {
+    printf("\n");
+  } else {
+    printf(", where %s = %s\n", kTypeParamLabel, test_case.type_param());
+  }
+  fflush(stdout);
+}
+#else
+void PrettyUnitTestResultPrinter::OnTestSuiteStart(
+    const TestSuite& test_suite) {
+  const std::string counts =
+      FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests");
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("%s from %s", counts.c_str(), test_suite.name());
+  if (test_suite.type_param() == nullptr) {
+    printf("\n");
+  } else {
+    printf(", where %s = %s\n", kTypeParamLabel, test_suite.type_param());
+  }
+  fflush(stdout);
+}
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) {
+  ColoredPrintf(GTestColor::kGreen, "[ RUN      ] ");
+  PrintTestName(test_info.test_suite_name(), test_info.name());
+  printf("\n");
+  fflush(stdout);
+}
+
+// Called after an assertion failure.
+void PrettyUnitTestResultPrinter::OnTestPartResult(
+    const TestPartResult& result) {
+  switch (result.type()) {
+    // If the test part succeeded, we don't need to do anything.
+    case TestPartResult::kSuccess:
+      return;
+    default:
+      // Print failure message from the assertion
+      // (e.g. expected this and got that).
+      PrintTestPartResult(result);
+      fflush(stdout);
+  }
+}
+
+void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) {
+  if (test_info.result()->Passed()) {
+    ColoredPrintf(GTestColor::kGreen, "[       OK ] ");
+  } else if (test_info.result()->Skipped()) {
+    ColoredPrintf(GTestColor::kGreen, "[  SKIPPED ] ");
+  } else {
+    ColoredPrintf(GTestColor::kRed, "[  FAILED  ] ");
+  }
+  PrintTestName(test_info.test_suite_name(), test_info.name());
+  if (test_info.result()->Failed())
+    PrintFullTestCommentIfPresent(test_info);
+
+  if (GTEST_FLAG(print_time)) {
+    printf(" (%s ms)\n", internal::StreamableToString(
+           test_info.result()->elapsed_time()).c_str());
+  } else {
+    printf("\n");
+  }
+  fflush(stdout);
+}
+
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) {
+  if (!GTEST_FLAG(print_time)) return;
+
+  const std::string counts =
+      FormatCountableNoun(test_case.test_to_run_count(), "test", "tests");
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_case.name(),
+         internal::StreamableToString(test_case.elapsed_time()).c_str());
+  fflush(stdout);
+}
+#else
+void PrettyUnitTestResultPrinter::OnTestSuiteEnd(const TestSuite& test_suite) {
+  if (!GTEST_FLAG(print_time)) return;
+
+  const std::string counts =
+      FormatCountableNoun(test_suite.test_to_run_count(), "test", "tests");
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("%s from %s (%s ms total)\n\n", counts.c_str(), test_suite.name(),
+         internal::StreamableToString(test_suite.elapsed_time()).c_str());
+  fflush(stdout);
+}
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart(
+    const UnitTest& /*unit_test*/) {
+  ColoredPrintf(GTestColor::kGreen, "[----------] ");
+  printf("Global test environment tear-down\n");
+  fflush(stdout);
+}
+
+// Internal helper for printing the list of failed tests.
+void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) {
+  const int failed_test_count = unit_test.failed_test_count();
+  ColoredPrintf(GTestColor::kRed, "[  FAILED  ] ");
+  printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str());
+
+  for (int i = 0; i < unit_test.total_test_suite_count(); ++i) {
+    const TestSuite& test_suite = *unit_test.GetTestSuite(i);
+    if (!test_suite.should_run() || (test_suite.failed_test_count() == 0)) {
+      continue;
+    }
+    for (int j = 0; j < test_suite.total_test_count(); ++j) {
+      const TestInfo& test_info = *test_suite.GetTestInfo(j);
+      if (!test_info.should_run() || !test_info.result()->Failed()) {
+        continue;
+      }
+      ColoredPrintf(GTestColor::kRed, "[  FAILED  ] ");
+      printf("%s.%s", test_suite.name(), test_info.name());
+      PrintFullTestCommentIfPresent(test_info);
+      printf("\n");
+    }
+  }
+  printf("\n%2d FAILED %s\n", failed_test_count,
+         failed_test_count == 1 ? "TEST" : "TESTS");
+}
+
+// Internal helper for printing the list of test suite failures not covered by
+// PrintFailedTests.
+void PrettyUnitTestResultPrinter::PrintFailedTestSuites(
+    const UnitTest& unit_test) {
+  int suite_failure_count = 0;
+  for (int i = 0; i < unit_test.total_test_suite_count(); ++i) {
+    const TestSuite& test_suite = *unit_test.GetTestSuite(i);
+    if (!test_suite.should_run()) {
+      continue;
+    }
+    if (test_suite.ad_hoc_test_result().Failed()) {
+      ColoredPrintf(GTestColor::kRed, "[  FAILED  ] ");
+      printf("%s: SetUpTestSuite or TearDownTestSuite\n", test_suite.name());
+      ++suite_failure_count;
+    }
+  }
+  if (suite_failure_count > 0) {
+    printf("\n%2d FAILED TEST %s\n", suite_failure_count,
+           suite_failure_count == 1 ? "SUITE" : "SUITES");
+  }
+}
+
+// Internal helper for printing the list of skipped tests.
+void PrettyUnitTestResultPrinter::PrintSkippedTests(const UnitTest& unit_test) {
+  const int skipped_test_count = unit_test.skipped_test_count();
+  if (skipped_test_count == 0) {
+    return;
+  }
+
+  for (int i = 0; i < unit_test.total_test_suite_count(); ++i) {
+    const TestSuite& test_suite = *unit_test.GetTestSuite(i);
+    if (!test_suite.should_run() || (test_suite.skipped_test_count() == 0)) {
+      continue;
+    }
+    for (int j = 0; j < test_suite.total_test_count(); ++j) {
+      const TestInfo& test_info = *test_suite.GetTestInfo(j);
+      if (!test_info.should_run() || !test_info.result()->Skipped()) {
+        continue;
+      }
+      ColoredPrintf(GTestColor::kGreen, "[  SKIPPED ] ");
+      printf("%s.%s", test_suite.name(), test_info.name());
+      printf("\n");
+    }
+  }
+}
+
+void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+                                                     int /*iteration*/) {
+  ColoredPrintf(GTestColor::kGreen, "[==========] ");
+  printf("%s from %s ran.",
+         FormatTestCount(unit_test.test_to_run_count()).c_str(),
+         FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str());
+  if (GTEST_FLAG(print_time)) {
+    printf(" (%s ms total)",
+           internal::StreamableToString(unit_test.elapsed_time()).c_str());
+  }
+  printf("\n");
+  ColoredPrintf(GTestColor::kGreen, "[  PASSED  ] ");
+  printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str());
+
+  const int skipped_test_count = unit_test.skipped_test_count();
+  if (skipped_test_count > 0) {
+    ColoredPrintf(GTestColor::kGreen, "[  SKIPPED ] ");
+    printf("%s, listed below:\n", FormatTestCount(skipped_test_count).c_str());
+    PrintSkippedTests(unit_test);
+  }
+
+  if (!unit_test.Passed()) {
+    PrintFailedTests(unit_test);
+    PrintFailedTestSuites(unit_test);
+  }
+
+  int num_disabled = unit_test.reportable_disabled_test_count();
+  if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) {
+    if (unit_test.Passed()) {
+      printf("\n");  // Add a spacer if no FAILURE banner is displayed.
+    }
+    ColoredPrintf(GTestColor::kYellow, "  YOU HAVE %d DISABLED %s\n\n",
+                  num_disabled, num_disabled == 1 ? "TEST" : "TESTS");
+  }
+  // Ensure that Google Test output is printed before, e.g., heapchecker output.
+  fflush(stdout);
+}
+
+// End PrettyUnitTestResultPrinter
+
+// This class implements the TestEventListener interface.
+//
+// Class BriefUnitTestResultPrinter is copyable.
+class BriefUnitTestResultPrinter : public TestEventListener {
+ public:
+  BriefUnitTestResultPrinter() {}
+  static void PrintTestName(const char* test_suite, const char* test) {
+    printf("%s.%s", test_suite, test);
+  }
+
+  // The following methods override what's in the TestEventListener class.
+  void OnTestProgramStart(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationStart(const UnitTest& /*unit_test*/,
+                            int /*iteration*/) override {}
+  void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {}
+  void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {}
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseStart(const TestCase& /*test_case*/) override {}
+#else
+  void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {}
+#endif  // OnTestCaseStart
+
+  void OnTestStart(const TestInfo& /*test_info*/) override {}
+
+  void OnTestPartResult(const TestPartResult& result) override;
+  void OnTestEnd(const TestInfo& test_info) override;
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseEnd(const TestCase& /*test_case*/) override {}
+#else
+  void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {}
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {}
+  void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override;
+  void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {}
+};
+
+// Called after an assertion failure.
+void BriefUnitTestResultPrinter::OnTestPartResult(
+    const TestPartResult& result) {
+  switch (result.type()) {
+    // If the test part succeeded, we don't need to do anything.
+    case TestPartResult::kSuccess:
+      return;
+    default:
+      // Print failure message from the assertion
+      // (e.g. expected this and got that).
+      PrintTestPartResult(result);
+      fflush(stdout);
+  }
+}
+
+void BriefUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) {
+  if (test_info.result()->Failed()) {
+    ColoredPrintf(GTestColor::kRed, "[  FAILED  ] ");
+    PrintTestName(test_info.test_suite_name(), test_info.name());
+    PrintFullTestCommentIfPresent(test_info);
+
+    if (GTEST_FLAG(print_time)) {
+      printf(" (%s ms)\n",
+             internal::StreamableToString(test_info.result()->elapsed_time())
+                 .c_str());
+    } else {
+      printf("\n");
+    }
+    fflush(stdout);
+  }
+}
+
+void BriefUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+                                                    int /*iteration*/) {
+  ColoredPrintf(GTestColor::kGreen, "[==========] ");
+  printf("%s from %s ran.",
+         FormatTestCount(unit_test.test_to_run_count()).c_str(),
+         FormatTestSuiteCount(unit_test.test_suite_to_run_count()).c_str());
+  if (GTEST_FLAG(print_time)) {
+    printf(" (%s ms total)",
+           internal::StreamableToString(unit_test.elapsed_time()).c_str());
+  }
+  printf("\n");
+  ColoredPrintf(GTestColor::kGreen, "[  PASSED  ] ");
+  printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str());
+
+  const int skipped_test_count = unit_test.skipped_test_count();
+  if (skipped_test_count > 0) {
+    ColoredPrintf(GTestColor::kGreen, "[  SKIPPED ] ");
+    printf("%s.\n", FormatTestCount(skipped_test_count).c_str());
+  }
+
+  int num_disabled = unit_test.reportable_disabled_test_count();
+  if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) {
+    if (unit_test.Passed()) {
+      printf("\n");  // Add a spacer if no FAILURE banner is displayed.
+    }
+    ColoredPrintf(GTestColor::kYellow, "  YOU HAVE %d DISABLED %s\n\n",
+                  num_disabled, num_disabled == 1 ? "TEST" : "TESTS");
+  }
+  // Ensure that Google Test output is printed before, e.g., heapchecker output.
+  fflush(stdout);
+}
+
+// End BriefUnitTestResultPrinter
+
+// class TestEventRepeater
+//
+// This class forwards events to other event listeners.
+class TestEventRepeater : public TestEventListener {
+ public:
+  TestEventRepeater() : forwarding_enabled_(true) {}
+  ~TestEventRepeater() override;
+  void Append(TestEventListener *listener);
+  TestEventListener* Release(TestEventListener* listener);
+
+  // Controls whether events will be forwarded to listeners_. Set to false
+  // in death test child processes.
+  bool forwarding_enabled() const { return forwarding_enabled_; }
+  void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; }
+
+  void OnTestProgramStart(const UnitTest& unit_test) override;
+  void OnTestIterationStart(const UnitTest& unit_test, int iteration) override;
+  void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override;
+  void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override;
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseStart(const TestSuite& parameter) override;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestSuiteStart(const TestSuite& parameter) override;
+  void OnTestStart(const TestInfo& test_info) override;
+  void OnTestPartResult(const TestPartResult& result) override;
+  void OnTestEnd(const TestInfo& test_info) override;
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseEnd(const TestCase& parameter) override;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestSuiteEnd(const TestSuite& parameter) override;
+  void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override;
+  void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override;
+  void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override;
+  void OnTestProgramEnd(const UnitTest& unit_test) override;
+
+ private:
+  // Controls whether events will be forwarded to listeners_. Set to false
+  // in death test child processes.
+  bool forwarding_enabled_;
+  // The list of listeners that receive events.
+  std::vector<TestEventListener*> listeners_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater);
+};
+
+TestEventRepeater::~TestEventRepeater() {
+  ForEach(listeners_, Delete<TestEventListener>);
+}
+
+void TestEventRepeater::Append(TestEventListener *listener) {
+  listeners_.push_back(listener);
+}
+
+TestEventListener* TestEventRepeater::Release(TestEventListener *listener) {
+  for (size_t i = 0; i < listeners_.size(); ++i) {
+    if (listeners_[i] == listener) {
+      listeners_.erase(listeners_.begin() + static_cast<int>(i));
+      return listener;
+    }
+  }
+
+  return nullptr;
+}
+
+// Since most methods are very similar, use macros to reduce boilerplate.
+// This defines a member that forwards the call to all listeners.
+#define GTEST_REPEATER_METHOD_(Name, Type) \
+void TestEventRepeater::Name(const Type& parameter) { \
+  if (forwarding_enabled_) { \
+    for (size_t i = 0; i < listeners_.size(); i++) { \
+      listeners_[i]->Name(parameter); \
+    } \
+  } \
+}
+// This defines a member that forwards the call to all listeners in reverse
+// order.
+#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type)      \
+  void TestEventRepeater::Name(const Type& parameter) { \
+    if (forwarding_enabled_) {                          \
+      for (size_t i = listeners_.size(); i != 0; i--) { \
+        listeners_[i - 1]->Name(parameter);             \
+      }                                                 \
+    }                                                   \
+  }
+
+GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest)
+GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest)
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+GTEST_REPEATER_METHOD_(OnTestCaseStart, TestSuite)
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+GTEST_REPEATER_METHOD_(OnTestSuiteStart, TestSuite)
+GTEST_REPEATER_METHOD_(OnTestStart, TestInfo)
+GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult)
+GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest)
+GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo)
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestSuite)
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+GTEST_REVERSE_REPEATER_METHOD_(OnTestSuiteEnd, TestSuite)
+GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest)
+
+#undef GTEST_REPEATER_METHOD_
+#undef GTEST_REVERSE_REPEATER_METHOD_
+
+void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test,
+                                             int iteration) {
+  if (forwarding_enabled_) {
+    for (size_t i = 0; i < listeners_.size(); i++) {
+      listeners_[i]->OnTestIterationStart(unit_test, iteration);
+    }
+  }
+}
+
+void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test,
+                                           int iteration) {
+  if (forwarding_enabled_) {
+    for (size_t i = listeners_.size(); i > 0; i--) {
+      listeners_[i - 1]->OnTestIterationEnd(unit_test, iteration);
+    }
+  }
+}
+
+// End TestEventRepeater
+
+// This class generates an XML output file.
+class XmlUnitTestResultPrinter : public EmptyTestEventListener {
+ public:
+  explicit XmlUnitTestResultPrinter(const char* output_file);
+
+  void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override;
+  void ListTestsMatchingFilter(const std::vector<TestSuite*>& test_suites);
+
+  // Prints an XML summary of all unit tests.
+  static void PrintXmlTestsList(std::ostream* stream,
+                                const std::vector<TestSuite*>& test_suites);
+
+ private:
+  // Is c a whitespace character that is normalized to a space character
+  // when it appears in an XML attribute value?
+  static bool IsNormalizableWhitespace(char c) {
+    return c == 0x9 || c == 0xA || c == 0xD;
+  }
+
+  // May c appear in a well-formed XML document?
+  static bool IsValidXmlCharacter(char c) {
+    return IsNormalizableWhitespace(c) || c >= 0x20;
+  }
+
+  // Returns an XML-escaped copy of the input string str.  If
+  // is_attribute is true, the text is meant to appear as an attribute
+  // value, and normalizable whitespace is preserved by replacing it
+  // with character references.
+  static std::string EscapeXml(const std::string& str, bool is_attribute);
+
+  // Returns the given string with all characters invalid in XML removed.
+  static std::string RemoveInvalidXmlCharacters(const std::string& str);
+
+  // Convenience wrapper around EscapeXml when str is an attribute value.
+  static std::string EscapeXmlAttribute(const std::string& str) {
+    return EscapeXml(str, true);
+  }
+
+  // Convenience wrapper around EscapeXml when str is not an attribute value.
+  static std::string EscapeXmlText(const char* str) {
+    return EscapeXml(str, false);
+  }
+
+  // Verifies that the given attribute belongs to the given element and
+  // streams the attribute as XML.
+  static void OutputXmlAttribute(std::ostream* stream,
+                                 const std::string& element_name,
+                                 const std::string& name,
+                                 const std::string& value);
+
+  // Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
+  static void OutputXmlCDataSection(::std::ostream* stream, const char* data);
+
+  // Streams a test suite XML stanza containing the given test result.
+  //
+  // Requires: result.Failed()
+  static void OutputXmlTestSuiteForTestResult(::std::ostream* stream,
+                                              const TestResult& result);
+
+  // Streams an XML representation of a TestResult object.
+  static void OutputXmlTestResult(::std::ostream* stream,
+                                  const TestResult& result);
+
+  // Streams an XML representation of a TestInfo object.
+  static void OutputXmlTestInfo(::std::ostream* stream,
+                                const char* test_suite_name,
+                                const TestInfo& test_info);
+
+  // Prints an XML representation of a TestSuite object
+  static void PrintXmlTestSuite(::std::ostream* stream,
+                                const TestSuite& test_suite);
+
+  // Prints an XML summary of unit_test to output stream out.
+  static void PrintXmlUnitTest(::std::ostream* stream,
+                               const UnitTest& unit_test);
+
+  // Produces a string representing the test properties in a result as space
+  // delimited XML attributes based on the property key="value" pairs.
+  // When the std::string is not empty, it includes a space at the beginning,
+  // to delimit this attribute from prior attributes.
+  static std::string TestPropertiesAsXmlAttributes(const TestResult& result);
+
+  // Streams an XML representation of the test properties of a TestResult
+  // object.
+  static void OutputXmlTestProperties(std::ostream* stream,
+                                      const TestResult& result);
+
+  // The output file.
+  const std::string output_file_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter);
+};
+
+// Creates a new XmlUnitTestResultPrinter.
+XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file)
+    : output_file_(output_file) {
+  if (output_file_.empty()) {
+    GTEST_LOG_(FATAL) << "XML output file may not be null";
+  }
+}
+
+// Called after the unit test ends.
+void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+                                                  int /*iteration*/) {
+  FILE* xmlout = OpenFileForWriting(output_file_);
+  std::stringstream stream;
+  PrintXmlUnitTest(&stream, unit_test);
+  fprintf(xmlout, "%s", StringStreamToString(&stream).c_str());
+  fclose(xmlout);
+}
+
+void XmlUnitTestResultPrinter::ListTestsMatchingFilter(
+    const std::vector<TestSuite*>& test_suites) {
+  FILE* xmlout = OpenFileForWriting(output_file_);
+  std::stringstream stream;
+  PrintXmlTestsList(&stream, test_suites);
+  fprintf(xmlout, "%s", StringStreamToString(&stream).c_str());
+  fclose(xmlout);
+}
+
+// Returns an XML-escaped copy of the input string str.  If is_attribute
+// is true, the text is meant to appear as an attribute value, and
+// normalizable whitespace is preserved by replacing it with character
+// references.
+//
+// Invalid XML characters in str, if any, are stripped from the output.
+// It is expected that most, if not all, of the text processed by this
+// module will consist of ordinary English text.
+// If this module is ever modified to produce version 1.1 XML output,
+// most invalid characters can be retained using character references.
+std::string XmlUnitTestResultPrinter::EscapeXml(
+    const std::string& str, bool is_attribute) {
+  Message m;
+
+  for (size_t i = 0; i < str.size(); ++i) {
+    const char ch = str[i];
+    switch (ch) {
+      case '<':
+        m << "&lt;";
+        break;
+      case '>':
+        m << "&gt;";
+        break;
+      case '&':
+        m << "&amp;";
+        break;
+      case '\'':
+        if (is_attribute)
+          m << "&apos;";
+        else
+          m << '\'';
+        break;
+      case '"':
+        if (is_attribute)
+          m << "&quot;";
+        else
+          m << '"';
+        break;
+      default:
+        if (IsValidXmlCharacter(ch)) {
+          if (is_attribute && IsNormalizableWhitespace(ch))
+            m << "&#x" << String::FormatByte(static_cast<unsigned char>(ch))
+              << ";";
+          else
+            m << ch;
+        }
+        break;
+    }
+  }
+
+  return m.GetString();
+}
+
+// Returns the given string with all characters invalid in XML removed.
+// Currently invalid characters are dropped from the string. An
+// alternative is to replace them with certain characters such as . or ?.
+std::string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(
+    const std::string& str) {
+  std::string output;
+  output.reserve(str.size());
+  for (std::string::const_iterator it = str.begin(); it != str.end(); ++it)
+    if (IsValidXmlCharacter(*it))
+      output.push_back(*it);
+
+  return output;
+}
+
+// The following routines generate an XML representation of a UnitTest
+// object.
+// GOOGLETEST_CM0009 DO NOT DELETE
+//
+// This is how Google Test concepts map to the DTD:
+//
+// <testsuites name="AllTests">        <-- corresponds to a UnitTest object
+//   <testsuite name="testcase-name">  <-- corresponds to a TestSuite object
+//     <testcase name="test-name">     <-- corresponds to a TestInfo object
+//       <failure message="...">...</failure>
+//       <failure message="...">...</failure>
+//       <failure message="...">...</failure>
+//                                     <-- individual assertion failures
+//     </testcase>
+//   </testsuite>
+// </testsuites>
+
+// Formats the given time in milliseconds as seconds.
+std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) {
+  ::std::stringstream ss;
+  ss << (static_cast<double>(ms) * 1e-3);
+  return ss.str();
+}
+
+static bool PortableLocaltime(time_t seconds, struct tm* out) {
+#if defined(_MSC_VER)
+  return localtime_s(out, &seconds) == 0;
+#elif defined(__MINGW32__) || defined(__MINGW64__)
+  // MINGW <time.h> provides neither localtime_r nor localtime_s, but uses
+  // Windows' localtime(), which has a thread-local tm buffer.
+  struct tm* tm_ptr = localtime(&seconds);  // NOLINT
+  if (tm_ptr == nullptr) return false;
+  *out = *tm_ptr;
+  return true;
+#elif defined(__STDC_LIB_EXT1__)
+  // Uses localtime_s when available as localtime_r is only available from
+  // C23 standard.
+  return localtime_s(&seconds, out) != nullptr;
+#else
+  return localtime_r(&seconds, out) != nullptr;
+#endif
+}
+
+// Converts the given epoch time in milliseconds to a date string in the ISO
+// 8601 format, without the timezone information.
+std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) {
+  struct tm time_struct;
+  if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct))
+    return "";
+  // YYYY-MM-DDThh:mm:ss.sss
+  return StreamableToString(time_struct.tm_year + 1900) + "-" +
+      String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" +
+      String::FormatIntWidth2(time_struct.tm_mday) + "T" +
+      String::FormatIntWidth2(time_struct.tm_hour) + ":" +
+      String::FormatIntWidth2(time_struct.tm_min) + ":" +
+      String::FormatIntWidth2(time_struct.tm_sec) + "." +
+      String::FormatIntWidthN(static_cast<int>(ms % 1000), 3);
+}
+
+// Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
+void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream,
+                                                     const char* data) {
+  const char* segment = data;
+  *stream << "<![CDATA[";
+  for (;;) {
+    const char* const next_segment = strstr(segment, "]]>");
+    if (next_segment != nullptr) {
+      stream->write(
+          segment, static_cast<std::streamsize>(next_segment - segment));
+      *stream << "]]>]]&gt;<![CDATA[";
+      segment = next_segment + strlen("]]>");
+    } else {
+      *stream << segment;
+      break;
+    }
+  }
+  *stream << "]]>";
+}
+
+void XmlUnitTestResultPrinter::OutputXmlAttribute(
+    std::ostream* stream,
+    const std::string& element_name,
+    const std::string& name,
+    const std::string& value) {
+  const std::vector<std::string>& allowed_names =
+      GetReservedOutputAttributesForElement(element_name);
+
+  GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+                   allowed_names.end())
+      << "Attribute " << name << " is not allowed for element <" << element_name
+      << ">.";
+
+  *stream << " " << name << "=\"" << EscapeXmlAttribute(value) << "\"";
+}
+
+// Streams a test suite XML stanza containing the given test result.
+void XmlUnitTestResultPrinter::OutputXmlTestSuiteForTestResult(
+    ::std::ostream* stream, const TestResult& result) {
+  // Output the boilerplate for a minimal test suite with one test.
+  *stream << "  <testsuite";
+  OutputXmlAttribute(stream, "testsuite", "name", "NonTestSuiteFailure");
+  OutputXmlAttribute(stream, "testsuite", "tests", "1");
+  OutputXmlAttribute(stream, "testsuite", "failures", "1");
+  OutputXmlAttribute(stream, "testsuite", "disabled", "0");
+  OutputXmlAttribute(stream, "testsuite", "skipped", "0");
+  OutputXmlAttribute(stream, "testsuite", "errors", "0");
+  OutputXmlAttribute(stream, "testsuite", "time",
+                     FormatTimeInMillisAsSeconds(result.elapsed_time()));
+  OutputXmlAttribute(
+      stream, "testsuite", "timestamp",
+      FormatEpochTimeInMillisAsIso8601(result.start_timestamp()));
+  *stream << ">";
+
+  // Output the boilerplate for a minimal test case with a single test.
+  *stream << "    <testcase";
+  OutputXmlAttribute(stream, "testcase", "name", "");
+  OutputXmlAttribute(stream, "testcase", "status", "run");
+  OutputXmlAttribute(stream, "testcase", "result", "completed");
+  OutputXmlAttribute(stream, "testcase", "classname", "");
+  OutputXmlAttribute(stream, "testcase", "time",
+                     FormatTimeInMillisAsSeconds(result.elapsed_time()));
+  OutputXmlAttribute(
+      stream, "testcase", "timestamp",
+      FormatEpochTimeInMillisAsIso8601(result.start_timestamp()));
+
+  // Output the actual test result.
+  OutputXmlTestResult(stream, result);
+
+  // Complete the test suite.
+  *stream << "  </testsuite>\n";
+}
+
+// Prints an XML representation of a TestInfo object.
+void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream,
+                                                 const char* test_suite_name,
+                                                 const TestInfo& test_info) {
+  const TestResult& result = *test_info.result();
+  const std::string kTestsuite = "testcase";
+
+  if (test_info.is_in_another_shard()) {
+    return;
+  }
+
+  *stream << "    <testcase";
+  OutputXmlAttribute(stream, kTestsuite, "name", test_info.name());
+
+  if (test_info.value_param() != nullptr) {
+    OutputXmlAttribute(stream, kTestsuite, "value_param",
+                       test_info.value_param());
+  }
+  if (test_info.type_param() != nullptr) {
+    OutputXmlAttribute(stream, kTestsuite, "type_param",
+                       test_info.type_param());
+  }
+  if (GTEST_FLAG(list_tests)) {
+    OutputXmlAttribute(stream, kTestsuite, "file", test_info.file());
+    OutputXmlAttribute(stream, kTestsuite, "line",
+                       StreamableToString(test_info.line()));
+    *stream << " />\n";
+    return;
+  }
+
+  OutputXmlAttribute(stream, kTestsuite, "status",
+                     test_info.should_run() ? "run" : "notrun");
+  OutputXmlAttribute(stream, kTestsuite, "result",
+                     test_info.should_run()
+                         ? (result.Skipped() ? "skipped" : "completed")
+                         : "suppressed");
+  OutputXmlAttribute(stream, kTestsuite, "time",
+                     FormatTimeInMillisAsSeconds(result.elapsed_time()));
+  OutputXmlAttribute(
+      stream, kTestsuite, "timestamp",
+      FormatEpochTimeInMillisAsIso8601(result.start_timestamp()));
+  OutputXmlAttribute(stream, kTestsuite, "classname", test_suite_name);
+
+  OutputXmlTestResult(stream, result);
+}
+
+void XmlUnitTestResultPrinter::OutputXmlTestResult(::std::ostream* stream,
+                                                   const TestResult& result) {
+  int failures = 0;
+  int skips = 0;
+  for (int i = 0; i < result.total_part_count(); ++i) {
+    const TestPartResult& part = result.GetTestPartResult(i);
+    if (part.failed()) {
+      if (++failures == 1 && skips == 0) {
+        *stream << ">\n";
+      }
+      const std::string location =
+          internal::FormatCompilerIndependentFileLocation(part.file_name(),
+                                                          part.line_number());
+      const std::string summary = location + "\n" + part.summary();
+      *stream << "      <failure message=\""
+              << EscapeXmlAttribute(summary)
+              << "\" type=\"\">";
+      const std::string detail = location + "\n" + part.message();
+      OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str());
+      *stream << "</failure>\n";
+    } else if (part.skipped()) {
+      if (++skips == 1 && failures == 0) {
+        *stream << ">\n";
+      }
+      const std::string location =
+          internal::FormatCompilerIndependentFileLocation(part.file_name(),
+                                                          part.line_number());
+      const std::string summary = location + "\n" + part.summary();
+      *stream << "      <skipped message=\""
+              << EscapeXmlAttribute(summary.c_str()) << "\">";
+      const std::string detail = location + "\n" + part.message();
+      OutputXmlCDataSection(stream, RemoveInvalidXmlCharacters(detail).c_str());
+      *stream << "</skipped>\n";
+    }
+  }
+
+  if (failures == 0 && skips == 0 && result.test_property_count() == 0) {
+    *stream << " />\n";
+  } else {
+    if (failures == 0 && skips == 0) {
+      *stream << ">\n";
+    }
+    OutputXmlTestProperties(stream, result);
+    *stream << "    </testcase>\n";
+  }
+}
+
+// Prints an XML representation of a TestSuite object
+void XmlUnitTestResultPrinter::PrintXmlTestSuite(std::ostream* stream,
+                                                 const TestSuite& test_suite) {
+  const std::string kTestsuite = "testsuite";
+  *stream << "  <" << kTestsuite;
+  OutputXmlAttribute(stream, kTestsuite, "name", test_suite.name());
+  OutputXmlAttribute(stream, kTestsuite, "tests",
+                     StreamableToString(test_suite.reportable_test_count()));
+  if (!GTEST_FLAG(list_tests)) {
+    OutputXmlAttribute(stream, kTestsuite, "failures",
+                       StreamableToString(test_suite.failed_test_count()));
+    OutputXmlAttribute(
+        stream, kTestsuite, "disabled",
+        StreamableToString(test_suite.reportable_disabled_test_count()));
+    OutputXmlAttribute(stream, kTestsuite, "skipped",
+                       StreamableToString(test_suite.skipped_test_count()));
+
+    OutputXmlAttribute(stream, kTestsuite, "errors", "0");
+
+    OutputXmlAttribute(stream, kTestsuite, "time",
+                       FormatTimeInMillisAsSeconds(test_suite.elapsed_time()));
+    OutputXmlAttribute(
+        stream, kTestsuite, "timestamp",
+        FormatEpochTimeInMillisAsIso8601(test_suite.start_timestamp()));
+    *stream << TestPropertiesAsXmlAttributes(test_suite.ad_hoc_test_result());
+  }
+  *stream << ">\n";
+  for (int i = 0; i < test_suite.total_test_count(); ++i) {
+    if (test_suite.GetTestInfo(i)->is_reportable())
+      OutputXmlTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i));
+  }
+  *stream << "  </" << kTestsuite << ">\n";
+}
+
+// Prints an XML summary of unit_test to output stream out.
+void XmlUnitTestResultPrinter::PrintXmlUnitTest(std::ostream* stream,
+                                                const UnitTest& unit_test) {
+  const std::string kTestsuites = "testsuites";
+
+  *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+  *stream << "<" << kTestsuites;
+
+  OutputXmlAttribute(stream, kTestsuites, "tests",
+                     StreamableToString(unit_test.reportable_test_count()));
+  OutputXmlAttribute(stream, kTestsuites, "failures",
+                     StreamableToString(unit_test.failed_test_count()));
+  OutputXmlAttribute(
+      stream, kTestsuites, "disabled",
+      StreamableToString(unit_test.reportable_disabled_test_count()));
+  OutputXmlAttribute(stream, kTestsuites, "errors", "0");
+  OutputXmlAttribute(stream, kTestsuites, "time",
+                     FormatTimeInMillisAsSeconds(unit_test.elapsed_time()));
+  OutputXmlAttribute(
+      stream, kTestsuites, "timestamp",
+      FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp()));
+
+  if (GTEST_FLAG(shuffle)) {
+    OutputXmlAttribute(stream, kTestsuites, "random_seed",
+                       StreamableToString(unit_test.random_seed()));
+  }
+  *stream << TestPropertiesAsXmlAttributes(unit_test.ad_hoc_test_result());
+
+  OutputXmlAttribute(stream, kTestsuites, "name", "AllTests");
+  *stream << ">\n";
+
+  for (int i = 0; i < unit_test.total_test_suite_count(); ++i) {
+    if (unit_test.GetTestSuite(i)->reportable_test_count() > 0)
+      PrintXmlTestSuite(stream, *unit_test.GetTestSuite(i));
+  }
+
+  // If there was a test failure outside of one of the test suites (like in a
+  // test environment) include that in the output.
+  if (unit_test.ad_hoc_test_result().Failed()) {
+    OutputXmlTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result());
+  }
+
+  *stream << "</" << kTestsuites << ">\n";
+}
+
+void XmlUnitTestResultPrinter::PrintXmlTestsList(
+    std::ostream* stream, const std::vector<TestSuite*>& test_suites) {
+  const std::string kTestsuites = "testsuites";
+
+  *stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+  *stream << "<" << kTestsuites;
+
+  int total_tests = 0;
+  for (auto test_suite : test_suites) {
+    total_tests += test_suite->total_test_count();
+  }
+  OutputXmlAttribute(stream, kTestsuites, "tests",
+                     StreamableToString(total_tests));
+  OutputXmlAttribute(stream, kTestsuites, "name", "AllTests");
+  *stream << ">\n";
+
+  for (auto test_suite : test_suites) {
+    PrintXmlTestSuite(stream, *test_suite);
+  }
+  *stream << "</" << kTestsuites << ">\n";
+}
+
+// Produces a string representing the test properties in a result as space
+// delimited XML attributes based on the property key="value" pairs.
+std::string XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes(
+    const TestResult& result) {
+  Message attributes;
+  for (int i = 0; i < result.test_property_count(); ++i) {
+    const TestProperty& property = result.GetTestProperty(i);
+    attributes << " " << property.key() << "="
+        << "\"" << EscapeXmlAttribute(property.value()) << "\"";
+  }
+  return attributes.GetString();
+}
+
+void XmlUnitTestResultPrinter::OutputXmlTestProperties(
+    std::ostream* stream, const TestResult& result) {
+  const std::string kProperties = "properties";
+  const std::string kProperty = "property";
+
+  if (result.test_property_count() <= 0) {
+    return;
+  }
+
+  *stream << "<" << kProperties << ">\n";
+  for (int i = 0; i < result.test_property_count(); ++i) {
+    const TestProperty& property = result.GetTestProperty(i);
+    *stream << "<" << kProperty;
+    *stream << " name=\"" << EscapeXmlAttribute(property.key()) << "\"";
+    *stream << " value=\"" << EscapeXmlAttribute(property.value()) << "\"";
+    *stream << "/>\n";
+  }
+  *stream << "</" << kProperties << ">\n";
+}
+
+// End XmlUnitTestResultPrinter
+
+// This class generates an JSON output file.
+class JsonUnitTestResultPrinter : public EmptyTestEventListener {
+ public:
+  explicit JsonUnitTestResultPrinter(const char* output_file);
+
+  void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override;
+
+  // Prints an JSON summary of all unit tests.
+  static void PrintJsonTestList(::std::ostream* stream,
+                                const std::vector<TestSuite*>& test_suites);
+
+ private:
+  // Returns an JSON-escaped copy of the input string str.
+  static std::string EscapeJson(const std::string& str);
+
+  //// Verifies that the given attribute belongs to the given element and
+  //// streams the attribute as JSON.
+  static void OutputJsonKey(std::ostream* stream,
+                            const std::string& element_name,
+                            const std::string& name,
+                            const std::string& value,
+                            const std::string& indent,
+                            bool comma = true);
+  static void OutputJsonKey(std::ostream* stream,
+                            const std::string& element_name,
+                            const std::string& name,
+                            int value,
+                            const std::string& indent,
+                            bool comma = true);
+
+  // Streams a test suite JSON stanza containing the given test result.
+  //
+  // Requires: result.Failed()
+  static void OutputJsonTestSuiteForTestResult(::std::ostream* stream,
+                                               const TestResult& result);
+
+  // Streams a JSON representation of a TestResult object.
+  static void OutputJsonTestResult(::std::ostream* stream,
+                                   const TestResult& result);
+
+  // Streams a JSON representation of a TestInfo object.
+  static void OutputJsonTestInfo(::std::ostream* stream,
+                                 const char* test_suite_name,
+                                 const TestInfo& test_info);
+
+  // Prints a JSON representation of a TestSuite object
+  static void PrintJsonTestSuite(::std::ostream* stream,
+                                 const TestSuite& test_suite);
+
+  // Prints a JSON summary of unit_test to output stream out.
+  static void PrintJsonUnitTest(::std::ostream* stream,
+                                const UnitTest& unit_test);
+
+  // Produces a string representing the test properties in a result as
+  // a JSON dictionary.
+  static std::string TestPropertiesAsJson(const TestResult& result,
+                                          const std::string& indent);
+
+  // The output file.
+  const std::string output_file_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(JsonUnitTestResultPrinter);
+};
+
+// Creates a new JsonUnitTestResultPrinter.
+JsonUnitTestResultPrinter::JsonUnitTestResultPrinter(const char* output_file)
+    : output_file_(output_file) {
+  if (output_file_.empty()) {
+    GTEST_LOG_(FATAL) << "JSON output file may not be null";
+  }
+}
+
+void JsonUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test,
+                                                  int /*iteration*/) {
+  FILE* jsonout = OpenFileForWriting(output_file_);
+  std::stringstream stream;
+  PrintJsonUnitTest(&stream, unit_test);
+  fprintf(jsonout, "%s", StringStreamToString(&stream).c_str());
+  fclose(jsonout);
+}
+
+// Returns an JSON-escaped copy of the input string str.
+std::string JsonUnitTestResultPrinter::EscapeJson(const std::string& str) {
+  Message m;
+
+  for (size_t i = 0; i < str.size(); ++i) {
+    const char ch = str[i];
+    switch (ch) {
+      case '\\':
+      case '"':
+      case '/':
+        m << '\\' << ch;
+        break;
+      case '\b':
+        m << "\\b";
+        break;
+      case '\t':
+        m << "\\t";
+        break;
+      case '\n':
+        m << "\\n";
+        break;
+      case '\f':
+        m << "\\f";
+        break;
+      case '\r':
+        m << "\\r";
+        break;
+      default:
+        if (ch < ' ') {
+          m << "\\u00" << String::FormatByte(static_cast<unsigned char>(ch));
+        } else {
+          m << ch;
+        }
+        break;
+    }
+  }
+
+  return m.GetString();
+}
+
+// The following routines generate an JSON representation of a UnitTest
+// object.
+
+// Formats the given time in milliseconds as seconds.
+static std::string FormatTimeInMillisAsDuration(TimeInMillis ms) {
+  ::std::stringstream ss;
+  ss << (static_cast<double>(ms) * 1e-3) << "s";
+  return ss.str();
+}
+
+// Converts the given epoch time in milliseconds to a date string in the
+// RFC3339 format, without the timezone information.
+static std::string FormatEpochTimeInMillisAsRFC3339(TimeInMillis ms) {
+  struct tm time_struct;
+  if (!PortableLocaltime(static_cast<time_t>(ms / 1000), &time_struct))
+    return "";
+  // YYYY-MM-DDThh:mm:ss
+  return StreamableToString(time_struct.tm_year + 1900) + "-" +
+      String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" +
+      String::FormatIntWidth2(time_struct.tm_mday) + "T" +
+      String::FormatIntWidth2(time_struct.tm_hour) + ":" +
+      String::FormatIntWidth2(time_struct.tm_min) + ":" +
+      String::FormatIntWidth2(time_struct.tm_sec) + "Z";
+}
+
+static inline std::string Indent(size_t width) {
+  return std::string(width, ' ');
+}
+
+void JsonUnitTestResultPrinter::OutputJsonKey(
+    std::ostream* stream,
+    const std::string& element_name,
+    const std::string& name,
+    const std::string& value,
+    const std::string& indent,
+    bool comma) {
+  const std::vector<std::string>& allowed_names =
+      GetReservedOutputAttributesForElement(element_name);
+
+  GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+                   allowed_names.end())
+      << "Key \"" << name << "\" is not allowed for value \"" << element_name
+      << "\".";
+
+  *stream << indent << "\"" << name << "\": \"" << EscapeJson(value) << "\"";
+  if (comma)
+    *stream << ",\n";
+}
+
+void JsonUnitTestResultPrinter::OutputJsonKey(
+    std::ostream* stream,
+    const std::string& element_name,
+    const std::string& name,
+    int value,
+    const std::string& indent,
+    bool comma) {
+  const std::vector<std::string>& allowed_names =
+      GetReservedOutputAttributesForElement(element_name);
+
+  GTEST_CHECK_(std::find(allowed_names.begin(), allowed_names.end(), name) !=
+                   allowed_names.end())
+      << "Key \"" << name << "\" is not allowed for value \"" << element_name
+      << "\".";
+
+  *stream << indent << "\"" << name << "\": " << StreamableToString(value);
+  if (comma)
+    *stream << ",\n";
+}
+
+// Streams a test suite JSON stanza containing the given test result.
+void JsonUnitTestResultPrinter::OutputJsonTestSuiteForTestResult(
+    ::std::ostream* stream, const TestResult& result) {
+  // Output the boilerplate for a new test suite.
+  *stream << Indent(4) << "{\n";
+  OutputJsonKey(stream, "testsuite", "name", "NonTestSuiteFailure", Indent(6));
+  OutputJsonKey(stream, "testsuite", "tests", 1, Indent(6));
+  if (!GTEST_FLAG(list_tests)) {
+    OutputJsonKey(stream, "testsuite", "failures", 1, Indent(6));
+    OutputJsonKey(stream, "testsuite", "disabled", 0, Indent(6));
+    OutputJsonKey(stream, "testsuite", "skipped", 0, Indent(6));
+    OutputJsonKey(stream, "testsuite", "errors", 0, Indent(6));
+    OutputJsonKey(stream, "testsuite", "time",
+                  FormatTimeInMillisAsDuration(result.elapsed_time()),
+                  Indent(6));
+    OutputJsonKey(stream, "testsuite", "timestamp",
+                  FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()),
+                  Indent(6));
+  }
+  *stream << Indent(6) << "\"testsuite\": [\n";
+
+  // Output the boilerplate for a new test case.
+  *stream << Indent(8) << "{\n";
+  OutputJsonKey(stream, "testcase", "name", "", Indent(10));
+  OutputJsonKey(stream, "testcase", "status", "RUN", Indent(10));
+  OutputJsonKey(stream, "testcase", "result", "COMPLETED", Indent(10));
+  OutputJsonKey(stream, "testcase", "timestamp",
+                FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()),
+                Indent(10));
+  OutputJsonKey(stream, "testcase", "time",
+                FormatTimeInMillisAsDuration(result.elapsed_time()),
+                Indent(10));
+  OutputJsonKey(stream, "testcase", "classname", "", Indent(10), false);
+  *stream << TestPropertiesAsJson(result, Indent(10));
+
+  // Output the actual test result.
+  OutputJsonTestResult(stream, result);
+
+  // Finish the test suite.
+  *stream << "\n" << Indent(6) << "]\n" << Indent(4) << "}";
+}
+
+// Prints a JSON representation of a TestInfo object.
+void JsonUnitTestResultPrinter::OutputJsonTestInfo(::std::ostream* stream,
+                                                   const char* test_suite_name,
+                                                   const TestInfo& test_info) {
+  const TestResult& result = *test_info.result();
+  const std::string kTestsuite = "testcase";
+  const std::string kIndent = Indent(10);
+
+  *stream << Indent(8) << "{\n";
+  OutputJsonKey(stream, kTestsuite, "name", test_info.name(), kIndent);
+
+  if (test_info.value_param() != nullptr) {
+    OutputJsonKey(stream, kTestsuite, "value_param", test_info.value_param(),
+                  kIndent);
+  }
+  if (test_info.type_param() != nullptr) {
+    OutputJsonKey(stream, kTestsuite, "type_param", test_info.type_param(),
+                  kIndent);
+  }
+  if (GTEST_FLAG(list_tests)) {
+    OutputJsonKey(stream, kTestsuite, "file", test_info.file(), kIndent);
+    OutputJsonKey(stream, kTestsuite, "line", test_info.line(), kIndent, false);
+    *stream << "\n" << Indent(8) << "}";
+    return;
+  }
+
+  OutputJsonKey(stream, kTestsuite, "status",
+                test_info.should_run() ? "RUN" : "NOTRUN", kIndent);
+  OutputJsonKey(stream, kTestsuite, "result",
+                test_info.should_run()
+                    ? (result.Skipped() ? "SKIPPED" : "COMPLETED")
+                    : "SUPPRESSED",
+                kIndent);
+  OutputJsonKey(stream, kTestsuite, "timestamp",
+                FormatEpochTimeInMillisAsRFC3339(result.start_timestamp()),
+                kIndent);
+  OutputJsonKey(stream, kTestsuite, "time",
+                FormatTimeInMillisAsDuration(result.elapsed_time()), kIndent);
+  OutputJsonKey(stream, kTestsuite, "classname", test_suite_name, kIndent,
+                false);
+  *stream << TestPropertiesAsJson(result, kIndent);
+
+  OutputJsonTestResult(stream, result);
+}
+
+void JsonUnitTestResultPrinter::OutputJsonTestResult(::std::ostream* stream,
+                                                     const TestResult& result) {
+  const std::string kIndent = Indent(10);
+
+  int failures = 0;
+  for (int i = 0; i < result.total_part_count(); ++i) {
+    const TestPartResult& part = result.GetTestPartResult(i);
+    if (part.failed()) {
+      *stream << ",\n";
+      if (++failures == 1) {
+        *stream << kIndent << "\"" << "failures" << "\": [\n";
+      }
+      const std::string location =
+          internal::FormatCompilerIndependentFileLocation(part.file_name(),
+                                                          part.line_number());
+      const std::string message = EscapeJson(location + "\n" + part.message());
+      *stream << kIndent << "  {\n"
+              << kIndent << "    \"failure\": \"" << message << "\",\n"
+              << kIndent << "    \"type\": \"\"\n"
+              << kIndent << "  }";
+    }
+  }
+
+  if (failures > 0)
+    *stream << "\n" << kIndent << "]";
+  *stream << "\n" << Indent(8) << "}";
+}
+
+// Prints an JSON representation of a TestSuite object
+void JsonUnitTestResultPrinter::PrintJsonTestSuite(
+    std::ostream* stream, const TestSuite& test_suite) {
+  const std::string kTestsuite = "testsuite";
+  const std::string kIndent = Indent(6);
+
+  *stream << Indent(4) << "{\n";
+  OutputJsonKey(stream, kTestsuite, "name", test_suite.name(), kIndent);
+  OutputJsonKey(stream, kTestsuite, "tests", test_suite.reportable_test_count(),
+                kIndent);
+  if (!GTEST_FLAG(list_tests)) {
+    OutputJsonKey(stream, kTestsuite, "failures",
+                  test_suite.failed_test_count(), kIndent);
+    OutputJsonKey(stream, kTestsuite, "disabled",
+                  test_suite.reportable_disabled_test_count(), kIndent);
+    OutputJsonKey(stream, kTestsuite, "errors", 0, kIndent);
+    OutputJsonKey(
+        stream, kTestsuite, "timestamp",
+        FormatEpochTimeInMillisAsRFC3339(test_suite.start_timestamp()),
+        kIndent);
+    OutputJsonKey(stream, kTestsuite, "time",
+                  FormatTimeInMillisAsDuration(test_suite.elapsed_time()),
+                  kIndent, false);
+    *stream << TestPropertiesAsJson(test_suite.ad_hoc_test_result(), kIndent)
+            << ",\n";
+  }
+
+  *stream << kIndent << "\"" << kTestsuite << "\": [\n";
+
+  bool comma = false;
+  for (int i = 0; i < test_suite.total_test_count(); ++i) {
+    if (test_suite.GetTestInfo(i)->is_reportable()) {
+      if (comma) {
+        *stream << ",\n";
+      } else {
+        comma = true;
+      }
+      OutputJsonTestInfo(stream, test_suite.name(), *test_suite.GetTestInfo(i));
+    }
+  }
+  *stream << "\n" << kIndent << "]\n" << Indent(4) << "}";
+}
+
+// Prints a JSON summary of unit_test to output stream out.
+void JsonUnitTestResultPrinter::PrintJsonUnitTest(std::ostream* stream,
+                                                  const UnitTest& unit_test) {
+  const std::string kTestsuites = "testsuites";
+  const std::string kIndent = Indent(2);
+  *stream << "{\n";
+
+  OutputJsonKey(stream, kTestsuites, "tests", unit_test.reportable_test_count(),
+                kIndent);
+  OutputJsonKey(stream, kTestsuites, "failures", unit_test.failed_test_count(),
+                kIndent);
+  OutputJsonKey(stream, kTestsuites, "disabled",
+                unit_test.reportable_disabled_test_count(), kIndent);
+  OutputJsonKey(stream, kTestsuites, "errors", 0, kIndent);
+  if (GTEST_FLAG(shuffle)) {
+    OutputJsonKey(stream, kTestsuites, "random_seed", unit_test.random_seed(),
+                  kIndent);
+  }
+  OutputJsonKey(stream, kTestsuites, "timestamp",
+                FormatEpochTimeInMillisAsRFC3339(unit_test.start_timestamp()),
+                kIndent);
+  OutputJsonKey(stream, kTestsuites, "time",
+                FormatTimeInMillisAsDuration(unit_test.elapsed_time()), kIndent,
+                false);
+
+  *stream << TestPropertiesAsJson(unit_test.ad_hoc_test_result(), kIndent)
+          << ",\n";
+
+  OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent);
+  *stream << kIndent << "\"" << kTestsuites << "\": [\n";
+
+  bool comma = false;
+  for (int i = 0; i < unit_test.total_test_suite_count(); ++i) {
+    if (unit_test.GetTestSuite(i)->reportable_test_count() > 0) {
+      if (comma) {
+        *stream << ",\n";
+      } else {
+        comma = true;
+      }
+      PrintJsonTestSuite(stream, *unit_test.GetTestSuite(i));
+    }
+  }
+
+  // If there was a test failure outside of one of the test suites (like in a
+  // test environment) include that in the output.
+  if (unit_test.ad_hoc_test_result().Failed()) {
+    OutputJsonTestSuiteForTestResult(stream, unit_test.ad_hoc_test_result());
+  }
+
+  *stream << "\n" << kIndent << "]\n" << "}\n";
+}
+
+void JsonUnitTestResultPrinter::PrintJsonTestList(
+    std::ostream* stream, const std::vector<TestSuite*>& test_suites) {
+  const std::string kTestsuites = "testsuites";
+  const std::string kIndent = Indent(2);
+  *stream << "{\n";
+  int total_tests = 0;
+  for (auto test_suite : test_suites) {
+    total_tests += test_suite->total_test_count();
+  }
+  OutputJsonKey(stream, kTestsuites, "tests", total_tests, kIndent);
+
+  OutputJsonKey(stream, kTestsuites, "name", "AllTests", kIndent);
+  *stream << kIndent << "\"" << kTestsuites << "\": [\n";
+
+  for (size_t i = 0; i < test_suites.size(); ++i) {
+    if (i != 0) {
+      *stream << ",\n";
+    }
+    PrintJsonTestSuite(stream, *test_suites[i]);
+  }
+
+  *stream << "\n"
+          << kIndent << "]\n"
+          << "}\n";
+}
+// Produces a string representing the test properties in a result as
+// a JSON dictionary.
+std::string JsonUnitTestResultPrinter::TestPropertiesAsJson(
+    const TestResult& result, const std::string& indent) {
+  Message attributes;
+  for (int i = 0; i < result.test_property_count(); ++i) {
+    const TestProperty& property = result.GetTestProperty(i);
+    attributes << ",\n" << indent << "\"" << property.key() << "\": "
+               << "\"" << EscapeJson(property.value()) << "\"";
+  }
+  return attributes.GetString();
+}
+
+// End JsonUnitTestResultPrinter
+
+#if GTEST_CAN_STREAM_RESULTS_
+
+// Checks if str contains '=', '&', '%' or '\n' characters. If yes,
+// replaces them by "%xx" where xx is their hexadecimal value. For
+// example, replaces "=" with "%3D".  This algorithm is O(strlen(str))
+// in both time and space -- important as the input str may contain an
+// arbitrarily long test failure message and stack trace.
+std::string StreamingListener::UrlEncode(const char* str) {
+  std::string result;
+  result.reserve(strlen(str) + 1);
+  for (char ch = *str; ch != '\0'; ch = *++str) {
+    switch (ch) {
+      case '%':
+      case '=':
+      case '&':
+      case '\n':
+        result.append("%" + String::FormatByte(static_cast<unsigned char>(ch)));
+        break;
+      default:
+        result.push_back(ch);
+        break;
+    }
+  }
+  return result;
+}
+
+void StreamingListener::SocketWriter::MakeConnection() {
+  GTEST_CHECK_(sockfd_ == -1)
+      << "MakeConnection() can't be called when there is already a connection.";
+
+  addrinfo hints;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;    // To allow both IPv4 and IPv6 addresses.
+  hints.ai_socktype = SOCK_STREAM;
+  addrinfo* servinfo = nullptr;
+
+  // Use the getaddrinfo() to get a linked list of IP addresses for
+  // the given host name.
+  const int error_num = getaddrinfo(
+      host_name_.c_str(), port_num_.c_str(), &hints, &servinfo);
+  if (error_num != 0) {
+    GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: "
+                        << gai_strerror(error_num);
+  }
+
+  // Loop through all the results and connect to the first we can.
+  for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != nullptr;
+       cur_addr = cur_addr->ai_next) {
+    sockfd_ = socket(
+        cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol);
+    if (sockfd_ != -1) {
+      // Connect the client socket to the server socket.
+      if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) {
+        close(sockfd_);
+        sockfd_ = -1;
+      }
+    }
+  }
+
+  freeaddrinfo(servinfo);  // all done with this structure
+
+  if (sockfd_ == -1) {
+    GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to "
+                        << host_name_ << ":" << port_num_;
+  }
+}
+
+// End of class Streaming Listener
+#endif  // GTEST_CAN_STREAM_RESULTS__
+
+// class OsStackTraceGetter
+
+const char* const OsStackTraceGetterInterface::kElidedFramesMarker =
+    "... " GTEST_NAME_ " internal frames ...";
+
+std::string OsStackTraceGetter::CurrentStackTrace(int max_depth, int skip_count)
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+#if GTEST_HAS_ABSL
+  std::string result;
+
+  if (max_depth <= 0) {
+    return result;
+  }
+
+  max_depth = std::min(max_depth, kMaxStackTraceDepth);
+
+  std::vector<void*> raw_stack(max_depth);
+  // Skips the frames requested by the caller, plus this function.
+  const int raw_stack_size =
+      absl::GetStackTrace(&raw_stack[0], max_depth, skip_count + 1);
+
+  void* caller_frame = nullptr;
+  {
+    MutexLock lock(&mutex_);
+    caller_frame = caller_frame_;
+  }
+
+  for (int i = 0; i < raw_stack_size; ++i) {
+    if (raw_stack[i] == caller_frame &&
+        !GTEST_FLAG(show_internal_stack_frames)) {
+      // Add a marker to the trace and stop adding frames.
+      absl::StrAppend(&result, kElidedFramesMarker, "\n");
+      break;
+    }
+
+    char tmp[1024];
+    const char* symbol = "(unknown)";
+    if (absl::Symbolize(raw_stack[i], tmp, sizeof(tmp))) {
+      symbol = tmp;
+    }
+
+    char line[1024];
+    snprintf(line, sizeof(line), "  %p: %s\n", raw_stack[i], symbol);
+    result += line;
+  }
+
+  return result;
+
+#else  // !GTEST_HAS_ABSL
+  static_cast<void>(max_depth);
+  static_cast<void>(skip_count);
+  return "";
+#endif  // GTEST_HAS_ABSL
+}
+
+void OsStackTraceGetter::UponLeavingGTest() GTEST_LOCK_EXCLUDED_(mutex_) {
+#if GTEST_HAS_ABSL
+  void* caller_frame = nullptr;
+  if (absl::GetStackTrace(&caller_frame, 1, 3) <= 0) {
+    caller_frame = nullptr;
+  }
+
+  MutexLock lock(&mutex_);
+  caller_frame_ = caller_frame;
+#endif  // GTEST_HAS_ABSL
+}
+
+// A helper class that creates the premature-exit file in its
+// constructor and deletes the file in its destructor.
+class ScopedPrematureExitFile {
+ public:
+  explicit ScopedPrematureExitFile(const char* premature_exit_filepath)
+      : premature_exit_filepath_(premature_exit_filepath ?
+                                 premature_exit_filepath : "") {
+    // If a path to the premature-exit file is specified...
+    if (!premature_exit_filepath_.empty()) {
+      // create the file with a single "0" character in it.  I/O
+      // errors are ignored as there's nothing better we can do and we
+      // don't want to fail the test because of this.
+      FILE* pfile = posix::FOpen(premature_exit_filepath, "w");
+      fwrite("0", 1, 1, pfile);
+      fclose(pfile);
+    }
+  }
+
+  ~ScopedPrematureExitFile() {
+#if !defined GTEST_OS_ESP8266
+    if (!premature_exit_filepath_.empty()) {
+      int retval = remove(premature_exit_filepath_.c_str());
+      if (retval) {
+        GTEST_LOG_(ERROR) << "Failed to remove premature exit filepath \""
+                          << premature_exit_filepath_ << "\" with error "
+                          << retval;
+      }
+    }
+#endif
+  }
+
+ private:
+  const std::string premature_exit_filepath_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedPrematureExitFile);
+};
+
+}  // namespace internal
+
+// class TestEventListeners
+
+TestEventListeners::TestEventListeners()
+    : repeater_(new internal::TestEventRepeater()),
+      default_result_printer_(nullptr),
+      default_xml_generator_(nullptr) {}
+
+TestEventListeners::~TestEventListeners() { delete repeater_; }
+
+// Returns the standard listener responsible for the default console
+// output.  Can be removed from the listeners list to shut down default
+// console output.  Note that removing this object from the listener list
+// with Release transfers its ownership to the user.
+void TestEventListeners::Append(TestEventListener* listener) {
+  repeater_->Append(listener);
+}
+
+// Removes the given event listener from the list and returns it.  It then
+// becomes the caller's responsibility to delete the listener. Returns
+// NULL if the listener is not found in the list.
+TestEventListener* TestEventListeners::Release(TestEventListener* listener) {
+  if (listener == default_result_printer_)
+    default_result_printer_ = nullptr;
+  else if (listener == default_xml_generator_)
+    default_xml_generator_ = nullptr;
+  return repeater_->Release(listener);
+}
+
+// Returns repeater that broadcasts the TestEventListener events to all
+// subscribers.
+TestEventListener* TestEventListeners::repeater() { return repeater_; }
+
+// Sets the default_result_printer attribute to the provided listener.
+// The listener is also added to the listener list and previous
+// default_result_printer is removed from it and deleted. The listener can
+// also be NULL in which case it will not be added to the list. Does
+// nothing if the previous and the current listener objects are the same.
+void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) {
+  if (default_result_printer_ != listener) {
+    // It is an error to pass this method a listener that is already in the
+    // list.
+    delete Release(default_result_printer_);
+    default_result_printer_ = listener;
+    if (listener != nullptr) Append(listener);
+  }
+}
+
+// Sets the default_xml_generator attribute to the provided listener.  The
+// listener is also added to the listener list and previous
+// default_xml_generator is removed from it and deleted. The listener can
+// also be NULL in which case it will not be added to the list. Does
+// nothing if the previous and the current listener objects are the same.
+void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) {
+  if (default_xml_generator_ != listener) {
+    // It is an error to pass this method a listener that is already in the
+    // list.
+    delete Release(default_xml_generator_);
+    default_xml_generator_ = listener;
+    if (listener != nullptr) Append(listener);
+  }
+}
+
+// Controls whether events will be forwarded by the repeater to the
+// listeners in the list.
+bool TestEventListeners::EventForwardingEnabled() const {
+  return repeater_->forwarding_enabled();
+}
+
+void TestEventListeners::SuppressEventForwarding() {
+  repeater_->set_forwarding_enabled(false);
+}
+
+// class UnitTest
+
+// Gets the singleton UnitTest object.  The first time this method is
+// called, a UnitTest object is constructed and returned.  Consecutive
+// calls will return the same object.
+//
+// We don't protect this under mutex_ as a user is not supposed to
+// call this before main() starts, from which point on the return
+// value will never change.
+UnitTest* UnitTest::GetInstance() {
+  // CodeGear C++Builder insists on a public destructor for the
+  // default implementation.  Use this implementation to keep good OO
+  // design with private destructor.
+
+#if defined(__BORLANDC__)
+  static UnitTest* const instance = new UnitTest;
+  return instance;
+#else
+  static UnitTest instance;
+  return &instance;
+#endif  // defined(__BORLANDC__)
+}
+
+// Gets the number of successful test suites.
+int UnitTest::successful_test_suite_count() const {
+  return impl()->successful_test_suite_count();
+}
+
+// Gets the number of failed test suites.
+int UnitTest::failed_test_suite_count() const {
+  return impl()->failed_test_suite_count();
+}
+
+// Gets the number of all test suites.
+int UnitTest::total_test_suite_count() const {
+  return impl()->total_test_suite_count();
+}
+
+// Gets the number of all test suites that contain at least one test
+// that should run.
+int UnitTest::test_suite_to_run_count() const {
+  return impl()->test_suite_to_run_count();
+}
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+int UnitTest::successful_test_case_count() const {
+  return impl()->successful_test_suite_count();
+}
+int UnitTest::failed_test_case_count() const {
+  return impl()->failed_test_suite_count();
+}
+int UnitTest::total_test_case_count() const {
+  return impl()->total_test_suite_count();
+}
+int UnitTest::test_case_to_run_count() const {
+  return impl()->test_suite_to_run_count();
+}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+// Gets the number of successful tests.
+int UnitTest::successful_test_count() const {
+  return impl()->successful_test_count();
+}
+
+// Gets the number of skipped tests.
+int UnitTest::skipped_test_count() const {
+  return impl()->skipped_test_count();
+}
+
+// Gets the number of failed tests.
+int UnitTest::failed_test_count() const { return impl()->failed_test_count(); }
+
+// Gets the number of disabled tests that will be reported in the XML report.
+int UnitTest::reportable_disabled_test_count() const {
+  return impl()->reportable_disabled_test_count();
+}
+
+// Gets the number of disabled tests.
+int UnitTest::disabled_test_count() const {
+  return impl()->disabled_test_count();
+}
+
+// Gets the number of tests to be printed in the XML report.
+int UnitTest::reportable_test_count() const {
+  return impl()->reportable_test_count();
+}
+
+// Gets the number of all tests.
+int UnitTest::total_test_count() const { return impl()->total_test_count(); }
+
+// Gets the number of tests that should run.
+int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); }
+
+// Gets the time of the test program start, in ms from the start of the
+// UNIX epoch.
+internal::TimeInMillis UnitTest::start_timestamp() const {
+    return impl()->start_timestamp();
+}
+
+// Gets the elapsed time, in milliseconds.
+internal::TimeInMillis UnitTest::elapsed_time() const {
+  return impl()->elapsed_time();
+}
+
+// Returns true if and only if the unit test passed (i.e. all test suites
+// passed).
+bool UnitTest::Passed() const { return impl()->Passed(); }
+
+// Returns true if and only if the unit test failed (i.e. some test suite
+// failed or something outside of all tests failed).
+bool UnitTest::Failed() const { return impl()->Failed(); }
+
+// Gets the i-th test suite among all the test suites. i can range from 0 to
+// total_test_suite_count() - 1. If i is not in that range, returns NULL.
+const TestSuite* UnitTest::GetTestSuite(int i) const {
+  return impl()->GetTestSuite(i);
+}
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+const TestCase* UnitTest::GetTestCase(int i) const {
+  return impl()->GetTestCase(i);
+}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+// Returns the TestResult containing information on test failures and
+// properties logged outside of individual test suites.
+const TestResult& UnitTest::ad_hoc_test_result() const {
+  return *impl()->ad_hoc_test_result();
+}
+
+// Gets the i-th test suite among all the test suites. i can range from 0 to
+// total_test_suite_count() - 1. If i is not in that range, returns NULL.
+TestSuite* UnitTest::GetMutableTestSuite(int i) {
+  return impl()->GetMutableSuiteCase(i);
+}
+
+// Returns the list of event listeners that can be used to track events
+// inside Google Test.
+TestEventListeners& UnitTest::listeners() {
+  return *impl()->listeners();
+}
+
+// Registers and returns a global test environment.  When a test
+// program is run, all global test environments will be set-up in the
+// order they were registered.  After all tests in the program have
+// finished, all global test environments will be torn-down in the
+// *reverse* order they were registered.
+//
+// The UnitTest object takes ownership of the given environment.
+//
+// We don't protect this under mutex_, as we only support calling it
+// from the main thread.
+Environment* UnitTest::AddEnvironment(Environment* env) {
+  if (env == nullptr) {
+    return nullptr;
+  }
+
+  impl_->environments().push_back(env);
+  return env;
+}
+
+// Adds a TestPartResult to the current TestResult object.  All Google Test
+// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call
+// this to report their results.  The user code should use the
+// assertion macros instead of calling this directly.
+void UnitTest::AddTestPartResult(
+    TestPartResult::Type result_type,
+    const char* file_name,
+    int line_number,
+    const std::string& message,
+    const std::string& os_stack_trace) GTEST_LOCK_EXCLUDED_(mutex_) {
+  Message msg;
+  msg << message;
+
+  internal::MutexLock lock(&mutex_);
+  if (impl_->gtest_trace_stack().size() > 0) {
+    msg << "\n" << GTEST_NAME_ << " trace:";
+
+    for (size_t i = impl_->gtest_trace_stack().size(); i > 0; --i) {
+      const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1];
+      msg << "\n" << internal::FormatFileLocation(trace.file, trace.line)
+          << " " << trace.message;
+    }
+  }
+
+  if (os_stack_trace.c_str() != nullptr && !os_stack_trace.empty()) {
+    msg << internal::kStackTraceMarker << os_stack_trace;
+  }
+
+  const TestPartResult result = TestPartResult(
+      result_type, file_name, line_number, msg.GetString().c_str());
+  impl_->GetTestPartResultReporterForCurrentThread()->
+      ReportTestPartResult(result);
+
+  if (result_type != TestPartResult::kSuccess &&
+      result_type != TestPartResult::kSkip) {
+    // gtest_break_on_failure takes precedence over
+    // gtest_throw_on_failure.  This allows a user to set the latter
+    // in the code (perhaps in order to use Google Test assertions
+    // with another testing framework) and specify the former on the
+    // command line for debugging.
+    if (GTEST_FLAG(break_on_failure)) {
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+      // Using DebugBreak on Windows allows gtest to still break into a debugger
+      // when a failure happens and both the --gtest_break_on_failure and
+      // the --gtest_catch_exceptions flags are specified.
+      DebugBreak();
+#elif (!defined(__native_client__)) &&            \
+    ((defined(__clang__) || defined(__GNUC__)) && \
+     (defined(__x86_64__) || defined(__i386__)))
+      // with clang/gcc we can achieve the same effect on x86 by invoking int3
+      asm("int3");
+#else
+      // Dereference nullptr through a volatile pointer to prevent the compiler
+      // from removing. We use this rather than abort() or __builtin_trap() for
+      // portability: some debuggers don't correctly trap abort().
+      *static_cast<volatile int*>(nullptr) = 1;
+#endif  // GTEST_OS_WINDOWS
+    } else if (GTEST_FLAG(throw_on_failure)) {
+#if GTEST_HAS_EXCEPTIONS
+      throw internal::GoogleTestFailureException(result);
+#else
+      // We cannot call abort() as it generates a pop-up in debug mode
+      // that cannot be suppressed in VC 7.1 or below.
+      exit(1);
+#endif
+    }
+  }
+}
+
+// Adds a TestProperty to the current TestResult object when invoked from
+// inside a test, to current TestSuite's ad_hoc_test_result_ when invoked
+// from SetUpTestSuite or TearDownTestSuite, or to the global property set
+// when invoked elsewhere.  If the result already contains a property with
+// the same key, the value will be updated.
+void UnitTest::RecordProperty(const std::string& key,
+                              const std::string& value) {
+  impl_->RecordProperty(TestProperty(key, value));
+}
+
+// Runs all tests in this UnitTest object and prints the result.
+// Returns 0 if successful, or 1 otherwise.
+//
+// We don't protect this under mutex_, as we only support calling it
+// from the main thread.
+int UnitTest::Run() {
+  const bool in_death_test_child_process =
+      internal::GTEST_FLAG(internal_run_death_test).length() > 0;
+
+  // Google Test implements this protocol for catching that a test
+  // program exits before returning control to Google Test:
+  //
+  //   1. Upon start, Google Test creates a file whose absolute path
+  //      is specified by the environment variable
+  //      TEST_PREMATURE_EXIT_FILE.
+  //   2. When Google Test has finished its work, it deletes the file.
+  //
+  // This allows a test runner to set TEST_PREMATURE_EXIT_FILE before
+  // running a Google-Test-based test program and check the existence
+  // of the file at the end of the test execution to see if it has
+  // exited prematurely.
+
+  // If we are in the child process of a death test, don't
+  // create/delete the premature exit file, as doing so is unnecessary
+  // and will confuse the parent process.  Otherwise, create/delete
+  // the file upon entering/leaving this function.  If the program
+  // somehow exits before this function has a chance to return, the
+  // premature-exit file will be left undeleted, causing a test runner
+  // that understands the premature-exit-file protocol to report the
+  // test as having failed.
+  const internal::ScopedPrematureExitFile premature_exit_file(
+      in_death_test_child_process
+          ? nullptr
+          : internal::posix::GetEnv("TEST_PREMATURE_EXIT_FILE"));
+
+  // Captures the value of GTEST_FLAG(catch_exceptions).  This value will be
+  // used for the duration of the program.
+  impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions));
+
+#if GTEST_OS_WINDOWS
+  // Either the user wants Google Test to catch exceptions thrown by the
+  // tests or this is executing in the context of death test child
+  // process. In either case the user does not want to see pop-up dialogs
+  // about crashes - they are expected.
+  if (impl()->catch_exceptions() || in_death_test_child_process) {
+# if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+    // SetErrorMode doesn't exist on CE.
+    SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT |
+                 SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+# endif  // !GTEST_OS_WINDOWS_MOBILE
+
+# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE
+    // Death test children can be terminated with _abort().  On Windows,
+    // _abort() can show a dialog with a warning message.  This forces the
+    // abort message to go to stderr instead.
+    _set_error_mode(_OUT_TO_STDERR);
+# endif
+
+# if defined(_MSC_VER) && !GTEST_OS_WINDOWS_MOBILE
+    // In the debug version, Visual Studio pops up a separate dialog
+    // offering a choice to debug the aborted program. We need to suppress
+    // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement
+    // executed. Google Test will notify the user of any unexpected
+    // failure via stderr.
+    if (!GTEST_FLAG(break_on_failure))
+      _set_abort_behavior(
+          0x0,                                    // Clear the following flags:
+          _WRITE_ABORT_MSG | _CALL_REPORTFAULT);  // pop-up window, core dump.
+
+    // In debug mode, the Windows CRT can crash with an assertion over invalid
+    // input (e.g. passing an invalid file descriptor).  The default handling
+    // for these assertions is to pop up a dialog and wait for user input.
+    // Instead ask the CRT to dump such assertions to stderr non-interactively.
+    if (!IsDebuggerPresent()) {
+      (void)_CrtSetReportMode(_CRT_ASSERT,
+                              _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+      (void)_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+    }
+# endif
+  }
+#endif  // GTEST_OS_WINDOWS
+
+  return internal::HandleExceptionsInMethodIfSupported(
+      impl(),
+      &internal::UnitTestImpl::RunAllTests,
+      "auxiliary test code (environments or event listeners)") ? 0 : 1;
+}
+
+// Returns the working directory when the first TEST() or TEST_F() was
+// executed.
+const char* UnitTest::original_working_dir() const {
+  return impl_->original_working_dir_.c_str();
+}
+
+// Returns the TestSuite object for the test that's currently running,
+// or NULL if no test is running.
+const TestSuite* UnitTest::current_test_suite() const
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  internal::MutexLock lock(&mutex_);
+  return impl_->current_test_suite();
+}
+
+// Legacy API is still available but deprecated
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+const TestCase* UnitTest::current_test_case() const
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  internal::MutexLock lock(&mutex_);
+  return impl_->current_test_suite();
+}
+#endif
+
+// Returns the TestInfo object for the test that's currently running,
+// or NULL if no test is running.
+const TestInfo* UnitTest::current_test_info() const
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  internal::MutexLock lock(&mutex_);
+  return impl_->current_test_info();
+}
+
+// Returns the random seed used at the start of the current test run.
+int UnitTest::random_seed() const { return impl_->random_seed(); }
+
+// Returns ParameterizedTestSuiteRegistry object used to keep track of
+// value-parameterized tests and instantiate and register them.
+internal::ParameterizedTestSuiteRegistry&
+UnitTest::parameterized_test_registry() GTEST_LOCK_EXCLUDED_(mutex_) {
+  return impl_->parameterized_test_registry();
+}
+
+// Creates an empty UnitTest.
+UnitTest::UnitTest() {
+  impl_ = new internal::UnitTestImpl(this);
+}
+
+// Destructor of UnitTest.
+UnitTest::~UnitTest() {
+  delete impl_;
+}
+
+// Pushes a trace defined by SCOPED_TRACE() on to the per-thread
+// Google Test trace stack.
+void UnitTest::PushGTestTrace(const internal::TraceInfo& trace)
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  internal::MutexLock lock(&mutex_);
+  impl_->gtest_trace_stack().push_back(trace);
+}
+
+// Pops a trace from the per-thread Google Test trace stack.
+void UnitTest::PopGTestTrace()
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  internal::MutexLock lock(&mutex_);
+  impl_->gtest_trace_stack().pop_back();
+}
+
+namespace internal {
+
+UnitTestImpl::UnitTestImpl(UnitTest* parent)
+    : parent_(parent),
+      GTEST_DISABLE_MSC_WARNINGS_PUSH_(4355 /* using this in initializer */)
+          default_global_test_part_result_reporter_(this),
+      default_per_thread_test_part_result_reporter_(this),
+      GTEST_DISABLE_MSC_WARNINGS_POP_() global_test_part_result_repoter_(
+          &default_global_test_part_result_reporter_),
+      per_thread_test_part_result_reporter_(
+          &default_per_thread_test_part_result_reporter_),
+      parameterized_test_registry_(),
+      parameterized_tests_registered_(false),
+      last_death_test_suite_(-1),
+      current_test_suite_(nullptr),
+      current_test_info_(nullptr),
+      ad_hoc_test_result_(),
+      os_stack_trace_getter_(nullptr),
+      post_flag_parse_init_performed_(false),
+      random_seed_(0),  // Will be overridden by the flag before first use.
+      random_(0),       // Will be reseeded before first use.
+      start_timestamp_(0),
+      elapsed_time_(0),
+#if GTEST_HAS_DEATH_TEST
+      death_test_factory_(new DefaultDeathTestFactory),
+#endif
+      // Will be overridden by the flag before first use.
+      catch_exceptions_(false) {
+  listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter);
+}
+
+UnitTestImpl::~UnitTestImpl() {
+  // Deletes every TestSuite.
+  ForEach(test_suites_, internal::Delete<TestSuite>);
+
+  // Deletes every Environment.
+  ForEach(environments_, internal::Delete<Environment>);
+
+  delete os_stack_trace_getter_;
+}
+
+// Adds a TestProperty to the current TestResult object when invoked in a
+// context of a test, to current test suite's ad_hoc_test_result when invoke
+// from SetUpTestSuite/TearDownTestSuite, or to the global property set
+// otherwise.  If the result already contains a property with the same key,
+// the value will be updated.
+void UnitTestImpl::RecordProperty(const TestProperty& test_property) {
+  std::string xml_element;
+  TestResult* test_result;  // TestResult appropriate for property recording.
+
+  if (current_test_info_ != nullptr) {
+    xml_element = "testcase";
+    test_result = &(current_test_info_->result_);
+  } else if (current_test_suite_ != nullptr) {
+    xml_element = "testsuite";
+    test_result = &(current_test_suite_->ad_hoc_test_result_);
+  } else {
+    xml_element = "testsuites";
+    test_result = &ad_hoc_test_result_;
+  }
+  test_result->RecordProperty(xml_element, test_property);
+}
+
+#if GTEST_HAS_DEATH_TEST
+// Disables event forwarding if the control is currently in a death test
+// subprocess. Must not be called before InitGoogleTest.
+void UnitTestImpl::SuppressTestEventsIfInSubprocess() {
+  if (internal_run_death_test_flag_.get() != nullptr)
+    listeners()->SuppressEventForwarding();
+}
+#endif  // GTEST_HAS_DEATH_TEST
+
+// Initializes event listeners performing XML output as specified by
+// UnitTestOptions. Must not be called before InitGoogleTest.
+void UnitTestImpl::ConfigureXmlOutput() {
+  const std::string& output_format = UnitTestOptions::GetOutputFormat();
+  if (output_format == "xml") {
+    listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter(
+        UnitTestOptions::GetAbsolutePathToOutputFile().c_str()));
+  } else if (output_format == "json") {
+    listeners()->SetDefaultXmlGenerator(new JsonUnitTestResultPrinter(
+        UnitTestOptions::GetAbsolutePathToOutputFile().c_str()));
+  } else if (output_format != "") {
+    GTEST_LOG_(WARNING) << "WARNING: unrecognized output format \""
+                        << output_format << "\" ignored.";
+  }
+}
+
+#if GTEST_CAN_STREAM_RESULTS_
+// Initializes event listeners for streaming test results in string form.
+// Must not be called before InitGoogleTest.
+void UnitTestImpl::ConfigureStreamingOutput() {
+  const std::string& target = GTEST_FLAG(stream_result_to);
+  if (!target.empty()) {
+    const size_t pos = target.find(':');
+    if (pos != std::string::npos) {
+      listeners()->Append(new StreamingListener(target.substr(0, pos),
+                                                target.substr(pos+1)));
+    } else {
+      GTEST_LOG_(WARNING) << "unrecognized streaming target \"" << target
+                          << "\" ignored.";
+    }
+  }
+}
+#endif  // GTEST_CAN_STREAM_RESULTS_
+
+// Performs initialization dependent upon flag values obtained in
+// ParseGoogleTestFlagsOnly.  Is called from InitGoogleTest after the call to
+// ParseGoogleTestFlagsOnly.  In case a user neglects to call InitGoogleTest
+// this function is also called from RunAllTests.  Since this function can be
+// called more than once, it has to be idempotent.
+void UnitTestImpl::PostFlagParsingInit() {
+  // Ensures that this function does not execute more than once.
+  if (!post_flag_parse_init_performed_) {
+    post_flag_parse_init_performed_ = true;
+
+#if defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_)
+    // Register to send notifications about key process state changes.
+    listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_());
+#endif  // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_)
+
+#if GTEST_HAS_DEATH_TEST
+    InitDeathTestSubprocessControlInfo();
+    SuppressTestEventsIfInSubprocess();
+#endif  // GTEST_HAS_DEATH_TEST
+
+    // Registers parameterized tests. This makes parameterized tests
+    // available to the UnitTest reflection API without running
+    // RUN_ALL_TESTS.
+    RegisterParameterizedTests();
+
+    // Configures listeners for XML output. This makes it possible for users
+    // to shut down the default XML output before invoking RUN_ALL_TESTS.
+    ConfigureXmlOutput();
+
+    if (GTEST_FLAG(brief)) {
+      listeners()->SetDefaultResultPrinter(new BriefUnitTestResultPrinter);
+    }
+
+#if GTEST_CAN_STREAM_RESULTS_
+    // Configures listeners for streaming test results to the specified server.
+    ConfigureStreamingOutput();
+#endif  // GTEST_CAN_STREAM_RESULTS_
+
+#if GTEST_HAS_ABSL
+    if (GTEST_FLAG(install_failure_signal_handler)) {
+      absl::FailureSignalHandlerOptions options;
+      absl::InstallFailureSignalHandler(options);
+    }
+#endif  // GTEST_HAS_ABSL
+  }
+}
+
+// A predicate that checks the name of a TestSuite against a known
+// value.
+//
+// This is used for implementation of the UnitTest class only.  We put
+// it in the anonymous namespace to prevent polluting the outer
+// namespace.
+//
+// TestSuiteNameIs is copyable.
+class TestSuiteNameIs {
+ public:
+  // Constructor.
+  explicit TestSuiteNameIs(const std::string& name) : name_(name) {}
+
+  // Returns true if and only if the name of test_suite matches name_.
+  bool operator()(const TestSuite* test_suite) const {
+    return test_suite != nullptr &&
+           strcmp(test_suite->name(), name_.c_str()) == 0;
+  }
+
+ private:
+  std::string name_;
+};
+
+// Finds and returns a TestSuite with the given name.  If one doesn't
+// exist, creates one and returns it.  It's the CALLER'S
+// RESPONSIBILITY to ensure that this function is only called WHEN THE
+// TESTS ARE NOT SHUFFLED.
+//
+// Arguments:
+//
+//   test_suite_name: name of the test suite
+//   type_param:      the name of the test suite's type parameter, or NULL if
+//                    this is not a typed or a type-parameterized test suite.
+//   set_up_tc:       pointer to the function that sets up the test suite
+//   tear_down_tc:    pointer to the function that tears down the test suite
+TestSuite* UnitTestImpl::GetTestSuite(
+    const char* test_suite_name, const char* type_param,
+    internal::SetUpTestSuiteFunc set_up_tc,
+    internal::TearDownTestSuiteFunc tear_down_tc) {
+  // Can we find a TestSuite with the given name?
+  const auto test_suite =
+      std::find_if(test_suites_.rbegin(), test_suites_.rend(),
+                   TestSuiteNameIs(test_suite_name));
+
+  if (test_suite != test_suites_.rend()) return *test_suite;
+
+  // No.  Let's create one.
+  auto* const new_test_suite =
+      new TestSuite(test_suite_name, type_param, set_up_tc, tear_down_tc);
+
+  // Is this a death test suite?
+  if (internal::UnitTestOptions::MatchesFilter(test_suite_name,
+                                               kDeathTestSuiteFilter)) {
+    // Yes.  Inserts the test suite after the last death test suite
+    // defined so far.  This only works when the test suites haven't
+    // been shuffled.  Otherwise we may end up running a death test
+    // after a non-death test.
+    ++last_death_test_suite_;
+    test_suites_.insert(test_suites_.begin() + last_death_test_suite_,
+                        new_test_suite);
+  } else {
+    // No.  Appends to the end of the list.
+    test_suites_.push_back(new_test_suite);
+  }
+
+  test_suite_indices_.push_back(static_cast<int>(test_suite_indices_.size()));
+  return new_test_suite;
+}
+
+// Helpers for setting up / tearing down the given environment.  They
+// are for use in the ForEach() function.
+static void SetUpEnvironment(Environment* env) { env->SetUp(); }
+static void TearDownEnvironment(Environment* env) { env->TearDown(); }
+
+// Runs all tests in this UnitTest object, prints the result, and
+// returns true if all tests are successful.  If any exception is
+// thrown during a test, the test is considered to be failed, but the
+// rest of the tests will still be run.
+//
+// When parameterized tests are enabled, it expands and registers
+// parameterized tests first in RegisterParameterizedTests().
+// All other functions called from RunAllTests() may safely assume that
+// parameterized tests are ready to be counted and run.
+bool UnitTestImpl::RunAllTests() {
+  // True if and only if Google Test is initialized before RUN_ALL_TESTS() is
+  // called.
+  const bool gtest_is_initialized_before_run_all_tests = GTestIsInitialized();
+
+  // Do not run any test if the --help flag was specified.
+  if (g_help_flag)
+    return true;
+
+  // Repeats the call to the post-flag parsing initialization in case the
+  // user didn't call InitGoogleTest.
+  PostFlagParsingInit();
+
+  // Even if sharding is not on, test runners may want to use the
+  // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding
+  // protocol.
+  internal::WriteToShardStatusFileIfNeeded();
+
+  // True if and only if we are in a subprocess for running a thread-safe-style
+  // death test.
+  bool in_subprocess_for_death_test = false;
+
+#if GTEST_HAS_DEATH_TEST
+  in_subprocess_for_death_test =
+      (internal_run_death_test_flag_.get() != nullptr);
+# if defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_)
+  if (in_subprocess_for_death_test) {
+    GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_();
+  }
+# endif  // defined(GTEST_EXTRA_DEATH_TEST_CHILD_SETUP_)
+#endif  // GTEST_HAS_DEATH_TEST
+
+  const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex,
+                                        in_subprocess_for_death_test);
+
+  // Compares the full test names with the filter to decide which
+  // tests to run.
+  const bool has_tests_to_run = FilterTests(should_shard
+                                              ? HONOR_SHARDING_PROTOCOL
+                                              : IGNORE_SHARDING_PROTOCOL) > 0;
+
+  // Lists the tests and exits if the --gtest_list_tests flag was specified.
+  if (GTEST_FLAG(list_tests)) {
+    // This must be called *after* FilterTests() has been called.
+    ListTestsMatchingFilter();
+    return true;
+  }
+
+  random_seed_ = GTEST_FLAG(shuffle) ?
+      GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0;
+
+  // True if and only if at least one test has failed.
+  bool failed = false;
+
+  TestEventListener* repeater = listeners()->repeater();
+
+  start_timestamp_ = GetTimeInMillis();
+  repeater->OnTestProgramStart(*parent_);
+
+  // How many times to repeat the tests?  We don't want to repeat them
+  // when we are inside the subprocess of a death test.
+  const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat);
+  // Repeats forever if the repeat count is negative.
+  const bool gtest_repeat_forever = repeat < 0;
+  for (int i = 0; gtest_repeat_forever || i != repeat; i++) {
+    // We want to preserve failures generated by ad-hoc test
+    // assertions executed before RUN_ALL_TESTS().
+    ClearNonAdHocTestResult();
+
+    Timer timer;
+
+    // Shuffles test suites and tests if requested.
+    if (has_tests_to_run && GTEST_FLAG(shuffle)) {
+      random()->Reseed(static_cast<uint32_t>(random_seed_));
+      // This should be done before calling OnTestIterationStart(),
+      // such that a test event listener can see the actual test order
+      // in the event.
+      ShuffleTests();
+    }
+
+    // Tells the unit test event listeners that the tests are about to start.
+    repeater->OnTestIterationStart(*parent_, i);
+
+    // Runs each test suite if there is at least one test to run.
+    if (has_tests_to_run) {
+      // Sets up all environments beforehand.
+      repeater->OnEnvironmentsSetUpStart(*parent_);
+      ForEach(environments_, SetUpEnvironment);
+      repeater->OnEnvironmentsSetUpEnd(*parent_);
+
+      // Runs the tests only if there was no fatal failure or skip triggered
+      // during global set-up.
+      if (Test::IsSkipped()) {
+        // Emit diagnostics when global set-up calls skip, as it will not be
+        // emitted by default.
+        TestResult& test_result =
+            *internal::GetUnitTestImpl()->current_test_result();
+        for (int j = 0; j < test_result.total_part_count(); ++j) {
+          const TestPartResult& test_part_result =
+              test_result.GetTestPartResult(j);
+          if (test_part_result.type() == TestPartResult::kSkip) {
+            const std::string& result = test_part_result.message();
+            printf("%s\n", result.c_str());
+          }
+        }
+        fflush(stdout);
+      } else if (!Test::HasFatalFailure()) {
+        for (int test_index = 0; test_index < total_test_suite_count();
+             test_index++) {
+          GetMutableSuiteCase(test_index)->Run();
+          if (GTEST_FLAG(fail_fast) &&
+              GetMutableSuiteCase(test_index)->Failed()) {
+            for (int j = test_index + 1; j < total_test_suite_count(); j++) {
+              GetMutableSuiteCase(j)->Skip();
+            }
+            break;
+          }
+        }
+      } else if (Test::HasFatalFailure()) {
+        // If there was a fatal failure during the global setup then we know we
+        // aren't going to run any tests. Explicitly mark all of the tests as
+        // skipped to make this obvious in the output.
+        for (int test_index = 0; test_index < total_test_suite_count();
+             test_index++) {
+          GetMutableSuiteCase(test_index)->Skip();
+        }
+      }
+
+      // Tears down all environments in reverse order afterwards.
+      repeater->OnEnvironmentsTearDownStart(*parent_);
+      std::for_each(environments_.rbegin(), environments_.rend(),
+                    TearDownEnvironment);
+      repeater->OnEnvironmentsTearDownEnd(*parent_);
+    }
+
+    elapsed_time_ = timer.Elapsed();
+
+    // Tells the unit test event listener that the tests have just finished.
+    repeater->OnTestIterationEnd(*parent_, i);
+
+    // Gets the result and clears it.
+    if (!Passed()) {
+      failed = true;
+    }
+
+    // Restores the original test order after the iteration.  This
+    // allows the user to quickly repro a failure that happens in the
+    // N-th iteration without repeating the first (N - 1) iterations.
+    // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in
+    // case the user somehow changes the value of the flag somewhere
+    // (it's always safe to unshuffle the tests).
+    UnshuffleTests();
+
+    if (GTEST_FLAG(shuffle)) {
+      // Picks a new random seed for each iteration.
+      random_seed_ = GetNextRandomSeed(random_seed_);
+    }
+  }
+
+  repeater->OnTestProgramEnd(*parent_);
+
+  if (!gtest_is_initialized_before_run_all_tests) {
+    ColoredPrintf(
+        GTestColor::kRed,
+        "\nIMPORTANT NOTICE - DO NOT IGNORE:\n"
+        "This test program did NOT call " GTEST_INIT_GOOGLE_TEST_NAME_
+        "() before calling RUN_ALL_TESTS(). This is INVALID. Soon " GTEST_NAME_
+        " will start to enforce the valid usage. "
+        "Please fix it ASAP, or IT WILL START TO FAIL.\n");  // NOLINT
+#if GTEST_FOR_GOOGLE_
+    ColoredPrintf(GTestColor::kRed,
+                  "For more details, see http://wiki/Main/ValidGUnitMain.\n");
+#endif  // GTEST_FOR_GOOGLE_
+  }
+
+  return !failed;
+}
+
+// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file
+// if the variable is present. If a file already exists at this location, this
+// function will write over it. If the variable is present, but the file cannot
+// be created, prints an error and exits.
+void WriteToShardStatusFileIfNeeded() {
+  const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile);
+  if (test_shard_file != nullptr) {
+    FILE* const file = posix::FOpen(test_shard_file, "w");
+    if (file == nullptr) {
+      ColoredPrintf(GTestColor::kRed,
+                    "Could not write to the test shard status file \"%s\" "
+                    "specified by the %s environment variable.\n",
+                    test_shard_file, kTestShardStatusFile);
+      fflush(stdout);
+      exit(EXIT_FAILURE);
+    }
+    fclose(file);
+  }
+}
+
+// Checks whether sharding is enabled by examining the relevant
+// environment variable values. If the variables are present,
+// but inconsistent (i.e., shard_index >= total_shards), prints
+// an error and exits. If in_subprocess_for_death_test, sharding is
+// disabled because it must only be applied to the original test
+// process. Otherwise, we could filter out death tests we intended to execute.
+bool ShouldShard(const char* total_shards_env,
+                 const char* shard_index_env,
+                 bool in_subprocess_for_death_test) {
+  if (in_subprocess_for_death_test) {
+    return false;
+  }
+
+  const int32_t total_shards = Int32FromEnvOrDie(total_shards_env, -1);
+  const int32_t shard_index = Int32FromEnvOrDie(shard_index_env, -1);
+
+  if (total_shards == -1 && shard_index == -1) {
+    return false;
+  } else if (total_shards == -1 && shard_index != -1) {
+    const Message msg = Message()
+      << "Invalid environment variables: you have "
+      << kTestShardIndex << " = " << shard_index
+      << ", but have left " << kTestTotalShards << " unset.\n";
+    ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str());
+    fflush(stdout);
+    exit(EXIT_FAILURE);
+  } else if (total_shards != -1 && shard_index == -1) {
+    const Message msg = Message()
+      << "Invalid environment variables: you have "
+      << kTestTotalShards << " = " << total_shards
+      << ", but have left " << kTestShardIndex << " unset.\n";
+    ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str());
+    fflush(stdout);
+    exit(EXIT_FAILURE);
+  } else if (shard_index < 0 || shard_index >= total_shards) {
+    const Message msg = Message()
+      << "Invalid environment variables: we require 0 <= "
+      << kTestShardIndex << " < " << kTestTotalShards
+      << ", but you have " << kTestShardIndex << "=" << shard_index
+      << ", " << kTestTotalShards << "=" << total_shards << ".\n";
+    ColoredPrintf(GTestColor::kRed, "%s", msg.GetString().c_str());
+    fflush(stdout);
+    exit(EXIT_FAILURE);
+  }
+
+  return total_shards > 1;
+}
+
+// Parses the environment variable var as an Int32. If it is unset,
+// returns default_val. If it is not an Int32, prints an error
+// and aborts.
+int32_t Int32FromEnvOrDie(const char* var, int32_t default_val) {
+  const char* str_val = posix::GetEnv(var);
+  if (str_val == nullptr) {
+    return default_val;
+  }
+
+  int32_t result;
+  if (!ParseInt32(Message() << "The value of environment variable " << var,
+                  str_val, &result)) {
+    exit(EXIT_FAILURE);
+  }
+  return result;
+}
+
+// Given the total number of shards, the shard index, and the test id,
+// returns true if and only if the test should be run on this shard. The test id
+// is some arbitrary but unique non-negative integer assigned to each test
+// method. Assumes that 0 <= shard_index < total_shards.
+bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) {
+  return (test_id % total_shards) == shard_index;
+}
+
+// Compares the name of each test with the user-specified filter to
+// decide whether the test should be run, then records the result in
+// each TestSuite and TestInfo object.
+// If shard_tests == true, further filters tests based on sharding
+// variables in the environment - see
+// https://github.com/google/googletest/blob/master/googletest/docs/advanced.md
+// . Returns the number of tests that should run.
+int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) {
+  const int32_t total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ?
+      Int32FromEnvOrDie(kTestTotalShards, -1) : -1;
+  const int32_t shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ?
+      Int32FromEnvOrDie(kTestShardIndex, -1) : -1;
+
+  // num_runnable_tests are the number of tests that will
+  // run across all shards (i.e., match filter and are not disabled).
+  // num_selected_tests are the number of tests to be run on
+  // this shard.
+  int num_runnable_tests = 0;
+  int num_selected_tests = 0;
+  for (auto* test_suite : test_suites_) {
+    const std::string& test_suite_name = test_suite->name();
+    test_suite->set_should_run(false);
+
+    for (size_t j = 0; j < test_suite->test_info_list().size(); j++) {
+      TestInfo* const test_info = test_suite->test_info_list()[j];
+      const std::string test_name(test_info->name());
+      // A test is disabled if test suite name or test name matches
+      // kDisableTestFilter.
+      const bool is_disabled = internal::UnitTestOptions::MatchesFilter(
+                                   test_suite_name, kDisableTestFilter) ||
+                               internal::UnitTestOptions::MatchesFilter(
+                                   test_name, kDisableTestFilter);
+      test_info->is_disabled_ = is_disabled;
+
+      const bool matches_filter = internal::UnitTestOptions::FilterMatchesTest(
+          test_suite_name, test_name);
+      test_info->matches_filter_ = matches_filter;
+
+      const bool is_runnable =
+          (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) &&
+          matches_filter;
+
+      const bool is_in_another_shard =
+          shard_tests != IGNORE_SHARDING_PROTOCOL &&
+          !ShouldRunTestOnShard(total_shards, shard_index, num_runnable_tests);
+      test_info->is_in_another_shard_ = is_in_another_shard;
+      const bool is_selected = is_runnable && !is_in_another_shard;
+
+      num_runnable_tests += is_runnable;
+      num_selected_tests += is_selected;
+
+      test_info->should_run_ = is_selected;
+      test_suite->set_should_run(test_suite->should_run() || is_selected);
+    }
+  }
+  return num_selected_tests;
+}
+
+// Prints the given C-string on a single line by replacing all '\n'
+// characters with string "\\n".  If the output takes more than
+// max_length characters, only prints the first max_length characters
+// and "...".
+static void PrintOnOneLine(const char* str, int max_length) {
+  if (str != nullptr) {
+    for (int i = 0; *str != '\0'; ++str) {
+      if (i >= max_length) {
+        printf("...");
+        break;
+      }
+      if (*str == '\n') {
+        printf("\\n");
+        i += 2;
+      } else {
+        printf("%c", *str);
+        ++i;
+      }
+    }
+  }
+}
+
+// Prints the names of the tests matching the user-specified filter flag.
+void UnitTestImpl::ListTestsMatchingFilter() {
+  // Print at most this many characters for each type/value parameter.
+  const int kMaxParamLength = 250;
+
+  for (auto* test_suite : test_suites_) {
+    bool printed_test_suite_name = false;
+
+    for (size_t j = 0; j < test_suite->test_info_list().size(); j++) {
+      const TestInfo* const test_info = test_suite->test_info_list()[j];
+      if (test_info->matches_filter_) {
+        if (!printed_test_suite_name) {
+          printed_test_suite_name = true;
+          printf("%s.", test_suite->name());
+          if (test_suite->type_param() != nullptr) {
+            printf("  # %s = ", kTypeParamLabel);
+            // We print the type parameter on a single line to make
+            // the output easy to parse by a program.
+            PrintOnOneLine(test_suite->type_param(), kMaxParamLength);
+          }
+          printf("\n");
+        }
+        printf("  %s", test_info->name());
+        if (test_info->value_param() != nullptr) {
+          printf("  # %s = ", kValueParamLabel);
+          // We print the value parameter on a single line to make the
+          // output easy to parse by a program.
+          PrintOnOneLine(test_info->value_param(), kMaxParamLength);
+        }
+        printf("\n");
+      }
+    }
+  }
+  fflush(stdout);
+  const std::string& output_format = UnitTestOptions::GetOutputFormat();
+  if (output_format == "xml" || output_format == "json") {
+    FILE* fileout = OpenFileForWriting(
+        UnitTestOptions::GetAbsolutePathToOutputFile().c_str());
+    std::stringstream stream;
+    if (output_format == "xml") {
+      XmlUnitTestResultPrinter(
+          UnitTestOptions::GetAbsolutePathToOutputFile().c_str())
+          .PrintXmlTestsList(&stream, test_suites_);
+    } else if (output_format == "json") {
+      JsonUnitTestResultPrinter(
+          UnitTestOptions::GetAbsolutePathToOutputFile().c_str())
+          .PrintJsonTestList(&stream, test_suites_);
+    }
+    fprintf(fileout, "%s", StringStreamToString(&stream).c_str());
+    fclose(fileout);
+  }
+}
+
+// Sets the OS stack trace getter.
+//
+// Does nothing if the input and the current OS stack trace getter are
+// the same; otherwise, deletes the old getter and makes the input the
+// current getter.
+void UnitTestImpl::set_os_stack_trace_getter(
+    OsStackTraceGetterInterface* getter) {
+  if (os_stack_trace_getter_ != getter) {
+    delete os_stack_trace_getter_;
+    os_stack_trace_getter_ = getter;
+  }
+}
+
+// Returns the current OS stack trace getter if it is not NULL;
+// otherwise, creates an OsStackTraceGetter, makes it the current
+// getter, and returns it.
+OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() {
+  if (os_stack_trace_getter_ == nullptr) {
+#ifdef GTEST_OS_STACK_TRACE_GETTER_
+    os_stack_trace_getter_ = new GTEST_OS_STACK_TRACE_GETTER_;
+#else
+    os_stack_trace_getter_ = new OsStackTraceGetter;
+#endif  // GTEST_OS_STACK_TRACE_GETTER_
+  }
+
+  return os_stack_trace_getter_;
+}
+
+// Returns the most specific TestResult currently running.
+TestResult* UnitTestImpl::current_test_result() {
+  if (current_test_info_ != nullptr) {
+    return &current_test_info_->result_;
+  }
+  if (current_test_suite_ != nullptr) {
+    return &current_test_suite_->ad_hoc_test_result_;
+  }
+  return &ad_hoc_test_result_;
+}
+
+// Shuffles all test suites, and the tests within each test suite,
+// making sure that death tests are still run first.
+void UnitTestImpl::ShuffleTests() {
+  // Shuffles the death test suites.
+  ShuffleRange(random(), 0, last_death_test_suite_ + 1, &test_suite_indices_);
+
+  // Shuffles the non-death test suites.
+  ShuffleRange(random(), last_death_test_suite_ + 1,
+               static_cast<int>(test_suites_.size()), &test_suite_indices_);
+
+  // Shuffles the tests inside each test suite.
+  for (auto& test_suite : test_suites_) {
+    test_suite->ShuffleTests(random());
+  }
+}
+
+// Restores the test suites and tests to their order before the first shuffle.
+void UnitTestImpl::UnshuffleTests() {
+  for (size_t i = 0; i < test_suites_.size(); i++) {
+    // Unshuffles the tests in each test suite.
+    test_suites_[i]->UnshuffleTests();
+    // Resets the index of each test suite.
+    test_suite_indices_[i] = static_cast<int>(i);
+  }
+}
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag.  The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in
+// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't.
+std::string GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/,
+                                            int skip_count) {
+  // We pass skip_count + 1 to skip this wrapper function in addition
+  // to what the user really wants to skip.
+  return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1);
+}
+
+// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to
+// suppress unreachable code warnings.
+namespace {
+class ClassUniqueToAlwaysTrue {};
+}
+
+bool IsTrue(bool condition) { return condition; }
+
+bool AlwaysTrue() {
+#if GTEST_HAS_EXCEPTIONS
+  // This condition is always false so AlwaysTrue() never actually throws,
+  // but it makes the compiler think that it may throw.
+  if (IsTrue(false))
+    throw ClassUniqueToAlwaysTrue();
+#endif  // GTEST_HAS_EXCEPTIONS
+  return true;
+}
+
+// If *pstr starts with the given prefix, modifies *pstr to be right
+// past the prefix and returns true; otherwise leaves *pstr unchanged
+// and returns false.  None of pstr, *pstr, and prefix can be NULL.
+bool SkipPrefix(const char* prefix, const char** pstr) {
+  const size_t prefix_len = strlen(prefix);
+  if (strncmp(*pstr, prefix, prefix_len) == 0) {
+    *pstr += prefix_len;
+    return true;
+  }
+  return false;
+}
+
+// Parses a string as a command line flag.  The string should have
+// the format "--flag=value".  When def_optional is true, the "=value"
+// part can be omitted.
+//
+// Returns the value of the flag, or NULL if the parsing failed.
+static const char* ParseFlagValue(const char* str, const char* flag,
+                                  bool def_optional) {
+  // str and flag must not be NULL.
+  if (str == nullptr || flag == nullptr) return nullptr;
+
+  // The flag must start with "--" followed by GTEST_FLAG_PREFIX_.
+  const std::string flag_str = std::string("--") + GTEST_FLAG_PREFIX_ + flag;
+  const size_t flag_len = flag_str.length();
+  if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr;
+
+  // Skips the flag name.
+  const char* flag_end = str + flag_len;
+
+  // When def_optional is true, it's OK to not have a "=value" part.
+  if (def_optional && (flag_end[0] == '\0')) {
+    return flag_end;
+  }
+
+  // If def_optional is true and there are more characters after the
+  // flag name, or if def_optional is false, there must be a '=' after
+  // the flag name.
+  if (flag_end[0] != '=') return nullptr;
+
+  // Returns the string after "=".
+  return flag_end + 1;
+}
+
+// Parses a string for a bool flag, in the form of either
+// "--flag=value" or "--flag".
+//
+// In the former case, the value is taken as true as long as it does
+// not start with '0', 'f', or 'F'.
+//
+// In the latter case, the value is taken as true.
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+static bool ParseBoolFlag(const char* str, const char* flag, bool* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseFlagValue(str, flag, true);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Converts the string value to a bool.
+  *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F');
+  return true;
+}
+
+// Parses a string for an int32_t flag, in the form of "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+bool ParseInt32Flag(const char* str, const char* flag, int32_t* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseFlagValue(str, flag, false);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Sets *value to the value of the flag.
+  return ParseInt32(Message() << "The value of flag --" << flag,
+                    value_str, value);
+}
+
+// Parses a string for a string flag, in the form of "--flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+template <typename String>
+static bool ParseStringFlag(const char* str, const char* flag, String* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseFlagValue(str, flag, false);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Sets *value to the value of the flag.
+  *value = value_str;
+  return true;
+}
+
+// Determines whether a string has a prefix that Google Test uses for its
+// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_.
+// If Google Test detects that a command line flag has its prefix but is not
+// recognized, it will print its help message. Flags starting with
+// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test
+// internal flags and do not trigger the help message.
+static bool HasGoogleTestFlagPrefix(const char* str) {
+  return (SkipPrefix("--", &str) ||
+          SkipPrefix("-", &str) ||
+          SkipPrefix("/", &str)) &&
+         !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) &&
+         (SkipPrefix(GTEST_FLAG_PREFIX_, &str) ||
+          SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str));
+}
+
+// Prints a string containing code-encoded text.  The following escape
+// sequences can be used in the string to control the text color:
+//
+//   @@    prints a single '@' character.
+//   @R    changes the color to red.
+//   @G    changes the color to green.
+//   @Y    changes the color to yellow.
+//   @D    changes to the default terminal text color.
+//
+static void PrintColorEncoded(const char* str) {
+  GTestColor color = GTestColor::kDefault;  // The current color.
+
+  // Conceptually, we split the string into segments divided by escape
+  // sequences.  Then we print one segment at a time.  At the end of
+  // each iteration, the str pointer advances to the beginning of the
+  // next segment.
+  for (;;) {
+    const char* p = strchr(str, '@');
+    if (p == nullptr) {
+      ColoredPrintf(color, "%s", str);
+      return;
+    }
+
+    ColoredPrintf(color, "%s", std::string(str, p).c_str());
+
+    const char ch = p[1];
+    str = p + 2;
+    if (ch == '@') {
+      ColoredPrintf(color, "@");
+    } else if (ch == 'D') {
+      color = GTestColor::kDefault;
+    } else if (ch == 'R') {
+      color = GTestColor::kRed;
+    } else if (ch == 'G') {
+      color = GTestColor::kGreen;
+    } else if (ch == 'Y') {
+      color = GTestColor::kYellow;
+    } else {
+      --str;
+    }
+  }
+}
+
+static const char kColorEncodedHelpMessage[] =
+    "This program contains tests written using " GTEST_NAME_
+    ". You can use the\n"
+    "following command line flags to control its behavior:\n"
+    "\n"
+    "Test Selection:\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "list_tests@D\n"
+    "      List the names of all tests instead of running them. The name of\n"
+    "      TEST(Foo, Bar) is \"Foo.Bar\".\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "filter=@YPOSITIVE_PATTERNS"
+    "[@G-@YNEGATIVE_PATTERNS]@D\n"
+    "      Run only the tests whose name matches one of the positive patterns "
+    "but\n"
+    "      none of the negative patterns. '?' matches any single character; "
+    "'*'\n"
+    "      matches any substring; ':' separates two patterns.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "also_run_disabled_tests@D\n"
+    "      Run all disabled tests too.\n"
+    "\n"
+    "Test Execution:\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "repeat=@Y[COUNT]@D\n"
+    "      Run the tests repeatedly; use a negative count to repeat forever.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "shuffle@D\n"
+    "      Randomize tests' orders on every iteration.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "random_seed=@Y[NUMBER]@D\n"
+    "      Random number seed to use for shuffling test orders (between 1 and\n"
+    "      99999, or 0 to use a seed based on the current time).\n"
+    "\n"
+    "Test Output:\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n"
+    "      Enable/disable colored output. The default is @Gauto@D.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "brief=1@D\n"
+    "      Only print test failures.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "print_time=0@D\n"
+    "      Don't print the elapsed time of each test.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "output=@Y(@Gjson@Y|@Gxml@Y)[@G:@YDIRECTORY_PATH@G" GTEST_PATH_SEP_
+    "@Y|@G:@YFILE_PATH]@D\n"
+    "      Generate a JSON or XML report in the given directory or with the "
+    "given\n"
+    "      file name. @YFILE_PATH@D defaults to @Gtest_detail.xml@D.\n"
+# if GTEST_CAN_STREAM_RESULTS_
+    "  @G--" GTEST_FLAG_PREFIX_
+    "stream_result_to=@YHOST@G:@YPORT@D\n"
+    "      Stream test results to the given server.\n"
+# endif  // GTEST_CAN_STREAM_RESULTS_
+    "\n"
+    "Assertion Behavior:\n"
+# if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS
+    "  @G--" GTEST_FLAG_PREFIX_
+    "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n"
+    "      Set the default death test style.\n"
+# endif  // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS
+    "  @G--" GTEST_FLAG_PREFIX_
+    "break_on_failure@D\n"
+    "      Turn assertion failures into debugger break-points.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "throw_on_failure@D\n"
+    "      Turn assertion failures into C++ exceptions for use by an external\n"
+    "      test framework.\n"
+    "  @G--" GTEST_FLAG_PREFIX_
+    "catch_exceptions=0@D\n"
+    "      Do not report exceptions as test failures. Instead, allow them\n"
+    "      to crash the program or throw a pop-up (on Windows).\n"
+    "\n"
+    "Except for @G--" GTEST_FLAG_PREFIX_
+    "list_tests@D, you can alternatively set "
+    "the corresponding\n"
+    "environment variable of a flag (all letters in upper-case). For example, "
+    "to\n"
+    "disable colored text output, you can either specify "
+    "@G--" GTEST_FLAG_PREFIX_
+    "color=no@D or set\n"
+    "the @G" GTEST_FLAG_PREFIX_UPPER_
+    "COLOR@D environment variable to @Gno@D.\n"
+    "\n"
+    "For more information, please read the " GTEST_NAME_
+    " documentation at\n"
+    "@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_
+    "\n"
+    "(not one in your own code or tests), please report it to\n"
+    "@G<" GTEST_DEV_EMAIL_ ">@D.\n";
+
+static bool ParseGoogleTestFlag(const char* const arg) {
+  return ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag,
+                       &GTEST_FLAG(also_run_disabled_tests)) ||
+         ParseBoolFlag(arg, kBreakOnFailureFlag,
+                       &GTEST_FLAG(break_on_failure)) ||
+         ParseBoolFlag(arg, kCatchExceptionsFlag,
+                       &GTEST_FLAG(catch_exceptions)) ||
+         ParseStringFlag(arg, kColorFlag, &GTEST_FLAG(color)) ||
+         ParseStringFlag(arg, kDeathTestStyleFlag,
+                         &GTEST_FLAG(death_test_style)) ||
+         ParseBoolFlag(arg, kDeathTestUseFork,
+                       &GTEST_FLAG(death_test_use_fork)) ||
+         ParseBoolFlag(arg, kFailFast, &GTEST_FLAG(fail_fast)) ||
+         ParseStringFlag(arg, kFilterFlag, &GTEST_FLAG(filter)) ||
+         ParseStringFlag(arg, kInternalRunDeathTestFlag,
+                         &GTEST_FLAG(internal_run_death_test)) ||
+         ParseBoolFlag(arg, kListTestsFlag, &GTEST_FLAG(list_tests)) ||
+         ParseStringFlag(arg, kOutputFlag, &GTEST_FLAG(output)) ||
+         ParseBoolFlag(arg, kBriefFlag, &GTEST_FLAG(brief)) ||
+         ParseBoolFlag(arg, kPrintTimeFlag, &GTEST_FLAG(print_time)) ||
+         ParseBoolFlag(arg, kPrintUTF8Flag, &GTEST_FLAG(print_utf8)) ||
+         ParseInt32Flag(arg, kRandomSeedFlag, &GTEST_FLAG(random_seed)) ||
+         ParseInt32Flag(arg, kRepeatFlag, &GTEST_FLAG(repeat)) ||
+         ParseBoolFlag(arg, kShuffleFlag, &GTEST_FLAG(shuffle)) ||
+         ParseInt32Flag(arg, kStackTraceDepthFlag,
+                        &GTEST_FLAG(stack_trace_depth)) ||
+         ParseStringFlag(arg, kStreamResultToFlag,
+                         &GTEST_FLAG(stream_result_to)) ||
+         ParseBoolFlag(arg, kThrowOnFailureFlag, &GTEST_FLAG(throw_on_failure));
+}
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+static void LoadFlagsFromFile(const std::string& path) {
+  FILE* flagfile = posix::FOpen(path.c_str(), "r");
+  if (!flagfile) {
+    GTEST_LOG_(FATAL) << "Unable to open file \"" << GTEST_FLAG(flagfile)
+                      << "\"";
+  }
+  std::string contents(ReadEntireFile(flagfile));
+  posix::FClose(flagfile);
+  std::vector<std::string> lines;
+  SplitString(contents, '\n', &lines);
+  for (size_t i = 0; i < lines.size(); ++i) {
+    if (lines[i].empty())
+      continue;
+    if (!ParseGoogleTestFlag(lines[i].c_str()))
+      g_help_flag = true;
+  }
+}
+#endif  // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test.  The type parameter CharType can be
+// instantiated to either char or wchar_t.
+template <typename CharType>
+void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) {
+  for (int i = 1; i < *argc; i++) {
+    const std::string arg_string = StreamableToString(argv[i]);
+    const char* const arg = arg_string.c_str();
+
+    using internal::ParseBoolFlag;
+    using internal::ParseInt32Flag;
+    using internal::ParseStringFlag;
+
+    bool remove_flag = false;
+    if (ParseGoogleTestFlag(arg)) {
+      remove_flag = true;
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+    } else if (ParseStringFlag(arg, kFlagfileFlag, &GTEST_FLAG(flagfile))) {
+      LoadFlagsFromFile(GTEST_FLAG(flagfile));
+      remove_flag = true;
+#endif  // GTEST_USE_OWN_FLAGFILE_FLAG_
+    } else if (arg_string == "--help" || arg_string == "-h" ||
+               arg_string == "-?" || arg_string == "/?" ||
+               HasGoogleTestFlagPrefix(arg)) {
+      // Both help flag and unrecognized Google Test flags (excluding
+      // internal ones) trigger help display.
+      g_help_flag = true;
+    }
+
+    if (remove_flag) {
+      // Shift the remainder of the argv list left by one.  Note
+      // that argv has (*argc + 1) elements, the last one always being
+      // NULL.  The following loop moves the trailing NULL element as
+      // well.
+      for (int j = i; j != *argc; j++) {
+        argv[j] = argv[j + 1];
+      }
+
+      // Decrements the argument count.
+      (*argc)--;
+
+      // We also need to decrement the iterator as we just removed
+      // an element.
+      i--;
+    }
+  }
+
+  if (g_help_flag) {
+    // We print the help here instead of in RUN_ALL_TESTS(), as the
+    // latter may not be called at all if the user is using Google
+    // Test with another testing framework.
+    PrintColorEncoded(kColorEncodedHelpMessage);
+  }
+}
+
+// Parses the command line for Google Test flags, without initializing
+// other parts of Google Test.
+void ParseGoogleTestFlagsOnly(int* argc, char** argv) {
+  ParseGoogleTestFlagsOnlyImpl(argc, argv);
+
+  // Fix the value of *_NSGetArgc() on macOS, but if and only if
+  // *_NSGetArgv() == argv
+  // Only applicable to char** version of argv
+#if GTEST_OS_MAC
+#ifndef GTEST_OS_IOS
+  if (*_NSGetArgv() == argv) {
+    *_NSGetArgc() = *argc;
+  }
+#endif
+#endif
+}
+void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) {
+  ParseGoogleTestFlagsOnlyImpl(argc, argv);
+}
+
+// The internal implementation of InitGoogleTest().
+//
+// The type parameter CharType can be instantiated to either char or
+// wchar_t.
+template <typename CharType>
+void InitGoogleTestImpl(int* argc, CharType** argv) {
+  // We don't want to run the initialization code twice.
+  if (GTestIsInitialized()) return;
+
+  if (*argc <= 0) return;
+
+  g_argvs.clear();
+  for (int i = 0; i != *argc; i++) {
+    g_argvs.push_back(StreamableToString(argv[i]));
+  }
+
+#if GTEST_HAS_ABSL
+  absl::InitializeSymbolizer(g_argvs[0].c_str());
+#endif  // GTEST_HAS_ABSL
+
+  ParseGoogleTestFlagsOnly(argc, argv);
+  GetUnitTestImpl()->PostFlagParsingInit();
+}
+
+}  // namespace internal
+
+// Initializes Google Test.  This must be called before calling
+// RUN_ALL_TESTS().  In particular, it parses a command line for the
+// flags that Google Test recognizes.  Whenever a Google Test flag is
+// seen, it is removed from argv, and *argc is decremented.
+//
+// No value is returned.  Instead, the Google Test flag variables are
+// updated.
+//
+// Calling the function for the second time has no user-visible effect.
+void InitGoogleTest(int* argc, char** argv) {
+#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv);
+#else  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  internal::InitGoogleTestImpl(argc, argv);
+#endif  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+}
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+void InitGoogleTest(int* argc, wchar_t** argv) {
+#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(argc, argv);
+#else  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  internal::InitGoogleTestImpl(argc, argv);
+#endif  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+}
+
+// This overloaded version can be used on Arduino/embedded platforms where
+// there is no argc/argv.
+void InitGoogleTest() {
+  // Since Arduino doesn't have a command line, fake out the argc/argv arguments
+  int argc = 1;
+  const auto arg0 = "dummy";
+  char* argv0 = const_cast<char*>(arg0);
+  char** argv = &argv0;
+
+#if defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_(&argc, argv);
+#else  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+  internal::InitGoogleTestImpl(&argc, argv);
+#endif  // defined(GTEST_CUSTOM_INIT_GOOGLE_TEST_FUNCTION_)
+}
+
+std::string TempDir() {
+#if defined(GTEST_CUSTOM_TEMPDIR_FUNCTION_)
+  return GTEST_CUSTOM_TEMPDIR_FUNCTION_();
+#elif GTEST_OS_WINDOWS_MOBILE
+  return "\\temp\\";
+#elif GTEST_OS_WINDOWS
+  const char* temp_dir = internal::posix::GetEnv("TEMP");
+  if (temp_dir == nullptr || temp_dir[0] == '\0') {
+    return "\\temp\\";
+  } else if (temp_dir[strlen(temp_dir) - 1] == '\\') {
+    return temp_dir;
+  } else {
+    return std::string(temp_dir) + "\\";
+  }
+#elif GTEST_OS_LINUX_ANDROID
+  const char* temp_dir = internal::posix::GetEnv("TEST_TMPDIR");
+  if (temp_dir == nullptr || temp_dir[0] == '\0') {
+    return "/data/local/tmp/";
+  } else {
+    return temp_dir;
+  }
+#elif GTEST_OS_LINUX
+  const char* temp_dir = internal::posix::GetEnv("TEST_TMPDIR");
+  if (temp_dir == nullptr || temp_dir[0] == '\0') {
+    return "/tmp/";
+  } else {
+    return temp_dir;
+  }
+#else
+  return "/tmp/";
+#endif  // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Class ScopedTrace
+
+// Pushes the given source file location and message onto a per-thread
+// trace stack maintained by Google Test.
+void ScopedTrace::PushTrace(const char* file, int line, std::string message) {
+  internal::TraceInfo trace;
+  trace.file = file;
+  trace.line = line;
+  trace.message.swap(message);
+
+  UnitTest::GetInstance()->PushGTestTrace(trace);
+}
+
+// Pops the info pushed by the c'tor.
+ScopedTrace::~ScopedTrace()
+    GTEST_LOCK_EXCLUDED_(&UnitTest::mutex_) {
+  UnitTest::GetInstance()->PopGTestTrace();
+}
+
+}  // namespace testing
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// This file implements death tests.
+
+
+#include <functional>
+#include <utility>
+
+
+#if GTEST_HAS_DEATH_TEST
+
+# if GTEST_OS_MAC
+#  include <crt_externs.h>
+# endif  // GTEST_OS_MAC
+
+# include <errno.h>
+# include <fcntl.h>
+# include <limits.h>
+
+# if GTEST_OS_LINUX
+#  include <signal.h>
+# endif  // GTEST_OS_LINUX
+
+# include <stdarg.h>
+
+# if GTEST_OS_WINDOWS
+#  include <windows.h>
+# else
+#  include <sys/mman.h>
+#  include <sys/wait.h>
+# endif  // GTEST_OS_WINDOWS
+
+# if GTEST_OS_QNX
+#  include <spawn.h>
+# endif  // GTEST_OS_QNX
+
+# if GTEST_OS_FUCHSIA
+#  include <lib/fdio/fd.h>
+#  include <lib/fdio/io.h>
+#  include <lib/fdio/spawn.h>
+#  include <lib/zx/channel.h>
+#  include <lib/zx/port.h>
+#  include <lib/zx/process.h>
+#  include <lib/zx/socket.h>
+#  include <zircon/processargs.h>
+#  include <zircon/syscalls.h>
+#  include <zircon/syscalls/policy.h>
+#  include <zircon/syscalls/port.h>
+# endif  // GTEST_OS_FUCHSIA
+
+#endif  // GTEST_HAS_DEATH_TEST
+
+
+namespace testing {
+
+// Constants.
+
+// The default death test style.
+//
+// This is defined in internal/gtest-port.h as "fast", but can be overridden by
+// a definition in internal/custom/gtest-port.h. The recommended value, which is
+// used internally at Google, is "threadsafe".
+static const char kDefaultDeathTestStyle[] = GTEST_DEFAULT_DEATH_TEST_STYLE;
+
+GTEST_DEFINE_string_(
+    death_test_style,
+    internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle),
+    "Indicates how to run a death test in a forked child process: "
+    "\"threadsafe\" (child process re-executes the test binary "
+    "from the beginning, running only the specific death test) or "
+    "\"fast\" (child process runs the death test immediately "
+    "after forking).");
+
+GTEST_DEFINE_bool_(
+    death_test_use_fork,
+    internal::BoolFromGTestEnv("death_test_use_fork", false),
+    "Instructs to use fork()/_exit() instead of clone() in death tests. "
+    "Ignored and always uses fork() on POSIX systems where clone() is not "
+    "implemented. Useful when running under valgrind or similar tools if "
+    "those do not support clone(). Valgrind 3.3.1 will just fail if "
+    "it sees an unsupported combination of clone() flags. "
+    "It is not recommended to use this flag w/o valgrind though it will "
+    "work in 99% of the cases. Once valgrind is fixed, this flag will "
+    "most likely be removed.");
+
+namespace internal {
+GTEST_DEFINE_string_(
+    internal_run_death_test, "",
+    "Indicates the file, line number, temporal index of "
+    "the single death test to run, and a file descriptor to "
+    "which a success code may be sent, all separated by "
+    "the '|' characters.  This flag is specified if and only if the "
+    "current process is a sub-process launched for running a thread-safe "
+    "death test.  FOR INTERNAL USE ONLY.");
+}  // namespace internal
+
+#if GTEST_HAS_DEATH_TEST
+
+namespace internal {
+
+// Valid only for fast death tests. Indicates the code is running in the
+// child process of a fast style death test.
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+static bool g_in_fast_death_test_child = false;
+# endif
+
+// Returns a Boolean value indicating whether the caller is currently
+// executing in the context of the death test child process.  Tools such as
+// Valgrind heap checkers may need this to modify their behavior in death
+// tests.  IMPORTANT: This is an internal utility.  Using it may break the
+// implementation of death tests.  User code MUST NOT use it.
+bool InDeathTestChild() {
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+  // On Windows and Fuchsia, death tests are thread-safe regardless of the value
+  // of the death_test_style flag.
+  return !GTEST_FLAG(internal_run_death_test).empty();
+
+# else
+
+  if (GTEST_FLAG(death_test_style) == "threadsafe")
+    return !GTEST_FLAG(internal_run_death_test).empty();
+  else
+    return g_in_fast_death_test_child;
+#endif
+}
+
+}  // namespace internal
+
+// ExitedWithCode constructor.
+ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) {
+}
+
+// ExitedWithCode function-call operator.
+bool ExitedWithCode::operator()(int exit_status) const {
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+  return exit_status == exit_code_;
+
+# else
+
+  return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_;
+
+# endif  // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+}
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// KilledBySignal constructor.
+KilledBySignal::KilledBySignal(int signum) : signum_(signum) {
+}
+
+// KilledBySignal function-call operator.
+bool KilledBySignal::operator()(int exit_status) const {
+#  if defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_)
+  {
+    bool result;
+    if (GTEST_KILLED_BY_SIGNAL_OVERRIDE_(signum_, exit_status, &result)) {
+      return result;
+    }
+  }
+#  endif  // defined(GTEST_KILLED_BY_SIGNAL_OVERRIDE_)
+  return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_;
+}
+# endif  // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+
+namespace internal {
+
+// Utilities needed for death tests.
+
+// Generates a textual description of a given exit code, in the format
+// specified by wait(2).
+static std::string ExitSummary(int exit_code) {
+  Message m;
+
+# if GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+  m << "Exited with exit status " << exit_code;
+
+# else
+
+  if (WIFEXITED(exit_code)) {
+    m << "Exited with exit status " << WEXITSTATUS(exit_code);
+  } else if (WIFSIGNALED(exit_code)) {
+    m << "Terminated by signal " << WTERMSIG(exit_code);
+  }
+#  ifdef WCOREDUMP
+  if (WCOREDUMP(exit_code)) {
+    m << " (core dumped)";
+  }
+#  endif
+# endif  // GTEST_OS_WINDOWS || GTEST_OS_FUCHSIA
+
+  return m.GetString();
+}
+
+// Returns true if exit_status describes a process that was terminated
+// by a signal, or exited normally with a nonzero exit code.
+bool ExitedUnsuccessfully(int exit_status) {
+  return !ExitedWithCode(0)(exit_status);
+}
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// Generates a textual failure message when a death test finds more than
+// one thread running, or cannot determine the number of threads, prior
+// to executing the given statement.  It is the responsibility of the
+// caller not to pass a thread_count of 1.
+static std::string DeathTestThreadWarning(size_t thread_count) {
+  Message msg;
+  msg << "Death tests use fork(), which is unsafe particularly"
+      << " in a threaded context. For this test, " << GTEST_NAME_ << " ";
+  if (thread_count == 0) {
+    msg << "couldn't detect the number of threads.";
+  } else {
+    msg << "detected " << thread_count << " threads.";
+  }
+  msg << " See "
+         "https://github.com/google/googletest/blob/master/docs/"
+         "advanced.md#death-tests-and-threads"
+      << " for more explanation and suggested solutions, especially if"
+      << " this is the last message you see before your test times out.";
+  return msg.GetString();
+}
+# endif  // !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+
+// Flag characters for reporting a death test that did not die.
+static const char kDeathTestLived = 'L';
+static const char kDeathTestReturned = 'R';
+static const char kDeathTestThrew = 'T';
+static const char kDeathTestInternalError = 'I';
+
+#if GTEST_OS_FUCHSIA
+
+// File descriptor used for the pipe in the child process.
+static const int kFuchsiaReadPipeFd = 3;
+
+#endif
+
+// An enumeration describing all of the possible ways that a death test can
+// conclude.  DIED means that the process died while executing the test
+// code; LIVED means that process lived beyond the end of the test code;
+// RETURNED means that the test statement attempted to execute a return
+// statement, which is not allowed; THREW means that the test statement
+// returned control by throwing an exception.  IN_PROGRESS means the test
+// has not yet concluded.
+enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW };
+
+// Routine for aborting the program which is safe to call from an
+// exec-style death test child process, in which case the error
+// message is propagated back to the parent process.  Otherwise, the
+// message is simply printed to stderr.  In either case, the program
+// then exits with status 1.
+static void DeathTestAbort(const std::string& message) {
+  // On a POSIX system, this function may be called from a threadsafe-style
+  // death test child process, which operates on a very small stack.  Use
+  // the heap for any additional non-minuscule memory requirements.
+  const InternalRunDeathTestFlag* const flag =
+      GetUnitTestImpl()->internal_run_death_test_flag();
+  if (flag != nullptr) {
+    FILE* parent = posix::FDOpen(flag->write_fd(), "w");
+    fputc(kDeathTestInternalError, parent);
+    fprintf(parent, "%s", message.c_str());
+    fflush(parent);
+    _exit(1);
+  } else {
+    fprintf(stderr, "%s", message.c_str());
+    fflush(stderr);
+    posix::Abort();
+  }
+}
+
+// A replacement for CHECK that calls DeathTestAbort if the assertion
+// fails.
+# define GTEST_DEATH_TEST_CHECK_(expression) \
+  do { \
+    if (!::testing::internal::IsTrue(expression)) { \
+      DeathTestAbort( \
+          ::std::string("CHECK failed: File ") + __FILE__ +  ", line " \
+          + ::testing::internal::StreamableToString(__LINE__) + ": " \
+          + #expression); \
+    } \
+  } while (::testing::internal::AlwaysFalse())
+
+// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for
+// evaluating any system call that fulfills two conditions: it must return
+// -1 on failure, and set errno to EINTR when it is interrupted and
+// should be tried again.  The macro expands to a loop that repeatedly
+// evaluates the expression as long as it evaluates to -1 and sets
+// errno to EINTR.  If the expression evaluates to -1 but errno is
+// something other than EINTR, DeathTestAbort is called.
+# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \
+  do { \
+    int gtest_retval; \
+    do { \
+      gtest_retval = (expression); \
+    } while (gtest_retval == -1 && errno == EINTR); \
+    if (gtest_retval == -1) { \
+      DeathTestAbort( \
+          ::std::string("CHECK failed: File ") + __FILE__ + ", line " \
+          + ::testing::internal::StreamableToString(__LINE__) + ": " \
+          + #expression + " != -1"); \
+    } \
+  } while (::testing::internal::AlwaysFalse())
+
+// Returns the message describing the last system error in errno.
+std::string GetLastErrnoDescription() {
+    return errno == 0 ? "" : posix::StrError(errno);
+}
+
+// This is called from a death test parent process to read a failure
+// message from the death test child process and log it with the FATAL
+// severity. On Windows, the message is read from a pipe handle. On other
+// platforms, it is read from a file descriptor.
+static void FailFromInternalError(int fd) {
+  Message error;
+  char buffer[256];
+  int num_read;
+
+  do {
+    while ((num_read = posix::Read(fd, buffer, 255)) > 0) {
+      buffer[num_read] = '\0';
+      error << buffer;
+    }
+  } while (num_read == -1 && errno == EINTR);
+
+  if (num_read == 0) {
+    GTEST_LOG_(FATAL) << error.GetString();
+  } else {
+    const int last_error = errno;
+    GTEST_LOG_(FATAL) << "Error while reading death test internal: "
+                      << GetLastErrnoDescription() << " [" << last_error << "]";
+  }
+}
+
+// Death test constructor.  Increments the running death test count
+// for the current test.
+DeathTest::DeathTest() {
+  TestInfo* const info = GetUnitTestImpl()->current_test_info();
+  if (info == nullptr) {
+    DeathTestAbort("Cannot run a death test outside of a TEST or "
+                   "TEST_F construct");
+  }
+}
+
+// Creates and returns a death test by dispatching to the current
+// death test factory.
+bool DeathTest::Create(const char* statement,
+                       Matcher<const std::string&> matcher, const char* file,
+                       int line, DeathTest** test) {
+  return GetUnitTestImpl()->death_test_factory()->Create(
+      statement, std::move(matcher), file, line, test);
+}
+
+const char* DeathTest::LastMessage() {
+  return last_death_test_message_.c_str();
+}
+
+void DeathTest::set_last_death_test_message(const std::string& message) {
+  last_death_test_message_ = message;
+}
+
+std::string DeathTest::last_death_test_message_;
+
+// Provides cross platform implementation for some death functionality.
+class DeathTestImpl : public DeathTest {
+ protected:
+  DeathTestImpl(const char* a_statement, Matcher<const std::string&> matcher)
+      : statement_(a_statement),
+        matcher_(std::move(matcher)),
+        spawned_(false),
+        status_(-1),
+        outcome_(IN_PROGRESS),
+        read_fd_(-1),
+        write_fd_(-1) {}
+
+  // read_fd_ is expected to be closed and cleared by a derived class.
+  ~DeathTestImpl() override { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); }
+
+  void Abort(AbortReason reason) override;
+  bool Passed(bool status_ok) override;
+
+  const char* statement() const { return statement_; }
+  bool spawned() const { return spawned_; }
+  void set_spawned(bool is_spawned) { spawned_ = is_spawned; }
+  int status() const { return status_; }
+  void set_status(int a_status) { status_ = a_status; }
+  DeathTestOutcome outcome() const { return outcome_; }
+  void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; }
+  int read_fd() const { return read_fd_; }
+  void set_read_fd(int fd) { read_fd_ = fd; }
+  int write_fd() const { return write_fd_; }
+  void set_write_fd(int fd) { write_fd_ = fd; }
+
+  // Called in the parent process only. Reads the result code of the death
+  // test child process via a pipe, interprets it to set the outcome_
+  // member, and closes read_fd_.  Outputs diagnostics and terminates in
+  // case of unexpected codes.
+  void ReadAndInterpretStatusByte();
+
+  // Returns stderr output from the child process.
+  virtual std::string GetErrorLogs();
+
+ private:
+  // The textual content of the code this object is testing.  This class
+  // doesn't own this string and should not attempt to delete it.
+  const char* const statement_;
+  // A matcher that's expected to match the stderr output by the child process.
+  Matcher<const std::string&> matcher_;
+  // True if the death test child process has been successfully spawned.
+  bool spawned_;
+  // The exit status of the child process.
+  int status_;
+  // How the death test concluded.
+  DeathTestOutcome outcome_;
+  // Descriptor to the read end of the pipe to the child process.  It is
+  // always -1 in the child process.  The child keeps its write end of the
+  // pipe in write_fd_.
+  int read_fd_;
+  // Descriptor to the child's write end of the pipe to the parent process.
+  // It is always -1 in the parent process.  The parent keeps its end of the
+  // pipe in read_fd_.
+  int write_fd_;
+};
+
+// Called in the parent process only. Reads the result code of the death
+// test child process via a pipe, interprets it to set the outcome_
+// member, and closes read_fd_.  Outputs diagnostics and terminates in
+// case of unexpected codes.
+void DeathTestImpl::ReadAndInterpretStatusByte() {
+  char flag;
+  int bytes_read;
+
+  // The read() here blocks until data is available (signifying the
+  // failure of the death test) or until the pipe is closed (signifying
+  // its success), so it's okay to call this in the parent before
+  // the child process has exited.
+  do {
+    bytes_read = posix::Read(read_fd(), &flag, 1);
+  } while (bytes_read == -1 && errno == EINTR);
+
+  if (bytes_read == 0) {
+    set_outcome(DIED);
+  } else if (bytes_read == 1) {
+    switch (flag) {
+      case kDeathTestReturned:
+        set_outcome(RETURNED);
+        break;
+      case kDeathTestThrew:
+        set_outcome(THREW);
+        break;
+      case kDeathTestLived:
+        set_outcome(LIVED);
+        break;
+      case kDeathTestInternalError:
+        FailFromInternalError(read_fd());  // Does not return.
+        break;
+      default:
+        GTEST_LOG_(FATAL) << "Death test child process reported "
+                          << "unexpected status byte ("
+                          << static_cast<unsigned int>(flag) << ")";
+    }
+  } else {
+    GTEST_LOG_(FATAL) << "Read from death test child process failed: "
+                      << GetLastErrnoDescription();
+  }
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd()));
+  set_read_fd(-1);
+}
+
+std::string DeathTestImpl::GetErrorLogs() {
+  return GetCapturedStderr();
+}
+
+// Signals that the death test code which should have exited, didn't.
+// Should be called only in a death test child process.
+// Writes a status byte to the child's status file descriptor, then
+// calls _exit(1).
+void DeathTestImpl::Abort(AbortReason reason) {
+  // The parent process considers the death test to be a failure if
+  // it finds any data in our pipe.  So, here we write a single flag byte
+  // to the pipe, then exit.
+  const char status_ch =
+      reason == TEST_DID_NOT_DIE ? kDeathTestLived :
+      reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned;
+
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1));
+  // We are leaking the descriptor here because on some platforms (i.e.,
+  // when built as Windows DLL), destructors of global objects will still
+  // run after calling _exit(). On such systems, write_fd_ will be
+  // indirectly closed from the destructor of UnitTestImpl, causing double
+  // close if it is also closed here. On debug configurations, double close
+  // may assert. As there are no in-process buffers to flush here, we are
+  // relying on the OS to close the descriptor after the process terminates
+  // when the destructors are not run.
+  _exit(1);  // Exits w/o any normal exit hooks (we were supposed to crash)
+}
+
+// Returns an indented copy of stderr output for a death test.
+// This makes distinguishing death test output lines from regular log lines
+// much easier.
+static ::std::string FormatDeathTestOutput(const ::std::string& output) {
+  ::std::string ret;
+  for (size_t at = 0; ; ) {
+    const size_t line_end = output.find('\n', at);
+    ret += "[  DEATH   ] ";
+    if (line_end == ::std::string::npos) {
+      ret += output.substr(at);
+      break;
+    }
+    ret += output.substr(at, line_end + 1 - at);
+    at = line_end + 1;
+  }
+  return ret;
+}
+
+// Assesses the success or failure of a death test, using both private
+// members which have previously been set, and one argument:
+//
+// Private data members:
+//   outcome:  An enumeration describing how the death test
+//             concluded: DIED, LIVED, THREW, or RETURNED.  The death test
+//             fails in the latter three cases.
+//   status:   The exit status of the child process. On *nix, it is in the
+//             in the format specified by wait(2). On Windows, this is the
+//             value supplied to the ExitProcess() API or a numeric code
+//             of the exception that terminated the program.
+//   matcher_: A matcher that's expected to match the stderr output by the child
+//             process.
+//
+// Argument:
+//   status_ok: true if exit_status is acceptable in the context of
+//              this particular death test, which fails if it is false
+//
+// Returns true if and only if all of the above conditions are met.  Otherwise,
+// the first failing condition, in the order given above, is the one that is
+// reported. Also sets the last death test message string.
+bool DeathTestImpl::Passed(bool status_ok) {
+  if (!spawned())
+    return false;
+
+  const std::string error_message = GetErrorLogs();
+
+  bool success = false;
+  Message buffer;
+
+  buffer << "Death test: " << statement() << "\n";
+  switch (outcome()) {
+    case LIVED:
+      buffer << "    Result: failed to die.\n"
+             << " Error msg:\n" << FormatDeathTestOutput(error_message);
+      break;
+    case THREW:
+      buffer << "    Result: threw an exception.\n"
+             << " Error msg:\n" << FormatDeathTestOutput(error_message);
+      break;
+    case RETURNED:
+      buffer << "    Result: illegal return in test statement.\n"
+             << " Error msg:\n" << FormatDeathTestOutput(error_message);
+      break;
+    case DIED:
+      if (status_ok) {
+        if (matcher_.Matches(error_message)) {
+          success = true;
+        } else {
+          std::ostringstream stream;
+          matcher_.DescribeTo(&stream);
+          buffer << "    Result: died but not with expected error.\n"
+                 << "  Expected: " << stream.str() << "\n"
+                 << "Actual msg:\n"
+                 << FormatDeathTestOutput(error_message);
+        }
+      } else {
+        buffer << "    Result: died but not with expected exit code:\n"
+               << "            " << ExitSummary(status()) << "\n"
+               << "Actual msg:\n" << FormatDeathTestOutput(error_message);
+      }
+      break;
+    case IN_PROGRESS:
+    default:
+      GTEST_LOG_(FATAL)
+          << "DeathTest::Passed somehow called before conclusion of test";
+  }
+
+  DeathTest::set_last_death_test_message(buffer.GetString());
+  return success;
+}
+
+# if GTEST_OS_WINDOWS
+// WindowsDeathTest implements death tests on Windows. Due to the
+// specifics of starting new processes on Windows, death tests there are
+// always threadsafe, and Google Test considers the
+// --gtest_death_test_style=fast setting to be equivalent to
+// --gtest_death_test_style=threadsafe there.
+//
+// A few implementation notes:  Like the Linux version, the Windows
+// implementation uses pipes for child-to-parent communication. But due to
+// the specifics of pipes on Windows, some extra steps are required:
+//
+// 1. The parent creates a communication pipe and stores handles to both
+//    ends of it.
+// 2. The parent starts the child and provides it with the information
+//    necessary to acquire the handle to the write end of the pipe.
+// 3. The child acquires the write end of the pipe and signals the parent
+//    using a Windows event.
+// 4. Now the parent can release the write end of the pipe on its side. If
+//    this is done before step 3, the object's reference count goes down to
+//    0 and it is destroyed, preventing the child from acquiring it. The
+//    parent now has to release it, or read operations on the read end of
+//    the pipe will not return when the child terminates.
+// 5. The parent reads child's output through the pipe (outcome code and
+//    any possible error messages) from the pipe, and its stderr and then
+//    determines whether to fail the test.
+//
+// Note: to distinguish Win32 API calls from the local method and function
+// calls, the former are explicitly resolved in the global namespace.
+//
+class WindowsDeathTest : public DeathTestImpl {
+ public:
+  WindowsDeathTest(const char* a_statement, Matcher<const std::string&> matcher,
+                   const char* file, int line)
+      : DeathTestImpl(a_statement, std::move(matcher)),
+        file_(file),
+        line_(line) {}
+
+  // All of these virtual functions are inherited from DeathTest.
+  virtual int Wait();
+  virtual TestRole AssumeRole();
+
+ private:
+  // The name of the file in which the death test is located.
+  const char* const file_;
+  // The line number on which the death test is located.
+  const int line_;
+  // Handle to the write end of the pipe to the child process.
+  AutoHandle write_handle_;
+  // Child process handle.
+  AutoHandle child_handle_;
+  // Event the child process uses to signal the parent that it has
+  // acquired the handle to the write end of the pipe. After seeing this
+  // event the parent can release its own handles to make sure its
+  // ReadFile() calls return when the child terminates.
+  AutoHandle event_handle_;
+};
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists.  As a side effect, sets the
+// outcome data member.
+int WindowsDeathTest::Wait() {
+  if (!spawned())
+    return 0;
+
+  // Wait until the child either signals that it has acquired the write end
+  // of the pipe or it dies.
+  const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() };
+  switch (::WaitForMultipleObjects(2,
+                                   wait_handles,
+                                   FALSE,  // Waits for any of the handles.
+                                   INFINITE)) {
+    case WAIT_OBJECT_0:
+    case WAIT_OBJECT_0 + 1:
+      break;
+    default:
+      GTEST_DEATH_TEST_CHECK_(false);  // Should not get here.
+  }
+
+  // The child has acquired the write end of the pipe or exited.
+  // We release the handle on our side and continue.
+  write_handle_.Reset();
+  event_handle_.Reset();
+
+  ReadAndInterpretStatusByte();
+
+  // Waits for the child process to exit if it haven't already. This
+  // returns immediately if the child has already exited, regardless of
+  // whether previous calls to WaitForMultipleObjects synchronized on this
+  // handle or not.
+  GTEST_DEATH_TEST_CHECK_(
+      WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(),
+                                             INFINITE));
+  DWORD status_code;
+  GTEST_DEATH_TEST_CHECK_(
+      ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE);
+  child_handle_.Reset();
+  set_status(static_cast<int>(status_code));
+  return status();
+}
+
+// The AssumeRole process for a Windows death test.  It creates a child
+// process with the same executable as the current process to run the
+// death test.  The child process is given the --gtest_filter and
+// --gtest_internal_run_death_test flags such that it knows to run the
+// current death test only.
+DeathTest::TestRole WindowsDeathTest::AssumeRole() {
+  const UnitTestImpl* const impl = GetUnitTestImpl();
+  const InternalRunDeathTestFlag* const flag =
+      impl->internal_run_death_test_flag();
+  const TestInfo* const info = impl->current_test_info();
+  const int death_test_index = info->result()->death_test_count();
+
+  if (flag != nullptr) {
+    // ParseInternalRunDeathTestFlag() has performed all the necessary
+    // processing.
+    set_write_fd(flag->write_fd());
+    return EXECUTE_TEST;
+  }
+
+  // WindowsDeathTest uses an anonymous pipe to communicate results of
+  // a death test.
+  SECURITY_ATTRIBUTES handles_are_inheritable = {sizeof(SECURITY_ATTRIBUTES),
+                                                 nullptr, TRUE};
+  HANDLE read_handle, write_handle;
+  GTEST_DEATH_TEST_CHECK_(
+      ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable,
+                   0)  // Default buffer size.
+      != FALSE);
+  set_read_fd(::_open_osfhandle(reinterpret_cast<intptr_t>(read_handle),
+                                O_RDONLY));
+  write_handle_.Reset(write_handle);
+  event_handle_.Reset(::CreateEvent(
+      &handles_are_inheritable,
+      TRUE,       // The event will automatically reset to non-signaled state.
+      FALSE,      // The initial state is non-signalled.
+      nullptr));  // The even is unnamed.
+  GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != nullptr);
+  const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ +
+                                  kFilterFlag + "=" + info->test_suite_name() +
+                                  "." + info->name();
+  const std::string internal_flag =
+      std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag +
+      "=" + file_ + "|" + StreamableToString(line_) + "|" +
+      StreamableToString(death_test_index) + "|" +
+      StreamableToString(static_cast<unsigned int>(::GetCurrentProcessId())) +
+      // size_t has the same width as pointers on both 32-bit and 64-bit
+      // Windows platforms.
+      // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx.
+      "|" + StreamableToString(reinterpret_cast<size_t>(write_handle)) +
+      "|" + StreamableToString(reinterpret_cast<size_t>(event_handle_.Get()));
+
+  char executable_path[_MAX_PATH + 1];  // NOLINT
+  GTEST_DEATH_TEST_CHECK_(_MAX_PATH + 1 != ::GetModuleFileNameA(nullptr,
+                                                                executable_path,
+                                                                _MAX_PATH));
+
+  std::string command_line =
+      std::string(::GetCommandLineA()) + " " + filter_flag + " \"" +
+      internal_flag + "\"";
+
+  DeathTest::set_last_death_test_message("");
+
+  CaptureStderr();
+  // Flush the log buffers since the log streams are shared with the child.
+  FlushInfoLog();
+
+  // The child process will share the standard handles with the parent.
+  STARTUPINFOA startup_info;
+  memset(&startup_info, 0, sizeof(STARTUPINFO));
+  startup_info.dwFlags = STARTF_USESTDHANDLES;
+  startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
+  startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
+  startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
+
+  PROCESS_INFORMATION process_info;
+  GTEST_DEATH_TEST_CHECK_(
+      ::CreateProcessA(
+          executable_path, const_cast<char*>(command_line.c_str()),
+          nullptr,  // Retuned process handle is not inheritable.
+          nullptr,  // Retuned thread handle is not inheritable.
+          TRUE,  // Child inherits all inheritable handles (for write_handle_).
+          0x0,   // Default creation flags.
+          nullptr,  // Inherit the parent's environment.
+          UnitTest::GetInstance()->original_working_dir(), &startup_info,
+          &process_info) != FALSE);
+  child_handle_.Reset(process_info.hProcess);
+  ::CloseHandle(process_info.hThread);
+  set_spawned(true);
+  return OVERSEE_TEST;
+}
+
+# elif GTEST_OS_FUCHSIA
+
+class FuchsiaDeathTest : public DeathTestImpl {
+ public:
+  FuchsiaDeathTest(const char* a_statement, Matcher<const std::string&> matcher,
+                   const char* file, int line)
+      : DeathTestImpl(a_statement, std::move(matcher)),
+        file_(file),
+        line_(line) {}
+
+  // All of these virtual functions are inherited from DeathTest.
+  int Wait() override;
+  TestRole AssumeRole() override;
+  std::string GetErrorLogs() override;
+
+ private:
+  // The name of the file in which the death test is located.
+  const char* const file_;
+  // The line number on which the death test is located.
+  const int line_;
+  // The stderr data captured by the child process.
+  std::string captured_stderr_;
+
+  zx::process child_process_;
+  zx::channel exception_channel_;
+  zx::socket stderr_socket_;
+};
+
+// Utility class for accumulating command-line arguments.
+class Arguments {
+ public:
+  Arguments() { args_.push_back(nullptr); }
+
+  ~Arguments() {
+    for (std::vector<char*>::iterator i = args_.begin(); i != args_.end();
+         ++i) {
+      free(*i);
+    }
+  }
+  void AddArgument(const char* argument) {
+    args_.insert(args_.end() - 1, posix::StrDup(argument));
+  }
+
+  template <typename Str>
+  void AddArguments(const ::std::vector<Str>& arguments) {
+    for (typename ::std::vector<Str>::const_iterator i = arguments.begin();
+         i != arguments.end();
+         ++i) {
+      args_.insert(args_.end() - 1, posix::StrDup(i->c_str()));
+    }
+  }
+  char* const* Argv() {
+    return &args_[0];
+  }
+
+  int size() {
+    return static_cast<int>(args_.size()) - 1;
+  }
+
+ private:
+  std::vector<char*> args_;
+};
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists.  As a side effect, sets the
+// outcome data member.
+int FuchsiaDeathTest::Wait() {
+  const int kProcessKey = 0;
+  const int kSocketKey = 1;
+  const int kExceptionKey = 2;
+
+  if (!spawned())
+    return 0;
+
+  // Create a port to wait for socket/task/exception events.
+  zx_status_t status_zx;
+  zx::port port;
+  status_zx = zx::port::create(0, &port);
+  GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+  // Register to wait for the child process to terminate.
+  status_zx = child_process_.wait_async(
+      port, kProcessKey, ZX_PROCESS_TERMINATED, 0);
+  GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+  // Register to wait for the socket to be readable or closed.
+  status_zx = stderr_socket_.wait_async(
+      port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0);
+  GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+  // Register to wait for an exception.
+  status_zx = exception_channel_.wait_async(
+      port, kExceptionKey, ZX_CHANNEL_READABLE, 0);
+  GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+  bool process_terminated = false;
+  bool socket_closed = false;
+  do {
+    zx_port_packet_t packet = {};
+    status_zx = port.wait(zx::time::infinite(), &packet);
+    GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+    if (packet.key == kExceptionKey) {
+      // Process encountered an exception. Kill it directly rather than
+      // letting other handlers process the event. We will get a kProcessKey
+      // event when the process actually terminates.
+      status_zx = child_process_.kill();
+      GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+    } else if (packet.key == kProcessKey) {
+      // Process terminated.
+      GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type));
+      GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_PROCESS_TERMINATED);
+      process_terminated = true;
+    } else if (packet.key == kSocketKey) {
+      GTEST_DEATH_TEST_CHECK_(ZX_PKT_IS_SIGNAL_ONE(packet.type));
+      if (packet.signal.observed & ZX_SOCKET_READABLE) {
+        // Read data from the socket.
+        constexpr size_t kBufferSize = 1024;
+        do {
+          size_t old_length = captured_stderr_.length();
+          size_t bytes_read = 0;
+          captured_stderr_.resize(old_length + kBufferSize);
+          status_zx = stderr_socket_.read(
+              0, &captured_stderr_.front() + old_length, kBufferSize,
+              &bytes_read);
+          captured_stderr_.resize(old_length + bytes_read);
+        } while (status_zx == ZX_OK);
+        if (status_zx == ZX_ERR_PEER_CLOSED) {
+          socket_closed = true;
+        } else {
+          GTEST_DEATH_TEST_CHECK_(status_zx == ZX_ERR_SHOULD_WAIT);
+          status_zx = stderr_socket_.wait_async(
+              port, kSocketKey, ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, 0);
+          GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+        }
+      } else {
+        GTEST_DEATH_TEST_CHECK_(packet.signal.observed & ZX_SOCKET_PEER_CLOSED);
+        socket_closed = true;
+      }
+    }
+  } while (!process_terminated && !socket_closed);
+
+  ReadAndInterpretStatusByte();
+
+  zx_info_process_v2_t buffer;
+  status_zx = child_process_.get_info(
+      ZX_INFO_PROCESS_V2, &buffer, sizeof(buffer), nullptr, nullptr);
+  GTEST_DEATH_TEST_CHECK_(status_zx == ZX_OK);
+
+  GTEST_DEATH_TEST_CHECK_(buffer.flags & ZX_INFO_PROCESS_FLAG_EXITED);
+  set_status(static_cast<int>(buffer.return_code));
+  return status();
+}
+
+// The AssumeRole process for a Fuchsia death test.  It creates a child
+// process with the same executable as the current process to run the
+// death test.  The child process is given the --gtest_filter and
+// --gtest_internal_run_death_test flags such that it knows to run the
+// current death test only.
+DeathTest::TestRole FuchsiaDeathTest::AssumeRole() {
+  const UnitTestImpl* const impl = GetUnitTestImpl();
+  const InternalRunDeathTestFlag* const flag =
+      impl->internal_run_death_test_flag();
+  const TestInfo* const info = impl->current_test_info();
+  const int death_test_index = info->result()->death_test_count();
+
+  if (flag != nullptr) {
+    // ParseInternalRunDeathTestFlag() has performed all the necessary
+    // processing.
+    set_write_fd(kFuchsiaReadPipeFd);
+    return EXECUTE_TEST;
+  }
+
+  // Flush the log buffers since the log streams are shared with the child.
+  FlushInfoLog();
+
+  // Build the child process command line.
+  const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ +
+                                  kFilterFlag + "=" + info->test_suite_name() +
+                                  "." + info->name();
+  const std::string internal_flag =
+      std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "="
+      + file_ + "|"
+      + StreamableToString(line_) + "|"
+      + StreamableToString(death_test_index);
+  Arguments args;
+  args.AddArguments(GetInjectableArgvs());
+  args.AddArgument(filter_flag.c_str());
+  args.AddArgument(internal_flag.c_str());
+
+  // Build the pipe for communication with the child.
+  zx_status_t status;
+  zx_handle_t child_pipe_handle;
+  int child_pipe_fd;
+  status = fdio_pipe_half(&child_pipe_fd, &child_pipe_handle);
+  GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+  set_read_fd(child_pipe_fd);
+
+  // Set the pipe handle for the child.
+  fdio_spawn_action_t spawn_actions[2] = {};
+  fdio_spawn_action_t* add_handle_action = &spawn_actions[0];
+  add_handle_action->action = FDIO_SPAWN_ACTION_ADD_HANDLE;
+  add_handle_action->h.id = PA_HND(PA_FD, kFuchsiaReadPipeFd);
+  add_handle_action->h.handle = child_pipe_handle;
+
+  // Create a socket pair will be used to receive the child process' stderr.
+  zx::socket stderr_producer_socket;
+  status =
+      zx::socket::create(0, &stderr_producer_socket, &stderr_socket_);
+  GTEST_DEATH_TEST_CHECK_(status >= 0);
+  int stderr_producer_fd = -1;
+  status =
+      fdio_fd_create(stderr_producer_socket.release(), &stderr_producer_fd);
+  GTEST_DEATH_TEST_CHECK_(status >= 0);
+
+  // Make the stderr socket nonblocking.
+  GTEST_DEATH_TEST_CHECK_(fcntl(stderr_producer_fd, F_SETFL, 0) == 0);
+
+  fdio_spawn_action_t* add_stderr_action = &spawn_actions[1];
+  add_stderr_action->action = FDIO_SPAWN_ACTION_CLONE_FD;
+  add_stderr_action->fd.local_fd = stderr_producer_fd;
+  add_stderr_action->fd.target_fd = STDERR_FILENO;
+
+  // Create a child job.
+  zx_handle_t child_job = ZX_HANDLE_INVALID;
+  status = zx_job_create(zx_job_default(), 0, & child_job);
+  GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+  zx_policy_basic_t policy;
+  policy.condition = ZX_POL_NEW_ANY;
+  policy.policy = ZX_POL_ACTION_ALLOW;
+  status = zx_job_set_policy(
+      child_job, ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, &policy, 1);
+  GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+
+  // Create an exception channel attached to the |child_job|, to allow
+  // us to suppress the system default exception handler from firing.
+  status =
+      zx_task_create_exception_channel(
+          child_job, 0, exception_channel_.reset_and_get_address());
+  GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+
+  // Spawn the child process.
+  status = fdio_spawn_etc(
+      child_job, FDIO_SPAWN_CLONE_ALL, args.Argv()[0], args.Argv(), nullptr,
+      2, spawn_actions, child_process_.reset_and_get_address(), nullptr);
+  GTEST_DEATH_TEST_CHECK_(status == ZX_OK);
+
+  set_spawned(true);
+  return OVERSEE_TEST;
+}
+
+std::string FuchsiaDeathTest::GetErrorLogs() {
+  return captured_stderr_;
+}
+
+#else  // We are neither on Windows, nor on Fuchsia.
+
+// ForkingDeathTest provides implementations for most of the abstract
+// methods of the DeathTest interface.  Only the AssumeRole method is
+// left undefined.
+class ForkingDeathTest : public DeathTestImpl {
+ public:
+  ForkingDeathTest(const char* statement, Matcher<const std::string&> matcher);
+
+  // All of these virtual functions are inherited from DeathTest.
+  int Wait() override;
+
+ protected:
+  void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; }
+
+ private:
+  // PID of child process during death test; 0 in the child process itself.
+  pid_t child_pid_;
+};
+
+// Constructs a ForkingDeathTest.
+ForkingDeathTest::ForkingDeathTest(const char* a_statement,
+                                   Matcher<const std::string&> matcher)
+    : DeathTestImpl(a_statement, std::move(matcher)), child_pid_(-1) {}
+
+// Waits for the child in a death test to exit, returning its exit
+// status, or 0 if no child process exists.  As a side effect, sets the
+// outcome data member.
+int ForkingDeathTest::Wait() {
+  if (!spawned())
+    return 0;
+
+  ReadAndInterpretStatusByte();
+
+  int status_value;
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0));
+  set_status(status_value);
+  return status_value;
+}
+
+// A concrete death test class that forks, then immediately runs the test
+// in the child process.
+class NoExecDeathTest : public ForkingDeathTest {
+ public:
+  NoExecDeathTest(const char* a_statement, Matcher<const std::string&> matcher)
+      : ForkingDeathTest(a_statement, std::move(matcher)) {}
+  TestRole AssumeRole() override;
+};
+
+// The AssumeRole process for a fork-and-run death test.  It implements a
+// straightforward fork, with a simple pipe to transmit the status byte.
+DeathTest::TestRole NoExecDeathTest::AssumeRole() {
+  const size_t thread_count = GetThreadCount();
+  if (thread_count != 1) {
+    GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count);
+  }
+
+  int pipe_fd[2];
+  GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1);
+
+  DeathTest::set_last_death_test_message("");
+  CaptureStderr();
+  // When we fork the process below, the log file buffers are copied, but the
+  // file descriptors are shared.  We flush all log files here so that closing
+  // the file descriptors in the child process doesn't throw off the
+  // synchronization between descriptors and buffers in the parent process.
+  // This is as close to the fork as possible to avoid a race condition in case
+  // there are multiple threads running before the death test, and another
+  // thread writes to the log file.
+  FlushInfoLog();
+
+  const pid_t child_pid = fork();
+  GTEST_DEATH_TEST_CHECK_(child_pid != -1);
+  set_child_pid(child_pid);
+  if (child_pid == 0) {
+    GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0]));
+    set_write_fd(pipe_fd[1]);
+    // Redirects all logging to stderr in the child process to prevent
+    // concurrent writes to the log files.  We capture stderr in the parent
+    // process and append the child process' output to a log.
+    LogToStderr();
+    // Event forwarding to the listeners of event listener API mush be shut
+    // down in death test subprocesses.
+    GetUnitTestImpl()->listeners()->SuppressEventForwarding();
+    g_in_fast_death_test_child = true;
+    return EXECUTE_TEST;
+  } else {
+    GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1]));
+    set_read_fd(pipe_fd[0]);
+    set_spawned(true);
+    return OVERSEE_TEST;
+  }
+}
+
+// A concrete death test class that forks and re-executes the main
+// program from the beginning, with command-line flags set that cause
+// only this specific death test to be run.
+class ExecDeathTest : public ForkingDeathTest {
+ public:
+  ExecDeathTest(const char* a_statement, Matcher<const std::string&> matcher,
+                const char* file, int line)
+      : ForkingDeathTest(a_statement, std::move(matcher)),
+        file_(file),
+        line_(line) {}
+  TestRole AssumeRole() override;
+
+ private:
+  static ::std::vector<std::string> GetArgvsForDeathTestChildProcess() {
+    ::std::vector<std::string> args = GetInjectableArgvs();
+#  if defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_)
+    ::std::vector<std::string> extra_args =
+        GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_();
+    args.insert(args.end(), extra_args.begin(), extra_args.end());
+#  endif  // defined(GTEST_EXTRA_DEATH_TEST_COMMAND_LINE_ARGS_)
+    return args;
+  }
+  // The name of the file in which the death test is located.
+  const char* const file_;
+  // The line number on which the death test is located.
+  const int line_;
+};
+
+// Utility class for accumulating command-line arguments.
+class Arguments {
+ public:
+  Arguments() { args_.push_back(nullptr); }
+
+  ~Arguments() {
+    for (std::vector<char*>::iterator i = args_.begin(); i != args_.end();
+         ++i) {
+      free(*i);
+    }
+  }
+  void AddArgument(const char* argument) {
+    args_.insert(args_.end() - 1, posix::StrDup(argument));
+  }
+
+  template <typename Str>
+  void AddArguments(const ::std::vector<Str>& arguments) {
+    for (typename ::std::vector<Str>::const_iterator i = arguments.begin();
+         i != arguments.end();
+         ++i) {
+      args_.insert(args_.end() - 1, posix::StrDup(i->c_str()));
+    }
+  }
+  char* const* Argv() {
+    return &args_[0];
+  }
+
+ private:
+  std::vector<char*> args_;
+};
+
+// A struct that encompasses the arguments to the child process of a
+// threadsafe-style death test process.
+struct ExecDeathTestArgs {
+  char* const* argv;  // Command-line arguments for the child's call to exec
+  int close_fd;       // File descriptor to close; the read end of a pipe
+};
+
+#  if GTEST_OS_QNX
+extern "C" char** environ;
+#  else  // GTEST_OS_QNX
+// The main function for a threadsafe-style death test child process.
+// This function is called in a clone()-ed process and thus must avoid
+// any potentially unsafe operations like malloc or libc functions.
+static int ExecDeathTestChildMain(void* child_arg) {
+  ExecDeathTestArgs* const args = static_cast<ExecDeathTestArgs*>(child_arg);
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd));
+
+  // We need to execute the test program in the same environment where
+  // it was originally invoked.  Therefore we change to the original
+  // working directory first.
+  const char* const original_dir =
+      UnitTest::GetInstance()->original_working_dir();
+  // We can safely call chdir() as it's a direct system call.
+  if (chdir(original_dir) != 0) {
+    DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " +
+                   GetLastErrnoDescription());
+    return EXIT_FAILURE;
+  }
+
+  // We can safely call execv() as it's almost a direct system call. We
+  // cannot use execvp() as it's a libc function and thus potentially
+  // unsafe.  Since execv() doesn't search the PATH, the user must
+  // invoke the test program via a valid path that contains at least
+  // one path separator.
+  execv(args->argv[0], args->argv);
+  DeathTestAbort(std::string("execv(") + args->argv[0] + ", ...) in " +
+                 original_dir + " failed: " +
+                 GetLastErrnoDescription());
+  return EXIT_FAILURE;
+}
+#  endif  // GTEST_OS_QNX
+
+#  if GTEST_HAS_CLONE
+// Two utility routines that together determine the direction the stack
+// grows.
+// This could be accomplished more elegantly by a single recursive
+// function, but we want to guard against the unlikely possibility of
+// a smart compiler optimizing the recursion away.
+//
+// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining
+// StackLowerThanAddress into StackGrowsDown, which then doesn't give
+// correct answer.
+static void StackLowerThanAddress(const void* ptr,
+                                  bool* result) GTEST_NO_INLINE_;
+// Make sure sanitizers do not tamper with the stack here.
+// Ideally, we want to use `__builtin_frame_address` instead of a local variable
+// address with sanitizer disabled, but it does not work when the
+// compiler optimizes the stack frame out, which happens on PowerPC targets.
+// HWAddressSanitizer add a random tag to the MSB of the local variable address,
+// making comparison result unpredictable.
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+static void StackLowerThanAddress(const void* ptr, bool* result) {
+  int dummy = 0;
+  *result = std::less<const void*>()(&dummy, ptr);
+}
+
+// Make sure AddressSanitizer does not tamper with the stack here.
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+static bool StackGrowsDown() {
+  int dummy = 0;
+  bool result;
+  StackLowerThanAddress(&dummy, &result);
+  return result;
+}
+#  endif  // GTEST_HAS_CLONE
+
+// Spawns a child process with the same executable as the current process in
+// a thread-safe manner and instructs it to run the death test.  The
+// implementation uses fork(2) + exec.  On systems where clone(2) is
+// available, it is used instead, being slightly more thread-safe.  On QNX,
+// fork supports only single-threaded environments, so this function uses
+// spawn(2) there instead.  The function dies with an error message if
+// anything goes wrong.
+static pid_t ExecDeathTestSpawnChild(char* const* argv, int close_fd) {
+  ExecDeathTestArgs args = { argv, close_fd };
+  pid_t child_pid = -1;
+
+#  if GTEST_OS_QNX
+  // Obtains the current directory and sets it to be closed in the child
+  // process.
+  const int cwd_fd = open(".", O_RDONLY);
+  GTEST_DEATH_TEST_CHECK_(cwd_fd != -1);
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(cwd_fd, F_SETFD, FD_CLOEXEC));
+  // We need to execute the test program in the same environment where
+  // it was originally invoked.  Therefore we change to the original
+  // working directory first.
+  const char* const original_dir =
+      UnitTest::GetInstance()->original_working_dir();
+  // We can safely call chdir() as it's a direct system call.
+  if (chdir(original_dir) != 0) {
+    DeathTestAbort(std::string("chdir(\"") + original_dir + "\") failed: " +
+                   GetLastErrnoDescription());
+    return EXIT_FAILURE;
+  }
+
+  int fd_flags;
+  // Set close_fd to be closed after spawn.
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(fd_flags = fcntl(close_fd, F_GETFD));
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(fcntl(close_fd, F_SETFD,
+                                        fd_flags | FD_CLOEXEC));
+  struct inheritance inherit = {0};
+  // spawn is a system call.
+  child_pid = spawn(args.argv[0], 0, nullptr, &inherit, args.argv, environ);
+  // Restores the current working directory.
+  GTEST_DEATH_TEST_CHECK_(fchdir(cwd_fd) != -1);
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(close(cwd_fd));
+
+#  else   // GTEST_OS_QNX
+#   if GTEST_OS_LINUX
+  // When a SIGPROF signal is received while fork() or clone() are executing,
+  // the process may hang. To avoid this, we ignore SIGPROF here and re-enable
+  // it after the call to fork()/clone() is complete.
+  struct sigaction saved_sigprof_action;
+  struct sigaction ignore_sigprof_action;
+  memset(&ignore_sigprof_action, 0, sizeof(ignore_sigprof_action));
+  sigemptyset(&ignore_sigprof_action.sa_mask);
+  ignore_sigprof_action.sa_handler = SIG_IGN;
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(sigaction(
+      SIGPROF, &ignore_sigprof_action, &saved_sigprof_action));
+#   endif  // GTEST_OS_LINUX
+
+#   if GTEST_HAS_CLONE
+  const bool use_fork = GTEST_FLAG(death_test_use_fork);
+
+  if (!use_fork) {
+    static const bool stack_grows_down = StackGrowsDown();
+    const auto stack_size = static_cast<size_t>(getpagesize() * 2);
+    // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead.
+    void* const stack = mmap(nullptr, stack_size, PROT_READ | PROT_WRITE,
+                             MAP_ANON | MAP_PRIVATE, -1, 0);
+    GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED);
+
+    // Maximum stack alignment in bytes:  For a downward-growing stack, this
+    // amount is subtracted from size of the stack space to get an address
+    // that is within the stack space and is aligned on all systems we care
+    // about.  As far as I know there is no ABI with stack alignment greater
+    // than 64.  We assume stack and stack_size already have alignment of
+    // kMaxStackAlignment.
+    const size_t kMaxStackAlignment = 64;
+    void* const stack_top =
+        static_cast<char*>(stack) +
+            (stack_grows_down ? stack_size - kMaxStackAlignment : 0);
+    GTEST_DEATH_TEST_CHECK_(
+        static_cast<size_t>(stack_size) > kMaxStackAlignment &&
+        reinterpret_cast<uintptr_t>(stack_top) % kMaxStackAlignment == 0);
+
+    child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args);
+
+    GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1);
+  }
+#   else
+  const bool use_fork = true;
+#   endif  // GTEST_HAS_CLONE
+
+  if (use_fork && (child_pid = fork()) == 0) {
+      ExecDeathTestChildMain(&args);
+      _exit(0);
+  }
+#  endif  // GTEST_OS_QNX
+#  if GTEST_OS_LINUX
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(
+      sigaction(SIGPROF, &saved_sigprof_action, nullptr));
+#  endif  // GTEST_OS_LINUX
+
+  GTEST_DEATH_TEST_CHECK_(child_pid != -1);
+  return child_pid;
+}
+
+// The AssumeRole process for a fork-and-exec death test.  It re-executes the
+// main program from the beginning, setting the --gtest_filter
+// and --gtest_internal_run_death_test flags to cause only the current
+// death test to be re-run.
+DeathTest::TestRole ExecDeathTest::AssumeRole() {
+  const UnitTestImpl* const impl = GetUnitTestImpl();
+  const InternalRunDeathTestFlag* const flag =
+      impl->internal_run_death_test_flag();
+  const TestInfo* const info = impl->current_test_info();
+  const int death_test_index = info->result()->death_test_count();
+
+  if (flag != nullptr) {
+    set_write_fd(flag->write_fd());
+    return EXECUTE_TEST;
+  }
+
+  int pipe_fd[2];
+  GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1);
+  // Clear the close-on-exec flag on the write end of the pipe, lest
+  // it be closed when the child process does an exec:
+  GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1);
+
+  const std::string filter_flag = std::string("--") + GTEST_FLAG_PREFIX_ +
+                                  kFilterFlag + "=" + info->test_suite_name() +
+                                  "." + info->name();
+  const std::string internal_flag =
+      std::string("--") + GTEST_FLAG_PREFIX_ + kInternalRunDeathTestFlag + "="
+      + file_ + "|" + StreamableToString(line_) + "|"
+      + StreamableToString(death_test_index) + "|"
+      + StreamableToString(pipe_fd[1]);
+  Arguments args;
+  args.AddArguments(GetArgvsForDeathTestChildProcess());
+  args.AddArgument(filter_flag.c_str());
+  args.AddArgument(internal_flag.c_str());
+
+  DeathTest::set_last_death_test_message("");
+
+  CaptureStderr();
+  // See the comment in NoExecDeathTest::AssumeRole for why the next line
+  // is necessary.
+  FlushInfoLog();
+
+  const pid_t child_pid = ExecDeathTestSpawnChild(args.Argv(), pipe_fd[0]);
+  GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1]));
+  set_child_pid(child_pid);
+  set_read_fd(pipe_fd[0]);
+  set_spawned(true);
+  return OVERSEE_TEST;
+}
+
+# endif  // !GTEST_OS_WINDOWS
+
+// Creates a concrete DeathTest-derived class that depends on the
+// --gtest_death_test_style flag, and sets the pointer pointed to
+// by the "test" argument to its address.  If the test should be
+// skipped, sets that pointer to NULL.  Returns true, unless the
+// flag is set to an invalid value.
+bool DefaultDeathTestFactory::Create(const char* statement,
+                                     Matcher<const std::string&> matcher,
+                                     const char* file, int line,
+                                     DeathTest** test) {
+  UnitTestImpl* const impl = GetUnitTestImpl();
+  const InternalRunDeathTestFlag* const flag =
+      impl->internal_run_death_test_flag();
+  const int death_test_index = impl->current_test_info()
+      ->increment_death_test_count();
+
+  if (flag != nullptr) {
+    if (death_test_index > flag->index()) {
+      DeathTest::set_last_death_test_message(
+          "Death test count (" + StreamableToString(death_test_index)
+          + ") somehow exceeded expected maximum ("
+          + StreamableToString(flag->index()) + ")");
+      return false;
+    }
+
+    if (!(flag->file() == file && flag->line() == line &&
+          flag->index() == death_test_index)) {
+      *test = nullptr;
+      return true;
+    }
+  }
+
+# if GTEST_OS_WINDOWS
+
+  if (GTEST_FLAG(death_test_style) == "threadsafe" ||
+      GTEST_FLAG(death_test_style) == "fast") {
+    *test = new WindowsDeathTest(statement, std::move(matcher), file, line);
+  }
+
+# elif GTEST_OS_FUCHSIA
+
+  if (GTEST_FLAG(death_test_style) == "threadsafe" ||
+      GTEST_FLAG(death_test_style) == "fast") {
+    *test = new FuchsiaDeathTest(statement, std::move(matcher), file, line);
+  }
+
+# else
+
+  if (GTEST_FLAG(death_test_style) == "threadsafe") {
+    *test = new ExecDeathTest(statement, std::move(matcher), file, line);
+  } else if (GTEST_FLAG(death_test_style) == "fast") {
+    *test = new NoExecDeathTest(statement, std::move(matcher));
+  }
+
+# endif  // GTEST_OS_WINDOWS
+
+  else {  // NOLINT - this is more readable than unbalanced brackets inside #if.
+    DeathTest::set_last_death_test_message(
+        "Unknown death test style \"" + GTEST_FLAG(death_test_style)
+        + "\" encountered");
+    return false;
+  }
+
+  return true;
+}
+
+# if GTEST_OS_WINDOWS
+// Recreates the pipe and event handles from the provided parameters,
+// signals the event, and returns a file descriptor wrapped around the pipe
+// handle. This function is called in the child process only.
+static int GetStatusFileDescriptor(unsigned int parent_process_id,
+                            size_t write_handle_as_size_t,
+                            size_t event_handle_as_size_t) {
+  AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE,
+                                                   FALSE,  // Non-inheritable.
+                                                   parent_process_id));
+  if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) {
+    DeathTestAbort("Unable to open parent process " +
+                   StreamableToString(parent_process_id));
+  }
+
+  GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t));
+
+  const HANDLE write_handle =
+      reinterpret_cast<HANDLE>(write_handle_as_size_t);
+  HANDLE dup_write_handle;
+
+  // The newly initialized handle is accessible only in the parent
+  // process. To obtain one accessible within the child, we need to use
+  // DuplicateHandle.
+  if (!::DuplicateHandle(parent_process_handle.Get(), write_handle,
+                         ::GetCurrentProcess(), &dup_write_handle,
+                         0x0,    // Requested privileges ignored since
+                                 // DUPLICATE_SAME_ACCESS is used.
+                         FALSE,  // Request non-inheritable handler.
+                         DUPLICATE_SAME_ACCESS)) {
+    DeathTestAbort("Unable to duplicate the pipe handle " +
+                   StreamableToString(write_handle_as_size_t) +
+                   " from the parent process " +
+                   StreamableToString(parent_process_id));
+  }
+
+  const HANDLE event_handle = reinterpret_cast<HANDLE>(event_handle_as_size_t);
+  HANDLE dup_event_handle;
+
+  if (!::DuplicateHandle(parent_process_handle.Get(), event_handle,
+                         ::GetCurrentProcess(), &dup_event_handle,
+                         0x0,
+                         FALSE,
+                         DUPLICATE_SAME_ACCESS)) {
+    DeathTestAbort("Unable to duplicate the event handle " +
+                   StreamableToString(event_handle_as_size_t) +
+                   " from the parent process " +
+                   StreamableToString(parent_process_id));
+  }
+
+  const int write_fd =
+      ::_open_osfhandle(reinterpret_cast<intptr_t>(dup_write_handle), O_APPEND);
+  if (write_fd == -1) {
+    DeathTestAbort("Unable to convert pipe handle " +
+                   StreamableToString(write_handle_as_size_t) +
+                   " to a file descriptor");
+  }
+
+  // Signals the parent that the write end of the pipe has been acquired
+  // so the parent can release its own write end.
+  ::SetEvent(dup_event_handle);
+
+  return write_fd;
+}
+# endif  // GTEST_OS_WINDOWS
+
+// Returns a newly created InternalRunDeathTestFlag object with fields
+// initialized from the GTEST_FLAG(internal_run_death_test) flag if
+// the flag is specified; otherwise returns NULL.
+InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() {
+  if (GTEST_FLAG(internal_run_death_test) == "") return nullptr;
+
+  // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we
+  // can use it here.
+  int line = -1;
+  int index = -1;
+  ::std::vector< ::std::string> fields;
+  SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields);
+  int write_fd = -1;
+
+# if GTEST_OS_WINDOWS
+
+  unsigned int parent_process_id = 0;
+  size_t write_handle_as_size_t = 0;
+  size_t event_handle_as_size_t = 0;
+
+  if (fields.size() != 6
+      || !ParseNaturalNumber(fields[1], &line)
+      || !ParseNaturalNumber(fields[2], &index)
+      || !ParseNaturalNumber(fields[3], &parent_process_id)
+      || !ParseNaturalNumber(fields[4], &write_handle_as_size_t)
+      || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) {
+    DeathTestAbort("Bad --gtest_internal_run_death_test flag: " +
+                   GTEST_FLAG(internal_run_death_test));
+  }
+  write_fd = GetStatusFileDescriptor(parent_process_id,
+                                     write_handle_as_size_t,
+                                     event_handle_as_size_t);
+
+# elif GTEST_OS_FUCHSIA
+
+  if (fields.size() != 3
+      || !ParseNaturalNumber(fields[1], &line)
+      || !ParseNaturalNumber(fields[2], &index)) {
+    DeathTestAbort("Bad --gtest_internal_run_death_test flag: "
+        + GTEST_FLAG(internal_run_death_test));
+  }
+
+# else
+
+  if (fields.size() != 4
+      || !ParseNaturalNumber(fields[1], &line)
+      || !ParseNaturalNumber(fields[2], &index)
+      || !ParseNaturalNumber(fields[3], &write_fd)) {
+    DeathTestAbort("Bad --gtest_internal_run_death_test flag: "
+        + GTEST_FLAG(internal_run_death_test));
+  }
+
+# endif  // GTEST_OS_WINDOWS
+
+  return new InternalRunDeathTestFlag(fields[0], line, index, write_fd);
+}
+
+}  // namespace internal
+
+#endif  // GTEST_HAS_DEATH_TEST
+
+}  // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#include <stdlib.h>
+
+#if GTEST_OS_WINDOWS_MOBILE
+# include <windows.h>
+#elif GTEST_OS_WINDOWS
+# include <direct.h>
+# include <io.h>
+#else
+# include <limits.h>
+# include <climits>  // Some Linux distributions define PATH_MAX here.
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+
+#if GTEST_OS_WINDOWS
+# define GTEST_PATH_MAX_ _MAX_PATH
+#elif defined(PATH_MAX)
+# define GTEST_PATH_MAX_ PATH_MAX
+#elif defined(_XOPEN_PATH_MAX)
+# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX
+#else
+# define GTEST_PATH_MAX_ _POSIX_PATH_MAX
+#endif  // GTEST_OS_WINDOWS
+
+namespace testing {
+namespace internal {
+
+#if GTEST_OS_WINDOWS
+// On Windows, '\\' is the standard path separator, but many tools and the
+// Windows API also accept '/' as an alternate path separator. Unless otherwise
+// noted, a file path can contain either kind of path separators, or a mixture
+// of them.
+const char kPathSeparator = '\\';
+const char kAlternatePathSeparator = '/';
+const char kAlternatePathSeparatorString[] = "/";
+# if GTEST_OS_WINDOWS_MOBILE
+// Windows CE doesn't have a current directory. You should not use
+// the current directory in tests on Windows CE, but this at least
+// provides a reasonable fallback.
+const char kCurrentDirectoryString[] = "\\";
+// Windows CE doesn't define INVALID_FILE_ATTRIBUTES
+const DWORD kInvalidFileAttributes = 0xffffffff;
+# else
+const char kCurrentDirectoryString[] = ".\\";
+# endif  // GTEST_OS_WINDOWS_MOBILE
+#else
+const char kPathSeparator = '/';
+const char kCurrentDirectoryString[] = "./";
+#endif  // GTEST_OS_WINDOWS
+
+// Returns whether the given character is a valid path separator.
+static bool IsPathSeparator(char c) {
+#if GTEST_HAS_ALT_PATH_SEP_
+  return (c == kPathSeparator) || (c == kAlternatePathSeparator);
+#else
+  return c == kPathSeparator;
+#endif
+}
+
+// Returns the current working directory, or "" if unsuccessful.
+FilePath FilePath::GetCurrentDir() {
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE ||         \
+    GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_ESP32 || \
+    GTEST_OS_XTENSA
+  // These platforms do not have a current directory, so we just return
+  // something reasonable.
+  return FilePath(kCurrentDirectoryString);
+#elif GTEST_OS_WINDOWS
+  char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
+  return FilePath(_getcwd(cwd, sizeof(cwd)) == nullptr ? "" : cwd);
+#else
+  char cwd[GTEST_PATH_MAX_ + 1] = { '\0' };
+  char* result = getcwd(cwd, sizeof(cwd));
+# if GTEST_OS_NACL
+  // getcwd will likely fail in NaCl due to the sandbox, so return something
+  // reasonable. The user may have provided a shim implementation for getcwd,
+  // however, so fallback only when failure is detected.
+  return FilePath(result == nullptr ? kCurrentDirectoryString : cwd);
+# endif  // GTEST_OS_NACL
+  return FilePath(result == nullptr ? "" : cwd);
+#endif  // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Returns a copy of the FilePath with the case-insensitive extension removed.
+// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
+// FilePath("dir/file"). If a case-insensitive extension is not
+// found, returns a copy of the original FilePath.
+FilePath FilePath::RemoveExtension(const char* extension) const {
+  const std::string dot_extension = std::string(".") + extension;
+  if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) {
+    return FilePath(pathname_.substr(
+        0, pathname_.length() - dot_extension.length()));
+  }
+  return *this;
+}
+
+// Returns a pointer to the last occurrence of a valid path separator in
+// the FilePath. On Windows, for example, both '/' and '\' are valid path
+// separators. Returns NULL if no path separator was found.
+const char* FilePath::FindLastPathSeparator() const {
+  const char* const last_sep = strrchr(c_str(), kPathSeparator);
+#if GTEST_HAS_ALT_PATH_SEP_
+  const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator);
+  // Comparing two pointers of which only one is NULL is undefined.
+  if (last_alt_sep != nullptr &&
+      (last_sep == nullptr || last_alt_sep > last_sep)) {
+    return last_alt_sep;
+  }
+#endif
+  return last_sep;
+}
+
+// Returns a copy of the FilePath with the directory part removed.
+// Example: FilePath("path/to/file").RemoveDirectoryName() returns
+// FilePath("file"). If there is no directory part ("just_a_file"), it returns
+// the FilePath unmodified. If there is no file part ("just_a_dir/") it
+// returns an empty FilePath ("").
+// On Windows platform, '\' is the path separator, otherwise it is '/'.
+FilePath FilePath::RemoveDirectoryName() const {
+  const char* const last_sep = FindLastPathSeparator();
+  return last_sep ? FilePath(last_sep + 1) : *this;
+}
+
+// RemoveFileName returns the directory path with the filename removed.
+// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
+// If the FilePath is "a_file" or "/a_file", RemoveFileName returns
+// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
+// not have a file, like "just/a/dir/", it returns the FilePath unmodified.
+// On Windows platform, '\' is the path separator, otherwise it is '/'.
+FilePath FilePath::RemoveFileName() const {
+  const char* const last_sep = FindLastPathSeparator();
+  std::string dir;
+  if (last_sep) {
+    dir = std::string(c_str(), static_cast<size_t>(last_sep + 1 - c_str()));
+  } else {
+    dir = kCurrentDirectoryString;
+  }
+  return FilePath(dir);
+}
+
+// Helper functions for naming files in a directory for xml output.
+
+// Given directory = "dir", base_name = "test", number = 0,
+// extension = "xml", returns "dir/test.xml". If number is greater
+// than zero (e.g., 12), returns "dir/test_12.xml".
+// On Windows platform, uses \ as the separator rather than /.
+FilePath FilePath::MakeFileName(const FilePath& directory,
+                                const FilePath& base_name,
+                                int number,
+                                const char* extension) {
+  std::string file;
+  if (number == 0) {
+    file = base_name.string() + "." + extension;
+  } else {
+    file = base_name.string() + "_" + StreamableToString(number)
+        + "." + extension;
+  }
+  return ConcatPaths(directory, FilePath(file));
+}
+
+// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml".
+// On Windows, uses \ as the separator rather than /.
+FilePath FilePath::ConcatPaths(const FilePath& directory,
+                               const FilePath& relative_path) {
+  if (directory.IsEmpty())
+    return relative_path;
+  const FilePath dir(directory.RemoveTrailingPathSeparator());
+  return FilePath(dir.string() + kPathSeparator + relative_path.string());
+}
+
+// Returns true if pathname describes something findable in the file-system,
+// either a file, directory, or whatever.
+bool FilePath::FileOrDirectoryExists() const {
+#if GTEST_OS_WINDOWS_MOBILE
+  LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str());
+  const DWORD attributes = GetFileAttributes(unicode);
+  delete [] unicode;
+  return attributes != kInvalidFileAttributes;
+#else
+  posix::StatStruct file_stat;
+  return posix::Stat(pathname_.c_str(), &file_stat) == 0;
+#endif  // GTEST_OS_WINDOWS_MOBILE
+}
+
+// Returns true if pathname describes a directory in the file-system
+// that exists.
+bool FilePath::DirectoryExists() const {
+  bool result = false;
+#if GTEST_OS_WINDOWS
+  // Don't strip off trailing separator if path is a root directory on
+  // Windows (like "C:\\").
+  const FilePath& path(IsRootDirectory() ? *this :
+                                           RemoveTrailingPathSeparator());
+#else
+  const FilePath& path(*this);
+#endif
+
+#if GTEST_OS_WINDOWS_MOBILE
+  LPCWSTR unicode = String::AnsiToUtf16(path.c_str());
+  const DWORD attributes = GetFileAttributes(unicode);
+  delete [] unicode;
+  if ((attributes != kInvalidFileAttributes) &&
+      (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+    result = true;
+  }
+#else
+  posix::StatStruct file_stat;
+  result = posix::Stat(path.c_str(), &file_stat) == 0 &&
+      posix::IsDir(file_stat);
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+  return result;
+}
+
+// Returns true if pathname describes a root directory. (Windows has one
+// root directory per disk drive.)
+bool FilePath::IsRootDirectory() const {
+#if GTEST_OS_WINDOWS
+  return pathname_.length() == 3 && IsAbsolutePath();
+#else
+  return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]);
+#endif
+}
+
+// Returns true if pathname describes an absolute path.
+bool FilePath::IsAbsolutePath() const {
+  const char* const name = pathname_.c_str();
+#if GTEST_OS_WINDOWS
+  return pathname_.length() >= 3 &&
+     ((name[0] >= 'a' && name[0] <= 'z') ||
+      (name[0] >= 'A' && name[0] <= 'Z')) &&
+     name[1] == ':' &&
+     IsPathSeparator(name[2]);
+#else
+  return IsPathSeparator(name[0]);
+#endif
+}
+
+// Returns a pathname for a file that does not currently exist. The pathname
+// will be directory/base_name.extension or
+// directory/base_name_<number>.extension if directory/base_name.extension
+// already exists. The number will be incremented until a pathname is found
+// that does not already exist.
+// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
+// There could be a race condition if two or more processes are calling this
+// function at the same time -- they could both pick the same filename.
+FilePath FilePath::GenerateUniqueFileName(const FilePath& directory,
+                                          const FilePath& base_name,
+                                          const char* extension) {
+  FilePath full_pathname;
+  int number = 0;
+  do {
+    full_pathname.Set(MakeFileName(directory, base_name, number++, extension));
+  } while (full_pathname.FileOrDirectoryExists());
+  return full_pathname;
+}
+
+// Returns true if FilePath ends with a path separator, which indicates that
+// it is intended to represent a directory. Returns false otherwise.
+// This does NOT check that a directory (or file) actually exists.
+bool FilePath::IsDirectory() const {
+  return !pathname_.empty() &&
+         IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]);
+}
+
+// Create directories so that path exists. Returns true if successful or if
+// the directories already exist; returns false if unable to create directories
+// for any reason.
+bool FilePath::CreateDirectoriesRecursively() const {
+  if (!this->IsDirectory()) {
+    return false;
+  }
+
+  if (pathname_.length() == 0 || this->DirectoryExists()) {
+    return true;
+  }
+
+  const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName());
+  return parent.CreateDirectoriesRecursively() && this->CreateFolder();
+}
+
+// Create the directory so that path exists. Returns true if successful or
+// if the directory already exists; returns false if unable to create the
+// directory for any reason, including if the parent directory does not
+// exist. Not named "CreateDirectory" because that's a macro on Windows.
+bool FilePath::CreateFolder() const {
+#if GTEST_OS_WINDOWS_MOBILE
+  FilePath removed_sep(this->RemoveTrailingPathSeparator());
+  LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str());
+  int result = CreateDirectory(unicode, nullptr) ? 0 : -1;
+  delete [] unicode;
+#elif GTEST_OS_WINDOWS
+  int result = _mkdir(pathname_.c_str());
+#elif GTEST_OS_ESP8266 || GTEST_OS_XTENSA
+  // do nothing
+  int result = 0;
+#else
+  int result = mkdir(pathname_.c_str(), 0777);
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+  if (result == -1) {
+    return this->DirectoryExists();  // An error is OK if the directory exists.
+  }
+  return true;  // No error.
+}
+
+// If input name has a trailing separator character, remove it and return the
+// name, otherwise return the name string unmodified.
+// On Windows platform, uses \ as the separator, other platforms use /.
+FilePath FilePath::RemoveTrailingPathSeparator() const {
+  return IsDirectory()
+      ? FilePath(pathname_.substr(0, pathname_.length() - 1))
+      : *this;
+}
+
+// Removes any redundant separators that might be in the pathname.
+// For example, "bar///foo" becomes "bar/foo". Does not eliminate other
+// redundancies that might be in a pathname involving "." or "..".
+void FilePath::Normalize() {
+  auto out = pathname_.begin();
+
+  for (const char character : pathname_) {
+    if (!IsPathSeparator(character)) {
+      *(out++) = character;
+    } else if (out == pathname_.begin() || *std::prev(out) != kPathSeparator) {
+      *(out++) = kPathSeparator;
+    } else {
+      continue;
+    }
+  }
+
+  pathname_.erase(out, pathname_.end());
+}
+
+}  // namespace internal
+}  // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This file implements just enough of the matcher interface to allow
+// EXPECT_DEATH and friends to accept a matcher argument.
+
+
+#include <string>
+
+namespace testing {
+
+// Constructs a matcher that matches a const std::string& whose value is
+// equal to s.
+Matcher<const std::string&>::Matcher(const std::string& s) { *this = Eq(s); }
+
+// Constructs a matcher that matches a const std::string& whose value is
+// equal to s.
+Matcher<const std::string&>::Matcher(const char* s) {
+  *this = Eq(std::string(s));
+}
+
+// Constructs a matcher that matches a std::string whose value is equal to
+// s.
+Matcher<std::string>::Matcher(const std::string& s) { *this = Eq(s); }
+
+// Constructs a matcher that matches a std::string whose value is equal to
+// s.
+Matcher<std::string>::Matcher(const char* s) { *this = Eq(std::string(s)); }
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+// Constructs a matcher that matches a const StringView& whose value is
+// equal to s.
+Matcher<const internal::StringView&>::Matcher(const std::string& s) {
+  *this = Eq(s);
+}
+
+// Constructs a matcher that matches a const StringView& whose value is
+// equal to s.
+Matcher<const internal::StringView&>::Matcher(const char* s) {
+  *this = Eq(std::string(s));
+}
+
+// Constructs a matcher that matches a const StringView& whose value is
+// equal to s.
+Matcher<const internal::StringView&>::Matcher(internal::StringView s) {
+  *this = Eq(std::string(s));
+}
+
+// Constructs a matcher that matches a StringView whose value is equal to
+// s.
+Matcher<internal::StringView>::Matcher(const std::string& s) { *this = Eq(s); }
+
+// Constructs a matcher that matches a StringView whose value is equal to
+// s.
+Matcher<internal::StringView>::Matcher(const char* s) {
+  *this = Eq(std::string(s));
+}
+
+// Constructs a matcher that matches a StringView whose value is equal to
+// s.
+Matcher<internal::StringView>::Matcher(internal::StringView s) {
+  *this = Eq(std::string(s));
+}
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+}  // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cstdint>
+#include <fstream>
+#include <memory>
+
+#if GTEST_OS_WINDOWS
+# include <windows.h>
+# include <io.h>
+# include <sys/stat.h>
+# include <map>  // Used in ThreadLocal.
+# ifdef _MSC_VER
+#  include <crtdbg.h>
+# endif  // _MSC_VER
+#else
+# include <unistd.h>
+#endif  // GTEST_OS_WINDOWS
+
+#if GTEST_OS_MAC
+# include <mach/mach_init.h>
+# include <mach/task.h>
+# include <mach/vm_map.h>
+#endif  // GTEST_OS_MAC
+
+#if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \
+    GTEST_OS_NETBSD || GTEST_OS_OPENBSD
+# include <sys/sysctl.h>
+# if GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD
+#  include <sys/user.h>
+# endif
+#endif
+
+#if GTEST_OS_QNX
+# include <devctl.h>
+# include <fcntl.h>
+# include <sys/procfs.h>
+#endif  // GTEST_OS_QNX
+
+#if GTEST_OS_AIX
+# include <procinfo.h>
+# include <sys/types.h>
+#endif  // GTEST_OS_AIX
+
+#if GTEST_OS_FUCHSIA
+# include <zircon/process.h>
+# include <zircon/syscalls.h>
+#endif  // GTEST_OS_FUCHSIA
+
+
+namespace testing {
+namespace internal {
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+// MSVC and C++Builder do not provide a definition of STDERR_FILENO.
+const int kStdOutFileno = 1;
+const int kStdErrFileno = 2;
+#else
+const int kStdOutFileno = STDOUT_FILENO;
+const int kStdErrFileno = STDERR_FILENO;
+#endif  // _MSC_VER
+
+#if GTEST_OS_LINUX
+
+namespace {
+template <typename T>
+T ReadProcFileField(const std::string& filename, int field) {
+  std::string dummy;
+  std::ifstream file(filename.c_str());
+  while (field-- > 0) {
+    file >> dummy;
+  }
+  T output = 0;
+  file >> output;
+  return output;
+}
+}  // namespace
+
+// Returns the number of active threads, or 0 when there is an error.
+size_t GetThreadCount() {
+  const std::string filename =
+      (Message() << "/proc/" << getpid() << "/stat").GetString();
+  return ReadProcFileField<size_t>(filename, 19);
+}
+
+#elif GTEST_OS_MAC
+
+size_t GetThreadCount() {
+  const task_t task = mach_task_self();
+  mach_msg_type_number_t thread_count;
+  thread_act_array_t thread_list;
+  const kern_return_t status = task_threads(task, &thread_list, &thread_count);
+  if (status == KERN_SUCCESS) {
+    // task_threads allocates resources in thread_list and we need to free them
+    // to avoid leaks.
+    vm_deallocate(task,
+                  reinterpret_cast<vm_address_t>(thread_list),
+                  sizeof(thread_t) * thread_count);
+    return static_cast<size_t>(thread_count);
+  } else {
+    return 0;
+  }
+}
+
+#elif GTEST_OS_DRAGONFLY || GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD || \
+      GTEST_OS_NETBSD
+
+#if GTEST_OS_NETBSD
+#undef KERN_PROC
+#define KERN_PROC KERN_PROC2
+#define kinfo_proc kinfo_proc2
+#endif
+
+#if GTEST_OS_DRAGONFLY
+#define KP_NLWP(kp) (kp.kp_nthreads)
+#elif GTEST_OS_FREEBSD || GTEST_OS_GNU_KFREEBSD
+#define KP_NLWP(kp) (kp.ki_numthreads)
+#elif GTEST_OS_NETBSD
+#define KP_NLWP(kp) (kp.p_nlwps)
+#endif
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+size_t GetThreadCount() {
+  int mib[] = {
+    CTL_KERN,
+    KERN_PROC,
+    KERN_PROC_PID,
+    getpid(),
+#if GTEST_OS_NETBSD
+    sizeof(struct kinfo_proc),
+    1,
+#endif
+  };
+  u_int miblen = sizeof(mib) / sizeof(mib[0]);
+  struct kinfo_proc info;
+  size_t size = sizeof(info);
+  if (sysctl(mib, miblen, &info, &size, NULL, 0)) {
+    return 0;
+  }
+  return static_cast<size_t>(KP_NLWP(info));
+}
+#elif GTEST_OS_OPENBSD
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+size_t GetThreadCount() {
+  int mib[] = {
+    CTL_KERN,
+    KERN_PROC,
+    KERN_PROC_PID | KERN_PROC_SHOW_THREADS,
+    getpid(),
+    sizeof(struct kinfo_proc),
+    0,
+  };
+  u_int miblen = sizeof(mib) / sizeof(mib[0]);
+
+  // get number of structs
+  size_t size;
+  if (sysctl(mib, miblen, NULL, &size, NULL, 0)) {
+    return 0;
+  }
+
+  mib[5] = static_cast<int>(size / static_cast<size_t>(mib[4]));
+
+  // populate array of structs
+  struct kinfo_proc info[mib[5]];
+  if (sysctl(mib, miblen, &info, &size, NULL, 0)) {
+    return 0;
+  }
+
+  // exclude empty members
+  size_t nthreads = 0;
+  for (size_t i = 0; i < size / static_cast<size_t>(mib[4]); i++) {
+    if (info[i].p_tid != -1)
+      nthreads++;
+  }
+  return nthreads;
+}
+
+#elif GTEST_OS_QNX
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+size_t GetThreadCount() {
+  const int fd = open("/proc/self/as", O_RDONLY);
+  if (fd < 0) {
+    return 0;
+  }
+  procfs_info process_info;
+  const int status =
+      devctl(fd, DCMD_PROC_INFO, &process_info, sizeof(process_info), nullptr);
+  close(fd);
+  if (status == EOK) {
+    return static_cast<size_t>(process_info.num_threads);
+  } else {
+    return 0;
+  }
+}
+
+#elif GTEST_OS_AIX
+
+size_t GetThreadCount() {
+  struct procentry64 entry;
+  pid_t pid = getpid();
+  int status = getprocs64(&entry, sizeof(entry), nullptr, 0, &pid, 1);
+  if (status == 1) {
+    return entry.pi_thcount;
+  } else {
+    return 0;
+  }
+}
+
+#elif GTEST_OS_FUCHSIA
+
+size_t GetThreadCount() {
+  int dummy_buffer;
+  size_t avail;
+  zx_status_t status = zx_object_get_info(
+      zx_process_self(),
+      ZX_INFO_PROCESS_THREADS,
+      &dummy_buffer,
+      0,
+      nullptr,
+      &avail);
+  if (status == ZX_OK) {
+    return avail;
+  } else {
+    return 0;
+  }
+}
+
+#else
+
+size_t GetThreadCount() {
+  // There's no portable way to detect the number of threads, so we just
+  // return 0 to indicate that we cannot detect it.
+  return 0;
+}
+
+#endif  // GTEST_OS_LINUX
+
+#if GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS
+
+void SleepMilliseconds(int n) {
+  ::Sleep(static_cast<DWORD>(n));
+}
+
+AutoHandle::AutoHandle()
+    : handle_(INVALID_HANDLE_VALUE) {}
+
+AutoHandle::AutoHandle(Handle handle)
+    : handle_(handle) {}
+
+AutoHandle::~AutoHandle() {
+  Reset();
+}
+
+AutoHandle::Handle AutoHandle::Get() const {
+  return handle_;
+}
+
+void AutoHandle::Reset() {
+  Reset(INVALID_HANDLE_VALUE);
+}
+
+void AutoHandle::Reset(HANDLE handle) {
+  // Resetting with the same handle we already own is invalid.
+  if (handle_ != handle) {
+    if (IsCloseable()) {
+      ::CloseHandle(handle_);
+    }
+    handle_ = handle;
+  } else {
+    GTEST_CHECK_(!IsCloseable())
+        << "Resetting a valid handle to itself is likely a programmer error "
+            "and thus not allowed.";
+  }
+}
+
+bool AutoHandle::IsCloseable() const {
+  // Different Windows APIs may use either of these values to represent an
+  // invalid handle.
+  return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE;
+}
+
+Notification::Notification()
+    : event_(::CreateEvent(nullptr,     // Default security attributes.
+                           TRUE,        // Do not reset automatically.
+                           FALSE,       // Initially unset.
+                           nullptr)) {  // Anonymous event.
+  GTEST_CHECK_(event_.Get() != nullptr);
+}
+
+void Notification::Notify() {
+  GTEST_CHECK_(::SetEvent(event_.Get()) != FALSE);
+}
+
+void Notification::WaitForNotification() {
+  GTEST_CHECK_(
+      ::WaitForSingleObject(event_.Get(), INFINITE) == WAIT_OBJECT_0);
+}
+
+Mutex::Mutex()
+    : owner_thread_id_(0),
+      type_(kDynamic),
+      critical_section_init_phase_(0),
+      critical_section_(new CRITICAL_SECTION) {
+  ::InitializeCriticalSection(critical_section_);
+}
+
+Mutex::~Mutex() {
+  // Static mutexes are leaked intentionally. It is not thread-safe to try
+  // to clean them up.
+  if (type_ == kDynamic) {
+    ::DeleteCriticalSection(critical_section_);
+    delete critical_section_;
+    critical_section_ = nullptr;
+  }
+}
+
+void Mutex::Lock() {
+  ThreadSafeLazyInit();
+  ::EnterCriticalSection(critical_section_);
+  owner_thread_id_ = ::GetCurrentThreadId();
+}
+
+void Mutex::Unlock() {
+  ThreadSafeLazyInit();
+  // We don't protect writing to owner_thread_id_ here, as it's the
+  // caller's responsibility to ensure that the current thread holds the
+  // mutex when this is called.
+  owner_thread_id_ = 0;
+  ::LeaveCriticalSection(critical_section_);
+}
+
+// Does nothing if the current thread holds the mutex. Otherwise, crashes
+// with high probability.
+void Mutex::AssertHeld() {
+  ThreadSafeLazyInit();
+  GTEST_CHECK_(owner_thread_id_ == ::GetCurrentThreadId())
+      << "The current thread is not holding the mutex @" << this;
+}
+
+namespace {
+
+#ifdef _MSC_VER
+// Use the RAII idiom to flag mem allocs that are intentionally never
+// deallocated. The motivation is to silence the false positive mem leaks
+// that are reported by the debug version of MS's CRT which can only detect
+// if an alloc is missing a matching deallocation.
+// Example:
+//    MemoryIsNotDeallocated memory_is_not_deallocated;
+//    critical_section_ = new CRITICAL_SECTION;
+//
+class MemoryIsNotDeallocated
+{
+ public:
+  MemoryIsNotDeallocated() : old_crtdbg_flag_(0) {
+    old_crtdbg_flag_ = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
+    // Set heap allocation block type to _IGNORE_BLOCK so that MS debug CRT
+    // doesn't report mem leak if there's no matching deallocation.
+    _CrtSetDbgFlag(old_crtdbg_flag_ & ~_CRTDBG_ALLOC_MEM_DF);
+  }
+
+  ~MemoryIsNotDeallocated() {
+    // Restore the original _CRTDBG_ALLOC_MEM_DF flag
+    _CrtSetDbgFlag(old_crtdbg_flag_);
+  }
+
+ private:
+  int old_crtdbg_flag_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(MemoryIsNotDeallocated);
+};
+#endif  // _MSC_VER
+
+}  // namespace
+
+// Initializes owner_thread_id_ and critical_section_ in static mutexes.
+void Mutex::ThreadSafeLazyInit() {
+  // Dynamic mutexes are initialized in the constructor.
+  if (type_ == kStatic) {
+    switch (
+        ::InterlockedCompareExchange(&critical_section_init_phase_, 1L, 0L)) {
+      case 0:
+        // If critical_section_init_phase_ was 0 before the exchange, we
+        // are the first to test it and need to perform the initialization.
+        owner_thread_id_ = 0;
+        {
+          // Use RAII to flag that following mem alloc is never deallocated.
+#ifdef _MSC_VER
+          MemoryIsNotDeallocated memory_is_not_deallocated;
+#endif  // _MSC_VER
+          critical_section_ = new CRITICAL_SECTION;
+        }
+        ::InitializeCriticalSection(critical_section_);
+        // Updates the critical_section_init_phase_ to 2 to signal
+        // initialization complete.
+        GTEST_CHECK_(::InterlockedCompareExchange(
+                          &critical_section_init_phase_, 2L, 1L) ==
+                      1L);
+        break;
+      case 1:
+        // Somebody else is already initializing the mutex; spin until they
+        // are done.
+        while (::InterlockedCompareExchange(&critical_section_init_phase_,
+                                            2L,
+                                            2L) != 2L) {
+          // Possibly yields the rest of the thread's time slice to other
+          // threads.
+          ::Sleep(0);
+        }
+        break;
+
+      case 2:
+        break;  // The mutex is already initialized and ready for use.
+
+      default:
+        GTEST_CHECK_(false)
+            << "Unexpected value of critical_section_init_phase_ "
+            << "while initializing a static mutex.";
+    }
+  }
+}
+
+namespace {
+
+class ThreadWithParamSupport : public ThreadWithParamBase {
+ public:
+  static HANDLE CreateThread(Runnable* runnable,
+                             Notification* thread_can_start) {
+    ThreadMainParam* param = new ThreadMainParam(runnable, thread_can_start);
+    DWORD thread_id;
+    HANDLE thread_handle = ::CreateThread(
+        nullptr,  // Default security.
+        0,        // Default stack size.
+        &ThreadWithParamSupport::ThreadMain,
+        param,        // Parameter to ThreadMainStatic
+        0x0,          // Default creation flags.
+        &thread_id);  // Need a valid pointer for the call to work under Win98.
+    GTEST_CHECK_(thread_handle != nullptr)
+        << "CreateThread failed with error " << ::GetLastError() << ".";
+    if (thread_handle == nullptr) {
+      delete param;
+    }
+    return thread_handle;
+  }
+
+ private:
+  struct ThreadMainParam {
+    ThreadMainParam(Runnable* runnable, Notification* thread_can_start)
+        : runnable_(runnable),
+          thread_can_start_(thread_can_start) {
+    }
+    std::unique_ptr<Runnable> runnable_;
+    // Does not own.
+    Notification* thread_can_start_;
+  };
+
+  static DWORD WINAPI ThreadMain(void* ptr) {
+    // Transfers ownership.
+    std::unique_ptr<ThreadMainParam> param(static_cast<ThreadMainParam*>(ptr));
+    if (param->thread_can_start_ != nullptr)
+      param->thread_can_start_->WaitForNotification();
+    param->runnable_->Run();
+    return 0;
+  }
+
+  // Prohibit instantiation.
+  ThreadWithParamSupport();
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParamSupport);
+};
+
+}  // namespace
+
+ThreadWithParamBase::ThreadWithParamBase(Runnable *runnable,
+                                         Notification* thread_can_start)
+      : thread_(ThreadWithParamSupport::CreateThread(runnable,
+                                                     thread_can_start)) {
+}
+
+ThreadWithParamBase::~ThreadWithParamBase() {
+  Join();
+}
+
+void ThreadWithParamBase::Join() {
+  GTEST_CHECK_(::WaitForSingleObject(thread_.Get(), INFINITE) == WAIT_OBJECT_0)
+      << "Failed to join the thread with error " << ::GetLastError() << ".";
+}
+
+// Maps a thread to a set of ThreadIdToThreadLocals that have values
+// instantiated on that thread and notifies them when the thread exits.  A
+// ThreadLocal instance is expected to persist until all threads it has
+// values on have terminated.
+class ThreadLocalRegistryImpl {
+ public:
+  // Registers thread_local_instance as having value on the current thread.
+  // Returns a value that can be used to identify the thread from other threads.
+  static ThreadLocalValueHolderBase* GetValueOnCurrentThread(
+      const ThreadLocalBase* thread_local_instance) {
+#ifdef _MSC_VER
+    MemoryIsNotDeallocated memory_is_not_deallocated;
+#endif  // _MSC_VER
+    DWORD current_thread = ::GetCurrentThreadId();
+    MutexLock lock(&mutex_);
+    ThreadIdToThreadLocals* const thread_to_thread_locals =
+        GetThreadLocalsMapLocked();
+    ThreadIdToThreadLocals::iterator thread_local_pos =
+        thread_to_thread_locals->find(current_thread);
+    if (thread_local_pos == thread_to_thread_locals->end()) {
+      thread_local_pos = thread_to_thread_locals->insert(
+          std::make_pair(current_thread, ThreadLocalValues())).first;
+      StartWatcherThreadFor(current_thread);
+    }
+    ThreadLocalValues& thread_local_values = thread_local_pos->second;
+    ThreadLocalValues::iterator value_pos =
+        thread_local_values.find(thread_local_instance);
+    if (value_pos == thread_local_values.end()) {
+      value_pos =
+          thread_local_values
+              .insert(std::make_pair(
+                  thread_local_instance,
+                  std::shared_ptr<ThreadLocalValueHolderBase>(
+                      thread_local_instance->NewValueForCurrentThread())))
+              .first;
+    }
+    return value_pos->second.get();
+  }
+
+  static void OnThreadLocalDestroyed(
+      const ThreadLocalBase* thread_local_instance) {
+    std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders;
+    // Clean up the ThreadLocalValues data structure while holding the lock, but
+    // defer the destruction of the ThreadLocalValueHolderBases.
+    {
+      MutexLock lock(&mutex_);
+      ThreadIdToThreadLocals* const thread_to_thread_locals =
+          GetThreadLocalsMapLocked();
+      for (ThreadIdToThreadLocals::iterator it =
+          thread_to_thread_locals->begin();
+          it != thread_to_thread_locals->end();
+          ++it) {
+        ThreadLocalValues& thread_local_values = it->second;
+        ThreadLocalValues::iterator value_pos =
+            thread_local_values.find(thread_local_instance);
+        if (value_pos != thread_local_values.end()) {
+          value_holders.push_back(value_pos->second);
+          thread_local_values.erase(value_pos);
+          // This 'if' can only be successful at most once, so theoretically we
+          // could break out of the loop here, but we don't bother doing so.
+        }
+      }
+    }
+    // Outside the lock, let the destructor for 'value_holders' deallocate the
+    // ThreadLocalValueHolderBases.
+  }
+
+  static void OnThreadExit(DWORD thread_id) {
+    GTEST_CHECK_(thread_id != 0) << ::GetLastError();
+    std::vector<std::shared_ptr<ThreadLocalValueHolderBase> > value_holders;
+    // Clean up the ThreadIdToThreadLocals data structure while holding the
+    // lock, but defer the destruction of the ThreadLocalValueHolderBases.
+    {
+      MutexLock lock(&mutex_);
+      ThreadIdToThreadLocals* const thread_to_thread_locals =
+          GetThreadLocalsMapLocked();
+      ThreadIdToThreadLocals::iterator thread_local_pos =
+          thread_to_thread_locals->find(thread_id);
+      if (thread_local_pos != thread_to_thread_locals->end()) {
+        ThreadLocalValues& thread_local_values = thread_local_pos->second;
+        for (ThreadLocalValues::iterator value_pos =
+            thread_local_values.begin();
+            value_pos != thread_local_values.end();
+            ++value_pos) {
+          value_holders.push_back(value_pos->second);
+        }
+        thread_to_thread_locals->erase(thread_local_pos);
+      }
+    }
+    // Outside the lock, let the destructor for 'value_holders' deallocate the
+    // ThreadLocalValueHolderBases.
+  }
+
+ private:
+  // In a particular thread, maps a ThreadLocal object to its value.
+  typedef std::map<const ThreadLocalBase*,
+                   std::shared_ptr<ThreadLocalValueHolderBase> >
+      ThreadLocalValues;
+  // Stores all ThreadIdToThreadLocals having values in a thread, indexed by
+  // thread's ID.
+  typedef std::map<DWORD, ThreadLocalValues> ThreadIdToThreadLocals;
+
+  // Holds the thread id and thread handle that we pass from
+  // StartWatcherThreadFor to WatcherThreadFunc.
+  typedef std::pair<DWORD, HANDLE> ThreadIdAndHandle;
+
+  static void StartWatcherThreadFor(DWORD thread_id) {
+    // The returned handle will be kept in thread_map and closed by
+    // watcher_thread in WatcherThreadFunc.
+    HANDLE thread = ::OpenThread(SYNCHRONIZE | THREAD_QUERY_INFORMATION,
+                                 FALSE,
+                                 thread_id);
+    GTEST_CHECK_(thread != nullptr);
+    // We need to pass a valid thread ID pointer into CreateThread for it
+    // to work correctly under Win98.
+    DWORD watcher_thread_id;
+    HANDLE watcher_thread = ::CreateThread(
+        nullptr,  // Default security.
+        0,        // Default stack size
+        &ThreadLocalRegistryImpl::WatcherThreadFunc,
+        reinterpret_cast<LPVOID>(new ThreadIdAndHandle(thread_id, thread)),
+        CREATE_SUSPENDED, &watcher_thread_id);
+    GTEST_CHECK_(watcher_thread != nullptr);
+    // Give the watcher thread the same priority as ours to avoid being
+    // blocked by it.
+    ::SetThreadPriority(watcher_thread,
+                        ::GetThreadPriority(::GetCurrentThread()));
+    ::ResumeThread(watcher_thread);
+    ::CloseHandle(watcher_thread);
+  }
+
+  // Monitors exit from a given thread and notifies those
+  // ThreadIdToThreadLocals about thread termination.
+  static DWORD WINAPI WatcherThreadFunc(LPVOID param) {
+    const ThreadIdAndHandle* tah =
+        reinterpret_cast<const ThreadIdAndHandle*>(param);
+    GTEST_CHECK_(
+        ::WaitForSingleObject(tah->second, INFINITE) == WAIT_OBJECT_0);
+    OnThreadExit(tah->first);
+    ::CloseHandle(tah->second);
+    delete tah;
+    return 0;
+  }
+
+  // Returns map of thread local instances.
+  static ThreadIdToThreadLocals* GetThreadLocalsMapLocked() {
+    mutex_.AssertHeld();
+#ifdef _MSC_VER
+    MemoryIsNotDeallocated memory_is_not_deallocated;
+#endif  // _MSC_VER
+    static ThreadIdToThreadLocals* map = new ThreadIdToThreadLocals();
+    return map;
+  }
+
+  // Protects access to GetThreadLocalsMapLocked() and its return value.
+  static Mutex mutex_;
+  // Protects access to GetThreadMapLocked() and its return value.
+  static Mutex thread_map_mutex_;
+};
+
+Mutex ThreadLocalRegistryImpl::mutex_(Mutex::kStaticMutex);
+Mutex ThreadLocalRegistryImpl::thread_map_mutex_(Mutex::kStaticMutex);
+
+ThreadLocalValueHolderBase* ThreadLocalRegistry::GetValueOnCurrentThread(
+      const ThreadLocalBase* thread_local_instance) {
+  return ThreadLocalRegistryImpl::GetValueOnCurrentThread(
+      thread_local_instance);
+}
+
+void ThreadLocalRegistry::OnThreadLocalDestroyed(
+      const ThreadLocalBase* thread_local_instance) {
+  ThreadLocalRegistryImpl::OnThreadLocalDestroyed(thread_local_instance);
+}
+
+#endif  // GTEST_IS_THREADSAFE && GTEST_OS_WINDOWS
+
+#if GTEST_USES_POSIX_RE
+
+// Implements RE.  Currently only needed for death tests.
+
+RE::~RE() {
+  if (is_valid_) {
+    // regfree'ing an invalid regex might crash because the content
+    // of the regex is undefined. Since the regex's are essentially
+    // the same, one cannot be valid (or invalid) without the other
+    // being so too.
+    regfree(&partial_regex_);
+    regfree(&full_regex_);
+  }
+  free(const_cast<char*>(pattern_));
+}
+
+// Returns true if and only if regular expression re matches the entire str.
+bool RE::FullMatch(const char* str, const RE& re) {
+  if (!re.is_valid_) return false;
+
+  regmatch_t match;
+  return regexec(&re.full_regex_, str, 1, &match, 0) == 0;
+}
+
+// Returns true if and only if regular expression re matches a substring of
+// str (including str itself).
+bool RE::PartialMatch(const char* str, const RE& re) {
+  if (!re.is_valid_) return false;
+
+  regmatch_t match;
+  return regexec(&re.partial_regex_, str, 1, &match, 0) == 0;
+}
+
+// Initializes an RE from its string representation.
+void RE::Init(const char* regex) {
+  pattern_ = posix::StrDup(regex);
+
+  // Reserves enough bytes to hold the regular expression used for a
+  // full match.
+  const size_t full_regex_len = strlen(regex) + 10;
+  char* const full_pattern = new char[full_regex_len];
+
+  snprintf(full_pattern, full_regex_len, "^(%s)$", regex);
+  is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0;
+  // We want to call regcomp(&partial_regex_, ...) even if the
+  // previous expression returns false.  Otherwise partial_regex_ may
+  // not be properly initialized can may cause trouble when it's
+  // freed.
+  //
+  // Some implementation of POSIX regex (e.g. on at least some
+  // versions of Cygwin) doesn't accept the empty string as a valid
+  // regex.  We change it to an equivalent form "()" to be safe.
+  if (is_valid_) {
+    const char* const partial_regex = (*regex == '\0') ? "()" : regex;
+    is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0;
+  }
+  EXPECT_TRUE(is_valid_)
+      << "Regular expression \"" << regex
+      << "\" is not a valid POSIX Extended regular expression.";
+
+  delete[] full_pattern;
+}
+
+#elif GTEST_USES_SIMPLE_RE
+
+// Returns true if and only if ch appears anywhere in str (excluding the
+// terminating '\0' character).
+bool IsInSet(char ch, const char* str) {
+  return ch != '\0' && strchr(str, ch) != nullptr;
+}
+
+// Returns true if and only if ch belongs to the given classification.
+// Unlike similar functions in <ctype.h>, these aren't affected by the
+// current locale.
+bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; }
+bool IsAsciiPunct(char ch) {
+  return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~");
+}
+bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); }
+bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); }
+bool IsAsciiWordChar(char ch) {
+  return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') ||
+      ('0' <= ch && ch <= '9') || ch == '_';
+}
+
+// Returns true if and only if "\\c" is a supported escape sequence.
+bool IsValidEscape(char c) {
+  return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW"));
+}
+
+// Returns true if and only if the given atom (specified by escaped and
+// pattern) matches ch.  The result is undefined if the atom is invalid.
+bool AtomMatchesChar(bool escaped, char pattern_char, char ch) {
+  if (escaped) {  // "\\p" where p is pattern_char.
+    switch (pattern_char) {
+      case 'd': return IsAsciiDigit(ch);
+      case 'D': return !IsAsciiDigit(ch);
+      case 'f': return ch == '\f';
+      case 'n': return ch == '\n';
+      case 'r': return ch == '\r';
+      case 's': return IsAsciiWhiteSpace(ch);
+      case 'S': return !IsAsciiWhiteSpace(ch);
+      case 't': return ch == '\t';
+      case 'v': return ch == '\v';
+      case 'w': return IsAsciiWordChar(ch);
+      case 'W': return !IsAsciiWordChar(ch);
+    }
+    return IsAsciiPunct(pattern_char) && pattern_char == ch;
+  }
+
+  return (pattern_char == '.' && ch != '\n') || pattern_char == ch;
+}
+
+// Helper function used by ValidateRegex() to format error messages.
+static std::string FormatRegexSyntaxError(const char* regex, int index) {
+  return (Message() << "Syntax error at index " << index
+          << " in simple regular expression \"" << regex << "\": ").GetString();
+}
+
+// Generates non-fatal failures and returns false if regex is invalid;
+// otherwise returns true.
+bool ValidateRegex(const char* regex) {
+  if (regex == nullptr) {
+    ADD_FAILURE() << "NULL is not a valid simple regular expression.";
+    return false;
+  }
+
+  bool is_valid = true;
+
+  // True if and only if ?, *, or + can follow the previous atom.
+  bool prev_repeatable = false;
+  for (int i = 0; regex[i]; i++) {
+    if (regex[i] == '\\') {  // An escape sequence
+      i++;
+      if (regex[i] == '\0') {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1)
+                      << "'\\' cannot appear at the end.";
+        return false;
+      }
+
+      if (!IsValidEscape(regex[i])) {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1)
+                      << "invalid escape sequence \"\\" << regex[i] << "\".";
+        is_valid = false;
+      }
+      prev_repeatable = true;
+    } else {  // Not an escape sequence.
+      const char ch = regex[i];
+
+      if (ch == '^' && i > 0) {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+                      << "'^' can only appear at the beginning.";
+        is_valid = false;
+      } else if (ch == '$' && regex[i + 1] != '\0') {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+                      << "'$' can only appear at the end.";
+        is_valid = false;
+      } else if (IsInSet(ch, "()[]{}|")) {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+                      << "'" << ch << "' is unsupported.";
+        is_valid = false;
+      } else if (IsRepeat(ch) && !prev_repeatable) {
+        ADD_FAILURE() << FormatRegexSyntaxError(regex, i)
+                      << "'" << ch << "' can only follow a repeatable token.";
+        is_valid = false;
+      }
+
+      prev_repeatable = !IsInSet(ch, "^$?*+");
+    }
+  }
+
+  return is_valid;
+}
+
+// Matches a repeated regex atom followed by a valid simple regular
+// expression.  The regex atom is defined as c if escaped is false,
+// or \c otherwise.  repeat is the repetition meta character (?, *,
+// or +).  The behavior is undefined if str contains too many
+// characters to be indexable by size_t, in which case the test will
+// probably time out anyway.  We are fine with this limitation as
+// std::string has it too.
+bool MatchRepetitionAndRegexAtHead(
+    bool escaped, char c, char repeat, const char* regex,
+    const char* str) {
+  const size_t min_count = (repeat == '+') ? 1 : 0;
+  const size_t max_count = (repeat == '?') ? 1 :
+      static_cast<size_t>(-1) - 1;
+  // We cannot call numeric_limits::max() as it conflicts with the
+  // max() macro on Windows.
+
+  for (size_t i = 0; i <= max_count; ++i) {
+    // We know that the atom matches each of the first i characters in str.
+    if (i >= min_count && MatchRegexAtHead(regex, str + i)) {
+      // We have enough matches at the head, and the tail matches too.
+      // Since we only care about *whether* the pattern matches str
+      // (as opposed to *how* it matches), there is no need to find a
+      // greedy match.
+      return true;
+    }
+    if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i]))
+      return false;
+  }
+  return false;
+}
+
+// Returns true if and only if regex matches a prefix of str. regex must
+// be a valid simple regular expression and not start with "^", or the
+// result is undefined.
+bool MatchRegexAtHead(const char* regex, const char* str) {
+  if (*regex == '\0')  // An empty regex matches a prefix of anything.
+    return true;
+
+  // "$" only matches the end of a string.  Note that regex being
+  // valid guarantees that there's nothing after "$" in it.
+  if (*regex == '$')
+    return *str == '\0';
+
+  // Is the first thing in regex an escape sequence?
+  const bool escaped = *regex == '\\';
+  if (escaped)
+    ++regex;
+  if (IsRepeat(regex[1])) {
+    // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so
+    // here's an indirect recursion.  It terminates as the regex gets
+    // shorter in each recursion.
+    return MatchRepetitionAndRegexAtHead(
+        escaped, regex[0], regex[1], regex + 2, str);
+  } else {
+    // regex isn't empty, isn't "$", and doesn't start with a
+    // repetition.  We match the first atom of regex with the first
+    // character of str and recurse.
+    return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) &&
+        MatchRegexAtHead(regex + 1, str + 1);
+  }
+}
+
+// Returns true if and only if regex matches any substring of str.  regex must
+// be a valid simple regular expression, or the result is undefined.
+//
+// The algorithm is recursive, but the recursion depth doesn't exceed
+// the regex length, so we won't need to worry about running out of
+// stack space normally.  In rare cases the time complexity can be
+// exponential with respect to the regex length + the string length,
+// but usually it's must faster (often close to linear).
+bool MatchRegexAnywhere(const char* regex, const char* str) {
+  if (regex == nullptr || str == nullptr) return false;
+
+  if (*regex == '^')
+    return MatchRegexAtHead(regex + 1, str);
+
+  // A successful match can be anywhere in str.
+  do {
+    if (MatchRegexAtHead(regex, str))
+      return true;
+  } while (*str++ != '\0');
+  return false;
+}
+
+// Implements the RE class.
+
+RE::~RE() {
+  free(const_cast<char*>(pattern_));
+  free(const_cast<char*>(full_pattern_));
+}
+
+// Returns true if and only if regular expression re matches the entire str.
+bool RE::FullMatch(const char* str, const RE& re) {
+  return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str);
+}
+
+// Returns true if and only if regular expression re matches a substring of
+// str (including str itself).
+bool RE::PartialMatch(const char* str, const RE& re) {
+  return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str);
+}
+
+// Initializes an RE from its string representation.
+void RE::Init(const char* regex) {
+  pattern_ = full_pattern_ = nullptr;
+  if (regex != nullptr) {
+    pattern_ = posix::StrDup(regex);
+  }
+
+  is_valid_ = ValidateRegex(regex);
+  if (!is_valid_) {
+    // No need to calculate the full pattern when the regex is invalid.
+    return;
+  }
+
+  const size_t len = strlen(regex);
+  // Reserves enough bytes to hold the regular expression used for a
+  // full match: we need space to prepend a '^', append a '$', and
+  // terminate the string with '\0'.
+  char* buffer = static_cast<char*>(malloc(len + 3));
+  full_pattern_ = buffer;
+
+  if (*regex != '^')
+    *buffer++ = '^';  // Makes sure full_pattern_ starts with '^'.
+
+  // We don't use snprintf or strncpy, as they trigger a warning when
+  // compiled with VC++ 8.0.
+  memcpy(buffer, regex, len);
+  buffer += len;
+
+  if (len == 0 || regex[len - 1] != '$')
+    *buffer++ = '$';  // Makes sure full_pattern_ ends with '$'.
+
+  *buffer = '\0';
+}
+
+#endif  // GTEST_USES_POSIX_RE
+
+const char kUnknownFile[] = "unknown file";
+
+// Formats a source file path and a line number as they would appear
+// in an error message from the compiler used to compile this code.
+GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) {
+  const std::string file_name(file == nullptr ? kUnknownFile : file);
+
+  if (line < 0) {
+    return file_name + ":";
+  }
+#ifdef _MSC_VER
+  return file_name + "(" + StreamableToString(line) + "):";
+#else
+  return file_name + ":" + StreamableToString(line) + ":";
+#endif  // _MSC_VER
+}
+
+// Formats a file location for compiler-independent XML output.
+// Although this function is not platform dependent, we put it next to
+// FormatFileLocation in order to contrast the two functions.
+// Note that FormatCompilerIndependentFileLocation() does NOT append colon
+// to the file location it produces, unlike FormatFileLocation().
+GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(
+    const char* file, int line) {
+  const std::string file_name(file == nullptr ? kUnknownFile : file);
+
+  if (line < 0)
+    return file_name;
+  else
+    return file_name + ":" + StreamableToString(line);
+}
+
+GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line)
+    : severity_(severity) {
+  const char* const marker =
+      severity == GTEST_INFO ?    "[  INFO ]" :
+      severity == GTEST_WARNING ? "[WARNING]" :
+      severity == GTEST_ERROR ?   "[ ERROR ]" : "[ FATAL ]";
+  GetStream() << ::std::endl << marker << " "
+              << FormatFileLocation(file, line).c_str() << ": ";
+}
+
+// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program.
+GTestLog::~GTestLog() {
+  GetStream() << ::std::endl;
+  if (severity_ == GTEST_FATAL) {
+    fflush(stderr);
+    posix::Abort();
+  }
+}
+
+// Disable Microsoft deprecation warnings for POSIX functions called from
+// this class (creat, dup, dup2, and close)
+GTEST_DISABLE_MSC_DEPRECATED_PUSH_()
+
+#if GTEST_HAS_STREAM_REDIRECTION
+
+// Object that captures an output stream (stdout/stderr).
+class CapturedStream {
+ public:
+  // The ctor redirects the stream to a temporary file.
+  explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) {
+# if GTEST_OS_WINDOWS
+    char temp_dir_path[MAX_PATH + 1] = { '\0' };  // NOLINT
+    char temp_file_path[MAX_PATH + 1] = { '\0' };  // NOLINT
+
+    ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path);
+    const UINT success = ::GetTempFileNameA(temp_dir_path,
+                                            "gtest_redir",
+                                            0,  // Generate unique file name.
+                                            temp_file_path);
+    GTEST_CHECK_(success != 0)
+        << "Unable to create a temporary file in " << temp_dir_path;
+    const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE);
+    GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file "
+                                    << temp_file_path;
+    filename_ = temp_file_path;
+# else
+    // There's no guarantee that a test has write access to the current
+    // directory, so we create the temporary file in the /tmp directory
+    // instead. We use /tmp on most systems, and /sdcard on Android.
+    // That's because Android doesn't have /tmp.
+#  if GTEST_OS_LINUX_ANDROID
+    // Note: Android applications are expected to call the framework's
+    // Context.getExternalStorageDirectory() method through JNI to get
+    // the location of the world-writable SD Card directory. However,
+    // this requires a Context handle, which cannot be retrieved
+    // globally from native code. Doing so also precludes running the
+    // code as part of a regular standalone executable, which doesn't
+    // run in a Dalvik process (e.g. when running it through 'adb shell').
+    //
+    // The location /data/local/tmp is directly accessible from native code.
+    // '/sdcard' and other variants cannot be relied on, as they are not
+    // guaranteed to be mounted, or may have a delay in mounting.
+    char name_template[] = "/data/local/tmp/gtest_captured_stream.XXXXXX";
+#  else
+    char name_template[] = "/tmp/captured_stream.XXXXXX";
+#  endif  // GTEST_OS_LINUX_ANDROID
+    const int captured_fd = mkstemp(name_template);
+    if (captured_fd == -1) {
+      GTEST_LOG_(WARNING)
+          << "Failed to create tmp file " << name_template
+          << " for test; does the test have access to the /tmp directory?";
+    }
+    filename_ = name_template;
+# endif  // GTEST_OS_WINDOWS
+    fflush(nullptr);
+    dup2(captured_fd, fd_);
+    close(captured_fd);
+  }
+
+  ~CapturedStream() {
+    remove(filename_.c_str());
+  }
+
+  std::string GetCapturedString() {
+    if (uncaptured_fd_ != -1) {
+      // Restores the original stream.
+      fflush(nullptr);
+      dup2(uncaptured_fd_, fd_);
+      close(uncaptured_fd_);
+      uncaptured_fd_ = -1;
+    }
+
+    FILE* const file = posix::FOpen(filename_.c_str(), "r");
+    if (file == nullptr) {
+      GTEST_LOG_(FATAL) << "Failed to open tmp file " << filename_
+                        << " for capturing stream.";
+    }
+    const std::string content = ReadEntireFile(file);
+    posix::FClose(file);
+    return content;
+  }
+
+ private:
+  const int fd_;  // A stream to capture.
+  int uncaptured_fd_;
+  // Name of the temporary file holding the stderr output.
+  ::std::string filename_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream);
+};
+
+GTEST_DISABLE_MSC_DEPRECATED_POP_()
+
+static CapturedStream* g_captured_stderr = nullptr;
+static CapturedStream* g_captured_stdout = nullptr;
+
+// Starts capturing an output stream (stdout/stderr).
+static void CaptureStream(int fd, const char* stream_name,
+                          CapturedStream** stream) {
+  if (*stream != nullptr) {
+    GTEST_LOG_(FATAL) << "Only one " << stream_name
+                      << " capturer can exist at a time.";
+  }
+  *stream = new CapturedStream(fd);
+}
+
+// Stops capturing the output stream and returns the captured string.
+static std::string GetCapturedStream(CapturedStream** captured_stream) {
+  const std::string content = (*captured_stream)->GetCapturedString();
+
+  delete *captured_stream;
+  *captured_stream = nullptr;
+
+  return content;
+}
+
+// Starts capturing stdout.
+void CaptureStdout() {
+  CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout);
+}
+
+// Starts capturing stderr.
+void CaptureStderr() {
+  CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr);
+}
+
+// Stops capturing stdout and returns the captured string.
+std::string GetCapturedStdout() {
+  return GetCapturedStream(&g_captured_stdout);
+}
+
+// Stops capturing stderr and returns the captured string.
+std::string GetCapturedStderr() {
+  return GetCapturedStream(&g_captured_stderr);
+}
+
+#endif  // GTEST_HAS_STREAM_REDIRECTION
+
+
+
+
+
+size_t GetFileSize(FILE* file) {
+  fseek(file, 0, SEEK_END);
+  return static_cast<size_t>(ftell(file));
+}
+
+std::string ReadEntireFile(FILE* file) {
+  const size_t file_size = GetFileSize(file);
+  char* const buffer = new char[file_size];
+
+  size_t bytes_last_read = 0;  // # of bytes read in the last fread()
+  size_t bytes_read = 0;       // # of bytes read so far
+
+  fseek(file, 0, SEEK_SET);
+
+  // Keeps reading the file until we cannot read further or the
+  // pre-determined file size is reached.
+  do {
+    bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file);
+    bytes_read += bytes_last_read;
+  } while (bytes_last_read > 0 && bytes_read < file_size);
+
+  const std::string content(buffer, bytes_read);
+  delete[] buffer;
+
+  return content;
+}
+
+#if GTEST_HAS_DEATH_TEST
+static const std::vector<std::string>* g_injected_test_argvs =
+    nullptr;  // Owned.
+
+std::vector<std::string> GetInjectableArgvs() {
+  if (g_injected_test_argvs != nullptr) {
+    return *g_injected_test_argvs;
+  }
+  return GetArgvs();
+}
+
+void SetInjectableArgvs(const std::vector<std::string>* new_argvs) {
+  if (g_injected_test_argvs != new_argvs) delete g_injected_test_argvs;
+  g_injected_test_argvs = new_argvs;
+}
+
+void SetInjectableArgvs(const std::vector<std::string>& new_argvs) {
+  SetInjectableArgvs(
+      new std::vector<std::string>(new_argvs.begin(), new_argvs.end()));
+}
+
+void ClearInjectableArgvs() {
+  delete g_injected_test_argvs;
+  g_injected_test_argvs = nullptr;
+}
+#endif  // GTEST_HAS_DEATH_TEST
+
+#if GTEST_OS_WINDOWS_MOBILE
+namespace posix {
+void Abort() {
+  DebugBreak();
+  TerminateProcess(GetCurrentProcess(), 1);
+}
+}  // namespace posix
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+// Returns the name of the environment variable corresponding to the
+// given flag.  For example, FlagToEnvVar("foo") will return
+// "GTEST_FOO" in the open-source version.
+static std::string FlagToEnvVar(const char* flag) {
+  const std::string full_flag =
+      (Message() << GTEST_FLAG_PREFIX_ << flag).GetString();
+
+  Message env_var;
+  for (size_t i = 0; i != full_flag.length(); i++) {
+    env_var << ToUpper(full_flag.c_str()[i]);
+  }
+
+  return env_var.GetString();
+}
+
+// Parses 'str' for a 32-bit signed integer.  If successful, writes
+// the result to *value and returns true; otherwise leaves *value
+// unchanged and returns false.
+bool ParseInt32(const Message& src_text, const char* str, int32_t* value) {
+  // Parses the environment variable as a decimal integer.
+  char* end = nullptr;
+  const long long_value = strtol(str, &end, 10);  // NOLINT
+
+  // Has strtol() consumed all characters in the string?
+  if (*end != '\0') {
+    // No - an invalid character was encountered.
+    Message msg;
+    msg << "WARNING: " << src_text
+        << " is expected to be a 32-bit integer, but actually"
+        << " has value \"" << str << "\".\n";
+    printf("%s", msg.GetString().c_str());
+    fflush(stdout);
+    return false;
+  }
+
+  // Is the parsed value in the range of an int32_t?
+  const auto result = static_cast<int32_t>(long_value);
+  if (long_value == LONG_MAX || long_value == LONG_MIN ||
+      // The parsed value overflows as a long.  (strtol() returns
+      // LONG_MAX or LONG_MIN when the input overflows.)
+      result != long_value
+      // The parsed value overflows as an int32_t.
+      ) {
+    Message msg;
+    msg << "WARNING: " << src_text
+        << " is expected to be a 32-bit integer, but actually"
+        << " has value " << str << ", which overflows.\n";
+    printf("%s", msg.GetString().c_str());
+    fflush(stdout);
+    return false;
+  }
+
+  *value = result;
+  return true;
+}
+
+// Reads and returns the Boolean environment variable corresponding to
+// the given flag; if it's not set, returns default_value.
+//
+// The value is considered true if and only if it's not "0".
+bool BoolFromGTestEnv(const char* flag, bool default_value) {
+#if defined(GTEST_GET_BOOL_FROM_ENV_)
+  return GTEST_GET_BOOL_FROM_ENV_(flag, default_value);
+#else
+  const std::string env_var = FlagToEnvVar(flag);
+  const char* const string_value = posix::GetEnv(env_var.c_str());
+  return string_value == nullptr ? default_value
+                                 : strcmp(string_value, "0") != 0;
+#endif  // defined(GTEST_GET_BOOL_FROM_ENV_)
+}
+
+// Reads and returns a 32-bit integer stored in the environment
+// variable corresponding to the given flag; if it isn't set or
+// doesn't represent a valid 32-bit integer, returns default_value.
+int32_t Int32FromGTestEnv(const char* flag, int32_t default_value) {
+#if defined(GTEST_GET_INT32_FROM_ENV_)
+  return GTEST_GET_INT32_FROM_ENV_(flag, default_value);
+#else
+  const std::string env_var = FlagToEnvVar(flag);
+  const char* const string_value = posix::GetEnv(env_var.c_str());
+  if (string_value == nullptr) {
+    // The environment variable is not set.
+    return default_value;
+  }
+
+  int32_t result = default_value;
+  if (!ParseInt32(Message() << "Environment variable " << env_var,
+                  string_value, &result)) {
+    printf("The default value %s is used.\n",
+           (Message() << default_value).GetString().c_str());
+    fflush(stdout);
+    return default_value;
+  }
+
+  return result;
+#endif  // defined(GTEST_GET_INT32_FROM_ENV_)
+}
+
+// As a special case for the 'output' flag, if GTEST_OUTPUT is not
+// set, we look for XML_OUTPUT_FILE, which is set by the Bazel build
+// system.  The value of XML_OUTPUT_FILE is a filename without the
+// "xml:" prefix of GTEST_OUTPUT.
+// Note that this is meant to be called at the call site so it does
+// not check that the flag is 'output'
+// In essence this checks an env variable called XML_OUTPUT_FILE
+// and if it is set we prepend "xml:" to its value, if it not set we return ""
+std::string OutputFlagAlsoCheckEnvVar(){
+  std::string default_value_for_output_flag = "";
+  const char* xml_output_file_env = posix::GetEnv("XML_OUTPUT_FILE");
+  if (nullptr != xml_output_file_env) {
+    default_value_for_output_flag = std::string("xml:") + xml_output_file_env;
+  }
+  return default_value_for_output_flag;
+}
+
+// Reads and returns the string environment variable corresponding to
+// the given flag; if it's not set, returns default_value.
+const char* StringFromGTestEnv(const char* flag, const char* default_value) {
+#if defined(GTEST_GET_STRING_FROM_ENV_)
+  return GTEST_GET_STRING_FROM_ENV_(flag, default_value);
+#else
+  const std::string env_var = FlagToEnvVar(flag);
+  const char* const value = posix::GetEnv(env_var.c_str());
+  return value == nullptr ? default_value : value;
+#endif  // defined(GTEST_GET_STRING_FROM_ENV_)
+}
+
+}  // namespace internal
+}  // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Test - The Google C++ Testing and Mocking Framework
+//
+// This file implements a universal value printer that can print a
+// value of any type T:
+//
+//   void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
+//
+// It uses the << operator when possible, and prints the bytes in the
+// object otherwise.  A user can override its behavior for a class
+// type Foo by defining either operator<<(::std::ostream&, const Foo&)
+// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that
+// defines Foo.
+
+
+#include <stdio.h>
+
+#include <cctype>
+#include <cstdint>
+#include <cwchar>
+#include <ostream>  // NOLINT
+#include <string>
+#include <type_traits>
+
+
+namespace testing {
+
+namespace {
+
+using ::std::ostream;
+
+// Prints a segment of bytes in the given object.
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start,
+                                size_t count, ostream* os) {
+  char text[5] = "";
+  for (size_t i = 0; i != count; i++) {
+    const size_t j = start + i;
+    if (i != 0) {
+      // Organizes the bytes into groups of 2 for easy parsing by
+      // human.
+      if ((j % 2) == 0)
+        *os << ' ';
+      else
+        *os << '-';
+    }
+    GTEST_SNPRINTF_(text, sizeof(text), "%02X", obj_bytes[j]);
+    *os << text;
+  }
+}
+
+// Prints the bytes in the given value to the given ostream.
+void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count,
+                              ostream* os) {
+  // Tells the user how big the object is.
+  *os << count << "-byte object <";
+
+  const size_t kThreshold = 132;
+  const size_t kChunkSize = 64;
+  // If the object size is bigger than kThreshold, we'll have to omit
+  // some details by printing only the first and the last kChunkSize
+  // bytes.
+  if (count < kThreshold) {
+    PrintByteSegmentInObjectTo(obj_bytes, 0, count, os);
+  } else {
+    PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os);
+    *os << " ... ";
+    // Rounds up to 2-byte boundary.
+    const size_t resume_pos = (count - kChunkSize + 1)/2*2;
+    PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os);
+  }
+  *os << ">";
+}
+
+// Helpers for widening a character to char32_t. Since the standard does not
+// specify if char / wchar_t is signed or unsigned, it is important to first
+// convert it to the unsigned type of the same width before widening it to
+// char32_t.
+template <typename CharType>
+char32_t ToChar32(CharType in) {
+  return static_cast<char32_t>(
+      static_cast<typename std::make_unsigned<CharType>::type>(in));
+}
+
+}  // namespace
+
+namespace internal {
+
+// Delegates to PrintBytesInObjectToImpl() to print the bytes in the
+// given object.  The delegation simplifies the implementation, which
+// uses the << operator and thus is easier done outside of the
+// ::testing::internal namespace, which contains a << operator that
+// sometimes conflicts with the one in STL.
+void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count,
+                          ostream* os) {
+  PrintBytesInObjectToImpl(obj_bytes, count, os);
+}
+
+// Depending on the value of a char (or wchar_t), we print it in one
+// of three formats:
+//   - as is if it's a printable ASCII (e.g. 'a', '2', ' '),
+//   - as a hexadecimal escape sequence (e.g. '\x7F'), or
+//   - as a special escape sequence (e.g. '\r', '\n').
+enum CharFormat {
+  kAsIs,
+  kHexEscape,
+  kSpecialEscape
+};
+
+// Returns true if c is a printable ASCII character.  We test the
+// value of c directly instead of calling isprint(), which is buggy on
+// Windows Mobile.
+inline bool IsPrintableAscii(char32_t c) { return 0x20 <= c && c <= 0x7E; }
+
+// Prints c (of type char, char8_t, char16_t, char32_t, or wchar_t) as a
+// character literal without the quotes, escaping it when necessary; returns how
+// c was formatted.
+template <typename Char>
+static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) {
+  const char32_t u_c = ToChar32(c);
+  switch (u_c) {
+    case L'\0':
+      *os << "\\0";
+      break;
+    case L'\'':
+      *os << "\\'";
+      break;
+    case L'\\':
+      *os << "\\\\";
+      break;
+    case L'\a':
+      *os << "\\a";
+      break;
+    case L'\b':
+      *os << "\\b";
+      break;
+    case L'\f':
+      *os << "\\f";
+      break;
+    case L'\n':
+      *os << "\\n";
+      break;
+    case L'\r':
+      *os << "\\r";
+      break;
+    case L'\t':
+      *os << "\\t";
+      break;
+    case L'\v':
+      *os << "\\v";
+      break;
+    default:
+      if (IsPrintableAscii(u_c)) {
+        *os << static_cast<char>(c);
+        return kAsIs;
+      } else {
+        ostream::fmtflags flags = os->flags();
+        *os << "\\x" << std::hex << std::uppercase << static_cast<int>(u_c);
+        os->flags(flags);
+        return kHexEscape;
+      }
+  }
+  return kSpecialEscape;
+}
+
+// Prints a char32_t c as if it's part of a string literal, escaping it when
+// necessary; returns how c was formatted.
+static CharFormat PrintAsStringLiteralTo(char32_t c, ostream* os) {
+  switch (c) {
+    case L'\'':
+      *os << "'";
+      return kAsIs;
+    case L'"':
+      *os << "\\\"";
+      return kSpecialEscape;
+    default:
+      return PrintAsCharLiteralTo(c, os);
+  }
+}
+
+static const char* GetCharWidthPrefix(char) {
+  return "";
+}
+
+static const char* GetCharWidthPrefix(signed char) {
+  return "";
+}
+
+static const char* GetCharWidthPrefix(unsigned char) {
+  return "";
+}
+
+#ifdef __cpp_char8_t
+static const char* GetCharWidthPrefix(char8_t) {
+  return "u8";
+}
+#endif
+
+static const char* GetCharWidthPrefix(char16_t) {
+  return "u";
+}
+
+static const char* GetCharWidthPrefix(char32_t) {
+  return "U";
+}
+
+static const char* GetCharWidthPrefix(wchar_t) {
+  return "L";
+}
+
+// Prints a char c as if it's part of a string literal, escaping it when
+// necessary; returns how c was formatted.
+static CharFormat PrintAsStringLiteralTo(char c, ostream* os) {
+  return PrintAsStringLiteralTo(ToChar32(c), os);
+}
+
+#ifdef __cpp_char8_t
+static CharFormat PrintAsStringLiteralTo(char8_t c, ostream* os) {
+  return PrintAsStringLiteralTo(ToChar32(c), os);
+}
+#endif
+
+static CharFormat PrintAsStringLiteralTo(char16_t c, ostream* os) {
+  return PrintAsStringLiteralTo(ToChar32(c), os);
+}
+
+static CharFormat PrintAsStringLiteralTo(wchar_t c, ostream* os) {
+  return PrintAsStringLiteralTo(ToChar32(c), os);
+}
+
+// Prints a character c (of type char, char8_t, char16_t, char32_t, or wchar_t)
+// and its code. '\0' is printed as "'\\0'", other unprintable characters are
+// also properly escaped using the standard C++ escape sequence.
+template <typename Char>
+void PrintCharAndCodeTo(Char c, ostream* os) {
+  // First, print c as a literal in the most readable form we can find.
+  *os << GetCharWidthPrefix(c) << "'";
+  const CharFormat format = PrintAsCharLiteralTo(c, os);
+  *os << "'";
+
+  // To aid user debugging, we also print c's code in decimal, unless
+  // it's 0 (in which case c was printed as '\\0', making the code
+  // obvious).
+  if (c == 0)
+    return;
+  *os << " (" << static_cast<int>(c);
+
+  // For more convenience, we print c's code again in hexadecimal,
+  // unless c was already printed in the form '\x##' or the code is in
+  // [1, 9].
+  if (format == kHexEscape || (1 <= c && c <= 9)) {
+    // Do nothing.
+  } else {
+    *os << ", 0x" << String::FormatHexInt(static_cast<int>(c));
+  }
+  *os << ")";
+}
+
+void PrintTo(unsigned char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); }
+void PrintTo(signed char c, ::std::ostream* os) { PrintCharAndCodeTo(c, os); }
+
+// Prints a wchar_t as a symbol if it is printable or as its internal
+// code otherwise and also as its code.  L'\0' is printed as "L'\\0'".
+void PrintTo(wchar_t wc, ostream* os) { PrintCharAndCodeTo(wc, os); }
+
+// TODO(dcheng): Consider making this delegate to PrintCharAndCodeTo() as well.
+void PrintTo(char32_t c, ::std::ostream* os) {
+  *os << std::hex << "U+" << std::uppercase << std::setfill('0') << std::setw(4)
+      << static_cast<uint32_t>(c);
+}
+
+// Prints the given array of characters to the ostream.  CharType must be either
+// char, char8_t, char16_t, char32_t, or wchar_t.
+// The array starts at begin, the length is len, it may include '\0' characters
+// and may not be NUL-terminated.
+template <typename CharType>
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+static CharFormat PrintCharsAsStringTo(
+    const CharType* begin, size_t len, ostream* os) {
+  const char* const quote_prefix = GetCharWidthPrefix(*begin);
+  *os << quote_prefix << "\"";
+  bool is_previous_hex = false;
+  CharFormat print_format = kAsIs;
+  for (size_t index = 0; index < len; ++index) {
+    const CharType cur = begin[index];
+    if (is_previous_hex && IsXDigit(cur)) {
+      // Previous character is of '\x..' form and this character can be
+      // interpreted as another hexadecimal digit in its number. Break string to
+      // disambiguate.
+      *os << "\" " << quote_prefix << "\"";
+    }
+    is_previous_hex = PrintAsStringLiteralTo(cur, os) == kHexEscape;
+    // Remember if any characters required hex escaping.
+    if (is_previous_hex) {
+      print_format = kHexEscape;
+    }
+  }
+  *os << "\"";
+  return print_format;
+}
+
+// Prints a (const) char/wchar_t array of 'len' elements, starting at address
+// 'begin'.  CharType must be either char or wchar_t.
+template <typename CharType>
+GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+static void UniversalPrintCharArray(
+    const CharType* begin, size_t len, ostream* os) {
+  // The code
+  //   const char kFoo[] = "foo";
+  // generates an array of 4, not 3, elements, with the last one being '\0'.
+  //
+  // Therefore when printing a char array, we don't print the last element if
+  // it's '\0', such that the output matches the string literal as it's
+  // written in the source code.
+  if (len > 0 && begin[len - 1] == '\0') {
+    PrintCharsAsStringTo(begin, len - 1, os);
+    return;
+  }
+
+  // If, however, the last element in the array is not '\0', e.g.
+  //    const char kFoo[] = { 'f', 'o', 'o' };
+  // we must print the entire array.  We also print a message to indicate
+  // that the array is not NUL-terminated.
+  PrintCharsAsStringTo(begin, len, os);
+  *os << " (no terminating NUL)";
+}
+
+// Prints a (const) char array of 'len' elements, starting at address 'begin'.
+void UniversalPrintArray(const char* begin, size_t len, ostream* os) {
+  UniversalPrintCharArray(begin, len, os);
+}
+
+#ifdef __cpp_char8_t
+// Prints a (const) char8_t array of 'len' elements, starting at address
+// 'begin'.
+void UniversalPrintArray(const char8_t* begin, size_t len, ostream* os) {
+  UniversalPrintCharArray(begin, len, os);
+}
+#endif
+
+// Prints a (const) char16_t array of 'len' elements, starting at address
+// 'begin'.
+void UniversalPrintArray(const char16_t* begin, size_t len, ostream* os) {
+  UniversalPrintCharArray(begin, len, os);
+}
+
+// Prints a (const) char32_t array of 'len' elements, starting at address
+// 'begin'.
+void UniversalPrintArray(const char32_t* begin, size_t len, ostream* os) {
+  UniversalPrintCharArray(begin, len, os);
+}
+
+// Prints a (const) wchar_t array of 'len' elements, starting at address
+// 'begin'.
+void UniversalPrintArray(const wchar_t* begin, size_t len, ostream* os) {
+  UniversalPrintCharArray(begin, len, os);
+}
+
+namespace {
+
+// Prints a null-terminated C-style string to the ostream.
+template <typename Char>
+void PrintCStringTo(const Char* s, ostream* os) {
+  if (s == nullptr) {
+    *os << "NULL";
+  } else {
+    *os << ImplicitCast_<const void*>(s) << " pointing to ";
+    PrintCharsAsStringTo(s, std::char_traits<Char>::length(s), os);
+  }
+}
+
+}  // anonymous namespace
+
+void PrintTo(const char* s, ostream* os) { PrintCStringTo(s, os); }
+
+#ifdef __cpp_char8_t
+void PrintTo(const char8_t* s, ostream* os) { PrintCStringTo(s, os); }
+#endif
+
+void PrintTo(const char16_t* s, ostream* os) { PrintCStringTo(s, os); }
+
+void PrintTo(const char32_t* s, ostream* os) { PrintCStringTo(s, os); }
+
+// MSVC compiler can be configured to define whar_t as a typedef
+// of unsigned short. Defining an overload for const wchar_t* in that case
+// would cause pointers to unsigned shorts be printed as wide strings,
+// possibly accessing more memory than intended and causing invalid
+// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when
+// wchar_t is implemented as a native type.
+#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
+// Prints the given wide C string to the ostream.
+void PrintTo(const wchar_t* s, ostream* os) { PrintCStringTo(s, os); }
+#endif  // wchar_t is native
+
+namespace {
+
+bool ContainsUnprintableControlCodes(const char* str, size_t length) {
+  const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
+
+  for (size_t i = 0; i < length; i++) {
+    unsigned char ch = *s++;
+    if (std::iscntrl(ch)) {
+        switch (ch) {
+        case '\t':
+        case '\n':
+        case '\r':
+          break;
+        default:
+          return true;
+        }
+      }
+  }
+  return false;
+}
+
+bool IsUTF8TrailByte(unsigned char t) { return 0x80 <= t && t<= 0xbf; }
+
+bool IsValidUTF8(const char* str, size_t length) {
+  const unsigned char *s = reinterpret_cast<const unsigned char *>(str);
+
+  for (size_t i = 0; i < length;) {
+    unsigned char lead = s[i++];
+
+    if (lead <= 0x7f) {
+      continue;  // single-byte character (ASCII) 0..7F
+    }
+    if (lead < 0xc2) {
+      return false;  // trail byte or non-shortest form
+    } else if (lead <= 0xdf && (i + 1) <= length && IsUTF8TrailByte(s[i])) {
+      ++i;  // 2-byte character
+    } else if (0xe0 <= lead && lead <= 0xef && (i + 2) <= length &&
+               IsUTF8TrailByte(s[i]) &&
+               IsUTF8TrailByte(s[i + 1]) &&
+               // check for non-shortest form and surrogate
+               (lead != 0xe0 || s[i] >= 0xa0) &&
+               (lead != 0xed || s[i] < 0xa0)) {
+      i += 2;  // 3-byte character
+    } else if (0xf0 <= lead && lead <= 0xf4 && (i + 3) <= length &&
+               IsUTF8TrailByte(s[i]) &&
+               IsUTF8TrailByte(s[i + 1]) &&
+               IsUTF8TrailByte(s[i + 2]) &&
+               // check for non-shortest form
+               (lead != 0xf0 || s[i] >= 0x90) &&
+               (lead != 0xf4 || s[i] < 0x90)) {
+      i += 3;  // 4-byte character
+    } else {
+      return false;
+    }
+  }
+  return true;
+}
+
+void ConditionalPrintAsText(const char* str, size_t length, ostream* os) {
+  if (!ContainsUnprintableControlCodes(str, length) &&
+      IsValidUTF8(str, length)) {
+    *os << "\n    As Text: \"" << str << "\"";
+  }
+}
+
+}  // anonymous namespace
+
+void PrintStringTo(const ::std::string& s, ostream* os) {
+  if (PrintCharsAsStringTo(s.data(), s.size(), os) == kHexEscape) {
+    if (GTEST_FLAG(print_utf8)) {
+      ConditionalPrintAsText(s.data(), s.size(), os);
+    }
+  }
+}
+
+#ifdef __cpp_char8_t
+void PrintU8StringTo(const ::std::u8string& s, ostream* os) {
+  PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+#endif
+
+void PrintU16StringTo(const ::std::u16string& s, ostream* os) {
+  PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+
+void PrintU32StringTo(const ::std::u32string& s, ostream* os) {
+  PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+
+#if GTEST_HAS_STD_WSTRING
+void PrintWideStringTo(const ::std::wstring& s, ostream* os) {
+  PrintCharsAsStringTo(s.data(), s.size(), os);
+}
+#endif  // GTEST_HAS_STD_WSTRING
+
+}  // namespace internal
+
+}  // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+
+
+
+namespace testing {
+
+using internal::GetUnitTestImpl;
+
+// Gets the summary of the failure message by omitting the stack trace
+// in it.
+std::string TestPartResult::ExtractSummary(const char* message) {
+  const char* const stack_trace = strstr(message, internal::kStackTraceMarker);
+  return stack_trace == nullptr ? message : std::string(message, stack_trace);
+}
+
+// Prints a TestPartResult object.
+std::ostream& operator<<(std::ostream& os, const TestPartResult& result) {
+  return os << internal::FormatFileLocation(result.file_name(),
+                                            result.line_number())
+            << " "
+            << (result.type() == TestPartResult::kSuccess
+                    ? "Success"
+                    : result.type() == TestPartResult::kSkip
+                          ? "Skipped"
+                          : result.type() == TestPartResult::kFatalFailure
+                                ? "Fatal failure"
+                                : "Non-fatal failure")
+            << ":\n"
+            << result.message() << std::endl;
+}
+
+// Appends a TestPartResult to the array.
+void TestPartResultArray::Append(const TestPartResult& result) {
+  array_.push_back(result);
+}
+
+// Returns the TestPartResult at the given index (0-based).
+const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const {
+  if (index < 0 || index >= size()) {
+    printf("\nInvalid index (%d) into TestPartResultArray.\n", index);
+    internal::posix::Abort();
+  }
+
+  return array_[static_cast<size_t>(index)];
+}
+
+// Returns the number of TestPartResult objects in the array.
+int TestPartResultArray::size() const {
+  return static_cast<int>(array_.size());
+}
+
+namespace internal {
+
+HasNewFatalFailureHelper::HasNewFatalFailureHelper()
+    : has_new_fatal_failure_(false),
+      original_reporter_(GetUnitTestImpl()->
+                         GetTestPartResultReporterForCurrentThread()) {
+  GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this);
+}
+
+HasNewFatalFailureHelper::~HasNewFatalFailureHelper() {
+  GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(
+      original_reporter_);
+}
+
+void HasNewFatalFailureHelper::ReportTestPartResult(
+    const TestPartResult& result) {
+  if (result.fatally_failed())
+    has_new_fatal_failure_ = true;
+  original_reporter_->ReportTestPartResult(result);
+}
+
+}  // namespace internal
+
+}  // namespace testing
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+namespace testing {
+namespace internal {
+
+// Skips to the first non-space char in str. Returns an empty string if str
+// contains only whitespace characters.
+static const char* SkipSpaces(const char* str) {
+  while (IsSpace(*str))
+    str++;
+  return str;
+}
+
+static std::vector<std::string> SplitIntoTestNames(const char* src) {
+  std::vector<std::string> name_vec;
+  src = SkipSpaces(src);
+  for (; src != nullptr; src = SkipComma(src)) {
+    name_vec.push_back(StripTrailingSpaces(GetPrefixUntilComma(src)));
+  }
+  return name_vec;
+}
+
+// Verifies that registered_tests match the test names in
+// registered_tests_; returns registered_tests if successful, or
+// aborts the program otherwise.
+const char* TypedTestSuitePState::VerifyRegisteredTestNames(
+    const char* test_suite_name, const char* file, int line,
+    const char* registered_tests) {
+  RegisterTypeParameterizedTestSuite(test_suite_name, CodeLocation(file, line));
+
+  typedef RegisteredTestsMap::const_iterator RegisteredTestIter;
+  registered_ = true;
+
+  std::vector<std::string> name_vec = SplitIntoTestNames(registered_tests);
+
+  Message errors;
+
+  std::set<std::string> tests;
+  for (std::vector<std::string>::const_iterator name_it = name_vec.begin();
+       name_it != name_vec.end(); ++name_it) {
+    const std::string& name = *name_it;
+    if (tests.count(name) != 0) {
+      errors << "Test " << name << " is listed more than once.\n";
+      continue;
+    }
+
+    if (registered_tests_.count(name) != 0) {
+      tests.insert(name);
+    } else {
+      errors << "No test named " << name
+             << " can be found in this test suite.\n";
+    }
+  }
+
+  for (RegisteredTestIter it = registered_tests_.begin();
+       it != registered_tests_.end();
+       ++it) {
+    if (tests.count(it->first) == 0) {
+      errors << "You forgot to list test " << it->first << ".\n";
+    }
+  }
+
+  const std::string& errors_str = errors.GetString();
+  if (errors_str != "") {
+    fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(),
+            errors_str.c_str());
+    fflush(stderr);
+    posix::Abort();
+  }
+
+  return registered_tests;
+}
+
+}  // namespace internal
+}  // namespace testing
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Google C++ Mocking Framework (Google Mock)
+//
+// This file #includes all Google Mock implementation .cc files.  The
+// purpose is to allow a user to build Google Mock by compiling this
+// file alone.
+
+// This line ensures that gmock.h can be compiled on its own, even
+// when it's fused.
+#include "gmock/gmock.h"
+
+// The following lines pull in the real gmock *.cc files.
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements cardinalities.
+
+
+#include <limits.h>
+#include <ostream>  // NOLINT
+#include <sstream>
+#include <string>
+
+namespace testing {
+
+namespace {
+
+// Implements the Between(m, n) cardinality.
+class BetweenCardinalityImpl : public CardinalityInterface {
+ public:
+  BetweenCardinalityImpl(int min, int max)
+      : min_(min >= 0 ? min : 0),
+        max_(max >= min_ ? max : min_) {
+    std::stringstream ss;
+    if (min < 0) {
+      ss << "The invocation lower bound must be >= 0, "
+         << "but is actually " << min << ".";
+      internal::Expect(false, __FILE__, __LINE__, ss.str());
+    } else if (max < 0) {
+      ss << "The invocation upper bound must be >= 0, "
+         << "but is actually " << max << ".";
+      internal::Expect(false, __FILE__, __LINE__, ss.str());
+    } else if (min > max) {
+      ss << "The invocation upper bound (" << max
+         << ") must be >= the invocation lower bound (" << min
+         << ").";
+      internal::Expect(false, __FILE__, __LINE__, ss.str());
+    }
+  }
+
+  // Conservative estimate on the lower/upper bound of the number of
+  // calls allowed.
+  int ConservativeLowerBound() const override { return min_; }
+  int ConservativeUpperBound() const override { return max_; }
+
+  bool IsSatisfiedByCallCount(int call_count) const override {
+    return min_ <= call_count && call_count <= max_;
+  }
+
+  bool IsSaturatedByCallCount(int call_count) const override {
+    return call_count >= max_;
+  }
+
+  void DescribeTo(::std::ostream* os) const override;
+
+ private:
+  const int min_;
+  const int max_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(BetweenCardinalityImpl);
+};
+
+// Formats "n times" in a human-friendly way.
+inline std::string FormatTimes(int n) {
+  if (n == 1) {
+    return "once";
+  } else if (n == 2) {
+    return "twice";
+  } else {
+    std::stringstream ss;
+    ss << n << " times";
+    return ss.str();
+  }
+}
+
+// Describes the Between(m, n) cardinality in human-friendly text.
+void BetweenCardinalityImpl::DescribeTo(::std::ostream* os) const {
+  if (min_ == 0) {
+    if (max_ == 0) {
+      *os << "never called";
+    } else if (max_ == INT_MAX) {
+      *os << "called any number of times";
+    } else {
+      *os << "called at most " << FormatTimes(max_);
+    }
+  } else if (min_ == max_) {
+    *os << "called " << FormatTimes(min_);
+  } else if (max_ == INT_MAX) {
+    *os << "called at least " << FormatTimes(min_);
+  } else {
+    // 0 < min_ < max_ < INT_MAX
+    *os << "called between " << min_ << " and " << max_ << " times";
+  }
+}
+
+}  // Unnamed namespace
+
+// Describes the given call count to an ostream.
+void Cardinality::DescribeActualCallCountTo(int actual_call_count,
+                                            ::std::ostream* os) {
+  if (actual_call_count > 0) {
+    *os << "called " << FormatTimes(actual_call_count);
+  } else {
+    *os << "never called";
+  }
+}
+
+// Creates a cardinality that allows at least n calls.
+GTEST_API_ Cardinality AtLeast(int n) { return Between(n, INT_MAX); }
+
+// Creates a cardinality that allows at most n calls.
+GTEST_API_ Cardinality AtMost(int n) { return Between(0, n); }
+
+// Creates a cardinality that allows any number of calls.
+GTEST_API_ Cardinality AnyNumber() { return AtLeast(0); }
+
+// Creates a cardinality that allows between min and max calls.
+GTEST_API_ Cardinality Between(int min, int max) {
+  return Cardinality(new BetweenCardinalityImpl(min, max));
+}
+
+// Creates a cardinality that allows exactly n calls.
+GTEST_API_ Cardinality Exactly(int n) { return Between(n, n); }
+
+}  // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file defines some utilities useful for implementing Google
+// Mock.  They are subject to change without notice, so please DO NOT
+// USE THEM IN USER CODE.
+
+
+#include <ctype.h>
+#include <ostream>  // NOLINT
+#include <string>
+
+namespace testing {
+namespace internal {
+
+// Joins a vector of strings as if they are fields of a tuple; returns
+// the joined string.
+GTEST_API_ std::string JoinAsTuple(const Strings& fields) {
+  switch (fields.size()) {
+    case 0:
+      return "";
+    case 1:
+      return fields[0];
+    default:
+      std::string result = "(" + fields[0];
+      for (size_t i = 1; i < fields.size(); i++) {
+        result += ", ";
+        result += fields[i];
+      }
+      result += ")";
+      return result;
+  }
+}
+
+// Converts an identifier name to a space-separated list of lower-case
+// words.  Each maximum substring of the form [A-Za-z][a-z]*|\d+ is
+// treated as one word.  For example, both "FooBar123" and
+// "foo_bar_123" are converted to "foo bar 123".
+GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name) {
+  std::string result;
+  char prev_char = '\0';
+  for (const char* p = id_name; *p != '\0'; prev_char = *(p++)) {
+    // We don't care about the current locale as the input is
+    // guaranteed to be a valid C++ identifier name.
+    const bool starts_new_word = IsUpper(*p) ||
+        (!IsAlpha(prev_char) && IsLower(*p)) ||
+        (!IsDigit(prev_char) && IsDigit(*p));
+
+    if (IsAlNum(*p)) {
+      if (starts_new_word && result != "")
+        result += ' ';
+      result += ToLower(*p);
+    }
+  }
+  return result;
+}
+
+// This class reports Google Mock failures as Google Test failures.  A
+// user can define another class in a similar fashion if they intend to
+// use Google Mock with a testing framework other than Google Test.
+class GoogleTestFailureReporter : public FailureReporterInterface {
+ public:
+  void ReportFailure(FailureType type, const char* file, int line,
+                     const std::string& message) override {
+    AssertHelper(type == kFatal ?
+                 TestPartResult::kFatalFailure :
+                 TestPartResult::kNonFatalFailure,
+                 file,
+                 line,
+                 message.c_str()) = Message();
+    if (type == kFatal) {
+      posix::Abort();
+    }
+  }
+};
+
+// Returns the global failure reporter.  Will create a
+// GoogleTestFailureReporter and return it the first time called.
+GTEST_API_ FailureReporterInterface* GetFailureReporter() {
+  // Points to the global failure reporter used by Google Mock.  gcc
+  // guarantees that the following use of failure_reporter is
+  // thread-safe.  We may need to add additional synchronization to
+  // protect failure_reporter if we port Google Mock to other
+  // compilers.
+  static FailureReporterInterface* const failure_reporter =
+      new GoogleTestFailureReporter();
+  return failure_reporter;
+}
+
+// Protects global resources (stdout in particular) used by Log().
+static GTEST_DEFINE_STATIC_MUTEX_(g_log_mutex);
+
+// Returns true if and only if a log with the given severity is visible
+// according to the --gmock_verbose flag.
+GTEST_API_ bool LogIsVisible(LogSeverity severity) {
+  if (GMOCK_FLAG(verbose) == kInfoVerbosity) {
+    // Always show the log if --gmock_verbose=info.
+    return true;
+  } else if (GMOCK_FLAG(verbose) == kErrorVerbosity) {
+    // Always hide it if --gmock_verbose=error.
+    return false;
+  } else {
+    // If --gmock_verbose is neither "info" nor "error", we treat it
+    // as "warning" (its default value).
+    return severity == kWarning;
+  }
+}
+
+// Prints the given message to stdout if and only if 'severity' >= the level
+// specified by the --gmock_verbose flag.  If stack_frames_to_skip >=
+// 0, also prints the stack trace excluding the top
+// stack_frames_to_skip frames.  In opt mode, any positive
+// stack_frames_to_skip is treated as 0, since we don't know which
+// function calls will be inlined by the compiler and need to be
+// conservative.
+GTEST_API_ void Log(LogSeverity severity, const std::string& message,
+                    int stack_frames_to_skip) {
+  if (!LogIsVisible(severity))
+    return;
+
+  // Ensures that logs from different threads don't interleave.
+  MutexLock l(&g_log_mutex);
+
+  if (severity == kWarning) {
+    // Prints a GMOCK WARNING marker to make the warnings easily searchable.
+    std::cout << "\nGMOCK WARNING:";
+  }
+  // Pre-pends a new-line to message if it doesn't start with one.
+  if (message.empty() || message[0] != '\n') {
+    std::cout << "\n";
+  }
+  std::cout << message;
+  if (stack_frames_to_skip >= 0) {
+#ifdef NDEBUG
+    // In opt mode, we have to be conservative and skip no stack frame.
+    const int actual_to_skip = 0;
+#else
+    // In dbg mode, we can do what the caller tell us to do (plus one
+    // for skipping this function's stack frame).
+    const int actual_to_skip = stack_frames_to_skip + 1;
+#endif  // NDEBUG
+
+    // Appends a new-line to message if it doesn't end with one.
+    if (!message.empty() && *message.rbegin() != '\n') {
+      std::cout << "\n";
+    }
+    std::cout << "Stack trace:\n"
+         << ::testing::internal::GetCurrentOsStackTraceExceptTop(
+             ::testing::UnitTest::GetInstance(), actual_to_skip);
+  }
+  std::cout << ::std::flush;
+}
+
+GTEST_API_ WithoutMatchers GetWithoutMatchers() { return WithoutMatchers(); }
+
+GTEST_API_ void IllegalDoDefault(const char* file, int line) {
+  internal::Assert(
+      false, file, line,
+      "You are using DoDefault() inside a composite action like "
+      "DoAll() or WithArgs().  This is not supported for technical "
+      "reasons.  Please instead spell out the default action, or "
+      "assign the default action to an Action variable and use "
+      "the variable in various places.");
+}
+
+}  // namespace internal
+}  // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements Matcher<const string&>, Matcher<string>, and
+// utilities for defining matchers.
+
+
+#include <string.h>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+namespace testing {
+namespace internal {
+
+// Returns the description for a matcher defined using the MATCHER*()
+// macro where the user-supplied description string is "", if
+// 'negation' is false; otherwise returns the description of the
+// negation of the matcher.  'param_values' contains a list of strings
+// that are the print-out of the matcher's parameters.
+GTEST_API_ std::string FormatMatcherDescription(bool negation,
+                                                const char* matcher_name,
+                                                const Strings& param_values) {
+  std::string result = ConvertIdentifierNameToWords(matcher_name);
+  if (param_values.size() >= 1) result += " " + JoinAsTuple(param_values);
+  return negation ? "not (" + result + ")" : result;
+}
+
+// FindMaxBipartiteMatching and its helper class.
+//
+// Uses the well-known Ford-Fulkerson max flow method to find a maximum
+// bipartite matching. Flow is considered to be from left to right.
+// There is an implicit source node that is connected to all of the left
+// nodes, and an implicit sink node that is connected to all of the
+// right nodes. All edges have unit capacity.
+//
+// Neither the flow graph nor the residual flow graph are represented
+// explicitly. Instead, they are implied by the information in 'graph' and
+// a vector<int> called 'left_' whose elements are initialized to the
+// value kUnused. This represents the initial state of the algorithm,
+// where the flow graph is empty, and the residual flow graph has the
+// following edges:
+//   - An edge from source to each left_ node
+//   - An edge from each right_ node to sink
+//   - An edge from each left_ node to each right_ node, if the
+//     corresponding edge exists in 'graph'.
+//
+// When the TryAugment() method adds a flow, it sets left_[l] = r for some
+// nodes l and r. This induces the following changes:
+//   - The edges (source, l), (l, r), and (r, sink) are added to the
+//     flow graph.
+//   - The same three edges are removed from the residual flow graph.
+//   - The reverse edges (l, source), (r, l), and (sink, r) are added
+//     to the residual flow graph, which is a directional graph
+//     representing unused flow capacity.
+//
+// When the method augments a flow (moving left_[l] from some r1 to some
+// other r2), this can be thought of as "undoing" the above steps with
+// respect to r1 and "redoing" them with respect to r2.
+//
+// It bears repeating that the flow graph and residual flow graph are
+// never represented explicitly, but can be derived by looking at the
+// information in 'graph' and in left_.
+//
+// As an optimization, there is a second vector<int> called right_ which
+// does not provide any new information. Instead, it enables more
+// efficient queries about edges entering or leaving the right-side nodes
+// of the flow or residual flow graphs. The following invariants are
+// maintained:
+//
+// left[l] == kUnused or right[left[l]] == l
+// right[r] == kUnused or left[right[r]] == r
+//
+// . [ source ]                                        .
+// .   |||                                             .
+// .   |||                                             .
+// .   ||\--> left[0]=1  ---\    right[0]=-1 ----\     .
+// .   ||                   |                    |     .
+// .   |\---> left[1]=-1    \--> right[1]=0  ---\|     .
+// .   |                                        ||     .
+// .   \----> left[2]=2  ------> right[2]=2  --\||     .
+// .                                           |||     .
+// .         elements           matchers       vvv     .
+// .                                         [ sink ]  .
+//
+// See Also:
+//   [1] Cormen, et al (2001). "Section 26.2: The Ford-Fulkerson method".
+//       "Introduction to Algorithms (Second ed.)", pp. 651-664.
+//   [2] "Ford-Fulkerson algorithm", Wikipedia,
+//       'http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm'
+class MaxBipartiteMatchState {
+ public:
+  explicit MaxBipartiteMatchState(const MatchMatrix& graph)
+      : graph_(&graph),
+        left_(graph_->LhsSize(), kUnused),
+        right_(graph_->RhsSize(), kUnused) {}
+
+  // Returns the edges of a maximal match, each in the form {left, right}.
+  ElementMatcherPairs Compute() {
+    // 'seen' is used for path finding { 0: unseen, 1: seen }.
+    ::std::vector<char> seen;
+    // Searches the residual flow graph for a path from each left node to
+    // the sink in the residual flow graph, and if one is found, add flow
+    // to the graph. It's okay to search through the left nodes once. The
+    // edge from the implicit source node to each previously-visited left
+    // node will have flow if that left node has any path to the sink
+    // whatsoever. Subsequent augmentations can only add flow to the
+    // network, and cannot take away that previous flow unit from the source.
+    // Since the source-to-left edge can only carry one flow unit (or,
+    // each element can be matched to only one matcher), there is no need
+    // to visit the left nodes more than once looking for augmented paths.
+    // The flow is known to be possible or impossible by looking at the
+    // node once.
+    for (size_t ilhs = 0; ilhs < graph_->LhsSize(); ++ilhs) {
+      // Reset the path-marking vector and try to find a path from
+      // source to sink starting at the left_[ilhs] node.
+      GTEST_CHECK_(left_[ilhs] == kUnused)
+          << "ilhs: " << ilhs << ", left_[ilhs]: " << left_[ilhs];
+      // 'seen' initialized to 'graph_->RhsSize()' copies of 0.
+      seen.assign(graph_->RhsSize(), 0);
+      TryAugment(ilhs, &seen);
+    }
+    ElementMatcherPairs result;
+    for (size_t ilhs = 0; ilhs < left_.size(); ++ilhs) {
+      size_t irhs = left_[ilhs];
+      if (irhs == kUnused) continue;
+      result.push_back(ElementMatcherPair(ilhs, irhs));
+    }
+    return result;
+  }
+
+ private:
+  static const size_t kUnused = static_cast<size_t>(-1);
+
+  // Perform a depth-first search from left node ilhs to the sink.  If a
+  // path is found, flow is added to the network by linking the left and
+  // right vector elements corresponding each segment of the path.
+  // Returns true if a path to sink was found, which means that a unit of
+  // flow was added to the network. The 'seen' vector elements correspond
+  // to right nodes and are marked to eliminate cycles from the search.
+  //
+  // Left nodes will only be explored at most once because they
+  // are accessible from at most one right node in the residual flow
+  // graph.
+  //
+  // Note that left_[ilhs] is the only element of left_ that TryAugment will
+  // potentially transition from kUnused to another value. Any other
+  // left_ element holding kUnused before TryAugment will be holding it
+  // when TryAugment returns.
+  //
+  bool TryAugment(size_t ilhs, ::std::vector<char>* seen) {
+    for (size_t irhs = 0; irhs < graph_->RhsSize(); ++irhs) {
+      if ((*seen)[irhs]) continue;
+      if (!graph_->HasEdge(ilhs, irhs)) continue;
+      // There's an available edge from ilhs to irhs.
+      (*seen)[irhs] = 1;
+      // Next a search is performed to determine whether
+      // this edge is a dead end or leads to the sink.
+      //
+      // right_[irhs] == kUnused means that there is residual flow from
+      // right node irhs to the sink, so we can use that to finish this
+      // flow path and return success.
+      //
+      // Otherwise there is residual flow to some ilhs. We push flow
+      // along that path and call ourselves recursively to see if this
+      // ultimately leads to sink.
+      if (right_[irhs] == kUnused || TryAugment(right_[irhs], seen)) {
+        // Add flow from left_[ilhs] to right_[irhs].
+        left_[ilhs] = irhs;
+        right_[irhs] = ilhs;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  const MatchMatrix* graph_;  // not owned
+  // Each element of the left_ vector represents a left hand side node
+  // (i.e. an element) and each element of right_ is a right hand side
+  // node (i.e. a matcher). The values in the left_ vector indicate
+  // outflow from that node to a node on the right_ side. The values
+  // in the right_ indicate inflow, and specify which left_ node is
+  // feeding that right_ node, if any. For example, left_[3] == 1 means
+  // there's a flow from element #3 to matcher #1. Such a flow would also
+  // be redundantly represented in the right_ vector as right_[1] == 3.
+  // Elements of left_ and right_ are either kUnused or mutually
+  // referent. Mutually referent means that left_[right_[i]] = i and
+  // right_[left_[i]] = i.
+  ::std::vector<size_t> left_;
+  ::std::vector<size_t> right_;
+};
+
+const size_t MaxBipartiteMatchState::kUnused;
+
+GTEST_API_ ElementMatcherPairs FindMaxBipartiteMatching(const MatchMatrix& g) {
+  return MaxBipartiteMatchState(g).Compute();
+}
+
+static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs,
+                                     ::std::ostream* stream) {
+  typedef ElementMatcherPairs::const_iterator Iter;
+  ::std::ostream& os = *stream;
+  os << "{";
+  const char* sep = "";
+  for (Iter it = pairs.begin(); it != pairs.end(); ++it) {
+    os << sep << "\n  ("
+       << "element #" << it->first << ", "
+       << "matcher #" << it->second << ")";
+    sep = ",";
+  }
+  os << "\n}";
+}
+
+bool MatchMatrix::NextGraph() {
+  for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) {
+    for (size_t irhs = 0; irhs < RhsSize(); ++irhs) {
+      char& b = matched_[SpaceIndex(ilhs, irhs)];
+      if (!b) {
+        b = 1;
+        return true;
+      }
+      b = 0;
+    }
+  }
+  return false;
+}
+
+void MatchMatrix::Randomize() {
+  for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) {
+    for (size_t irhs = 0; irhs < RhsSize(); ++irhs) {
+      char& b = matched_[SpaceIndex(ilhs, irhs)];
+      b = static_cast<char>(rand() & 1);  // NOLINT
+    }
+  }
+}
+
+std::string MatchMatrix::DebugString() const {
+  ::std::stringstream ss;
+  const char* sep = "";
+  for (size_t i = 0; i < LhsSize(); ++i) {
+    ss << sep;
+    for (size_t j = 0; j < RhsSize(); ++j) {
+      ss << HasEdge(i, j);
+    }
+    sep = ";";
+  }
+  return ss.str();
+}
+
+void UnorderedElementsAreMatcherImplBase::DescribeToImpl(
+    ::std::ostream* os) const {
+  switch (match_flags()) {
+    case UnorderedMatcherRequire::ExactMatch:
+      if (matcher_describers_.empty()) {
+        *os << "is empty";
+        return;
+      }
+      if (matcher_describers_.size() == 1) {
+        *os << "has " << Elements(1) << " and that element ";
+        matcher_describers_[0]->DescribeTo(os);
+        return;
+      }
+      *os << "has " << Elements(matcher_describers_.size())
+          << " and there exists some permutation of elements such that:\n";
+      break;
+    case UnorderedMatcherRequire::Superset:
+      *os << "a surjection from elements to requirements exists such that:\n";
+      break;
+    case UnorderedMatcherRequire::Subset:
+      *os << "an injection from elements to requirements exists such that:\n";
+      break;
+  }
+
+  const char* sep = "";
+  for (size_t i = 0; i != matcher_describers_.size(); ++i) {
+    *os << sep;
+    if (match_flags() == UnorderedMatcherRequire::ExactMatch) {
+      *os << " - element #" << i << " ";
+    } else {
+      *os << " - an element ";
+    }
+    matcher_describers_[i]->DescribeTo(os);
+    if (match_flags() == UnorderedMatcherRequire::ExactMatch) {
+      sep = ", and\n";
+    } else {
+      sep = "\n";
+    }
+  }
+}
+
+void UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl(
+    ::std::ostream* os) const {
+  switch (match_flags()) {
+    case UnorderedMatcherRequire::ExactMatch:
+      if (matcher_describers_.empty()) {
+        *os << "isn't empty";
+        return;
+      }
+      if (matcher_describers_.size() == 1) {
+        *os << "doesn't have " << Elements(1) << ", or has " << Elements(1)
+            << " that ";
+        matcher_describers_[0]->DescribeNegationTo(os);
+        return;
+      }
+      *os << "doesn't have " << Elements(matcher_describers_.size())
+          << ", or there exists no permutation of elements such that:\n";
+      break;
+    case UnorderedMatcherRequire::Superset:
+      *os << "no surjection from elements to requirements exists such that:\n";
+      break;
+    case UnorderedMatcherRequire::Subset:
+      *os << "no injection from elements to requirements exists such that:\n";
+      break;
+  }
+  const char* sep = "";
+  for (size_t i = 0; i != matcher_describers_.size(); ++i) {
+    *os << sep;
+    if (match_flags() == UnorderedMatcherRequire::ExactMatch) {
+      *os << " - element #" << i << " ";
+    } else {
+      *os << " - an element ";
+    }
+    matcher_describers_[i]->DescribeTo(os);
+    if (match_flags() == UnorderedMatcherRequire::ExactMatch) {
+      sep = ", and\n";
+    } else {
+      sep = "\n";
+    }
+  }
+}
+
+// Checks that all matchers match at least one element, and that all
+// elements match at least one matcher. This enables faster matching
+// and better error reporting.
+// Returns false, writing an explanation to 'listener', if and only
+// if the success criteria are not met.
+bool UnorderedElementsAreMatcherImplBase::VerifyMatchMatrix(
+    const ::std::vector<std::string>& element_printouts,
+    const MatchMatrix& matrix, MatchResultListener* listener) const {
+  bool result = true;
+  ::std::vector<char> element_matched(matrix.LhsSize(), 0);
+  ::std::vector<char> matcher_matched(matrix.RhsSize(), 0);
+
+  for (size_t ilhs = 0; ilhs < matrix.LhsSize(); ilhs++) {
+    for (size_t irhs = 0; irhs < matrix.RhsSize(); irhs++) {
+      char matched = matrix.HasEdge(ilhs, irhs);
+      element_matched[ilhs] |= matched;
+      matcher_matched[irhs] |= matched;
+    }
+  }
+
+  if (match_flags() & UnorderedMatcherRequire::Superset) {
+    const char* sep =
+        "where the following matchers don't match any elements:\n";
+    for (size_t mi = 0; mi < matcher_matched.size(); ++mi) {
+      if (matcher_matched[mi]) continue;
+      result = false;
+      if (listener->IsInterested()) {
+        *listener << sep << "matcher #" << mi << ": ";
+        matcher_describers_[mi]->DescribeTo(listener->stream());
+        sep = ",\n";
+      }
+    }
+  }
+
+  if (match_flags() & UnorderedMatcherRequire::Subset) {
+    const char* sep =
+        "where the following elements don't match any matchers:\n";
+    const char* outer_sep = "";
+    if (!result) {
+      outer_sep = "\nand ";
+    }
+    for (size_t ei = 0; ei < element_matched.size(); ++ei) {
+      if (element_matched[ei]) continue;
+      result = false;
+      if (listener->IsInterested()) {
+        *listener << outer_sep << sep << "element #" << ei << ": "
+                  << element_printouts[ei];
+        sep = ",\n";
+        outer_sep = "";
+      }
+    }
+  }
+  return result;
+}
+
+bool UnorderedElementsAreMatcherImplBase::FindPairing(
+    const MatchMatrix& matrix, MatchResultListener* listener) const {
+  ElementMatcherPairs matches = FindMaxBipartiteMatching(matrix);
+
+  size_t max_flow = matches.size();
+  if ((match_flags() & UnorderedMatcherRequire::Superset) &&
+      max_flow < matrix.RhsSize()) {
+    if (listener->IsInterested()) {
+      *listener << "where no permutation of the elements can satisfy all "
+                   "matchers, and the closest match is "
+                << max_flow << " of " << matrix.RhsSize()
+                << " matchers with the pairings:\n";
+      LogElementMatcherPairVec(matches, listener->stream());
+    }
+    return false;
+  }
+  if ((match_flags() & UnorderedMatcherRequire::Subset) &&
+      max_flow < matrix.LhsSize()) {
+    if (listener->IsInterested()) {
+      *listener
+          << "where not all elements can be matched, and the closest match is "
+          << max_flow << " of " << matrix.RhsSize()
+          << " matchers with the pairings:\n";
+      LogElementMatcherPairVec(matches, listener->stream());
+    }
+    return false;
+  }
+
+  if (matches.size() > 1) {
+    if (listener->IsInterested()) {
+      const char* sep = "where:\n";
+      for (size_t mi = 0; mi < matches.size(); ++mi) {
+        *listener << sep << " - element #" << matches[mi].first
+                  << " is matched by matcher #" << matches[mi].second;
+        sep = ",\n";
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace internal
+}  // namespace testing
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements the spec builder syntax (ON_CALL and
+// EXPECT_CALL).
+
+
+#include <stdlib.h>
+
+#include <iostream>  // NOLINT
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+
+#if GTEST_OS_CYGWIN || GTEST_OS_LINUX || GTEST_OS_MAC
+# include <unistd.h>  // NOLINT
+#endif
+
+// Silence C4800 (C4800: 'int *const ': forcing value
+// to bool 'true' or 'false') for MSVC 15
+#ifdef _MSC_VER
+#if _MSC_VER == 1900
+#  pragma warning(push)
+#  pragma warning(disable:4800)
+#endif
+#endif
+
+namespace testing {
+namespace internal {
+
+// Protects the mock object registry (in class Mock), all function
+// mockers, and all expectations.
+GTEST_API_ GTEST_DEFINE_STATIC_MUTEX_(g_gmock_mutex);
+
+// Logs a message including file and line number information.
+GTEST_API_ void LogWithLocation(testing::internal::LogSeverity severity,
+                                const char* file, int line,
+                                const std::string& message) {
+  ::std::ostringstream s;
+  s << internal::FormatFileLocation(file, line) << " " << message
+    << ::std::endl;
+  Log(severity, s.str(), 0);
+}
+
+// Constructs an ExpectationBase object.
+ExpectationBase::ExpectationBase(const char* a_file, int a_line,
+                                 const std::string& a_source_text)
+    : file_(a_file),
+      line_(a_line),
+      source_text_(a_source_text),
+      cardinality_specified_(false),
+      cardinality_(Exactly(1)),
+      call_count_(0),
+      retired_(false),
+      extra_matcher_specified_(false),
+      repeated_action_specified_(false),
+      retires_on_saturation_(false),
+      last_clause_(kNone),
+      action_count_checked_(false) {}
+
+// Destructs an ExpectationBase object.
+ExpectationBase::~ExpectationBase() {}
+
+// Explicitly specifies the cardinality of this expectation.  Used by
+// the subclasses to implement the .Times() clause.
+void ExpectationBase::SpecifyCardinality(const Cardinality& a_cardinality) {
+  cardinality_specified_ = true;
+  cardinality_ = a_cardinality;
+}
+
+// Retires all pre-requisites of this expectation.
+void ExpectationBase::RetireAllPreRequisites()
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+  if (is_retired()) {
+    // We can take this short-cut as we never retire an expectation
+    // until we have retired all its pre-requisites.
+    return;
+  }
+
+  ::std::vector<ExpectationBase*> expectations(1, this);
+  while (!expectations.empty()) {
+    ExpectationBase* exp = expectations.back();
+    expectations.pop_back();
+
+    for (ExpectationSet::const_iterator it =
+             exp->immediate_prerequisites_.begin();
+         it != exp->immediate_prerequisites_.end(); ++it) {
+      ExpectationBase* next = it->expectation_base().get();
+      if (!next->is_retired()) {
+        next->Retire();
+        expectations.push_back(next);
+      }
+    }
+  }
+}
+
+// Returns true if and only if all pre-requisites of this expectation
+// have been satisfied.
+bool ExpectationBase::AllPrerequisitesAreSatisfied() const
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+  g_gmock_mutex.AssertHeld();
+  ::std::vector<const ExpectationBase*> expectations(1, this);
+  while (!expectations.empty()) {
+    const ExpectationBase* exp = expectations.back();
+    expectations.pop_back();
+
+    for (ExpectationSet::const_iterator it =
+             exp->immediate_prerequisites_.begin();
+         it != exp->immediate_prerequisites_.end(); ++it) {
+      const ExpectationBase* next = it->expectation_base().get();
+      if (!next->IsSatisfied()) return false;
+      expectations.push_back(next);
+    }
+  }
+  return true;
+}
+
+// Adds unsatisfied pre-requisites of this expectation to 'result'.
+void ExpectationBase::FindUnsatisfiedPrerequisites(ExpectationSet* result) const
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+  g_gmock_mutex.AssertHeld();
+  ::std::vector<const ExpectationBase*> expectations(1, this);
+  while (!expectations.empty()) {
+    const ExpectationBase* exp = expectations.back();
+    expectations.pop_back();
+
+    for (ExpectationSet::const_iterator it =
+             exp->immediate_prerequisites_.begin();
+         it != exp->immediate_prerequisites_.end(); ++it) {
+      const ExpectationBase* next = it->expectation_base().get();
+
+      if (next->IsSatisfied()) {
+        // If *it is satisfied and has a call count of 0, some of its
+        // pre-requisites may not be satisfied yet.
+        if (next->call_count_ == 0) {
+          expectations.push_back(next);
+        }
+      } else {
+        // Now that we know next is unsatisfied, we are not so interested
+        // in whether its pre-requisites are satisfied.  Therefore we
+        // don't iterate into it here.
+        *result += *it;
+      }
+    }
+  }
+}
+
+// Describes how many times a function call matching this
+// expectation has occurred.
+void ExpectationBase::DescribeCallCountTo(::std::ostream* os) const
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+  g_gmock_mutex.AssertHeld();
+
+  // Describes how many times the function is expected to be called.
+  *os << "         Expected: to be ";
+  cardinality().DescribeTo(os);
+  *os << "\n           Actual: ";
+  Cardinality::DescribeActualCallCountTo(call_count(), os);
+
+  // Describes the state of the expectation (e.g. is it satisfied?
+  // is it active?).
+  *os << " - " << (IsOverSaturated() ? "over-saturated" :
+                   IsSaturated() ? "saturated" :
+                   IsSatisfied() ? "satisfied" : "unsatisfied")
+      << " and "
+      << (is_retired() ? "retired" : "active");
+}
+
+// Checks the action count (i.e. the number of WillOnce() and
+// WillRepeatedly() clauses) against the cardinality if this hasn't
+// been done before.  Prints a warning if there are too many or too
+// few actions.
+void ExpectationBase::CheckActionCountIfNotDone() const
+    GTEST_LOCK_EXCLUDED_(mutex_) {
+  bool should_check = false;
+  {
+    MutexLock l(&mutex_);
+    if (!action_count_checked_) {
+      action_count_checked_ = true;
+      should_check = true;
+    }
+  }
+
+  if (should_check) {
+    if (!cardinality_specified_) {
+      // The cardinality was inferred - no need to check the action
+      // count against it.
+      return;
+    }
+
+    // The cardinality was explicitly specified.
+    const int action_count = static_cast<int>(untyped_actions_.size());
+    const int upper_bound = cardinality().ConservativeUpperBound();
+    const int lower_bound = cardinality().ConservativeLowerBound();
+    bool too_many;  // True if there are too many actions, or false
+    // if there are too few.
+    if (action_count > upper_bound ||
+        (action_count == upper_bound && repeated_action_specified_)) {
+      too_many = true;
+    } else if (0 < action_count && action_count < lower_bound &&
+               !repeated_action_specified_) {
+      too_many = false;
+    } else {
+      return;
+    }
+
+    ::std::stringstream ss;
+    DescribeLocationTo(&ss);
+    ss << "Too " << (too_many ? "many" : "few")
+       << " actions specified in " << source_text() << "...\n"
+       << "Expected to be ";
+    cardinality().DescribeTo(&ss);
+    ss << ", but has " << (too_many ? "" : "only ")
+       << action_count << " WillOnce()"
+       << (action_count == 1 ? "" : "s");
+    if (repeated_action_specified_) {
+      ss << " and a WillRepeatedly()";
+    }
+    ss << ".";
+    Log(kWarning, ss.str(), -1);  // -1 means "don't print stack trace".
+  }
+}
+
+// Implements the .Times() clause.
+void ExpectationBase::UntypedTimes(const Cardinality& a_cardinality) {
+  if (last_clause_ == kTimes) {
+    ExpectSpecProperty(false,
+                       ".Times() cannot appear "
+                       "more than once in an EXPECT_CALL().");
+  } else {
+    ExpectSpecProperty(last_clause_ < kTimes,
+                       ".Times() cannot appear after "
+                       ".InSequence(), .WillOnce(), .WillRepeatedly(), "
+                       "or .RetiresOnSaturation().");
+  }
+  last_clause_ = kTimes;
+
+  SpecifyCardinality(a_cardinality);
+}
+
+// Points to the implicit sequence introduced by a living InSequence
+// object (if any) in the current thread or NULL.
+GTEST_API_ ThreadLocal<Sequence*> g_gmock_implicit_sequence;
+
+// Reports an uninteresting call (whose description is in msg) in the
+// manner specified by 'reaction'.
+void ReportUninterestingCall(CallReaction reaction, const std::string& msg) {
+  // Include a stack trace only if --gmock_verbose=info is specified.
+  const int stack_frames_to_skip =
+      GMOCK_FLAG(verbose) == kInfoVerbosity ? 3 : -1;
+  switch (reaction) {
+    case kAllow:
+      Log(kInfo, msg, stack_frames_to_skip);
+      break;
+    case kWarn:
+      Log(kWarning,
+          msg +
+              "\nNOTE: You can safely ignore the above warning unless this "
+              "call should not happen.  Do not suppress it by blindly adding "
+              "an EXPECT_CALL() if you don't mean to enforce the call.  "
+              "See "
+              "https://github.com/google/googletest/blob/master/docs/"
+              "gmock_cook_book.md#"
+              "knowing-when-to-expect for details.\n",
+          stack_frames_to_skip);
+      break;
+    default:  // FAIL
+      Expect(false, nullptr, -1, msg);
+  }
+}
+
+UntypedFunctionMockerBase::UntypedFunctionMockerBase()
+    : mock_obj_(nullptr), name_("") {}
+
+UntypedFunctionMockerBase::~UntypedFunctionMockerBase() {}
+
+// Sets the mock object this mock method belongs to, and registers
+// this information in the global mock registry.  Will be called
+// whenever an EXPECT_CALL() or ON_CALL() is executed on this mock
+// method.
+void UntypedFunctionMockerBase::RegisterOwner(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+  {
+    MutexLock l(&g_gmock_mutex);
+    mock_obj_ = mock_obj;
+  }
+  Mock::Register(mock_obj, this);
+}
+
+// Sets the mock object this mock method belongs to, and sets the name
+// of the mock function.  Will be called upon each invocation of this
+// mock function.
+void UntypedFunctionMockerBase::SetOwnerAndName(const void* mock_obj,
+                                                const char* name)
+    GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+  // We protect name_ under g_gmock_mutex in case this mock function
+  // is called from two threads concurrently.
+  MutexLock l(&g_gmock_mutex);
+  mock_obj_ = mock_obj;
+  name_ = name;
+}
+
+// Returns the name of the function being mocked.  Must be called
+// after RegisterOwner() or SetOwnerAndName() has been called.
+const void* UntypedFunctionMockerBase::MockObject() const
+    GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+  const void* mock_obj;
+  {
+    // We protect mock_obj_ under g_gmock_mutex in case this mock
+    // function is called from two threads concurrently.
+    MutexLock l(&g_gmock_mutex);
+    Assert(mock_obj_ != nullptr, __FILE__, __LINE__,
+           "MockObject() must not be called before RegisterOwner() or "
+           "SetOwnerAndName() has been called.");
+    mock_obj = mock_obj_;
+  }
+  return mock_obj;
+}
+
+// Returns the name of this mock method.  Must be called after
+// SetOwnerAndName() has been called.
+const char* UntypedFunctionMockerBase::Name() const
+    GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+  const char* name;
+  {
+    // We protect name_ under g_gmock_mutex in case this mock
+    // function is called from two threads concurrently.
+    MutexLock l(&g_gmock_mutex);
+    Assert(name_ != nullptr, __FILE__, __LINE__,
+           "Name() must not be called before SetOwnerAndName() has "
+           "been called.");
+    name = name_;
+  }
+  return name;
+}
+
+// Calculates the result of invoking this mock function with the given
+// arguments, prints it, and returns it.  The caller is responsible
+// for deleting the result.
+UntypedActionResultHolderBase* UntypedFunctionMockerBase::UntypedInvokeWith(
+    void* const untyped_args) GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+  // See the definition of untyped_expectations_ for why access to it
+  // is unprotected here.
+  if (untyped_expectations_.size() == 0) {
+    // No expectation is set on this mock method - we have an
+    // uninteresting call.
+
+    // We must get Google Mock's reaction on uninteresting calls
+    // made on this mock object BEFORE performing the action,
+    // because the action may DELETE the mock object and make the
+    // following expression meaningless.
+    const CallReaction reaction =
+        Mock::GetReactionOnUninterestingCalls(MockObject());
+
+    // True if and only if we need to print this call's arguments and return
+    // value.  This definition must be kept in sync with
+    // the behavior of ReportUninterestingCall().
+    const bool need_to_report_uninteresting_call =
+        // If the user allows this uninteresting call, we print it
+        // only when they want informational messages.
+        reaction == kAllow ? LogIsVisible(kInfo) :
+                           // If the user wants this to be a warning, we print
+                           // it only when they want to see warnings.
+            reaction == kWarn
+                ? LogIsVisible(kWarning)
+                :
+                // Otherwise, the user wants this to be an error, and we
+                // should always print detailed information in the error.
+                true;
+
+    if (!need_to_report_uninteresting_call) {
+      // Perform the action without printing the call information.
+      return this->UntypedPerformDefaultAction(
+          untyped_args, "Function call: " + std::string(Name()));
+    }
+
+    // Warns about the uninteresting call.
+    ::std::stringstream ss;
+    this->UntypedDescribeUninterestingCall(untyped_args, &ss);
+
+    // Calculates the function result.
+    UntypedActionResultHolderBase* const result =
+        this->UntypedPerformDefaultAction(untyped_args, ss.str());
+
+    // Prints the function result.
+    if (result != nullptr) result->PrintAsActionResult(&ss);
+
+    ReportUninterestingCall(reaction, ss.str());
+    return result;
+  }
+
+  bool is_excessive = false;
+  ::std::stringstream ss;
+  ::std::stringstream why;
+  ::std::stringstream loc;
+  const void* untyped_action = nullptr;
+
+  // The UntypedFindMatchingExpectation() function acquires and
+  // releases g_gmock_mutex.
+
+  const ExpectationBase* const untyped_expectation =
+      this->UntypedFindMatchingExpectation(untyped_args, &untyped_action,
+                                           &is_excessive, &ss, &why);
+  const bool found = untyped_expectation != nullptr;
+
+  // True if and only if we need to print the call's arguments
+  // and return value.
+  // This definition must be kept in sync with the uses of Expect()
+  // and Log() in this function.
+  const bool need_to_report_call =
+      !found || is_excessive || LogIsVisible(kInfo);
+  if (!need_to_report_call) {
+    // Perform the action without printing the call information.
+    return untyped_action == nullptr
+               ? this->UntypedPerformDefaultAction(untyped_args, "")
+               : this->UntypedPerformAction(untyped_action, untyped_args);
+  }
+
+  ss << "    Function call: " << Name();
+  this->UntypedPrintArgs(untyped_args, &ss);
+
+  // In case the action deletes a piece of the expectation, we
+  // generate the message beforehand.
+  if (found && !is_excessive) {
+    untyped_expectation->DescribeLocationTo(&loc);
+  }
+
+  UntypedActionResultHolderBase* result = nullptr;
+
+  auto perform_action = [&] {
+    return untyped_action == nullptr
+               ? this->UntypedPerformDefaultAction(untyped_args, ss.str())
+               : this->UntypedPerformAction(untyped_action, untyped_args);
+  };
+  auto handle_failures = [&] {
+    ss << "\n" << why.str();
+
+    if (!found) {
+      // No expectation matches this call - reports a failure.
+      Expect(false, nullptr, -1, ss.str());
+    } else if (is_excessive) {
+      // We had an upper-bound violation and the failure message is in ss.
+      Expect(false, untyped_expectation->file(), untyped_expectation->line(),
+             ss.str());
+    } else {
+      // We had an expected call and the matching expectation is
+      // described in ss.
+      Log(kInfo, loc.str() + ss.str(), 2);
+    }
+  };
+#if GTEST_HAS_EXCEPTIONS
+  try {
+    result = perform_action();
+  } catch (...) {
+    handle_failures();
+    throw;
+  }
+#else
+  result = perform_action();
+#endif
+
+  if (result != nullptr) result->PrintAsActionResult(&ss);
+  handle_failures();
+  return result;
+}
+
+// Returns an Expectation object that references and co-owns exp,
+// which must be an expectation on this mock function.
+Expectation UntypedFunctionMockerBase::GetHandleOf(ExpectationBase* exp) {
+  // See the definition of untyped_expectations_ for why access to it
+  // is unprotected here.
+  for (UntypedExpectations::const_iterator it =
+           untyped_expectations_.begin();
+       it != untyped_expectations_.end(); ++it) {
+    if (it->get() == exp) {
+      return Expectation(*it);
+    }
+  }
+
+  Assert(false, __FILE__, __LINE__, "Cannot find expectation.");
+  return Expectation();
+  // The above statement is just to make the code compile, and will
+  // never be executed.
+}
+
+// Verifies that all expectations on this mock function have been
+// satisfied.  Reports one or more Google Test non-fatal failures
+// and returns false if not.
+bool UntypedFunctionMockerBase::VerifyAndClearExpectationsLocked()
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+  g_gmock_mutex.AssertHeld();
+  bool expectations_met = true;
+  for (UntypedExpectations::const_iterator it =
+           untyped_expectations_.begin();
+       it != untyped_expectations_.end(); ++it) {
+    ExpectationBase* const untyped_expectation = it->get();
+    if (untyped_expectation->IsOverSaturated()) {
+      // There was an upper-bound violation.  Since the error was
+      // already reported when it occurred, there is no need to do
+      // anything here.
+      expectations_met = false;
+    } else if (!untyped_expectation->IsSatisfied()) {
+      expectations_met = false;
+      ::std::stringstream ss;
+      ss  << "Actual function call count doesn't match "
+          << untyped_expectation->source_text() << "...\n";
+      // No need to show the source file location of the expectation
+      // in the description, as the Expect() call that follows already
+      // takes care of it.
+      untyped_expectation->MaybeDescribeExtraMatcherTo(&ss);
+      untyped_expectation->DescribeCallCountTo(&ss);
+      Expect(false, untyped_expectation->file(),
+             untyped_expectation->line(), ss.str());
+    }
+  }
+
+  // Deleting our expectations may trigger other mock objects to be deleted, for
+  // example if an action contains a reference counted smart pointer to that
+  // mock object, and that is the last reference. So if we delete our
+  // expectations within the context of the global mutex we may deadlock when
+  // this method is called again. Instead, make a copy of the set of
+  // expectations to delete, clear our set within the mutex, and then clear the
+  // copied set outside of it.
+  UntypedExpectations expectations_to_delete;
+  untyped_expectations_.swap(expectations_to_delete);
+
+  g_gmock_mutex.Unlock();
+  expectations_to_delete.clear();
+  g_gmock_mutex.Lock();
+
+  return expectations_met;
+}
+
+CallReaction intToCallReaction(int mock_behavior) {
+  if (mock_behavior >= kAllow && mock_behavior <= kFail) {
+    return static_cast<internal::CallReaction>(mock_behavior);
+  }
+  return kWarn;
+}
+
+}  // namespace internal
+
+// Class Mock.
+
+namespace {
+
+typedef std::set<internal::UntypedFunctionMockerBase*> FunctionMockers;
+
+// The current state of a mock object.  Such information is needed for
+// detecting leaked mock objects and explicitly verifying a mock's
+// expectations.
+struct MockObjectState {
+  MockObjectState()
+      : first_used_file(nullptr), first_used_line(-1), leakable(false) {}
+
+  // Where in the source file an ON_CALL or EXPECT_CALL is first
+  // invoked on this mock object.
+  const char* first_used_file;
+  int first_used_line;
+  ::std::string first_used_test_suite;
+  ::std::string first_used_test;
+  bool leakable;  // true if and only if it's OK to leak the object.
+  FunctionMockers function_mockers;  // All registered methods of the object.
+};
+
+// A global registry holding the state of all mock objects that are
+// alive.  A mock object is added to this registry the first time
+// Mock::AllowLeak(), ON_CALL(), or EXPECT_CALL() is called on it.  It
+// is removed from the registry in the mock object's destructor.
+class MockObjectRegistry {
+ public:
+  // Maps a mock object (identified by its address) to its state.
+  typedef std::map<const void*, MockObjectState> StateMap;
+
+  // This destructor will be called when a program exits, after all
+  // tests in it have been run.  By then, there should be no mock
+  // object alive.  Therefore we report any living object as test
+  // failure, unless the user explicitly asked us to ignore it.
+  ~MockObjectRegistry() {
+    if (!GMOCK_FLAG(catch_leaked_mocks))
+      return;
+
+    int leaked_count = 0;
+    for (StateMap::const_iterator it = states_.begin(); it != states_.end();
+         ++it) {
+      if (it->second.leakable)  // The user said it's fine to leak this object.
+        continue;
+
+      // FIXME: Print the type of the leaked object.
+      // This can help the user identify the leaked object.
+      std::cout << "\n";
+      const MockObjectState& state = it->second;
+      std::cout << internal::FormatFileLocation(state.first_used_file,
+                                                state.first_used_line);
+      std::cout << " ERROR: this mock object";
+      if (state.first_used_test != "") {
+        std::cout << " (used in test " << state.first_used_test_suite << "."
+                  << state.first_used_test << ")";
+      }
+      std::cout << " should be deleted but never is. Its address is @"
+           << it->first << ".";
+      leaked_count++;
+    }
+    if (leaked_count > 0) {
+      std::cout << "\nERROR: " << leaked_count << " leaked mock "
+                << (leaked_count == 1 ? "object" : "objects")
+                << " found at program exit. Expectations on a mock object are "
+                   "verified when the object is destructed. Leaking a mock "
+                   "means that its expectations aren't verified, which is "
+                   "usually a test bug. If you really intend to leak a mock, "
+                   "you can suppress this error using "
+                   "testing::Mock::AllowLeak(mock_object), or you may use a "
+                   "fake or stub instead of a mock.\n";
+      std::cout.flush();
+      ::std::cerr.flush();
+      // RUN_ALL_TESTS() has already returned when this destructor is
+      // called.  Therefore we cannot use the normal Google Test
+      // failure reporting mechanism.
+      _exit(1);  // We cannot call exit() as it is not reentrant and
+                 // may already have been called.
+    }
+  }
+
+  StateMap& states() { return states_; }
+
+ private:
+  StateMap states_;
+};
+
+// Protected by g_gmock_mutex.
+MockObjectRegistry g_mock_object_registry;
+
+// Maps a mock object to the reaction Google Mock should have when an
+// uninteresting method is called.  Protected by g_gmock_mutex.
+std::map<const void*, internal::CallReaction> g_uninteresting_call_reaction;
+
+// Sets the reaction Google Mock should have when an uninteresting
+// method of the given mock object is called.
+void SetReactionOnUninterestingCalls(const void* mock_obj,
+                                     internal::CallReaction reaction)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  g_uninteresting_call_reaction[mock_obj] = reaction;
+}
+
+}  // namespace
+
+// Tells Google Mock to allow uninteresting calls on the given mock
+// object.
+void Mock::AllowUninterestingCalls(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  SetReactionOnUninterestingCalls(mock_obj, internal::kAllow);
+}
+
+// Tells Google Mock to warn the user about uninteresting calls on the
+// given mock object.
+void Mock::WarnUninterestingCalls(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  SetReactionOnUninterestingCalls(mock_obj, internal::kWarn);
+}
+
+// Tells Google Mock to fail uninteresting calls on the given mock
+// object.
+void Mock::FailUninterestingCalls(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  SetReactionOnUninterestingCalls(mock_obj, internal::kFail);
+}
+
+// Tells Google Mock the given mock object is being destroyed and its
+// entry in the call-reaction table should be removed.
+void Mock::UnregisterCallReaction(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  g_uninteresting_call_reaction.erase(mock_obj);
+}
+
+// Returns the reaction Google Mock will have on uninteresting calls
+// made on the given mock object.
+internal::CallReaction Mock::GetReactionOnUninterestingCalls(
+    const void* mock_obj)
+        GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  return (g_uninteresting_call_reaction.count(mock_obj) == 0) ?
+      internal::intToCallReaction(GMOCK_FLAG(default_mock_behavior)) :
+      g_uninteresting_call_reaction[mock_obj];
+}
+
+// Tells Google Mock to ignore mock_obj when checking for leaked mock
+// objects.
+void Mock::AllowLeak(const void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  g_mock_object_registry.states()[mock_obj].leakable = true;
+}
+
+// Verifies and clears all expectations on the given mock object.  If
+// the expectations aren't satisfied, generates one or more Google
+// Test non-fatal failures and returns false.
+bool Mock::VerifyAndClearExpectations(void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  return VerifyAndClearExpectationsLocked(mock_obj);
+}
+
+// Verifies all expectations on the given mock object and clears its
+// default actions and expectations.  Returns true if and only if the
+// verification was successful.
+bool Mock::VerifyAndClear(void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  ClearDefaultActionsLocked(mock_obj);
+  return VerifyAndClearExpectationsLocked(mock_obj);
+}
+
+// Verifies and clears all expectations on the given mock object.  If
+// the expectations aren't satisfied, generates one or more Google
+// Test non-fatal failures and returns false.
+bool Mock::VerifyAndClearExpectationsLocked(void* mock_obj)
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) {
+  internal::g_gmock_mutex.AssertHeld();
+  if (g_mock_object_registry.states().count(mock_obj) == 0) {
+    // No EXPECT_CALL() was set on the given mock object.
+    return true;
+  }
+
+  // Verifies and clears the expectations on each mock method in the
+  // given mock object.
+  bool expectations_met = true;
+  FunctionMockers& mockers =
+      g_mock_object_registry.states()[mock_obj].function_mockers;
+  for (FunctionMockers::const_iterator it = mockers.begin();
+       it != mockers.end(); ++it) {
+    if (!(*it)->VerifyAndClearExpectationsLocked()) {
+      expectations_met = false;
+    }
+  }
+
+  // We don't clear the content of mockers, as they may still be
+  // needed by ClearDefaultActionsLocked().
+  return expectations_met;
+}
+
+bool Mock::IsNaggy(void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kWarn;
+}
+bool Mock::IsNice(void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kAllow;
+}
+bool Mock::IsStrict(void* mock_obj)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  return Mock::GetReactionOnUninterestingCalls(mock_obj) == internal::kFail;
+}
+
+// Registers a mock object and a mock method it owns.
+void Mock::Register(const void* mock_obj,
+                    internal::UntypedFunctionMockerBase* mocker)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  g_mock_object_registry.states()[mock_obj].function_mockers.insert(mocker);
+}
+
+// Tells Google Mock where in the source code mock_obj is used in an
+// ON_CALL or EXPECT_CALL.  In case mock_obj is leaked, this
+// information helps the user identify which object it is.
+void Mock::RegisterUseByOnCallOrExpectCall(const void* mock_obj,
+                                           const char* file, int line)
+    GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex) {
+  internal::MutexLock l(&internal::g_gmock_mutex);
+  MockObjectState& state = g_mock_object_registry.states()[mock_obj];
+  if (state.first_used_file == nullptr) {
+    state.first_used_file = file;
+    state.first_used_line = line;
+    const TestInfo* const test_info =
+        UnitTest::GetInstance()->current_test_info();
+    if (test_info != nullptr) {
+      state.first_used_test_suite = test_info->test_suite_name();
+      state.first_used_test = test_info->name();
+    }
+  }
+}
+
+// Unregisters a mock method; removes the owning mock object from the
+// registry when the last mock method associated with it has been
+// unregistered.  This is called only in the destructor of
+// FunctionMockerBase.
+void Mock::UnregisterLocked(internal::UntypedFunctionMockerBase* mocker)
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) {
+  internal::g_gmock_mutex.AssertHeld();
+  for (MockObjectRegistry::StateMap::iterator it =
+           g_mock_object_registry.states().begin();
+       it != g_mock_object_registry.states().end(); ++it) {
+    FunctionMockers& mockers = it->second.function_mockers;
+    if (mockers.erase(mocker) > 0) {
+      // mocker was in mockers and has been just removed.
+      if (mockers.empty()) {
+        g_mock_object_registry.states().erase(it);
+      }
+      return;
+    }
+  }
+}
+
+// Clears all ON_CALL()s set on the given mock object.
+void Mock::ClearDefaultActionsLocked(void* mock_obj)
+    GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex) {
+  internal::g_gmock_mutex.AssertHeld();
+
+  if (g_mock_object_registry.states().count(mock_obj) == 0) {
+    // No ON_CALL() was set on the given mock object.
+    return;
+  }
+
+  // Clears the default actions for each mock method in the given mock
+  // object.
+  FunctionMockers& mockers =
+      g_mock_object_registry.states()[mock_obj].function_mockers;
+  for (FunctionMockers::const_iterator it = mockers.begin();
+       it != mockers.end(); ++it) {
+    (*it)->ClearDefaultActionsLocked();
+  }
+
+  // We don't clear the content of mockers, as they may still be
+  // needed by VerifyAndClearExpectationsLocked().
+}
+
+Expectation::Expectation() {}
+
+Expectation::Expectation(
+    const std::shared_ptr<internal::ExpectationBase>& an_expectation_base)
+    : expectation_base_(an_expectation_base) {}
+
+Expectation::~Expectation() {}
+
+// Adds an expectation to a sequence.
+void Sequence::AddExpectation(const Expectation& expectation) const {
+  if (*last_expectation_ != expectation) {
+    if (last_expectation_->expectation_base() != nullptr) {
+      expectation.expectation_base()->immediate_prerequisites_
+          += *last_expectation_;
+    }
+    *last_expectation_ = expectation;
+  }
+}
+
+// Creates the implicit sequence if there isn't one.
+InSequence::InSequence() {
+  if (internal::g_gmock_implicit_sequence.get() == nullptr) {
+    internal::g_gmock_implicit_sequence.set(new Sequence);
+    sequence_created_ = true;
+  } else {
+    sequence_created_ = false;
+  }
+}
+
+// Deletes the implicit sequence if it was created by the constructor
+// of this object.
+InSequence::~InSequence() {
+  if (sequence_created_) {
+    delete internal::g_gmock_implicit_sequence.get();
+    internal::g_gmock_implicit_sequence.set(nullptr);
+  }
+}
+
+}  // namespace testing
+
+#ifdef _MSC_VER
+#if _MSC_VER == 1900
+#  pragma warning(pop)
+#endif
+#endif
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+namespace testing {
+
+GMOCK_DEFINE_bool_(catch_leaked_mocks, true,
+                   "true if and only if Google Mock should report leaked "
+                   "mock objects as failures.");
+
+GMOCK_DEFINE_string_(verbose, internal::kWarningVerbosity,
+                     "Controls how verbose Google Mock's output is."
+                     "  Valid values:\n"
+                     "  info    - prints all messages.\n"
+                     "  warning - prints warnings and errors.\n"
+                     "  error   - prints errors only.");
+
+GMOCK_DEFINE_int32_(default_mock_behavior, 1,
+                    "Controls the default behavior of mocks."
+                    "  Valid values:\n"
+                    "  0 - by default, mocks act as NiceMocks.\n"
+                    "  1 - by default, mocks act as NaggyMocks.\n"
+                    "  2 - by default, mocks act as StrictMocks.");
+
+namespace internal {
+
+// Parses a string as a command line flag.  The string should have the
+// format "--gmock_flag=value".  When def_optional is true, the
+// "=value" part can be omitted.
+//
+// Returns the value of the flag, or NULL if the parsing failed.
+static const char* ParseGoogleMockFlagValue(const char* str,
+                                            const char* flag,
+                                            bool def_optional) {
+  // str and flag must not be NULL.
+  if (str == nullptr || flag == nullptr) return nullptr;
+
+  // The flag must start with "--gmock_".
+  const std::string flag_str = std::string("--gmock_") + flag;
+  const size_t flag_len = flag_str.length();
+  if (strncmp(str, flag_str.c_str(), flag_len) != 0) return nullptr;
+
+  // Skips the flag name.
+  const char* flag_end = str + flag_len;
+
+  // When def_optional is true, it's OK to not have a "=value" part.
+  if (def_optional && (flag_end[0] == '\0')) {
+    return flag_end;
+  }
+
+  // If def_optional is true and there are more characters after the
+  // flag name, or if def_optional is false, there must be a '=' after
+  // the flag name.
+  if (flag_end[0] != '=') return nullptr;
+
+  // Returns the string after "=".
+  return flag_end + 1;
+}
+
+// Parses a string for a Google Mock bool flag, in the form of
+// "--gmock_flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+static bool ParseGoogleMockBoolFlag(const char* str, const char* flag,
+                                    bool* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseGoogleMockFlagValue(str, flag, true);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Converts the string value to a bool.
+  *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F');
+  return true;
+}
+
+// Parses a string for a Google Mock string flag, in the form of
+// "--gmock_flag=value".
+//
+// On success, stores the value of the flag in *value, and returns
+// true.  On failure, returns false without changing *value.
+template <typename String>
+static bool ParseGoogleMockStringFlag(const char* str, const char* flag,
+                                      String* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseGoogleMockFlagValue(str, flag, false);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Sets *value to the value of the flag.
+  *value = value_str;
+  return true;
+}
+
+static bool ParseGoogleMockIntFlag(const char* str, const char* flag,
+                                   int32_t* value) {
+  // Gets the value of the flag as a string.
+  const char* const value_str = ParseGoogleMockFlagValue(str, flag, true);
+
+  // Aborts if the parsing failed.
+  if (value_str == nullptr) return false;
+
+  // Sets *value to the value of the flag.
+  return ParseInt32(Message() << "The value of flag --" << flag,
+                    value_str, value);
+}
+
+// The internal implementation of InitGoogleMock().
+//
+// The type parameter CharType can be instantiated to either char or
+// wchar_t.
+template <typename CharType>
+void InitGoogleMockImpl(int* argc, CharType** argv) {
+  // Makes sure Google Test is initialized.  InitGoogleTest() is
+  // idempotent, so it's fine if the user has already called it.
+  InitGoogleTest(argc, argv);
+  if (*argc <= 0) return;
+
+  for (int i = 1; i != *argc; i++) {
+    const std::string arg_string = StreamableToString(argv[i]);
+    const char* const arg = arg_string.c_str();
+
+    // Do we see a Google Mock flag?
+    if (ParseGoogleMockBoolFlag(arg, "catch_leaked_mocks",
+                                &GMOCK_FLAG(catch_leaked_mocks)) ||
+        ParseGoogleMockStringFlag(arg, "verbose", &GMOCK_FLAG(verbose)) ||
+        ParseGoogleMockIntFlag(arg, "default_mock_behavior",
+                               &GMOCK_FLAG(default_mock_behavior))) {
+      // Yes.  Shift the remainder of the argv list left by one.  Note
+      // that argv has (*argc + 1) elements, the last one always being
+      // NULL.  The following loop moves the trailing NULL element as
+      // well.
+      for (int j = i; j != *argc; j++) {
+        argv[j] = argv[j + 1];
+      }
+
+      // Decrements the argument count.
+      (*argc)--;
+
+      // We also need to decrement the iterator as we just removed
+      // an element.
+      i--;
+    }
+  }
+}
+
+}  // namespace internal
+
+// Initializes Google Mock.  This must be called before running the
+// tests.  In particular, it parses a command line for the flags that
+// Google Mock recognizes.  Whenever a Google Mock flag is seen, it is
+// removed from argv, and *argc is decremented.
+//
+// No value is returned.  Instead, the Google Mock flag variables are
+// updated.
+//
+// Since Google Test is needed for Google Mock to work, this function
+// also initializes Google Test and parses its flags, if that hasn't
+// been done.
+GTEST_API_ void InitGoogleMock(int* argc, char** argv) {
+  internal::InitGoogleMockImpl(argc, argv);
+}
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+GTEST_API_ void InitGoogleMock(int* argc, wchar_t** argv) {
+  internal::InitGoogleMockImpl(argc, argv);
+}
+
+// This overloaded version can be used on Arduino/embedded platforms where
+// there is no argc/argv.
+GTEST_API_ void InitGoogleMock() {
+  // Since Arduino doesn't have a command line, fake out the argc/argv arguments
+  int argc = 1;
+  const auto arg0 = "dummy";
+  char* argv0 = const_cast<char*>(arg0);
+  char** argv = &argv0;
+
+  internal::InitGoogleMockImpl(&argc, argv);
+}
+
+}  // namespace testing
diff --git a/libs/fmt/test/gtest/gmock/gmock.h b/libs/fmt/test/gtest/gmock/gmock.h
new file mode 100644
index 0000000000000000000000000000000000000000..d121f7df2b7b470fc9f8490ecf0985bbe0ebf7d7
--- /dev/null
+++ b/libs/fmt/test/gtest/gmock/gmock.h
@@ -0,0 +1,11645 @@
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This is the main header file a user should include.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
+
+// This file implements the following syntax:
+//
+//   ON_CALL(mock_object, Method(...))
+//     .With(...) ?
+//     .WillByDefault(...);
+//
+// where With() is optional and WillByDefault() must appear exactly
+// once.
+//
+//   EXPECT_CALL(mock_object, Method(...))
+//     .With(...) ?
+//     .Times(...) ?
+//     .InSequence(...) *
+//     .WillOnce(...) *
+//     .WillRepeatedly(...) ?
+//     .RetiresOnSaturation() ? ;
+//
+// where all clauses are optional and WillOnce() can be repeated.
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// The ACTION* family of macros can be used in a namespace scope to
+// define custom actions easily.  The syntax:
+//
+//   ACTION(name) { statements; }
+//
+// will define an action with the given name that executes the
+// statements.  The value returned by the statements will be used as
+// the return value of the action.  Inside the statements, you can
+// refer to the K-th (0-based) argument of the mock function by
+// 'argK', and refer to its type by 'argK_type'.  For example:
+//
+//   ACTION(IncrementArg1) {
+//     arg1_type temp = arg1;
+//     return ++(*temp);
+//   }
+//
+// allows you to write
+//
+//   ...WillOnce(IncrementArg1());
+//
+// You can also refer to the entire argument tuple and its type by
+// 'args' and 'args_type', and refer to the mock function type and its
+// return type by 'function_type' and 'return_type'.
+//
+// Note that you don't need to specify the types of the mock function
+// arguments.  However rest assured that your code is still type-safe:
+// you'll get a compiler error if *arg1 doesn't support the ++
+// operator, or if the type of ++(*arg1) isn't compatible with the
+// mock function's return type, for example.
+//
+// Sometimes you'll want to parameterize the action.   For that you can use
+// another macro:
+//
+//   ACTION_P(name, param_name) { statements; }
+//
+// For example:
+//
+//   ACTION_P(Add, n) { return arg0 + n; }
+//
+// will allow you to write:
+//
+//   ...WillOnce(Add(5));
+//
+// Note that you don't need to provide the type of the parameter
+// either.  If you need to reference the type of a parameter named
+// 'foo', you can write 'foo_type'.  For example, in the body of
+// ACTION_P(Add, n) above, you can write 'n_type' to refer to the type
+// of 'n'.
+//
+// We also provide ACTION_P2, ACTION_P3, ..., up to ACTION_P10 to support
+// multi-parameter actions.
+//
+// For the purpose of typing, you can view
+//
+//   ACTION_Pk(Foo, p1, ..., pk) { ... }
+//
+// as shorthand for
+//
+//   template <typename p1_type, ..., typename pk_type>
+//   FooActionPk<p1_type, ..., pk_type> Foo(p1_type p1, ..., pk_type pk) { ... }
+//
+// In particular, you can provide the template type arguments
+// explicitly when invoking Foo(), as in Foo<long, bool>(5, false);
+// although usually you can rely on the compiler to infer the types
+// for you automatically.  You can assign the result of expression
+// Foo(p1, ..., pk) to a variable of type FooActionPk<p1_type, ...,
+// pk_type>.  This can be useful when composing actions.
+//
+// You can also overload actions with different numbers of parameters:
+//
+//   ACTION_P(Plus, a) { ... }
+//   ACTION_P2(Plus, a, b) { ... }
+//
+// While it's tempting to always use the ACTION* macros when defining
+// a new action, you should also consider implementing ActionInterface
+// or using MakePolymorphicAction() instead, especially if you need to
+// use the action a lot.  While these approaches require more work,
+// they give you more control on the types of the mock function
+// arguments and the action parameters, which in general leads to
+// better compiler error messages that pay off in the long run.  They
+// also allow overloading actions based on parameter types (as opposed
+// to just based on the number of parameters).
+//
+// CAVEAT:
+//
+// ACTION*() can only be used in a namespace scope as templates cannot be
+// declared inside of a local class.
+// Users can, however, define any local functors (e.g. a lambda) that
+// can be used as actions.
+//
+// MORE INFORMATION:
+//
+// To learn more about using these macros, please search for 'ACTION' on
+// https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_
+
+#ifndef _WIN32_WCE
+# include <errno.h>
+#endif
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file defines some utilities useful for implementing Google
+// Mock.  They are subject to change without notice, so please DO NOT
+// USE THEM IN USER CODE.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
+
+#include <stdio.h>
+#include <ostream>  // NOLINT
+#include <string>
+#include <type_traits>
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Low-level types and utilities for porting Google Mock to various
+// platforms.  All macros ending with _ and symbols defined in an
+// internal namespace are subject to change without notice.  Code
+// outside Google Mock MUST NOT USE THEM DIRECTLY.  Macros that don't
+// end with _ are part of Google Mock's public API and can be used by
+// code outside Google Mock.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
+
+#include <assert.h>
+#include <stdlib.h>
+#include <cstdint>
+#include <iostream>
+
+// Most of the utilities needed for porting Google Mock are also
+// required for Google Test and are defined in gtest-port.h.
+//
+// Note to maintainers: to reduce code duplication, prefer adding
+// portability utilities to Google Test's gtest-port.h instead of
+// here, as Google Mock depends on Google Test.  Only add a utility
+// here if it's truly specific to Google Mock.
+
+#include "gtest/gtest.h"
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
+
+// For MS Visual C++, check the compiler version. At least VS 2015 is
+// required to compile Google Mock.
+#if defined(_MSC_VER) && _MSC_VER < 1900
+# error "At least Visual C++ 2015 (14.0) is required to compile Google Mock."
+#endif
+
+// Macro for referencing flags.  This is public as we want the user to
+// use this syntax to reference Google Mock flags.
+#define GMOCK_FLAG(name) FLAGS_gmock_##name
+
+#if !defined(GMOCK_DECLARE_bool_)
+
+// Macros for declaring flags.
+# define GMOCK_DECLARE_bool_(name) extern GTEST_API_ bool GMOCK_FLAG(name)
+# define GMOCK_DECLARE_int32_(name) extern GTEST_API_ int32_t GMOCK_FLAG(name)
+# define GMOCK_DECLARE_string_(name) \
+    extern GTEST_API_ ::std::string GMOCK_FLAG(name)
+
+// Macros for defining flags.
+# define GMOCK_DEFINE_bool_(name, default_val, doc) \
+    GTEST_API_ bool GMOCK_FLAG(name) = (default_val)
+# define GMOCK_DEFINE_int32_(name, default_val, doc) \
+    GTEST_API_ int32_t GMOCK_FLAG(name) = (default_val)
+# define GMOCK_DEFINE_string_(name, default_val, doc) \
+    GTEST_API_ ::std::string GMOCK_FLAG(name) = (default_val)
+
+#endif  // !defined(GMOCK_DECLARE_bool_)
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
+
+namespace testing {
+
+template <typename>
+class Matcher;
+
+namespace internal {
+
+// Silence MSVC C4100 (unreferenced formal parameter) and
+// C4805('==': unsafe mix of type 'const int' and type 'const bool')
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4100)
+# pragma warning(disable:4805)
+#endif
+
+// Joins a vector of strings as if they are fields of a tuple; returns
+// the joined string.
+GTEST_API_ std::string JoinAsTuple(const Strings& fields);
+
+// Converts an identifier name to a space-separated list of lower-case
+// words.  Each maximum substring of the form [A-Za-z][a-z]*|\d+ is
+// treated as one word.  For example, both "FooBar123" and
+// "foo_bar_123" are converted to "foo bar 123".
+GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name);
+
+// GetRawPointer(p) returns the raw pointer underlying p when p is a
+// smart pointer, or returns p itself when p is already a raw pointer.
+// The following default implementation is for the smart pointer case.
+template <typename Pointer>
+inline const typename Pointer::element_type* GetRawPointer(const Pointer& p) {
+  return p.get();
+}
+// This overloaded version is for the raw pointer case.
+template <typename Element>
+inline Element* GetRawPointer(Element* p) { return p; }
+
+// MSVC treats wchar_t as a native type usually, but treats it as the
+// same as unsigned short when the compiler option /Zc:wchar_t- is
+// specified.  It defines _NATIVE_WCHAR_T_DEFINED symbol when wchar_t
+// is a native type.
+#if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)
+// wchar_t is a typedef.
+#else
+# define GMOCK_WCHAR_T_IS_NATIVE_ 1
+#endif
+
+// In what follows, we use the term "kind" to indicate whether a type
+// is bool, an integer type (excluding bool), a floating-point type,
+// or none of them.  This categorization is useful for determining
+// when a matcher argument type can be safely converted to another
+// type in the implementation of SafeMatcherCast.
+enum TypeKind {
+  kBool, kInteger, kFloatingPoint, kOther
+};
+
+// KindOf<T>::value is the kind of type T.
+template <typename T> struct KindOf {
+  enum { value = kOther };  // The default kind.
+};
+
+// This macro declares that the kind of 'type' is 'kind'.
+#define GMOCK_DECLARE_KIND_(type, kind) \
+  template <> struct KindOf<type> { enum { value = kind }; }
+
+GMOCK_DECLARE_KIND_(bool, kBool);
+
+// All standard integer types.
+GMOCK_DECLARE_KIND_(char, kInteger);
+GMOCK_DECLARE_KIND_(signed char, kInteger);
+GMOCK_DECLARE_KIND_(unsigned char, kInteger);
+GMOCK_DECLARE_KIND_(short, kInteger);  // NOLINT
+GMOCK_DECLARE_KIND_(unsigned short, kInteger);  // NOLINT
+GMOCK_DECLARE_KIND_(int, kInteger);
+GMOCK_DECLARE_KIND_(unsigned int, kInteger);
+GMOCK_DECLARE_KIND_(long, kInteger);  // NOLINT
+GMOCK_DECLARE_KIND_(unsigned long, kInteger);  // NOLINT
+GMOCK_DECLARE_KIND_(long long, kInteger);  // NOLINT
+GMOCK_DECLARE_KIND_(unsigned long long, kInteger);  // NOLINT
+
+#if GMOCK_WCHAR_T_IS_NATIVE_
+GMOCK_DECLARE_KIND_(wchar_t, kInteger);
+#endif
+
+// All standard floating-point types.
+GMOCK_DECLARE_KIND_(float, kFloatingPoint);
+GMOCK_DECLARE_KIND_(double, kFloatingPoint);
+GMOCK_DECLARE_KIND_(long double, kFloatingPoint);
+
+#undef GMOCK_DECLARE_KIND_
+
+// Evaluates to the kind of 'type'.
+#define GMOCK_KIND_OF_(type) \
+  static_cast< ::testing::internal::TypeKind>( \
+      ::testing::internal::KindOf<type>::value)
+
+// LosslessArithmeticConvertibleImpl<kFromKind, From, kToKind, To>::value
+// is true if and only if arithmetic type From can be losslessly converted to
+// arithmetic type To.
+//
+// It's the user's responsibility to ensure that both From and To are
+// raw (i.e. has no CV modifier, is not a pointer, and is not a
+// reference) built-in arithmetic types, kFromKind is the kind of
+// From, and kToKind is the kind of To; the value is
+// implementation-defined when the above pre-condition is violated.
+template <TypeKind kFromKind, typename From, TypeKind kToKind, typename To>
+using LosslessArithmeticConvertibleImpl = std::integral_constant<
+    bool,
+    // clang-format off
+      // Converting from bool is always lossless
+      (kFromKind == kBool) ? true
+      // Converting between any other type kinds will be lossy if the type
+      // kinds are not the same.
+    : (kFromKind != kToKind) ? false
+    : (kFromKind == kInteger &&
+       // Converting between integers of different widths is allowed so long
+       // as the conversion does not go from signed to unsigned.
+      (((sizeof(From) < sizeof(To)) &&
+        !(std::is_signed<From>::value && !std::is_signed<To>::value)) ||
+       // Converting between integers of the same width only requires the
+       // two types to have the same signedness.
+       ((sizeof(From) == sizeof(To)) &&
+        (std::is_signed<From>::value == std::is_signed<To>::value)))
+       ) ? true
+      // Floating point conversions are lossless if and only if `To` is at least
+      // as wide as `From`.
+    : (kFromKind == kFloatingPoint && (sizeof(From) <= sizeof(To))) ? true
+    : false
+    // clang-format on
+    >;
+
+// LosslessArithmeticConvertible<From, To>::value is true if and only if
+// arithmetic type From can be losslessly converted to arithmetic type To.
+//
+// It's the user's responsibility to ensure that both From and To are
+// raw (i.e. has no CV modifier, is not a pointer, and is not a
+// reference) built-in arithmetic types; the value is
+// implementation-defined when the above pre-condition is violated.
+template <typename From, typename To>
+using LosslessArithmeticConvertible =
+    LosslessArithmeticConvertibleImpl<GMOCK_KIND_OF_(From), From,
+                                      GMOCK_KIND_OF_(To), To>;
+
+// This interface knows how to report a Google Mock failure (either
+// non-fatal or fatal).
+class FailureReporterInterface {
+ public:
+  // The type of a failure (either non-fatal or fatal).
+  enum FailureType {
+    kNonfatal, kFatal
+  };
+
+  virtual ~FailureReporterInterface() {}
+
+  // Reports a failure that occurred at the given source file location.
+  virtual void ReportFailure(FailureType type, const char* file, int line,
+                             const std::string& message) = 0;
+};
+
+// Returns the failure reporter used by Google Mock.
+GTEST_API_ FailureReporterInterface* GetFailureReporter();
+
+// Asserts that condition is true; aborts the process with the given
+// message if condition is false.  We cannot use LOG(FATAL) or CHECK()
+// as Google Mock might be used to mock the log sink itself.  We
+// inline this function to prevent it from showing up in the stack
+// trace.
+inline void Assert(bool condition, const char* file, int line,
+                   const std::string& msg) {
+  if (!condition) {
+    GetFailureReporter()->ReportFailure(FailureReporterInterface::kFatal,
+                                        file, line, msg);
+  }
+}
+inline void Assert(bool condition, const char* file, int line) {
+  Assert(condition, file, line, "Assertion failed.");
+}
+
+// Verifies that condition is true; generates a non-fatal failure if
+// condition is false.
+inline void Expect(bool condition, const char* file, int line,
+                   const std::string& msg) {
+  if (!condition) {
+    GetFailureReporter()->ReportFailure(FailureReporterInterface::kNonfatal,
+                                        file, line, msg);
+  }
+}
+inline void Expect(bool condition, const char* file, int line) {
+  Expect(condition, file, line, "Expectation failed.");
+}
+
+// Severity level of a log.
+enum LogSeverity {
+  kInfo = 0,
+  kWarning = 1
+};
+
+// Valid values for the --gmock_verbose flag.
+
+// All logs (informational and warnings) are printed.
+const char kInfoVerbosity[] = "info";
+// Only warnings are printed.
+const char kWarningVerbosity[] = "warning";
+// No logs are printed.
+const char kErrorVerbosity[] = "error";
+
+// Returns true if and only if a log with the given severity is visible
+// according to the --gmock_verbose flag.
+GTEST_API_ bool LogIsVisible(LogSeverity severity);
+
+// Prints the given message to stdout if and only if 'severity' >= the level
+// specified by the --gmock_verbose flag.  If stack_frames_to_skip >=
+// 0, also prints the stack trace excluding the top
+// stack_frames_to_skip frames.  In opt mode, any positive
+// stack_frames_to_skip is treated as 0, since we don't know which
+// function calls will be inlined by the compiler and need to be
+// conservative.
+GTEST_API_ void Log(LogSeverity severity, const std::string& message,
+                    int stack_frames_to_skip);
+
+// A marker class that is used to resolve parameterless expectations to the
+// correct overload. This must not be instantiable, to prevent client code from
+// accidentally resolving to the overload; for example:
+//
+//    ON_CALL(mock, Method({}, nullptr))...
+//
+class WithoutMatchers {
+ private:
+  WithoutMatchers() {}
+  friend GTEST_API_ WithoutMatchers GetWithoutMatchers();
+};
+
+// Internal use only: access the singleton instance of WithoutMatchers.
+GTEST_API_ WithoutMatchers GetWithoutMatchers();
+
+// Disable MSVC warnings for infinite recursion, since in this case the
+// the recursion is unreachable.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4717)
+#endif
+
+// Invalid<T>() is usable as an expression of type T, but will terminate
+// the program with an assertion failure if actually run.  This is useful
+// when a value of type T is needed for compilation, but the statement
+// will not really be executed (or we don't care if the statement
+// crashes).
+template <typename T>
+inline T Invalid() {
+  Assert(false, "", -1, "Internal error: attempt to return invalid value");
+  // This statement is unreachable, and would never terminate even if it
+  // could be reached. It is provided only to placate compiler warnings
+  // about missing return statements.
+  return Invalid<T>();
+}
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+// Given a raw type (i.e. having no top-level reference or const
+// modifier) RawContainer that's either an STL-style container or a
+// native array, class StlContainerView<RawContainer> has the
+// following members:
+//
+//   - type is a type that provides an STL-style container view to
+//     (i.e. implements the STL container concept for) RawContainer;
+//   - const_reference is a type that provides a reference to a const
+//     RawContainer;
+//   - ConstReference(raw_container) returns a const reference to an STL-style
+//     container view to raw_container, which is a RawContainer.
+//   - Copy(raw_container) returns an STL-style container view of a
+//     copy of raw_container, which is a RawContainer.
+//
+// This generic version is used when RawContainer itself is already an
+// STL-style container.
+template <class RawContainer>
+class StlContainerView {
+ public:
+  typedef RawContainer type;
+  typedef const type& const_reference;
+
+  static const_reference ConstReference(const RawContainer& container) {
+    static_assert(!std::is_const<RawContainer>::value,
+                  "RawContainer type must not be const");
+    return container;
+  }
+  static type Copy(const RawContainer& container) { return container; }
+};
+
+// This specialization is used when RawContainer is a native array type.
+template <typename Element, size_t N>
+class StlContainerView<Element[N]> {
+ public:
+  typedef typename std::remove_const<Element>::type RawElement;
+  typedef internal::NativeArray<RawElement> type;
+  // NativeArray<T> can represent a native array either by value or by
+  // reference (selected by a constructor argument), so 'const type'
+  // can be used to reference a const native array.  We cannot
+  // 'typedef const type& const_reference' here, as that would mean
+  // ConstReference() has to return a reference to a local variable.
+  typedef const type const_reference;
+
+  static const_reference ConstReference(const Element (&array)[N]) {
+    static_assert(std::is_same<Element, RawElement>::value,
+                  "Element type must not be const");
+    return type(array, N, RelationToSourceReference());
+  }
+  static type Copy(const Element (&array)[N]) {
+    return type(array, N, RelationToSourceCopy());
+  }
+};
+
+// This specialization is used when RawContainer is a native array
+// represented as a (pointer, size) tuple.
+template <typename ElementPointer, typename Size>
+class StlContainerView< ::std::tuple<ElementPointer, Size> > {
+ public:
+  typedef typename std::remove_const<
+      typename std::pointer_traits<ElementPointer>::element_type>::type
+      RawElement;
+  typedef internal::NativeArray<RawElement> type;
+  typedef const type const_reference;
+
+  static const_reference ConstReference(
+      const ::std::tuple<ElementPointer, Size>& array) {
+    return type(std::get<0>(array), std::get<1>(array),
+                RelationToSourceReference());
+  }
+  static type Copy(const ::std::tuple<ElementPointer, Size>& array) {
+    return type(std::get<0>(array), std::get<1>(array), RelationToSourceCopy());
+  }
+};
+
+// The following specialization prevents the user from instantiating
+// StlContainer with a reference type.
+template <typename T> class StlContainerView<T&>;
+
+// A type transform to remove constness from the first part of a pair.
+// Pairs like that are used as the value_type of associative containers,
+// and this transform produces a similar but assignable pair.
+template <typename T>
+struct RemoveConstFromKey {
+  typedef T type;
+};
+
+// Partially specialized to remove constness from std::pair<const K, V>.
+template <typename K, typename V>
+struct RemoveConstFromKey<std::pair<const K, V> > {
+  typedef std::pair<K, V> type;
+};
+
+// Emit an assertion failure due to incorrect DoDefault() usage. Out-of-lined to
+// reduce code size.
+GTEST_API_ void IllegalDoDefault(const char* file, int line);
+
+template <typename F, typename Tuple, size_t... Idx>
+auto ApplyImpl(F&& f, Tuple&& args, IndexSequence<Idx...>) -> decltype(
+    std::forward<F>(f)(std::get<Idx>(std::forward<Tuple>(args))...)) {
+  return std::forward<F>(f)(std::get<Idx>(std::forward<Tuple>(args))...);
+}
+
+// Apply the function to a tuple of arguments.
+template <typename F, typename Tuple>
+auto Apply(F&& f, Tuple&& args) -> decltype(
+    ApplyImpl(std::forward<F>(f), std::forward<Tuple>(args),
+              MakeIndexSequence<std::tuple_size<
+                  typename std::remove_reference<Tuple>::type>::value>())) {
+  return ApplyImpl(std::forward<F>(f), std::forward<Tuple>(args),
+                   MakeIndexSequence<std::tuple_size<
+                       typename std::remove_reference<Tuple>::type>::value>());
+}
+
+// Template struct Function<F>, where F must be a function type, contains
+// the following typedefs:
+//
+//   Result:               the function's return type.
+//   Arg<N>:               the type of the N-th argument, where N starts with 0.
+//   ArgumentTuple:        the tuple type consisting of all parameters of F.
+//   ArgumentMatcherTuple: the tuple type consisting of Matchers for all
+//                         parameters of F.
+//   MakeResultVoid:       the function type obtained by substituting void
+//                         for the return type of F.
+//   MakeResultIgnoredValue:
+//                         the function type obtained by substituting Something
+//                         for the return type of F.
+template <typename T>
+struct Function;
+
+template <typename R, typename... Args>
+struct Function<R(Args...)> {
+  using Result = R;
+  static constexpr size_t ArgumentCount = sizeof...(Args);
+  template <size_t I>
+  using Arg = ElemFromList<I, Args...>;
+  using ArgumentTuple = std::tuple<Args...>;
+  using ArgumentMatcherTuple = std::tuple<Matcher<Args>...>;
+  using MakeResultVoid = void(Args...);
+  using MakeResultIgnoredValue = IgnoredValue(Args...);
+};
+
+template <typename R, typename... Args>
+constexpr size_t Function<R(Args...)>::ArgumentCount;
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+}  // namespace internal
+}  // namespace testing
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
+
+// Expands and concatenates the arguments. Constructed macros reevaluate.
+#define GMOCK_PP_CAT(_1, _2) GMOCK_PP_INTERNAL_CAT(_1, _2)
+
+// Expands and stringifies the only argument.
+#define GMOCK_PP_STRINGIZE(...) GMOCK_PP_INTERNAL_STRINGIZE(__VA_ARGS__)
+
+// Returns empty. Given a variadic number of arguments.
+#define GMOCK_PP_EMPTY(...)
+
+// Returns a comma. Given a variadic number of arguments.
+#define GMOCK_PP_COMMA(...) ,
+
+// Returns the only argument.
+#define GMOCK_PP_IDENTITY(_1) _1
+
+// Evaluates to the number of arguments after expansion.
+//
+//   #define PAIR x, y
+//
+//   GMOCK_PP_NARG() => 1
+//   GMOCK_PP_NARG(x) => 1
+//   GMOCK_PP_NARG(x, y) => 2
+//   GMOCK_PP_NARG(PAIR) => 2
+//
+// Requires: the number of arguments after expansion is at most 15.
+#define GMOCK_PP_NARG(...) \
+  GMOCK_PP_INTERNAL_16TH(  \
+      (__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
+
+// Returns 1 if the expansion of arguments has an unprotected comma. Otherwise
+// returns 0. Requires no more than 15 unprotected commas.
+#define GMOCK_PP_HAS_COMMA(...) \
+  GMOCK_PP_INTERNAL_16TH(       \
+      (__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0))
+
+// Returns the first argument.
+#define GMOCK_PP_HEAD(...) GMOCK_PP_INTERNAL_HEAD((__VA_ARGS__, unusedArg))
+
+// Returns the tail. A variadic list of all arguments minus the first. Requires
+// at least one argument.
+#define GMOCK_PP_TAIL(...) GMOCK_PP_INTERNAL_TAIL((__VA_ARGS__))
+
+// Calls CAT(_Macro, NARG(__VA_ARGS__))(__VA_ARGS__)
+#define GMOCK_PP_VARIADIC_CALL(_Macro, ...) \
+  GMOCK_PP_IDENTITY(                        \
+      GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__))
+
+// If the arguments after expansion have no tokens, evaluates to `1`. Otherwise
+// evaluates to `0`.
+//
+// Requires: * the number of arguments after expansion is at most 15.
+//           * If the argument is a macro, it must be able to be called with one
+//             argument.
+//
+// Implementation details:
+//
+// There is one case when it generates a compile error: if the argument is macro
+// that cannot be called with one argument.
+//
+//   #define M(a, b)  // it doesn't matter what it expands to
+//
+//   // Expected: expands to `0`.
+//   // Actual: compile error.
+//   GMOCK_PP_IS_EMPTY(M)
+//
+// There are 4 cases tested:
+//
+// * __VA_ARGS__ possible expansion has no unparen'd commas. Expected 0.
+// * __VA_ARGS__ possible expansion is not enclosed in parenthesis. Expected 0.
+// * __VA_ARGS__ possible expansion is not a macro that ()-evaluates to a comma.
+//   Expected 0
+// * __VA_ARGS__ is empty, or has unparen'd commas, or is enclosed in
+//   parenthesis, or is a macro that ()-evaluates to comma. Expected 1.
+//
+// We trigger detection on '0001', i.e. on empty.
+#define GMOCK_PP_IS_EMPTY(...)                                               \
+  GMOCK_PP_INTERNAL_IS_EMPTY(GMOCK_PP_HAS_COMMA(__VA_ARGS__),                \
+                             GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__), \
+                             GMOCK_PP_HAS_COMMA(__VA_ARGS__()),              \
+                             GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__()))
+
+// Evaluates to _Then if _Cond is 1 and _Else if _Cond is 0.
+#define GMOCK_PP_IF(_Cond, _Then, _Else) \
+  GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IF_, _Cond)(_Then, _Else)
+
+// Similar to GMOCK_PP_IF but takes _Then and _Else in parentheses.
+//
+// GMOCK_PP_GENERIC_IF(1, (a, b, c), (d, e, f)) => a, b, c
+// GMOCK_PP_GENERIC_IF(0, (a, b, c), (d, e, f)) => d, e, f
+//
+#define GMOCK_PP_GENERIC_IF(_Cond, _Then, _Else) \
+  GMOCK_PP_REMOVE_PARENS(GMOCK_PP_IF(_Cond, _Then, _Else))
+
+// Evaluates to the number of arguments after expansion. Identifies 'empty' as
+// 0.
+//
+//   #define PAIR x, y
+//
+//   GMOCK_PP_NARG0() => 0
+//   GMOCK_PP_NARG0(x) => 1
+//   GMOCK_PP_NARG0(x, y) => 2
+//   GMOCK_PP_NARG0(PAIR) => 2
+//
+// Requires: * the number of arguments after expansion is at most 15.
+//           * If the argument is a macro, it must be able to be called with one
+//             argument.
+#define GMOCK_PP_NARG0(...) \
+  GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(__VA_ARGS__), 0, GMOCK_PP_NARG(__VA_ARGS__))
+
+// Expands to 1 if the first argument starts with something in parentheses,
+// otherwise to 0.
+#define GMOCK_PP_IS_BEGIN_PARENS(...)                              \
+  GMOCK_PP_HEAD(GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_, \
+                             GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C __VA_ARGS__))
+
+// Expands to 1 is there is only one argument and it is enclosed in parentheses.
+#define GMOCK_PP_IS_ENCLOSED_PARENS(...)             \
+  GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(__VA_ARGS__), \
+              GMOCK_PP_IS_EMPTY(GMOCK_PP_EMPTY __VA_ARGS__), 0)
+
+// Remove the parens, requires GMOCK_PP_IS_ENCLOSED_PARENS(args) => 1.
+#define GMOCK_PP_REMOVE_PARENS(...) GMOCK_PP_INTERNAL_REMOVE_PARENS __VA_ARGS__
+
+// Expands to _Macro(0, _Data, e1) _Macro(1, _Data, e2) ... _Macro(K -1, _Data,
+// eK) as many of GMOCK_INTERNAL_NARG0 _Tuple.
+// Requires: * |_Macro| can be called with 3 arguments.
+//           * |_Tuple| expansion has no more than 15 elements.
+#define GMOCK_PP_FOR_EACH(_Macro, _Data, _Tuple)                        \
+  GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, GMOCK_PP_NARG0 _Tuple) \
+  (0, _Macro, _Data, _Tuple)
+
+// Expands to _Macro(0, _Data, ) _Macro(1, _Data, ) ... _Macro(K - 1, _Data, )
+// Empty if _K = 0.
+// Requires: * |_Macro| can be called with 3 arguments.
+//           * |_K| literal between 0 and 15
+#define GMOCK_PP_REPEAT(_Macro, _Data, _N)           \
+  GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, _N) \
+  (0, _Macro, _Data, GMOCK_PP_INTENRAL_EMPTY_TUPLE)
+
+// Increments the argument, requires the argument to be between 0 and 15.
+#define GMOCK_PP_INC(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_INC_, _i)
+
+// Returns comma if _i != 0. Requires _i to be between 0 and 15.
+#define GMOCK_PP_COMMA_IF(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_COMMA_IF_, _i)
+
+// Internal details follow. Do not use any of these symbols outside of this
+// file or we will break your code.
+#define GMOCK_PP_INTENRAL_EMPTY_TUPLE (, , , , , , , , , , , , , , , )
+#define GMOCK_PP_INTERNAL_CAT(_1, _2) _1##_2
+#define GMOCK_PP_INTERNAL_STRINGIZE(...) #__VA_ARGS__
+#define GMOCK_PP_INTERNAL_CAT_5(_1, _2, _3, _4, _5) _1##_2##_3##_4##_5
+#define GMOCK_PP_INTERNAL_IS_EMPTY(_1, _2, _3, _4)                             \
+  GMOCK_PP_HAS_COMMA(GMOCK_PP_INTERNAL_CAT_5(GMOCK_PP_INTERNAL_IS_EMPTY_CASE_, \
+                                             _1, _2, _3, _4))
+#define GMOCK_PP_INTERNAL_IS_EMPTY_CASE_0001 ,
+#define GMOCK_PP_INTERNAL_IF_1(_Then, _Else) _Then
+#define GMOCK_PP_INTERNAL_IF_0(_Then, _Else) _Else
+
+// Because of MSVC treating a token with a comma in it as a single token when
+// passed to another macro, we need to force it to evaluate it as multiple
+// tokens. We do that by using a "IDENTITY(MACRO PARENTHESIZED_ARGS)" macro. We
+// define one per possible macro that relies on this behavior. Note "_Args" must
+// be parenthesized.
+#define GMOCK_PP_INTERNAL_INTERNAL_16TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, \
+                                        _10, _11, _12, _13, _14, _15, _16,  \
+                                        ...)                                \
+  _16
+#define GMOCK_PP_INTERNAL_16TH(_Args) \
+  GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_16TH _Args)
+#define GMOCK_PP_INTERNAL_INTERNAL_HEAD(_1, ...) _1
+#define GMOCK_PP_INTERNAL_HEAD(_Args) \
+  GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_HEAD _Args)
+#define GMOCK_PP_INTERNAL_INTERNAL_TAIL(_1, ...) __VA_ARGS__
+#define GMOCK_PP_INTERNAL_TAIL(_Args) \
+  GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_TAIL _Args)
+
+#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C(...) 1 _
+#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_1 1,
+#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C \
+  0,
+#define GMOCK_PP_INTERNAL_REMOVE_PARENS(...) __VA_ARGS__
+#define GMOCK_PP_INTERNAL_INC_0 1
+#define GMOCK_PP_INTERNAL_INC_1 2
+#define GMOCK_PP_INTERNAL_INC_2 3
+#define GMOCK_PP_INTERNAL_INC_3 4
+#define GMOCK_PP_INTERNAL_INC_4 5
+#define GMOCK_PP_INTERNAL_INC_5 6
+#define GMOCK_PP_INTERNAL_INC_6 7
+#define GMOCK_PP_INTERNAL_INC_7 8
+#define GMOCK_PP_INTERNAL_INC_8 9
+#define GMOCK_PP_INTERNAL_INC_9 10
+#define GMOCK_PP_INTERNAL_INC_10 11
+#define GMOCK_PP_INTERNAL_INC_11 12
+#define GMOCK_PP_INTERNAL_INC_12 13
+#define GMOCK_PP_INTERNAL_INC_13 14
+#define GMOCK_PP_INTERNAL_INC_14 15
+#define GMOCK_PP_INTERNAL_INC_15 16
+#define GMOCK_PP_INTERNAL_COMMA_IF_0
+#define GMOCK_PP_INTERNAL_COMMA_IF_1 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_2 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_3 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_4 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_5 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_6 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_7 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_8 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_9 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_10 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_11 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_12 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_13 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_14 ,
+#define GMOCK_PP_INTERNAL_COMMA_IF_15 ,
+#define GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, _element) \
+  _Macro(_i, _Data, _element)
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_0(_i, _Macro, _Data, _Tuple)
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(_i, _Macro, _Data, _Tuple) \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple)
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(_i, _Macro, _Data, _Tuple)    \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(GMOCK_PP_INC(_i), _Macro, _Data,    \
+                                    (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(GMOCK_PP_INC(_i), _Macro, _Data,   \
+                                     (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(GMOCK_PP_INC(_i), _Macro, _Data,   \
+                                     (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(GMOCK_PP_INC(_i), _Macro, _Data,   \
+                                     (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(GMOCK_PP_INC(_i), _Macro, _Data,   \
+                                     (GMOCK_PP_TAIL _Tuple))
+#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_15(_i, _Macro, _Data, _Tuple)   \
+  GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
+  GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(GMOCK_PP_INC(_i), _Macro, _Data,   \
+                                     (GMOCK_PP_TAIL _Tuple))
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4100)
+#endif
+
+namespace testing {
+
+// To implement an action Foo, define:
+//   1. a class FooAction that implements the ActionInterface interface, and
+//   2. a factory function that creates an Action object from a
+//      const FooAction*.
+//
+// The two-level delegation design follows that of Matcher, providing
+// consistency for extension developers.  It also eases ownership
+// management as Action objects can now be copied like plain values.
+
+namespace internal {
+
+// BuiltInDefaultValueGetter<T, true>::Get() returns a
+// default-constructed T value.  BuiltInDefaultValueGetter<T,
+// false>::Get() crashes with an error.
+//
+// This primary template is used when kDefaultConstructible is true.
+template <typename T, bool kDefaultConstructible>
+struct BuiltInDefaultValueGetter {
+  static T Get() { return T(); }
+};
+template <typename T>
+struct BuiltInDefaultValueGetter<T, false> {
+  static T Get() {
+    Assert(false, __FILE__, __LINE__,
+           "Default action undefined for the function return type.");
+    return internal::Invalid<T>();
+    // The above statement will never be reached, but is required in
+    // order for this function to compile.
+  }
+};
+
+// BuiltInDefaultValue<T>::Get() returns the "built-in" default value
+// for type T, which is NULL when T is a raw pointer type, 0 when T is
+// a numeric type, false when T is bool, or "" when T is string or
+// std::string.  In addition, in C++11 and above, it turns a
+// default-constructed T value if T is default constructible.  For any
+// other type T, the built-in default T value is undefined, and the
+// function will abort the process.
+template <typename T>
+class BuiltInDefaultValue {
+ public:
+  // This function returns true if and only if type T has a built-in default
+  // value.
+  static bool Exists() {
+    return ::std::is_default_constructible<T>::value;
+  }
+
+  static T Get() {
+    return BuiltInDefaultValueGetter<
+        T, ::std::is_default_constructible<T>::value>::Get();
+  }
+};
+
+// This partial specialization says that we use the same built-in
+// default value for T and const T.
+template <typename T>
+class BuiltInDefaultValue<const T> {
+ public:
+  static bool Exists() { return BuiltInDefaultValue<T>::Exists(); }
+  static T Get() { return BuiltInDefaultValue<T>::Get(); }
+};
+
+// This partial specialization defines the default values for pointer
+// types.
+template <typename T>
+class BuiltInDefaultValue<T*> {
+ public:
+  static bool Exists() { return true; }
+  static T* Get() { return nullptr; }
+};
+
+// The following specializations define the default values for
+// specific types we care about.
+#define GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(type, value) \
+  template <> \
+  class BuiltInDefaultValue<type> { \
+   public: \
+    static bool Exists() { return true; } \
+    static type Get() { return value; } \
+  }
+
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(void, );  // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(::std::string, "");
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(bool, false);
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned char, '\0');
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed char, '\0');
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(char, '\0');
+
+// There's no need for a default action for signed wchar_t, as that
+// type is the same as wchar_t for gcc, and invalid for MSVC.
+//
+// There's also no need for a default action for unsigned wchar_t, as
+// that type is the same as unsigned int for gcc, and invalid for
+// MSVC.
+#if GMOCK_WCHAR_T_IS_NATIVE_
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(wchar_t, 0U);  // NOLINT
+#endif
+
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned short, 0U);  // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed short, 0);     // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned int, 0U);
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed int, 0);
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long, 0UL);  // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long, 0L);     // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(unsigned long long, 0);  // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(signed long long, 0);  // NOLINT
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(float, 0);
+GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_(double, 0);
+
+#undef GMOCK_DEFINE_DEFAULT_ACTION_FOR_RETURN_TYPE_
+
+// Simple two-arg form of std::disjunction.
+template <typename P, typename Q>
+using disjunction = typename ::std::conditional<P::value, P, Q>::type;
+
+}  // namespace internal
+
+// When an unexpected function call is encountered, Google Mock will
+// let it return a default value if the user has specified one for its
+// return type, or if the return type has a built-in default value;
+// otherwise Google Mock won't know what value to return and will have
+// to abort the process.
+//
+// The DefaultValue<T> class allows a user to specify the
+// default value for a type T that is both copyable and publicly
+// destructible (i.e. anything that can be used as a function return
+// type).  The usage is:
+//
+//   // Sets the default value for type T to be foo.
+//   DefaultValue<T>::Set(foo);
+template <typename T>
+class DefaultValue {
+ public:
+  // Sets the default value for type T; requires T to be
+  // copy-constructable and have a public destructor.
+  static void Set(T x) {
+    delete producer_;
+    producer_ = new FixedValueProducer(x);
+  }
+
+  // Provides a factory function to be called to generate the default value.
+  // This method can be used even if T is only move-constructible, but it is not
+  // limited to that case.
+  typedef T (*FactoryFunction)();
+  static void SetFactory(FactoryFunction factory) {
+    delete producer_;
+    producer_ = new FactoryValueProducer(factory);
+  }
+
+  // Unsets the default value for type T.
+  static void Clear() {
+    delete producer_;
+    producer_ = nullptr;
+  }
+
+  // Returns true if and only if the user has set the default value for type T.
+  static bool IsSet() { return producer_ != nullptr; }
+
+  // Returns true if T has a default return value set by the user or there
+  // exists a built-in default value.
+  static bool Exists() {
+    return IsSet() || internal::BuiltInDefaultValue<T>::Exists();
+  }
+
+  // Returns the default value for type T if the user has set one;
+  // otherwise returns the built-in default value. Requires that Exists()
+  // is true, which ensures that the return value is well-defined.
+  static T Get() {
+    return producer_ == nullptr ? internal::BuiltInDefaultValue<T>::Get()
+                                : producer_->Produce();
+  }
+
+ private:
+  class ValueProducer {
+   public:
+    virtual ~ValueProducer() {}
+    virtual T Produce() = 0;
+  };
+
+  class FixedValueProducer : public ValueProducer {
+   public:
+    explicit FixedValueProducer(T value) : value_(value) {}
+    T Produce() override { return value_; }
+
+   private:
+    const T value_;
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(FixedValueProducer);
+  };
+
+  class FactoryValueProducer : public ValueProducer {
+   public:
+    explicit FactoryValueProducer(FactoryFunction factory)
+        : factory_(factory) {}
+    T Produce() override { return factory_(); }
+
+   private:
+    const FactoryFunction factory_;
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(FactoryValueProducer);
+  };
+
+  static ValueProducer* producer_;
+};
+
+// This partial specialization allows a user to set default values for
+// reference types.
+template <typename T>
+class DefaultValue<T&> {
+ public:
+  // Sets the default value for type T&.
+  static void Set(T& x) {  // NOLINT
+    address_ = &x;
+  }
+
+  // Unsets the default value for type T&.
+  static void Clear() { address_ = nullptr; }
+
+  // Returns true if and only if the user has set the default value for type T&.
+  static bool IsSet() { return address_ != nullptr; }
+
+  // Returns true if T has a default return value set by the user or there
+  // exists a built-in default value.
+  static bool Exists() {
+    return IsSet() || internal::BuiltInDefaultValue<T&>::Exists();
+  }
+
+  // Returns the default value for type T& if the user has set one;
+  // otherwise returns the built-in default value if there is one;
+  // otherwise aborts the process.
+  static T& Get() {
+    return address_ == nullptr ? internal::BuiltInDefaultValue<T&>::Get()
+                               : *address_;
+  }
+
+ private:
+  static T* address_;
+};
+
+// This specialization allows DefaultValue<void>::Get() to
+// compile.
+template <>
+class DefaultValue<void> {
+ public:
+  static bool Exists() { return true; }
+  static void Get() {}
+};
+
+// Points to the user-set default value for type T.
+template <typename T>
+typename DefaultValue<T>::ValueProducer* DefaultValue<T>::producer_ = nullptr;
+
+// Points to the user-set default value for type T&.
+template <typename T>
+T* DefaultValue<T&>::address_ = nullptr;
+
+// Implement this interface to define an action for function type F.
+template <typename F>
+class ActionInterface {
+ public:
+  typedef typename internal::Function<F>::Result Result;
+  typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple;
+
+  ActionInterface() {}
+  virtual ~ActionInterface() {}
+
+  // Performs the action.  This method is not const, as in general an
+  // action can have side effects and be stateful.  For example, a
+  // get-the-next-element-from-the-collection action will need to
+  // remember the current element.
+  virtual Result Perform(const ArgumentTuple& args) = 0;
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionInterface);
+};
+
+// An Action<F> is a copyable and IMMUTABLE (except by assignment)
+// object that represents an action to be taken when a mock function
+// of type F is called.  The implementation of Action<T> is just a
+// std::shared_ptr to const ActionInterface<T>. Don't inherit from Action!
+// You can view an object implementing ActionInterface<F> as a
+// concrete action (including its current state), and an Action<F>
+// object as a handle to it.
+template <typename F>
+class Action {
+  // Adapter class to allow constructing Action from a legacy ActionInterface.
+  // New code should create Actions from functors instead.
+  struct ActionAdapter {
+    // Adapter must be copyable to satisfy std::function requirements.
+    ::std::shared_ptr<ActionInterface<F>> impl_;
+
+    template <typename... Args>
+    typename internal::Function<F>::Result operator()(Args&&... args) {
+      return impl_->Perform(
+          ::std::forward_as_tuple(::std::forward<Args>(args)...));
+    }
+  };
+
+  template <typename G>
+  using IsCompatibleFunctor = std::is_constructible<std::function<F>, G>;
+
+ public:
+  typedef typename internal::Function<F>::Result Result;
+  typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple;
+
+  // Constructs a null Action.  Needed for storing Action objects in
+  // STL containers.
+  Action() {}
+
+  // Construct an Action from a specified callable.
+  // This cannot take std::function directly, because then Action would not be
+  // directly constructible from lambda (it would require two conversions).
+  template <
+      typename G,
+      typename = typename std::enable_if<internal::disjunction<
+          IsCompatibleFunctor<G>, std::is_constructible<std::function<Result()>,
+                                                        G>>::value>::type>
+  Action(G&& fun) {  // NOLINT
+    Init(::std::forward<G>(fun), IsCompatibleFunctor<G>());
+  }
+
+  // Constructs an Action from its implementation.
+  explicit Action(ActionInterface<F>* impl)
+      : fun_(ActionAdapter{::std::shared_ptr<ActionInterface<F>>(impl)}) {}
+
+  // This constructor allows us to turn an Action<Func> object into an
+  // Action<F>, as long as F's arguments can be implicitly converted
+  // to Func's and Func's return type can be implicitly converted to F's.
+  template <typename Func>
+  explicit Action(const Action<Func>& action) : fun_(action.fun_) {}
+
+  // Returns true if and only if this is the DoDefault() action.
+  bool IsDoDefault() const { return fun_ == nullptr; }
+
+  // Performs the action.  Note that this method is const even though
+  // the corresponding method in ActionInterface is not.  The reason
+  // is that a const Action<F> means that it cannot be re-bound to
+  // another concrete action, not that the concrete action it binds to
+  // cannot change state.  (Think of the difference between a const
+  // pointer and a pointer to const.)
+  Result Perform(ArgumentTuple args) const {
+    if (IsDoDefault()) {
+      internal::IllegalDoDefault(__FILE__, __LINE__);
+    }
+    return internal::Apply(fun_, ::std::move(args));
+  }
+
+ private:
+  template <typename G>
+  friend class Action;
+
+  template <typename G>
+  void Init(G&& g, ::std::true_type) {
+    fun_ = ::std::forward<G>(g);
+  }
+
+  template <typename G>
+  void Init(G&& g, ::std::false_type) {
+    fun_ = IgnoreArgs<typename ::std::decay<G>::type>{::std::forward<G>(g)};
+  }
+
+  template <typename FunctionImpl>
+  struct IgnoreArgs {
+    template <typename... Args>
+    Result operator()(const Args&...) const {
+      return function_impl();
+    }
+
+    FunctionImpl function_impl;
+  };
+
+  // fun_ is an empty function if and only if this is the DoDefault() action.
+  ::std::function<F> fun_;
+};
+
+// The PolymorphicAction class template makes it easy to implement a
+// polymorphic action (i.e. an action that can be used in mock
+// functions of than one type, e.g. Return()).
+//
+// To define a polymorphic action, a user first provides a COPYABLE
+// implementation class that has a Perform() method template:
+//
+//   class FooAction {
+//    public:
+//     template <typename Result, typename ArgumentTuple>
+//     Result Perform(const ArgumentTuple& args) const {
+//       // Processes the arguments and returns a result, using
+//       // std::get<N>(args) to get the N-th (0-based) argument in the tuple.
+//     }
+//     ...
+//   };
+//
+// Then the user creates the polymorphic action using
+// MakePolymorphicAction(object) where object has type FooAction.  See
+// the definition of Return(void) and SetArgumentPointee<N>(value) for
+// complete examples.
+template <typename Impl>
+class PolymorphicAction {
+ public:
+  explicit PolymorphicAction(const Impl& impl) : impl_(impl) {}
+
+  template <typename F>
+  operator Action<F>() const {
+    return Action<F>(new MonomorphicImpl<F>(impl_));
+  }
+
+ private:
+  template <typename F>
+  class MonomorphicImpl : public ActionInterface<F> {
+   public:
+    typedef typename internal::Function<F>::Result Result;
+    typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple;
+
+    explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {}
+
+    Result Perform(const ArgumentTuple& args) override {
+      return impl_.template Perform<Result>(args);
+    }
+
+   private:
+    Impl impl_;
+  };
+
+  Impl impl_;
+};
+
+// Creates an Action from its implementation and returns it.  The
+// created Action object owns the implementation.
+template <typename F>
+Action<F> MakeAction(ActionInterface<F>* impl) {
+  return Action<F>(impl);
+}
+
+// Creates a polymorphic action from its implementation.  This is
+// easier to use than the PolymorphicAction<Impl> constructor as it
+// doesn't require you to explicitly write the template argument, e.g.
+//
+//   MakePolymorphicAction(foo);
+// vs
+//   PolymorphicAction<TypeOfFoo>(foo);
+template <typename Impl>
+inline PolymorphicAction<Impl> MakePolymorphicAction(const Impl& impl) {
+  return PolymorphicAction<Impl>(impl);
+}
+
+namespace internal {
+
+// Helper struct to specialize ReturnAction to execute a move instead of a copy
+// on return. Useful for move-only types, but could be used on any type.
+template <typename T>
+struct ByMoveWrapper {
+  explicit ByMoveWrapper(T value) : payload(std::move(value)) {}
+  T payload;
+};
+
+// Implements the polymorphic Return(x) action, which can be used in
+// any function that returns the type of x, regardless of the argument
+// types.
+//
+// Note: The value passed into Return must be converted into
+// Function<F>::Result when this action is cast to Action<F> rather than
+// when that action is performed. This is important in scenarios like
+//
+// MOCK_METHOD1(Method, T(U));
+// ...
+// {
+//   Foo foo;
+//   X x(&foo);
+//   EXPECT_CALL(mock, Method(_)).WillOnce(Return(x));
+// }
+//
+// In the example above the variable x holds reference to foo which leaves
+// scope and gets destroyed.  If copying X just copies a reference to foo,
+// that copy will be left with a hanging reference.  If conversion to T
+// makes a copy of foo, the above code is safe. To support that scenario, we
+// need to make sure that the type conversion happens inside the EXPECT_CALL
+// statement, and conversion of the result of Return to Action<T(U)> is a
+// good place for that.
+//
+// The real life example of the above scenario happens when an invocation
+// of gtl::Container() is passed into Return.
+//
+template <typename R>
+class ReturnAction {
+ public:
+  // Constructs a ReturnAction object from the value to be returned.
+  // 'value' is passed by value instead of by const reference in order
+  // to allow Return("string literal") to compile.
+  explicit ReturnAction(R value) : value_(new R(std::move(value))) {}
+
+  // This template type conversion operator allows Return(x) to be
+  // used in ANY function that returns x's type.
+  template <typename F>
+  operator Action<F>() const {  // NOLINT
+    // Assert statement belongs here because this is the best place to verify
+    // conditions on F. It produces the clearest error messages
+    // in most compilers.
+    // Impl really belongs in this scope as a local class but can't
+    // because MSVC produces duplicate symbols in different translation units
+    // in this case. Until MS fixes that bug we put Impl into the class scope
+    // and put the typedef both here (for use in assert statement) and
+    // in the Impl class. But both definitions must be the same.
+    typedef typename Function<F>::Result Result;
+    GTEST_COMPILE_ASSERT_(
+        !std::is_reference<Result>::value,
+        use_ReturnRef_instead_of_Return_to_return_a_reference);
+    static_assert(!std::is_void<Result>::value,
+                  "Can't use Return() on an action expected to return `void`.");
+    return Action<F>(new Impl<R, F>(value_));
+  }
+
+ private:
+  // Implements the Return(x) action for a particular function type F.
+  template <typename R_, typename F>
+  class Impl : public ActionInterface<F> {
+   public:
+    typedef typename Function<F>::Result Result;
+    typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+
+    // The implicit cast is necessary when Result has more than one
+    // single-argument constructor (e.g. Result is std::vector<int>) and R
+    // has a type conversion operator template.  In that case, value_(value)
+    // won't compile as the compiler doesn't known which constructor of
+    // Result to call.  ImplicitCast_ forces the compiler to convert R to
+    // Result without considering explicit constructors, thus resolving the
+    // ambiguity. value_ is then initialized using its copy constructor.
+    explicit Impl(const std::shared_ptr<R>& value)
+        : value_before_cast_(*value),
+          value_(ImplicitCast_<Result>(value_before_cast_)) {}
+
+    Result Perform(const ArgumentTuple&) override { return value_; }
+
+   private:
+    GTEST_COMPILE_ASSERT_(!std::is_reference<Result>::value,
+                          Result_cannot_be_a_reference_type);
+    // We save the value before casting just in case it is being cast to a
+    // wrapper type.
+    R value_before_cast_;
+    Result value_;
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl);
+  };
+
+  // Partially specialize for ByMoveWrapper. This version of ReturnAction will
+  // move its contents instead.
+  template <typename R_, typename F>
+  class Impl<ByMoveWrapper<R_>, F> : public ActionInterface<F> {
+   public:
+    typedef typename Function<F>::Result Result;
+    typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+
+    explicit Impl(const std::shared_ptr<R>& wrapper)
+        : performed_(false), wrapper_(wrapper) {}
+
+    Result Perform(const ArgumentTuple&) override {
+      GTEST_CHECK_(!performed_)
+          << "A ByMove() action should only be performed once.";
+      performed_ = true;
+      return std::move(wrapper_->payload);
+    }
+
+   private:
+    bool performed_;
+    const std::shared_ptr<R> wrapper_;
+  };
+
+  const std::shared_ptr<R> value_;
+};
+
+// Implements the ReturnNull() action.
+class ReturnNullAction {
+ public:
+  // Allows ReturnNull() to be used in any pointer-returning function. In C++11
+  // this is enforced by returning nullptr, and in non-C++11 by asserting a
+  // pointer type on compile time.
+  template <typename Result, typename ArgumentTuple>
+  static Result Perform(const ArgumentTuple&) {
+    return nullptr;
+  }
+};
+
+// Implements the Return() action.
+class ReturnVoidAction {
+ public:
+  // Allows Return() to be used in any void-returning function.
+  template <typename Result, typename ArgumentTuple>
+  static void Perform(const ArgumentTuple&) {
+    static_assert(std::is_void<Result>::value, "Result should be void.");
+  }
+};
+
+// Implements the polymorphic ReturnRef(x) action, which can be used
+// in any function that returns a reference to the type of x,
+// regardless of the argument types.
+template <typename T>
+class ReturnRefAction {
+ public:
+  // Constructs a ReturnRefAction object from the reference to be returned.
+  explicit ReturnRefAction(T& ref) : ref_(ref) {}  // NOLINT
+
+  // This template type conversion operator allows ReturnRef(x) to be
+  // used in ANY function that returns a reference to x's type.
+  template <typename F>
+  operator Action<F>() const {
+    typedef typename Function<F>::Result Result;
+    // Asserts that the function return type is a reference.  This
+    // catches the user error of using ReturnRef(x) when Return(x)
+    // should be used, and generates some helpful error message.
+    GTEST_COMPILE_ASSERT_(std::is_reference<Result>::value,
+                          use_Return_instead_of_ReturnRef_to_return_a_value);
+    return Action<F>(new Impl<F>(ref_));
+  }
+
+ private:
+  // Implements the ReturnRef(x) action for a particular function type F.
+  template <typename F>
+  class Impl : public ActionInterface<F> {
+   public:
+    typedef typename Function<F>::Result Result;
+    typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+
+    explicit Impl(T& ref) : ref_(ref) {}  // NOLINT
+
+    Result Perform(const ArgumentTuple&) override { return ref_; }
+
+   private:
+    T& ref_;
+  };
+
+  T& ref_;
+};
+
+// Implements the polymorphic ReturnRefOfCopy(x) action, which can be
+// used in any function that returns a reference to the type of x,
+// regardless of the argument types.
+template <typename T>
+class ReturnRefOfCopyAction {
+ public:
+  // Constructs a ReturnRefOfCopyAction object from the reference to
+  // be returned.
+  explicit ReturnRefOfCopyAction(const T& value) : value_(value) {}  // NOLINT
+
+  // This template type conversion operator allows ReturnRefOfCopy(x) to be
+  // used in ANY function that returns a reference to x's type.
+  template <typename F>
+  operator Action<F>() const {
+    typedef typename Function<F>::Result Result;
+    // Asserts that the function return type is a reference.  This
+    // catches the user error of using ReturnRefOfCopy(x) when Return(x)
+    // should be used, and generates some helpful error message.
+    GTEST_COMPILE_ASSERT_(
+        std::is_reference<Result>::value,
+        use_Return_instead_of_ReturnRefOfCopy_to_return_a_value);
+    return Action<F>(new Impl<F>(value_));
+  }
+
+ private:
+  // Implements the ReturnRefOfCopy(x) action for a particular function type F.
+  template <typename F>
+  class Impl : public ActionInterface<F> {
+   public:
+    typedef typename Function<F>::Result Result;
+    typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+
+    explicit Impl(const T& value) : value_(value) {}  // NOLINT
+
+    Result Perform(const ArgumentTuple&) override { return value_; }
+
+   private:
+    T value_;
+  };
+
+  const T value_;
+};
+
+// Implements the polymorphic ReturnRoundRobin(v) action, which can be
+// used in any function that returns the element_type of v.
+template <typename T>
+class ReturnRoundRobinAction {
+ public:
+  explicit ReturnRoundRobinAction(std::vector<T> values) {
+    GTEST_CHECK_(!values.empty())
+        << "ReturnRoundRobin requires at least one element.";
+    state_->values = std::move(values);
+  }
+
+  template <typename... Args>
+  T operator()(Args&&...) const {
+     return state_->Next();
+  }
+
+ private:
+  struct State {
+    T Next() {
+      T ret_val = values[i++];
+      if (i == values.size()) i = 0;
+      return ret_val;
+    }
+
+    std::vector<T> values;
+    size_t i = 0;
+  };
+  std::shared_ptr<State> state_ = std::make_shared<State>();
+};
+
+// Implements the polymorphic DoDefault() action.
+class DoDefaultAction {
+ public:
+  // This template type conversion operator allows DoDefault() to be
+  // used in any function.
+  template <typename F>
+  operator Action<F>() const { return Action<F>(); }  // NOLINT
+};
+
+// Implements the Assign action to set a given pointer referent to a
+// particular value.
+template <typename T1, typename T2>
+class AssignAction {
+ public:
+  AssignAction(T1* ptr, T2 value) : ptr_(ptr), value_(value) {}
+
+  template <typename Result, typename ArgumentTuple>
+  void Perform(const ArgumentTuple& /* args */) const {
+    *ptr_ = value_;
+  }
+
+ private:
+  T1* const ptr_;
+  const T2 value_;
+};
+
+#if !GTEST_OS_WINDOWS_MOBILE
+
+// Implements the SetErrnoAndReturn action to simulate return from
+// various system calls and libc functions.
+template <typename T>
+class SetErrnoAndReturnAction {
+ public:
+  SetErrnoAndReturnAction(int errno_value, T result)
+      : errno_(errno_value),
+        result_(result) {}
+  template <typename Result, typename ArgumentTuple>
+  Result Perform(const ArgumentTuple& /* args */) const {
+    errno = errno_;
+    return result_;
+  }
+
+ private:
+  const int errno_;
+  const T result_;
+};
+
+#endif  // !GTEST_OS_WINDOWS_MOBILE
+
+// Implements the SetArgumentPointee<N>(x) action for any function
+// whose N-th argument (0-based) is a pointer to x's type.
+template <size_t N, typename A, typename = void>
+struct SetArgumentPointeeAction {
+  A value;
+
+  template <typename... Args>
+  void operator()(const Args&... args) const {
+    *::std::get<N>(std::tie(args...)) = value;
+  }
+};
+
+// Implements the Invoke(object_ptr, &Class::Method) action.
+template <class Class, typename MethodPtr>
+struct InvokeMethodAction {
+  Class* const obj_ptr;
+  const MethodPtr method_ptr;
+
+  template <typename... Args>
+  auto operator()(Args&&... args) const
+      -> decltype((obj_ptr->*method_ptr)(std::forward<Args>(args)...)) {
+    return (obj_ptr->*method_ptr)(std::forward<Args>(args)...);
+  }
+};
+
+// Implements the InvokeWithoutArgs(f) action.  The template argument
+// FunctionImpl is the implementation type of f, which can be either a
+// function pointer or a functor.  InvokeWithoutArgs(f) can be used as an
+// Action<F> as long as f's type is compatible with F.
+template <typename FunctionImpl>
+struct InvokeWithoutArgsAction {
+  FunctionImpl function_impl;
+
+  // Allows InvokeWithoutArgs(f) to be used as any action whose type is
+  // compatible with f.
+  template <typename... Args>
+  auto operator()(const Args&...) -> decltype(function_impl()) {
+    return function_impl();
+  }
+};
+
+// Implements the InvokeWithoutArgs(object_ptr, &Class::Method) action.
+template <class Class, typename MethodPtr>
+struct InvokeMethodWithoutArgsAction {
+  Class* const obj_ptr;
+  const MethodPtr method_ptr;
+
+  using ReturnType =
+      decltype((std::declval<Class*>()->*std::declval<MethodPtr>())());
+
+  template <typename... Args>
+  ReturnType operator()(const Args&...) const {
+    return (obj_ptr->*method_ptr)();
+  }
+};
+
+// Implements the IgnoreResult(action) action.
+template <typename A>
+class IgnoreResultAction {
+ public:
+  explicit IgnoreResultAction(const A& action) : action_(action) {}
+
+  template <typename F>
+  operator Action<F>() const {
+    // Assert statement belongs here because this is the best place to verify
+    // conditions on F. It produces the clearest error messages
+    // in most compilers.
+    // Impl really belongs in this scope as a local class but can't
+    // because MSVC produces duplicate symbols in different translation units
+    // in this case. Until MS fixes that bug we put Impl into the class scope
+    // and put the typedef both here (for use in assert statement) and
+    // in the Impl class. But both definitions must be the same.
+    typedef typename internal::Function<F>::Result Result;
+
+    // Asserts at compile time that F returns void.
+    static_assert(std::is_void<Result>::value, "Result type should be void.");
+
+    return Action<F>(new Impl<F>(action_));
+  }
+
+ private:
+  template <typename F>
+  class Impl : public ActionInterface<F> {
+   public:
+    typedef typename internal::Function<F>::Result Result;
+    typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple;
+
+    explicit Impl(const A& action) : action_(action) {}
+
+    void Perform(const ArgumentTuple& args) override {
+      // Performs the action and ignores its result.
+      action_.Perform(args);
+    }
+
+   private:
+    // Type OriginalFunction is the same as F except that its return
+    // type is IgnoredValue.
+    typedef typename internal::Function<F>::MakeResultIgnoredValue
+        OriginalFunction;
+
+    const Action<OriginalFunction> action_;
+  };
+
+  const A action_;
+};
+
+template <typename InnerAction, size_t... I>
+struct WithArgsAction {
+  InnerAction action;
+
+  // The inner action could be anything convertible to Action<X>.
+  // We use the conversion operator to detect the signature of the inner Action.
+  template <typename R, typename... Args>
+  operator Action<R(Args...)>() const {  // NOLINT
+    using TupleType = std::tuple<Args...>;
+    Action<R(typename std::tuple_element<I, TupleType>::type...)>
+        converted(action);
+
+    return [converted](Args... args) -> R {
+      return converted.Perform(std::forward_as_tuple(
+        std::get<I>(std::forward_as_tuple(std::forward<Args>(args)...))...));
+    };
+  }
+};
+
+template <typename... Actions>
+struct DoAllAction {
+ private:
+  template <typename T>
+  using NonFinalType =
+      typename std::conditional<std::is_scalar<T>::value, T, const T&>::type;
+
+  template <typename ActionT, size_t... I>
+  std::vector<ActionT> Convert(IndexSequence<I...>) const {
+    return {ActionT(std::get<I>(actions))...};
+  }
+
+ public:
+  std::tuple<Actions...> actions;
+
+  template <typename R, typename... Args>
+  operator Action<R(Args...)>() const {  // NOLINT
+    struct Op {
+      std::vector<Action<void(NonFinalType<Args>...)>> converted;
+      Action<R(Args...)> last;
+      R operator()(Args... args) const {
+        auto tuple_args = std::forward_as_tuple(std::forward<Args>(args)...);
+        for (auto& a : converted) {
+          a.Perform(tuple_args);
+        }
+        return last.Perform(std::move(tuple_args));
+      }
+    };
+    return Op{Convert<Action<void(NonFinalType<Args>...)>>(
+                  MakeIndexSequence<sizeof...(Actions) - 1>()),
+              std::get<sizeof...(Actions) - 1>(actions)};
+  }
+};
+
+template <typename T, typename... Params>
+struct ReturnNewAction {
+  T* operator()() const {
+    return internal::Apply(
+        [](const Params&... unpacked_params) {
+          return new T(unpacked_params...);
+        },
+        params);
+  }
+  std::tuple<Params...> params;
+};
+
+template <size_t k>
+struct ReturnArgAction {
+  template <typename... Args>
+  auto operator()(const Args&... args) const ->
+      typename std::tuple_element<k, std::tuple<Args...>>::type {
+    return std::get<k>(std::tie(args...));
+  }
+};
+
+template <size_t k, typename Ptr>
+struct SaveArgAction {
+  Ptr pointer;
+
+  template <typename... Args>
+  void operator()(const Args&... args) const {
+    *pointer = std::get<k>(std::tie(args...));
+  }
+};
+
+template <size_t k, typename Ptr>
+struct SaveArgPointeeAction {
+  Ptr pointer;
+
+  template <typename... Args>
+  void operator()(const Args&... args) const {
+    *pointer = *std::get<k>(std::tie(args...));
+  }
+};
+
+template <size_t k, typename T>
+struct SetArgRefereeAction {
+  T value;
+
+  template <typename... Args>
+  void operator()(Args&&... args) const {
+    using argk_type =
+        typename ::std::tuple_element<k, std::tuple<Args...>>::type;
+    static_assert(std::is_lvalue_reference<argk_type>::value,
+                  "Argument must be a reference type.");
+    std::get<k>(std::tie(args...)) = value;
+  }
+};
+
+template <size_t k, typename I1, typename I2>
+struct SetArrayArgumentAction {
+  I1 first;
+  I2 last;
+
+  template <typename... Args>
+  void operator()(const Args&... args) const {
+    auto value = std::get<k>(std::tie(args...));
+    for (auto it = first; it != last; ++it, (void)++value) {
+      *value = *it;
+    }
+  }
+};
+
+template <size_t k>
+struct DeleteArgAction {
+  template <typename... Args>
+  void operator()(const Args&... args) const {
+    delete std::get<k>(std::tie(args...));
+  }
+};
+
+template <typename Ptr>
+struct ReturnPointeeAction {
+  Ptr pointer;
+  template <typename... Args>
+  auto operator()(const Args&...) const -> decltype(*pointer) {
+    return *pointer;
+  }
+};
+
+#if GTEST_HAS_EXCEPTIONS
+template <typename T>
+struct ThrowAction {
+  T exception;
+  // We use a conversion operator to adapt to any return type.
+  template <typename R, typename... Args>
+  operator Action<R(Args...)>() const {  // NOLINT
+    T copy = exception;
+    return [copy](Args...) -> R { throw copy; };
+  }
+};
+#endif  // GTEST_HAS_EXCEPTIONS
+
+}  // namespace internal
+
+// An Unused object can be implicitly constructed from ANY value.
+// This is handy when defining actions that ignore some or all of the
+// mock function arguments.  For example, given
+//
+//   MOCK_METHOD3(Foo, double(const string& label, double x, double y));
+//   MOCK_METHOD3(Bar, double(int index, double x, double y));
+//
+// instead of
+//
+//   double DistanceToOriginWithLabel(const string& label, double x, double y) {
+//     return sqrt(x*x + y*y);
+//   }
+//   double DistanceToOriginWithIndex(int index, double x, double y) {
+//     return sqrt(x*x + y*y);
+//   }
+//   ...
+//   EXPECT_CALL(mock, Foo("abc", _, _))
+//       .WillOnce(Invoke(DistanceToOriginWithLabel));
+//   EXPECT_CALL(mock, Bar(5, _, _))
+//       .WillOnce(Invoke(DistanceToOriginWithIndex));
+//
+// you could write
+//
+//   // We can declare any uninteresting argument as Unused.
+//   double DistanceToOrigin(Unused, double x, double y) {
+//     return sqrt(x*x + y*y);
+//   }
+//   ...
+//   EXPECT_CALL(mock, Foo("abc", _, _)).WillOnce(Invoke(DistanceToOrigin));
+//   EXPECT_CALL(mock, Bar(5, _, _)).WillOnce(Invoke(DistanceToOrigin));
+typedef internal::IgnoredValue Unused;
+
+// Creates an action that does actions a1, a2, ..., sequentially in
+// each invocation. All but the last action will have a readonly view of the
+// arguments.
+template <typename... Action>
+internal::DoAllAction<typename std::decay<Action>::type...> DoAll(
+    Action&&... action) {
+  return {std::forward_as_tuple(std::forward<Action>(action)...)};
+}
+
+// WithArg<k>(an_action) creates an action that passes the k-th
+// (0-based) argument of the mock function to an_action and performs
+// it.  It adapts an action accepting one argument to one that accepts
+// multiple arguments.  For convenience, we also provide
+// WithArgs<k>(an_action) (defined below) as a synonym.
+template <size_t k, typename InnerAction>
+internal::WithArgsAction<typename std::decay<InnerAction>::type, k>
+WithArg(InnerAction&& action) {
+  return {std::forward<InnerAction>(action)};
+}
+
+// WithArgs<N1, N2, ..., Nk>(an_action) creates an action that passes
+// the selected arguments of the mock function to an_action and
+// performs it.  It serves as an adaptor between actions with
+// different argument lists.
+template <size_t k, size_t... ks, typename InnerAction>
+internal::WithArgsAction<typename std::decay<InnerAction>::type, k, ks...>
+WithArgs(InnerAction&& action) {
+  return {std::forward<InnerAction>(action)};
+}
+
+// WithoutArgs(inner_action) can be used in a mock function with a
+// non-empty argument list to perform inner_action, which takes no
+// argument.  In other words, it adapts an action accepting no
+// argument to one that accepts (and ignores) arguments.
+template <typename InnerAction>
+internal::WithArgsAction<typename std::decay<InnerAction>::type>
+WithoutArgs(InnerAction&& action) {
+  return {std::forward<InnerAction>(action)};
+}
+
+// Creates an action that returns 'value'.  'value' is passed by value
+// instead of const reference - otherwise Return("string literal")
+// will trigger a compiler error about using array as initializer.
+template <typename R>
+internal::ReturnAction<R> Return(R value) {
+  return internal::ReturnAction<R>(std::move(value));
+}
+
+// Creates an action that returns NULL.
+inline PolymorphicAction<internal::ReturnNullAction> ReturnNull() {
+  return MakePolymorphicAction(internal::ReturnNullAction());
+}
+
+// Creates an action that returns from a void function.
+inline PolymorphicAction<internal::ReturnVoidAction> Return() {
+  return MakePolymorphicAction(internal::ReturnVoidAction());
+}
+
+// Creates an action that returns the reference to a variable.
+template <typename R>
+inline internal::ReturnRefAction<R> ReturnRef(R& x) {  // NOLINT
+  return internal::ReturnRefAction<R>(x);
+}
+
+// Prevent using ReturnRef on reference to temporary.
+template <typename R, R* = nullptr>
+internal::ReturnRefAction<R> ReturnRef(R&&) = delete;
+
+// Creates an action that returns the reference to a copy of the
+// argument.  The copy is created when the action is constructed and
+// lives as long as the action.
+template <typename R>
+inline internal::ReturnRefOfCopyAction<R> ReturnRefOfCopy(const R& x) {
+  return internal::ReturnRefOfCopyAction<R>(x);
+}
+
+// Modifies the parent action (a Return() action) to perform a move of the
+// argument instead of a copy.
+// Return(ByMove()) actions can only be executed once and will assert this
+// invariant.
+template <typename R>
+internal::ByMoveWrapper<R> ByMove(R x) {
+  return internal::ByMoveWrapper<R>(std::move(x));
+}
+
+// Creates an action that returns an element of `vals`. Calling this action will
+// repeatedly return the next value from `vals` until it reaches the end and
+// will restart from the beginning.
+template <typename T>
+internal::ReturnRoundRobinAction<T> ReturnRoundRobin(std::vector<T> vals) {
+  return internal::ReturnRoundRobinAction<T>(std::move(vals));
+}
+
+// Creates an action that returns an element of `vals`. Calling this action will
+// repeatedly return the next value from `vals` until it reaches the end and
+// will restart from the beginning.
+template <typename T>
+internal::ReturnRoundRobinAction<T> ReturnRoundRobin(
+    std::initializer_list<T> vals) {
+  return internal::ReturnRoundRobinAction<T>(std::vector<T>(vals));
+}
+
+// Creates an action that does the default action for the give mock function.
+inline internal::DoDefaultAction DoDefault() {
+  return internal::DoDefaultAction();
+}
+
+// Creates an action that sets the variable pointed by the N-th
+// (0-based) function argument to 'value'.
+template <size_t N, typename T>
+internal::SetArgumentPointeeAction<N, T> SetArgPointee(T value) {
+  return {std::move(value)};
+}
+
+// The following version is DEPRECATED.
+template <size_t N, typename T>
+internal::SetArgumentPointeeAction<N, T> SetArgumentPointee(T value) {
+  return {std::move(value)};
+}
+
+// Creates an action that sets a pointer referent to a given value.
+template <typename T1, typename T2>
+PolymorphicAction<internal::AssignAction<T1, T2> > Assign(T1* ptr, T2 val) {
+  return MakePolymorphicAction(internal::AssignAction<T1, T2>(ptr, val));
+}
+
+#if !GTEST_OS_WINDOWS_MOBILE
+
+// Creates an action that sets errno and returns the appropriate error.
+template <typename T>
+PolymorphicAction<internal::SetErrnoAndReturnAction<T> >
+SetErrnoAndReturn(int errval, T result) {
+  return MakePolymorphicAction(
+      internal::SetErrnoAndReturnAction<T>(errval, result));
+}
+
+#endif  // !GTEST_OS_WINDOWS_MOBILE
+
+// Various overloads for Invoke().
+
+// Legacy function.
+// Actions can now be implicitly constructed from callables. No need to create
+// wrapper objects.
+// This function exists for backwards compatibility.
+template <typename FunctionImpl>
+typename std::decay<FunctionImpl>::type Invoke(FunctionImpl&& function_impl) {
+  return std::forward<FunctionImpl>(function_impl);
+}
+
+// Creates an action that invokes the given method on the given object
+// with the mock function's arguments.
+template <class Class, typename MethodPtr>
+internal::InvokeMethodAction<Class, MethodPtr> Invoke(Class* obj_ptr,
+                                                      MethodPtr method_ptr) {
+  return {obj_ptr, method_ptr};
+}
+
+// Creates an action that invokes 'function_impl' with no argument.
+template <typename FunctionImpl>
+internal::InvokeWithoutArgsAction<typename std::decay<FunctionImpl>::type>
+InvokeWithoutArgs(FunctionImpl function_impl) {
+  return {std::move(function_impl)};
+}
+
+// Creates an action that invokes the given method on the given object
+// with no argument.
+template <class Class, typename MethodPtr>
+internal::InvokeMethodWithoutArgsAction<Class, MethodPtr> InvokeWithoutArgs(
+    Class* obj_ptr, MethodPtr method_ptr) {
+  return {obj_ptr, method_ptr};
+}
+
+// Creates an action that performs an_action and throws away its
+// result.  In other words, it changes the return type of an_action to
+// void.  an_action MUST NOT return void, or the code won't compile.
+template <typename A>
+inline internal::IgnoreResultAction<A> IgnoreResult(const A& an_action) {
+  return internal::IgnoreResultAction<A>(an_action);
+}
+
+// Creates a reference wrapper for the given L-value.  If necessary,
+// you can explicitly specify the type of the reference.  For example,
+// suppose 'derived' is an object of type Derived, ByRef(derived)
+// would wrap a Derived&.  If you want to wrap a const Base& instead,
+// where Base is a base class of Derived, just write:
+//
+//   ByRef<const Base>(derived)
+//
+// N.B. ByRef is redundant with std::ref, std::cref and std::reference_wrapper.
+// However, it may still be used for consistency with ByMove().
+template <typename T>
+inline ::std::reference_wrapper<T> ByRef(T& l_value) {  // NOLINT
+  return ::std::reference_wrapper<T>(l_value);
+}
+
+// The ReturnNew<T>(a1, a2, ..., a_k) action returns a pointer to a new
+// instance of type T, constructed on the heap with constructor arguments
+// a1, a2, ..., and a_k. The caller assumes ownership of the returned value.
+template <typename T, typename... Params>
+internal::ReturnNewAction<T, typename std::decay<Params>::type...> ReturnNew(
+    Params&&... params) {
+  return {std::forward_as_tuple(std::forward<Params>(params)...)};
+}
+
+// Action ReturnArg<k>() returns the k-th argument of the mock function.
+template <size_t k>
+internal::ReturnArgAction<k> ReturnArg() {
+  return {};
+}
+
+// Action SaveArg<k>(pointer) saves the k-th (0-based) argument of the
+// mock function to *pointer.
+template <size_t k, typename Ptr>
+internal::SaveArgAction<k, Ptr> SaveArg(Ptr pointer) {
+  return {pointer};
+}
+
+// Action SaveArgPointee<k>(pointer) saves the value pointed to
+// by the k-th (0-based) argument of the mock function to *pointer.
+template <size_t k, typename Ptr>
+internal::SaveArgPointeeAction<k, Ptr> SaveArgPointee(Ptr pointer) {
+  return {pointer};
+}
+
+// Action SetArgReferee<k>(value) assigns 'value' to the variable
+// referenced by the k-th (0-based) argument of the mock function.
+template <size_t k, typename T>
+internal::SetArgRefereeAction<k, typename std::decay<T>::type> SetArgReferee(
+    T&& value) {
+  return {std::forward<T>(value)};
+}
+
+// Action SetArrayArgument<k>(first, last) copies the elements in
+// source range [first, last) to the array pointed to by the k-th
+// (0-based) argument, which can be either a pointer or an
+// iterator. The action does not take ownership of the elements in the
+// source range.
+template <size_t k, typename I1, typename I2>
+internal::SetArrayArgumentAction<k, I1, I2> SetArrayArgument(I1 first,
+                                                             I2 last) {
+  return {first, last};
+}
+
+// Action DeleteArg<k>() deletes the k-th (0-based) argument of the mock
+// function.
+template <size_t k>
+internal::DeleteArgAction<k> DeleteArg() {
+  return {};
+}
+
+// This action returns the value pointed to by 'pointer'.
+template <typename Ptr>
+internal::ReturnPointeeAction<Ptr> ReturnPointee(Ptr pointer) {
+  return {pointer};
+}
+
+// Action Throw(exception) can be used in a mock function of any type
+// to throw the given exception.  Any copyable value can be thrown.
+#if GTEST_HAS_EXCEPTIONS
+template <typename T>
+internal::ThrowAction<typename std::decay<T>::type> Throw(T&& exception) {
+  return {std::forward<T>(exception)};
+}
+#endif  // GTEST_HAS_EXCEPTIONS
+
+namespace internal {
+
+// A macro from the ACTION* family (defined later in gmock-generated-actions.h)
+// defines an action that can be used in a mock function.  Typically,
+// these actions only care about a subset of the arguments of the mock
+// function.  For example, if such an action only uses the second
+// argument, it can be used in any mock function that takes >= 2
+// arguments where the type of the second argument is compatible.
+//
+// Therefore, the action implementation must be prepared to take more
+// arguments than it needs.  The ExcessiveArg type is used to
+// represent those excessive arguments.  In order to keep the compiler
+// error messages tractable, we define it in the testing namespace
+// instead of testing::internal.  However, this is an INTERNAL TYPE
+// and subject to change without notice, so a user MUST NOT USE THIS
+// TYPE DIRECTLY.
+struct ExcessiveArg {};
+
+// Builds an implementation of an Action<> for some particular signature, using
+// a class defined by an ACTION* macro.
+template <typename F, typename Impl> struct ActionImpl;
+
+template <typename Impl>
+struct ImplBase {
+  struct Holder {
+    // Allows each copy of the Action<> to get to the Impl.
+    explicit operator const Impl&() const { return *ptr; }
+    std::shared_ptr<Impl> ptr;
+  };
+  using type = typename std::conditional<std::is_constructible<Impl>::value,
+                                         Impl, Holder>::type;
+};
+
+template <typename R, typename... Args, typename Impl>
+struct ActionImpl<R(Args...), Impl> : ImplBase<Impl>::type {
+  using Base = typename ImplBase<Impl>::type;
+  using function_type = R(Args...);
+  using args_type = std::tuple<Args...>;
+
+  ActionImpl() = default;  // Only defined if appropriate for Base.
+  explicit ActionImpl(std::shared_ptr<Impl> impl) : Base{std::move(impl)} { }
+
+  R operator()(Args&&... arg) const {
+    static constexpr size_t kMaxArgs =
+        sizeof...(Args) <= 10 ? sizeof...(Args) : 10;
+    return Apply(MakeIndexSequence<kMaxArgs>{},
+                 MakeIndexSequence<10 - kMaxArgs>{},
+                 args_type{std::forward<Args>(arg)...});
+  }
+
+  template <std::size_t... arg_id, std::size_t... excess_id>
+  R Apply(IndexSequence<arg_id...>, IndexSequence<excess_id...>,
+          const args_type& args) const {
+    // Impl need not be specific to the signature of action being implemented;
+    // only the implementing function body needs to have all of the specific
+    // types instantiated.  Up to 10 of the args that are provided by the
+    // args_type get passed, followed by a dummy of unspecified type for the
+    // remainder up to 10 explicit args.
+    static constexpr ExcessiveArg kExcessArg{};
+    return static_cast<const Impl&>(*this).template gmock_PerformImpl<
+        /*function_type=*/function_type, /*return_type=*/R,
+        /*args_type=*/args_type,
+        /*argN_type=*/typename std::tuple_element<arg_id, args_type>::type...>(
+        /*args=*/args, std::get<arg_id>(args)...,
+        ((void)excess_id, kExcessArg)...);
+  }
+};
+
+// Stores a default-constructed Impl as part of the Action<>'s
+// std::function<>. The Impl should be trivial to copy.
+template <typename F, typename Impl>
+::testing::Action<F> MakeAction() {
+  return ::testing::Action<F>(ActionImpl<F, Impl>());
+}
+
+// Stores just the one given instance of Impl.
+template <typename F, typename Impl>
+::testing::Action<F> MakeAction(std::shared_ptr<Impl> impl) {
+  return ::testing::Action<F>(ActionImpl<F, Impl>(std::move(impl)));
+}
+
+#define GMOCK_INTERNAL_ARG_UNUSED(i, data, el) \
+  , const arg##i##_type& arg##i GTEST_ATTRIBUTE_UNUSED_
+#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_           \
+  const args_type& args GTEST_ATTRIBUTE_UNUSED_ GMOCK_PP_REPEAT( \
+      GMOCK_INTERNAL_ARG_UNUSED, , 10)
+
+#define GMOCK_INTERNAL_ARG(i, data, el) , const arg##i##_type& arg##i
+#define GMOCK_ACTION_ARG_TYPES_AND_NAMES_ \
+  const args_type& args GMOCK_PP_REPEAT(GMOCK_INTERNAL_ARG, , 10)
+
+#define GMOCK_INTERNAL_TEMPLATE_ARG(i, data, el) , typename arg##i##_type
+#define GMOCK_ACTION_TEMPLATE_ARGS_NAMES_ \
+  GMOCK_PP_TAIL(GMOCK_PP_REPEAT(GMOCK_INTERNAL_TEMPLATE_ARG, , 10))
+
+#define GMOCK_INTERNAL_TYPENAME_PARAM(i, data, param) , typename param##_type
+#define GMOCK_ACTION_TYPENAME_PARAMS_(params) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPENAME_PARAM, , params))
+
+#define GMOCK_INTERNAL_TYPE_PARAM(i, data, param) , param##_type
+#define GMOCK_ACTION_TYPE_PARAMS_(params) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_PARAM, , params))
+
+#define GMOCK_INTERNAL_TYPE_GVALUE_PARAM(i, data, param) \
+  , param##_type gmock_p##i
+#define GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_TYPE_GVALUE_PARAM, , params))
+
+#define GMOCK_INTERNAL_GVALUE_PARAM(i, data, param) \
+  , std::forward<param##_type>(gmock_p##i)
+#define GMOCK_ACTION_GVALUE_PARAMS_(params) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GVALUE_PARAM, , params))
+
+#define GMOCK_INTERNAL_INIT_PARAM(i, data, param) \
+  , param(::std::forward<param##_type>(gmock_p##i))
+#define GMOCK_ACTION_INIT_PARAMS_(params) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_INIT_PARAM, , params))
+
+#define GMOCK_INTERNAL_FIELD_PARAM(i, data, param) param##_type param;
+#define GMOCK_ACTION_FIELD_PARAMS_(params) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_FIELD_PARAM, , params)
+
+#define GMOCK_INTERNAL_ACTION(name, full_name, params)                        \
+  template <GMOCK_ACTION_TYPENAME_PARAMS_(params)>                            \
+  class full_name {                                                           \
+   public:                                                                    \
+    explicit full_name(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params))              \
+        : impl_(std::make_shared<gmock_Impl>(                                 \
+                GMOCK_ACTION_GVALUE_PARAMS_(params))) { }                     \
+    full_name(const full_name&) = default;                                    \
+    full_name(full_name&&) noexcept = default;                                \
+    template <typename F>                                                     \
+    operator ::testing::Action<F>() const {                                   \
+      return ::testing::internal::MakeAction<F>(impl_);                       \
+    }                                                                         \
+   private:                                                                   \
+    class gmock_Impl {                                                        \
+     public:                                                                  \
+      explicit gmock_Impl(GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params))           \
+          : GMOCK_ACTION_INIT_PARAMS_(params) {}                              \
+      template <typename function_type, typename return_type,                 \
+                typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>        \
+      return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \
+      GMOCK_ACTION_FIELD_PARAMS_(params)                                      \
+    };                                                                        \
+    std::shared_ptr<const gmock_Impl> impl_;                                  \
+  };                                                                          \
+  template <GMOCK_ACTION_TYPENAME_PARAMS_(params)>                            \
+  inline full_name<GMOCK_ACTION_TYPE_PARAMS_(params)> name(                   \
+      GMOCK_ACTION_TYPE_GVALUE_PARAMS_(params)) {                             \
+    return full_name<GMOCK_ACTION_TYPE_PARAMS_(params)>(                      \
+        GMOCK_ACTION_GVALUE_PARAMS_(params));                                 \
+  }                                                                           \
+  template <GMOCK_ACTION_TYPENAME_PARAMS_(params)>                            \
+  template <typename function_type, typename return_type, typename args_type, \
+            GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>                                \
+  return_type full_name<GMOCK_ACTION_TYPE_PARAMS_(params)>::gmock_Impl::      \
+  gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const
+
+}  // namespace internal
+
+// Similar to GMOCK_INTERNAL_ACTION, but no bound parameters are stored.
+#define ACTION(name)                                                          \
+  class name##Action {                                                        \
+   public:                                                                    \
+   explicit name##Action() noexcept {}                                        \
+   name##Action(const name##Action&) noexcept {}                              \
+    template <typename F>                                                     \
+    operator ::testing::Action<F>() const {                                   \
+      return ::testing::internal::MakeAction<F, gmock_Impl>();                \
+    }                                                                         \
+   private:                                                                   \
+    class gmock_Impl {                                                        \
+     public:                                                                  \
+      template <typename function_type, typename return_type,                 \
+                typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>        \
+      return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \
+    };                                                                        \
+  };                                                                          \
+  inline name##Action name() GTEST_MUST_USE_RESULT_;                          \
+  inline name##Action name() { return name##Action(); }                       \
+  template <typename function_type, typename return_type, typename args_type, \
+            GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>                                \
+  return_type name##Action::gmock_Impl::gmock_PerformImpl(                    \
+      GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const
+
+#define ACTION_P(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP, (__VA_ARGS__))
+
+#define ACTION_P2(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP2, (__VA_ARGS__))
+
+#define ACTION_P3(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP3, (__VA_ARGS__))
+
+#define ACTION_P4(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP4, (__VA_ARGS__))
+
+#define ACTION_P5(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP5, (__VA_ARGS__))
+
+#define ACTION_P6(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP6, (__VA_ARGS__))
+
+#define ACTION_P7(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP7, (__VA_ARGS__))
+
+#define ACTION_P8(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP8, (__VA_ARGS__))
+
+#define ACTION_P9(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP9, (__VA_ARGS__))
+
+#define ACTION_P10(name, ...) \
+  GMOCK_INTERNAL_ACTION(name, name##ActionP10, (__VA_ARGS__))
+
+}  // namespace testing
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_ACTIONS_H_
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements some commonly used cardinalities.  More
+// cardinalities can be defined by the user implementing the
+// CardinalityInterface interface if necessary.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
+
+#include <limits.h>
+#include <memory>
+#include <ostream>  // NOLINT
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// To implement a cardinality Foo, define:
+//   1. a class FooCardinality that implements the
+//      CardinalityInterface interface, and
+//   2. a factory function that creates a Cardinality object from a
+//      const FooCardinality*.
+//
+// The two-level delegation design follows that of Matcher, providing
+// consistency for extension developers.  It also eases ownership
+// management as Cardinality objects can now be copied like plain values.
+
+// The implementation of a cardinality.
+class CardinalityInterface {
+ public:
+  virtual ~CardinalityInterface() {}
+
+  // Conservative estimate on the lower/upper bound of the number of
+  // calls allowed.
+  virtual int ConservativeLowerBound() const { return 0; }
+  virtual int ConservativeUpperBound() const { return INT_MAX; }
+
+  // Returns true if and only if call_count calls will satisfy this
+  // cardinality.
+  virtual bool IsSatisfiedByCallCount(int call_count) const = 0;
+
+  // Returns true if and only if call_count calls will saturate this
+  // cardinality.
+  virtual bool IsSaturatedByCallCount(int call_count) const = 0;
+
+  // Describes self to an ostream.
+  virtual void DescribeTo(::std::ostream* os) const = 0;
+};
+
+// A Cardinality is a copyable and IMMUTABLE (except by assignment)
+// object that specifies how many times a mock function is expected to
+// be called.  The implementation of Cardinality is just a std::shared_ptr
+// to const CardinalityInterface. Don't inherit from Cardinality!
+class GTEST_API_ Cardinality {
+ public:
+  // Constructs a null cardinality.  Needed for storing Cardinality
+  // objects in STL containers.
+  Cardinality() {}
+
+  // Constructs a Cardinality from its implementation.
+  explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {}
+
+  // Conservative estimate on the lower/upper bound of the number of
+  // calls allowed.
+  int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); }
+  int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); }
+
+  // Returns true if and only if call_count calls will satisfy this
+  // cardinality.
+  bool IsSatisfiedByCallCount(int call_count) const {
+    return impl_->IsSatisfiedByCallCount(call_count);
+  }
+
+  // Returns true if and only if call_count calls will saturate this
+  // cardinality.
+  bool IsSaturatedByCallCount(int call_count) const {
+    return impl_->IsSaturatedByCallCount(call_count);
+  }
+
+  // Returns true if and only if call_count calls will over-saturate this
+  // cardinality, i.e. exceed the maximum number of allowed calls.
+  bool IsOverSaturatedByCallCount(int call_count) const {
+    return impl_->IsSaturatedByCallCount(call_count) &&
+        !impl_->IsSatisfiedByCallCount(call_count);
+  }
+
+  // Describes self to an ostream
+  void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); }
+
+  // Describes the given actual call count to an ostream.
+  static void DescribeActualCallCountTo(int actual_call_count,
+                                        ::std::ostream* os);
+
+ private:
+  std::shared_ptr<const CardinalityInterface> impl_;
+};
+
+// Creates a cardinality that allows at least n calls.
+GTEST_API_ Cardinality AtLeast(int n);
+
+// Creates a cardinality that allows at most n calls.
+GTEST_API_ Cardinality AtMost(int n);
+
+// Creates a cardinality that allows any number of calls.
+GTEST_API_ Cardinality AnyNumber();
+
+// Creates a cardinality that allows between min and max calls.
+GTEST_API_ Cardinality Between(int min, int max);
+
+// Creates a cardinality that allows exactly n calls.
+GTEST_API_ Cardinality Exactly(int n);
+
+// Creates a cardinality from its implementation.
+inline Cardinality MakeCardinality(const CardinalityInterface* c) {
+  return Cardinality(c);
+}
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements MOCK_METHOD.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_  // NOLINT
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_  // NOLINT
+
+#include <type_traits>  // IWYU pragma: keep
+#include <utility>      // IWYU pragma: keep
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements the ON_CALL() and EXPECT_CALL() macros.
+//
+// A user can use the ON_CALL() macro to specify the default action of
+// a mock method.  The syntax is:
+//
+//   ON_CALL(mock_object, Method(argument-matchers))
+//       .With(multi-argument-matcher)
+//       .WillByDefault(action);
+//
+//  where the .With() clause is optional.
+//
+// A user can use the EXPECT_CALL() macro to specify an expectation on
+// a mock method.  The syntax is:
+//
+//   EXPECT_CALL(mock_object, Method(argument-matchers))
+//       .With(multi-argument-matchers)
+//       .Times(cardinality)
+//       .InSequence(sequences)
+//       .After(expectations)
+//       .WillOnce(action)
+//       .WillRepeatedly(action)
+//       .RetiresOnSaturation();
+//
+// where all clauses are optional, and .InSequence()/.After()/
+// .WillOnce() can appear any number of times.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// The MATCHER* family of macros can be used in a namespace scope to
+// define custom matchers easily.
+//
+// Basic Usage
+// ===========
+//
+// The syntax
+//
+//   MATCHER(name, description_string) { statements; }
+//
+// defines a matcher with the given name that executes the statements,
+// which must return a bool to indicate if the match succeeds.  Inside
+// the statements, you can refer to the value being matched by 'arg',
+// and refer to its type by 'arg_type'.
+//
+// The description string documents what the matcher does, and is used
+// to generate the failure message when the match fails.  Since a
+// MATCHER() is usually defined in a header file shared by multiple
+// C++ source files, we require the description to be a C-string
+// literal to avoid possible side effects.  It can be empty, in which
+// case we'll use the sequence of words in the matcher name as the
+// description.
+//
+// For example:
+//
+//   MATCHER(IsEven, "") { return (arg % 2) == 0; }
+//
+// allows you to write
+//
+//   // Expects mock_foo.Bar(n) to be called where n is even.
+//   EXPECT_CALL(mock_foo, Bar(IsEven()));
+//
+// or,
+//
+//   // Verifies that the value of some_expression is even.
+//   EXPECT_THAT(some_expression, IsEven());
+//
+// If the above assertion fails, it will print something like:
+//
+//   Value of: some_expression
+//   Expected: is even
+//     Actual: 7
+//
+// where the description "is even" is automatically calculated from the
+// matcher name IsEven.
+//
+// Argument Type
+// =============
+//
+// Note that the type of the value being matched (arg_type) is
+// determined by the context in which you use the matcher and is
+// supplied to you by the compiler, so you don't need to worry about
+// declaring it (nor can you).  This allows the matcher to be
+// polymorphic.  For example, IsEven() can be used to match any type
+// where the value of "(arg % 2) == 0" can be implicitly converted to
+// a bool.  In the "Bar(IsEven())" example above, if method Bar()
+// takes an int, 'arg_type' will be int; if it takes an unsigned long,
+// 'arg_type' will be unsigned long; and so on.
+//
+// Parameterizing Matchers
+// =======================
+//
+// Sometimes you'll want to parameterize the matcher.  For that you
+// can use another macro:
+//
+//   MATCHER_P(name, param_name, description_string) { statements; }
+//
+// For example:
+//
+//   MATCHER_P(HasAbsoluteValue, value, "") { return abs(arg) == value; }
+//
+// will allow you to write:
+//
+//   EXPECT_THAT(Blah("a"), HasAbsoluteValue(n));
+//
+// which may lead to this message (assuming n is 10):
+//
+//   Value of: Blah("a")
+//   Expected: has absolute value 10
+//     Actual: -9
+//
+// Note that both the matcher description and its parameter are
+// printed, making the message human-friendly.
+//
+// In the matcher definition body, you can write 'foo_type' to
+// reference the type of a parameter named 'foo'.  For example, in the
+// body of MATCHER_P(HasAbsoluteValue, value) above, you can write
+// 'value_type' to refer to the type of 'value'.
+//
+// We also provide MATCHER_P2, MATCHER_P3, ..., up to MATCHER_P$n to
+// support multi-parameter matchers.
+//
+// Describing Parameterized Matchers
+// =================================
+//
+// The last argument to MATCHER*() is a string-typed expression.  The
+// expression can reference all of the matcher's parameters and a
+// special bool-typed variable named 'negation'.  When 'negation' is
+// false, the expression should evaluate to the matcher's description;
+// otherwise it should evaluate to the description of the negation of
+// the matcher.  For example,
+//
+//   using testing::PrintToString;
+//
+//   MATCHER_P2(InClosedRange, low, hi,
+//       std::string(negation ? "is not" : "is") + " in range [" +
+//       PrintToString(low) + ", " + PrintToString(hi) + "]") {
+//     return low <= arg && arg <= hi;
+//   }
+//   ...
+//   EXPECT_THAT(3, InClosedRange(4, 6));
+//   EXPECT_THAT(3, Not(InClosedRange(2, 4)));
+//
+// would generate two failures that contain the text:
+//
+//   Expected: is in range [4, 6]
+//   ...
+//   Expected: is not in range [2, 4]
+//
+// If you specify "" as the description, the failure message will
+// contain the sequence of words in the matcher name followed by the
+// parameter values printed as a tuple.  For example,
+//
+//   MATCHER_P2(InClosedRange, low, hi, "") { ... }
+//   ...
+//   EXPECT_THAT(3, InClosedRange(4, 6));
+//   EXPECT_THAT(3, Not(InClosedRange(2, 4)));
+//
+// would generate two failures that contain the text:
+//
+//   Expected: in closed range (4, 6)
+//   ...
+//   Expected: not (in closed range (2, 4))
+//
+// Types of Matcher Parameters
+// ===========================
+//
+// For the purpose of typing, you can view
+//
+//   MATCHER_Pk(Foo, p1, ..., pk, description_string) { ... }
+//
+// as shorthand for
+//
+//   template <typename p1_type, ..., typename pk_type>
+//   FooMatcherPk<p1_type, ..., pk_type>
+//   Foo(p1_type p1, ..., pk_type pk) { ... }
+//
+// When you write Foo(v1, ..., vk), the compiler infers the types of
+// the parameters v1, ..., and vk for you.  If you are not happy with
+// the result of the type inference, you can specify the types by
+// explicitly instantiating the template, as in Foo<long, bool>(5,
+// false).  As said earlier, you don't get to (or need to) specify
+// 'arg_type' as that's determined by the context in which the matcher
+// is used.  You can assign the result of expression Foo(p1, ..., pk)
+// to a variable of type FooMatcherPk<p1_type, ..., pk_type>.  This
+// can be useful when composing matchers.
+//
+// While you can instantiate a matcher template with reference types,
+// passing the parameters by pointer usually makes your code more
+// readable.  If, however, you still want to pass a parameter by
+// reference, be aware that in the failure message generated by the
+// matcher you will see the value of the referenced object but not its
+// address.
+//
+// Explaining Match Results
+// ========================
+//
+// Sometimes the matcher description alone isn't enough to explain why
+// the match has failed or succeeded.  For example, when expecting a
+// long string, it can be very helpful to also print the diff between
+// the expected string and the actual one.  To achieve that, you can
+// optionally stream additional information to a special variable
+// named result_listener, whose type is a pointer to class
+// MatchResultListener:
+//
+//   MATCHER_P(EqualsLongString, str, "") {
+//     if (arg == str) return true;
+//
+//     *result_listener << "the difference: "
+///                     << DiffStrings(str, arg);
+//     return false;
+//   }
+//
+// Overloading Matchers
+// ====================
+//
+// You can overload matchers with different numbers of parameters:
+//
+//   MATCHER_P(Blah, a, description_string1) { ... }
+//   MATCHER_P2(Blah, a, b, description_string2) { ... }
+//
+// Caveats
+// =======
+//
+// When defining a new matcher, you should also consider implementing
+// MatcherInterface or using MakePolymorphicMatcher().  These
+// approaches require more work than the MATCHER* macros, but also
+// give you more control on the types of the value being matched and
+// the matcher parameters, which may leads to better compiler error
+// messages when the matcher is used wrong.  They also allow
+// overloading matchers based on parameter types (as opposed to just
+// based on the number of parameters).
+//
+// MATCHER*() can only be used in a namespace scope as templates cannot be
+// declared inside of a local class.
+//
+// More Information
+// ================
+//
+// To learn more about using these macros, please search for 'MATCHER'
+// on
+// https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md
+//
+// This file also implements some commonly used argument matchers.  More
+// matchers can be defined by the user implementing the
+// MatcherInterface<T> interface if necessary.
+//
+// See googletest/include/gtest/gtest-matchers.h for the definition of class
+// Matcher, class MatcherInterface, and others.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_
+
+#include <algorithm>
+#include <cmath>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <ostream>  // NOLINT
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+
+// MSVC warning C5046 is new as of VS2017 version 15.8.
+#if defined(_MSC_VER) && _MSC_VER >= 1915
+#define GMOCK_MAYBE_5046_ 5046
+#else
+#define GMOCK_MAYBE_5046_
+#endif
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(
+    4251 GMOCK_MAYBE_5046_ /* class A needs to have dll-interface to be used by
+                              clients of class B */
+    /* Symbol involving type with internal linkage not defined */)
+
+namespace testing {
+
+// To implement a matcher Foo for type T, define:
+//   1. a class FooMatcherImpl that implements the
+//      MatcherInterface<T> interface, and
+//   2. a factory function that creates a Matcher<T> object from a
+//      FooMatcherImpl*.
+//
+// The two-level delegation design makes it possible to allow a user
+// to write "v" instead of "Eq(v)" where a Matcher is expected, which
+// is impossible if we pass matchers by pointers.  It also eases
+// ownership management as Matcher objects can now be copied like
+// plain values.
+
+// A match result listener that stores the explanation in a string.
+class StringMatchResultListener : public MatchResultListener {
+ public:
+  StringMatchResultListener() : MatchResultListener(&ss_) {}
+
+  // Returns the explanation accumulated so far.
+  std::string str() const { return ss_.str(); }
+
+  // Clears the explanation accumulated so far.
+  void Clear() { ss_.str(""); }
+
+ private:
+  ::std::stringstream ss_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener);
+};
+
+// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION
+// and MUST NOT BE USED IN USER CODE!!!
+namespace internal {
+
+// The MatcherCastImpl class template is a helper for implementing
+// MatcherCast().  We need this helper in order to partially
+// specialize the implementation of MatcherCast() (C++ allows
+// class/struct templates to be partially specialized, but not
+// function templates.).
+
+// This general version is used when MatcherCast()'s argument is a
+// polymorphic matcher (i.e. something that can be converted to a
+// Matcher but is not one yet; for example, Eq(value)) or a value (for
+// example, "hello").
+template <typename T, typename M>
+class MatcherCastImpl {
+ public:
+  static Matcher<T> Cast(const M& polymorphic_matcher_or_value) {
+    // M can be a polymorphic matcher, in which case we want to use
+    // its conversion operator to create Matcher<T>.  Or it can be a value
+    // that should be passed to the Matcher<T>'s constructor.
+    //
+    // We can't call Matcher<T>(polymorphic_matcher_or_value) when M is a
+    // polymorphic matcher because it'll be ambiguous if T has an implicit
+    // constructor from M (this usually happens when T has an implicit
+    // constructor from any type).
+    //
+    // It won't work to unconditionally implicit_cast
+    // polymorphic_matcher_or_value to Matcher<T> because it won't trigger
+    // a user-defined conversion from M to T if one exists (assuming M is
+    // a value).
+    return CastImpl(polymorphic_matcher_or_value,
+                    std::is_convertible<M, Matcher<T>>{},
+                    std::is_convertible<M, T>{});
+  }
+
+ private:
+  template <bool Ignore>
+  static Matcher<T> CastImpl(const M& polymorphic_matcher_or_value,
+                             std::true_type /* convertible_to_matcher */,
+                             std::integral_constant<bool, Ignore>) {
+    // M is implicitly convertible to Matcher<T>, which means that either
+    // M is a polymorphic matcher or Matcher<T> has an implicit constructor
+    // from M.  In both cases using the implicit conversion will produce a
+    // matcher.
+    //
+    // Even if T has an implicit constructor from M, it won't be called because
+    // creating Matcher<T> would require a chain of two user-defined conversions
+    // (first to create T from M and then to create Matcher<T> from T).
+    return polymorphic_matcher_or_value;
+  }
+
+  // M can't be implicitly converted to Matcher<T>, so M isn't a polymorphic
+  // matcher. It's a value of a type implicitly convertible to T. Use direct
+  // initialization to create a matcher.
+  static Matcher<T> CastImpl(const M& value,
+                             std::false_type /* convertible_to_matcher */,
+                             std::true_type /* convertible_to_T */) {
+    return Matcher<T>(ImplicitCast_<T>(value));
+  }
+
+  // M can't be implicitly converted to either Matcher<T> or T. Attempt to use
+  // polymorphic matcher Eq(value) in this case.
+  //
+  // Note that we first attempt to perform an implicit cast on the value and
+  // only fall back to the polymorphic Eq() matcher afterwards because the
+  // latter calls bool operator==(const Lhs& lhs, const Rhs& rhs) in the end
+  // which might be undefined even when Rhs is implicitly convertible to Lhs
+  // (e.g. std::pair<const int, int> vs. std::pair<int, int>).
+  //
+  // We don't define this method inline as we need the declaration of Eq().
+  static Matcher<T> CastImpl(const M& value,
+                             std::false_type /* convertible_to_matcher */,
+                             std::false_type /* convertible_to_T */);
+};
+
+// This more specialized version is used when MatcherCast()'s argument
+// is already a Matcher.  This only compiles when type T can be
+// statically converted to type U.
+template <typename T, typename U>
+class MatcherCastImpl<T, Matcher<U> > {
+ public:
+  static Matcher<T> Cast(const Matcher<U>& source_matcher) {
+    return Matcher<T>(new Impl(source_matcher));
+  }
+
+ private:
+  class Impl : public MatcherInterface<T> {
+   public:
+    explicit Impl(const Matcher<U>& source_matcher)
+        : source_matcher_(source_matcher) {}
+
+    // We delegate the matching logic to the source matcher.
+    bool MatchAndExplain(T x, MatchResultListener* listener) const override {
+      using FromType = typename std::remove_cv<typename std::remove_pointer<
+          typename std::remove_reference<T>::type>::type>::type;
+      using ToType = typename std::remove_cv<typename std::remove_pointer<
+          typename std::remove_reference<U>::type>::type>::type;
+      // Do not allow implicitly converting base*/& to derived*/&.
+      static_assert(
+          // Do not trigger if only one of them is a pointer. That implies a
+          // regular conversion and not a down_cast.
+          (std::is_pointer<typename std::remove_reference<T>::type>::value !=
+           std::is_pointer<typename std::remove_reference<U>::type>::value) ||
+              std::is_same<FromType, ToType>::value ||
+              !std::is_base_of<FromType, ToType>::value,
+          "Can't implicitly convert from <base> to <derived>");
+
+      // Do the cast to `U` explicitly if necessary.
+      // Otherwise, let implicit conversions do the trick.
+      using CastType =
+          typename std::conditional<std::is_convertible<T&, const U&>::value,
+                                    T&, U>::type;
+
+      return source_matcher_.MatchAndExplain(static_cast<CastType>(x),
+                                             listener);
+    }
+
+    void DescribeTo(::std::ostream* os) const override {
+      source_matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      source_matcher_.DescribeNegationTo(os);
+    }
+
+   private:
+    const Matcher<U> source_matcher_;
+  };
+};
+
+// This even more specialized version is used for efficiently casting
+// a matcher to its own type.
+template <typename T>
+class MatcherCastImpl<T, Matcher<T> > {
+ public:
+  static Matcher<T> Cast(const Matcher<T>& matcher) { return matcher; }
+};
+
+// Template specialization for parameterless Matcher.
+template <typename Derived>
+class MatcherBaseImpl {
+ public:
+  MatcherBaseImpl() = default;
+
+  template <typename T>
+  operator ::testing::Matcher<T>() const {  // NOLINT(runtime/explicit)
+    return ::testing::Matcher<T>(new
+                                 typename Derived::template gmock_Impl<T>());
+  }
+};
+
+// Template specialization for Matcher with parameters.
+template <template <typename...> class Derived, typename... Ts>
+class MatcherBaseImpl<Derived<Ts...>> {
+ public:
+  // Mark the constructor explicit for single argument T to avoid implicit
+  // conversions.
+  template <typename E = std::enable_if<sizeof...(Ts) == 1>,
+            typename E::type* = nullptr>
+  explicit MatcherBaseImpl(Ts... params)
+      : params_(std::forward<Ts>(params)...) {}
+  template <typename E = std::enable_if<sizeof...(Ts) != 1>,
+            typename = typename E::type>
+  MatcherBaseImpl(Ts... params)  // NOLINT
+      : params_(std::forward<Ts>(params)...) {}
+
+  template <typename F>
+  operator ::testing::Matcher<F>() const {  // NOLINT(runtime/explicit)
+    return Apply<F>(MakeIndexSequence<sizeof...(Ts)>{});
+  }
+
+ private:
+  template <typename F, std::size_t... tuple_ids>
+  ::testing::Matcher<F> Apply(IndexSequence<tuple_ids...>) const {
+    return ::testing::Matcher<F>(
+        new typename Derived<Ts...>::template gmock_Impl<F>(
+            std::get<tuple_ids>(params_)...));
+  }
+
+  const std::tuple<Ts...> params_;
+};
+
+}  // namespace internal
+
+// In order to be safe and clear, casting between different matcher
+// types is done explicitly via MatcherCast<T>(m), which takes a
+// matcher m and returns a Matcher<T>.  It compiles only when T can be
+// statically converted to the argument type of m.
+template <typename T, typename M>
+inline Matcher<T> MatcherCast(const M& matcher) {
+  return internal::MatcherCastImpl<T, M>::Cast(matcher);
+}
+
+// This overload handles polymorphic matchers and values only since
+// monomorphic matchers are handled by the next one.
+template <typename T, typename M>
+inline Matcher<T> SafeMatcherCast(const M& polymorphic_matcher_or_value) {
+  return MatcherCast<T>(polymorphic_matcher_or_value);
+}
+
+// This overload handles monomorphic matchers.
+//
+// In general, if type T can be implicitly converted to type U, we can
+// safely convert a Matcher<U> to a Matcher<T> (i.e. Matcher is
+// contravariant): just keep a copy of the original Matcher<U>, convert the
+// argument from type T to U, and then pass it to the underlying Matcher<U>.
+// The only exception is when U is a reference and T is not, as the
+// underlying Matcher<U> may be interested in the argument's address, which
+// is not preserved in the conversion from T to U.
+template <typename T, typename U>
+inline Matcher<T> SafeMatcherCast(const Matcher<U>& matcher) {
+  // Enforce that T can be implicitly converted to U.
+  static_assert(std::is_convertible<const T&, const U&>::value,
+                "T must be implicitly convertible to U");
+  // Enforce that we are not converting a non-reference type T to a reference
+  // type U.
+  GTEST_COMPILE_ASSERT_(
+      std::is_reference<T>::value || !std::is_reference<U>::value,
+      cannot_convert_non_reference_arg_to_reference);
+  // In case both T and U are arithmetic types, enforce that the
+  // conversion is not lossy.
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT;
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(U) RawU;
+  constexpr bool kTIsOther = GMOCK_KIND_OF_(RawT) == internal::kOther;
+  constexpr bool kUIsOther = GMOCK_KIND_OF_(RawU) == internal::kOther;
+  GTEST_COMPILE_ASSERT_(
+      kTIsOther || kUIsOther ||
+      (internal::LosslessArithmeticConvertible<RawT, RawU>::value),
+      conversion_of_arithmetic_types_must_be_lossless);
+  return MatcherCast<T>(matcher);
+}
+
+// A<T>() returns a matcher that matches any value of type T.
+template <typename T>
+Matcher<T> A();
+
+// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION
+// and MUST NOT BE USED IN USER CODE!!!
+namespace internal {
+
+// If the explanation is not empty, prints it to the ostream.
+inline void PrintIfNotEmpty(const std::string& explanation,
+                            ::std::ostream* os) {
+  if (explanation != "" && os != nullptr) {
+    *os << ", " << explanation;
+  }
+}
+
+// Returns true if the given type name is easy to read by a human.
+// This is used to decide whether printing the type of a value might
+// be helpful.
+inline bool IsReadableTypeName(const std::string& type_name) {
+  // We consider a type name readable if it's short or doesn't contain
+  // a template or function type.
+  return (type_name.length() <= 20 ||
+          type_name.find_first_of("<(") == std::string::npos);
+}
+
+// Matches the value against the given matcher, prints the value and explains
+// the match result to the listener. Returns the match result.
+// 'listener' must not be NULL.
+// Value cannot be passed by const reference, because some matchers take a
+// non-const argument.
+template <typename Value, typename T>
+bool MatchPrintAndExplain(Value& value, const Matcher<T>& matcher,
+                          MatchResultListener* listener) {
+  if (!listener->IsInterested()) {
+    // If the listener is not interested, we do not need to construct the
+    // inner explanation.
+    return matcher.Matches(value);
+  }
+
+  StringMatchResultListener inner_listener;
+  const bool match = matcher.MatchAndExplain(value, &inner_listener);
+
+  UniversalPrint(value, listener->stream());
+#if GTEST_HAS_RTTI
+  const std::string& type_name = GetTypeName<Value>();
+  if (IsReadableTypeName(type_name))
+    *listener->stream() << " (of type " << type_name << ")";
+#endif
+  PrintIfNotEmpty(inner_listener.str(), listener->stream());
+
+  return match;
+}
+
+// An internal helper class for doing compile-time loop on a tuple's
+// fields.
+template <size_t N>
+class TuplePrefix {
+ public:
+  // TuplePrefix<N>::Matches(matcher_tuple, value_tuple) returns true
+  // if and only if the first N fields of matcher_tuple matches
+  // the first N fields of value_tuple, respectively.
+  template <typename MatcherTuple, typename ValueTuple>
+  static bool Matches(const MatcherTuple& matcher_tuple,
+                      const ValueTuple& value_tuple) {
+    return TuplePrefix<N - 1>::Matches(matcher_tuple, value_tuple) &&
+           std::get<N - 1>(matcher_tuple).Matches(std::get<N - 1>(value_tuple));
+  }
+
+  // TuplePrefix<N>::ExplainMatchFailuresTo(matchers, values, os)
+  // describes failures in matching the first N fields of matchers
+  // against the first N fields of values.  If there is no failure,
+  // nothing will be streamed to os.
+  template <typename MatcherTuple, typename ValueTuple>
+  static void ExplainMatchFailuresTo(const MatcherTuple& matchers,
+                                     const ValueTuple& values,
+                                     ::std::ostream* os) {
+    // First, describes failures in the first N - 1 fields.
+    TuplePrefix<N - 1>::ExplainMatchFailuresTo(matchers, values, os);
+
+    // Then describes the failure (if any) in the (N - 1)-th (0-based)
+    // field.
+    typename std::tuple_element<N - 1, MatcherTuple>::type matcher =
+        std::get<N - 1>(matchers);
+    typedef typename std::tuple_element<N - 1, ValueTuple>::type Value;
+    const Value& value = std::get<N - 1>(values);
+    StringMatchResultListener listener;
+    if (!matcher.MatchAndExplain(value, &listener)) {
+      *os << "  Expected arg #" << N - 1 << ": ";
+      std::get<N - 1>(matchers).DescribeTo(os);
+      *os << "\n           Actual: ";
+      // We remove the reference in type Value to prevent the
+      // universal printer from printing the address of value, which
+      // isn't interesting to the user most of the time.  The
+      // matcher's MatchAndExplain() method handles the case when
+      // the address is interesting.
+      internal::UniversalPrint(value, os);
+      PrintIfNotEmpty(listener.str(), os);
+      *os << "\n";
+    }
+  }
+};
+
+// The base case.
+template <>
+class TuplePrefix<0> {
+ public:
+  template <typename MatcherTuple, typename ValueTuple>
+  static bool Matches(const MatcherTuple& /* matcher_tuple */,
+                      const ValueTuple& /* value_tuple */) {
+    return true;
+  }
+
+  template <typename MatcherTuple, typename ValueTuple>
+  static void ExplainMatchFailuresTo(const MatcherTuple& /* matchers */,
+                                     const ValueTuple& /* values */,
+                                     ::std::ostream* /* os */) {}
+};
+
+// TupleMatches(matcher_tuple, value_tuple) returns true if and only if
+// all matchers in matcher_tuple match the corresponding fields in
+// value_tuple.  It is a compiler error if matcher_tuple and
+// value_tuple have different number of fields or incompatible field
+// types.
+template <typename MatcherTuple, typename ValueTuple>
+bool TupleMatches(const MatcherTuple& matcher_tuple,
+                  const ValueTuple& value_tuple) {
+  // Makes sure that matcher_tuple and value_tuple have the same
+  // number of fields.
+  GTEST_COMPILE_ASSERT_(std::tuple_size<MatcherTuple>::value ==
+                            std::tuple_size<ValueTuple>::value,
+                        matcher_and_value_have_different_numbers_of_fields);
+  return TuplePrefix<std::tuple_size<ValueTuple>::value>::Matches(matcher_tuple,
+                                                                  value_tuple);
+}
+
+// Describes failures in matching matchers against values.  If there
+// is no failure, nothing will be streamed to os.
+template <typename MatcherTuple, typename ValueTuple>
+void ExplainMatchFailureTupleTo(const MatcherTuple& matchers,
+                                const ValueTuple& values,
+                                ::std::ostream* os) {
+  TuplePrefix<std::tuple_size<MatcherTuple>::value>::ExplainMatchFailuresTo(
+      matchers, values, os);
+}
+
+// TransformTupleValues and its helper.
+//
+// TransformTupleValuesHelper hides the internal machinery that
+// TransformTupleValues uses to implement a tuple traversal.
+template <typename Tuple, typename Func, typename OutIter>
+class TransformTupleValuesHelper {
+ private:
+  typedef ::std::tuple_size<Tuple> TupleSize;
+
+ public:
+  // For each member of tuple 't', taken in order, evaluates '*out++ = f(t)'.
+  // Returns the final value of 'out' in case the caller needs it.
+  static OutIter Run(Func f, const Tuple& t, OutIter out) {
+    return IterateOverTuple<Tuple, TupleSize::value>()(f, t, out);
+  }
+
+ private:
+  template <typename Tup, size_t kRemainingSize>
+  struct IterateOverTuple {
+    OutIter operator() (Func f, const Tup& t, OutIter out) const {
+      *out++ = f(::std::get<TupleSize::value - kRemainingSize>(t));
+      return IterateOverTuple<Tup, kRemainingSize - 1>()(f, t, out);
+    }
+  };
+  template <typename Tup>
+  struct IterateOverTuple<Tup, 0> {
+    OutIter operator() (Func /* f */, const Tup& /* t */, OutIter out) const {
+      return out;
+    }
+  };
+};
+
+// Successively invokes 'f(element)' on each element of the tuple 't',
+// appending each result to the 'out' iterator. Returns the final value
+// of 'out'.
+template <typename Tuple, typename Func, typename OutIter>
+OutIter TransformTupleValues(Func f, const Tuple& t, OutIter out) {
+  return TransformTupleValuesHelper<Tuple, Func, OutIter>::Run(f, t, out);
+}
+
+// Implements _, a matcher that matches any value of any
+// type.  This is a polymorphic matcher, so we need a template type
+// conversion operator to make it appearing as a Matcher<T> for any
+// type T.
+class AnythingMatcher {
+ public:
+  using is_gtest_matcher = void;
+
+  template <typename T>
+  bool MatchAndExplain(const T& /* x */, std::ostream* /* listener */) const {
+    return true;
+  }
+  void DescribeTo(std::ostream* os) const { *os << "is anything"; }
+  void DescribeNegationTo(::std::ostream* os) const {
+    // This is mostly for completeness' sake, as it's not very useful
+    // to write Not(A<bool>()).  However we cannot completely rule out
+    // such a possibility, and it doesn't hurt to be prepared.
+    *os << "never matches";
+  }
+};
+
+// Implements the polymorphic IsNull() matcher, which matches any raw or smart
+// pointer that is NULL.
+class IsNullMatcher {
+ public:
+  template <typename Pointer>
+  bool MatchAndExplain(const Pointer& p,
+                       MatchResultListener* /* listener */) const {
+    return p == nullptr;
+  }
+
+  void DescribeTo(::std::ostream* os) const { *os << "is NULL"; }
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "isn't NULL";
+  }
+};
+
+// Implements the polymorphic NotNull() matcher, which matches any raw or smart
+// pointer that is not NULL.
+class NotNullMatcher {
+ public:
+  template <typename Pointer>
+  bool MatchAndExplain(const Pointer& p,
+                       MatchResultListener* /* listener */) const {
+    return p != nullptr;
+  }
+
+  void DescribeTo(::std::ostream* os) const { *os << "isn't NULL"; }
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "is NULL";
+  }
+};
+
+// Ref(variable) matches any argument that is a reference to
+// 'variable'.  This matcher is polymorphic as it can match any
+// super type of the type of 'variable'.
+//
+// The RefMatcher template class implements Ref(variable).  It can
+// only be instantiated with a reference type.  This prevents a user
+// from mistakenly using Ref(x) to match a non-reference function
+// argument.  For example, the following will righteously cause a
+// compiler error:
+//
+//   int n;
+//   Matcher<int> m1 = Ref(n);   // This won't compile.
+//   Matcher<int&> m2 = Ref(n);  // This will compile.
+template <typename T>
+class RefMatcher;
+
+template <typename T>
+class RefMatcher<T&> {
+  // Google Mock is a generic framework and thus needs to support
+  // mocking any function types, including those that take non-const
+  // reference arguments.  Therefore the template parameter T (and
+  // Super below) can be instantiated to either a const type or a
+  // non-const type.
+ public:
+  // RefMatcher() takes a T& instead of const T&, as we want the
+  // compiler to catch using Ref(const_value) as a matcher for a
+  // non-const reference.
+  explicit RefMatcher(T& x) : object_(x) {}  // NOLINT
+
+  template <typename Super>
+  operator Matcher<Super&>() const {
+    // By passing object_ (type T&) to Impl(), which expects a Super&,
+    // we make sure that Super is a super type of T.  In particular,
+    // this catches using Ref(const_value) as a matcher for a
+    // non-const reference, as you cannot implicitly convert a const
+    // reference to a non-const reference.
+    return MakeMatcher(new Impl<Super>(object_));
+  }
+
+ private:
+  template <typename Super>
+  class Impl : public MatcherInterface<Super&> {
+   public:
+    explicit Impl(Super& x) : object_(x) {}  // NOLINT
+
+    // MatchAndExplain() takes a Super& (as opposed to const Super&)
+    // in order to match the interface MatcherInterface<Super&>.
+    bool MatchAndExplain(Super& x,
+                         MatchResultListener* listener) const override {
+      *listener << "which is located @" << static_cast<const void*>(&x);
+      return &x == &object_;
+    }
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "references the variable ";
+      UniversalPrinter<Super&>::Print(object_, os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "does not reference the variable ";
+      UniversalPrinter<Super&>::Print(object_, os);
+    }
+
+   private:
+    const Super& object_;
+  };
+
+  T& object_;
+};
+
+// Polymorphic helper functions for narrow and wide string matchers.
+inline bool CaseInsensitiveCStringEquals(const char* lhs, const char* rhs) {
+  return String::CaseInsensitiveCStringEquals(lhs, rhs);
+}
+
+inline bool CaseInsensitiveCStringEquals(const wchar_t* lhs,
+                                         const wchar_t* rhs) {
+  return String::CaseInsensitiveWideCStringEquals(lhs, rhs);
+}
+
+// String comparison for narrow or wide strings that can have embedded NUL
+// characters.
+template <typename StringType>
+bool CaseInsensitiveStringEquals(const StringType& s1,
+                                 const StringType& s2) {
+  // Are the heads equal?
+  if (!CaseInsensitiveCStringEquals(s1.c_str(), s2.c_str())) {
+    return false;
+  }
+
+  // Skip the equal heads.
+  const typename StringType::value_type nul = 0;
+  const size_t i1 = s1.find(nul), i2 = s2.find(nul);
+
+  // Are we at the end of either s1 or s2?
+  if (i1 == StringType::npos || i2 == StringType::npos) {
+    return i1 == i2;
+  }
+
+  // Are the tails equal?
+  return CaseInsensitiveStringEquals(s1.substr(i1 + 1), s2.substr(i2 + 1));
+}
+
+// String matchers.
+
+// Implements equality-based string matchers like StrEq, StrCaseNe, and etc.
+template <typename StringType>
+class StrEqualityMatcher {
+ public:
+  StrEqualityMatcher(StringType str, bool expect_eq, bool case_sensitive)
+      : string_(std::move(str)),
+        expect_eq_(expect_eq),
+        case_sensitive_(case_sensitive) {}
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  bool MatchAndExplain(const internal::StringView& s,
+                       MatchResultListener* listener) const {
+    // This should fail to compile if StringView is used with wide
+    // strings.
+    const StringType& str = std::string(s);
+    return MatchAndExplain(str, listener);
+  }
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+  // Accepts pointer types, particularly:
+  //   const char*
+  //   char*
+  //   const wchar_t*
+  //   wchar_t*
+  template <typename CharType>
+  bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
+    if (s == nullptr) {
+      return !expect_eq_;
+    }
+    return MatchAndExplain(StringType(s), listener);
+  }
+
+  // Matches anything that can convert to StringType.
+  //
+  // This is a template, not just a plain function with const StringType&,
+  // because StringView has some interfering non-explicit constructors.
+  template <typename MatcheeStringType>
+  bool MatchAndExplain(const MatcheeStringType& s,
+                       MatchResultListener* /* listener */) const {
+    const StringType s2(s);
+    const bool eq = case_sensitive_ ? s2 == string_ :
+        CaseInsensitiveStringEquals(s2, string_);
+    return expect_eq_ == eq;
+  }
+
+  void DescribeTo(::std::ostream* os) const {
+    DescribeToHelper(expect_eq_, os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    DescribeToHelper(!expect_eq_, os);
+  }
+
+ private:
+  void DescribeToHelper(bool expect_eq, ::std::ostream* os) const {
+    *os << (expect_eq ? "is " : "isn't ");
+    *os << "equal to ";
+    if (!case_sensitive_) {
+      *os << "(ignoring case) ";
+    }
+    UniversalPrint(string_, os);
+  }
+
+  const StringType string_;
+  const bool expect_eq_;
+  const bool case_sensitive_;
+};
+
+// Implements the polymorphic HasSubstr(substring) matcher, which
+// can be used as a Matcher<T> as long as T can be converted to a
+// string.
+template <typename StringType>
+class HasSubstrMatcher {
+ public:
+  explicit HasSubstrMatcher(const StringType& substring)
+      : substring_(substring) {}
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  bool MatchAndExplain(const internal::StringView& s,
+                       MatchResultListener* listener) const {
+    // This should fail to compile if StringView is used with wide
+    // strings.
+    const StringType& str = std::string(s);
+    return MatchAndExplain(str, listener);
+  }
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+  // Accepts pointer types, particularly:
+  //   const char*
+  //   char*
+  //   const wchar_t*
+  //   wchar_t*
+  template <typename CharType>
+  bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
+    return s != nullptr && MatchAndExplain(StringType(s), listener);
+  }
+
+  // Matches anything that can convert to StringType.
+  //
+  // This is a template, not just a plain function with const StringType&,
+  // because StringView has some interfering non-explicit constructors.
+  template <typename MatcheeStringType>
+  bool MatchAndExplain(const MatcheeStringType& s,
+                       MatchResultListener* /* listener */) const {
+    return StringType(s).find(substring_) != StringType::npos;
+  }
+
+  // Describes what this matcher matches.
+  void DescribeTo(::std::ostream* os) const {
+    *os << "has substring ";
+    UniversalPrint(substring_, os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "has no substring ";
+    UniversalPrint(substring_, os);
+  }
+
+ private:
+  const StringType substring_;
+};
+
+// Implements the polymorphic StartsWith(substring) matcher, which
+// can be used as a Matcher<T> as long as T can be converted to a
+// string.
+template <typename StringType>
+class StartsWithMatcher {
+ public:
+  explicit StartsWithMatcher(const StringType& prefix) : prefix_(prefix) {
+  }
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  bool MatchAndExplain(const internal::StringView& s,
+                       MatchResultListener* listener) const {
+    // This should fail to compile if StringView is used with wide
+    // strings.
+    const StringType& str = std::string(s);
+    return MatchAndExplain(str, listener);
+  }
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+  // Accepts pointer types, particularly:
+  //   const char*
+  //   char*
+  //   const wchar_t*
+  //   wchar_t*
+  template <typename CharType>
+  bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
+    return s != nullptr && MatchAndExplain(StringType(s), listener);
+  }
+
+  // Matches anything that can convert to StringType.
+  //
+  // This is a template, not just a plain function with const StringType&,
+  // because StringView has some interfering non-explicit constructors.
+  template <typename MatcheeStringType>
+  bool MatchAndExplain(const MatcheeStringType& s,
+                       MatchResultListener* /* listener */) const {
+    const StringType& s2(s);
+    return s2.length() >= prefix_.length() &&
+        s2.substr(0, prefix_.length()) == prefix_;
+  }
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "starts with ";
+    UniversalPrint(prefix_, os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "doesn't start with ";
+    UniversalPrint(prefix_, os);
+  }
+
+ private:
+  const StringType prefix_;
+};
+
+// Implements the polymorphic EndsWith(substring) matcher, which
+// can be used as a Matcher<T> as long as T can be converted to a
+// string.
+template <typename StringType>
+class EndsWithMatcher {
+ public:
+  explicit EndsWithMatcher(const StringType& suffix) : suffix_(suffix) {}
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  bool MatchAndExplain(const internal::StringView& s,
+                       MatchResultListener* listener) const {
+    // This should fail to compile if StringView is used with wide
+    // strings.
+    const StringType& str = std::string(s);
+    return MatchAndExplain(str, listener);
+  }
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+  // Accepts pointer types, particularly:
+  //   const char*
+  //   char*
+  //   const wchar_t*
+  //   wchar_t*
+  template <typename CharType>
+  bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
+    return s != nullptr && MatchAndExplain(StringType(s), listener);
+  }
+
+  // Matches anything that can convert to StringType.
+  //
+  // This is a template, not just a plain function with const StringType&,
+  // because StringView has some interfering non-explicit constructors.
+  template <typename MatcheeStringType>
+  bool MatchAndExplain(const MatcheeStringType& s,
+                       MatchResultListener* /* listener */) const {
+    const StringType& s2(s);
+    return s2.length() >= suffix_.length() &&
+        s2.substr(s2.length() - suffix_.length()) == suffix_;
+  }
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "ends with ";
+    UniversalPrint(suffix_, os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "doesn't end with ";
+    UniversalPrint(suffix_, os);
+  }
+
+ private:
+  const StringType suffix_;
+};
+
+// Implements a matcher that compares the two fields of a 2-tuple
+// using one of the ==, <=, <, etc, operators.  The two fields being
+// compared don't have to have the same type.
+//
+// The matcher defined here is polymorphic (for example, Eq() can be
+// used to match a std::tuple<int, short>, a std::tuple<const long&, double>,
+// etc).  Therefore we use a template type conversion operator in the
+// implementation.
+template <typename D, typename Op>
+class PairMatchBase {
+ public:
+  template <typename T1, typename T2>
+  operator Matcher<::std::tuple<T1, T2>>() const {
+    return Matcher<::std::tuple<T1, T2>>(new Impl<const ::std::tuple<T1, T2>&>);
+  }
+  template <typename T1, typename T2>
+  operator Matcher<const ::std::tuple<T1, T2>&>() const {
+    return MakeMatcher(new Impl<const ::std::tuple<T1, T2>&>);
+  }
+
+ private:
+  static ::std::ostream& GetDesc(::std::ostream& os) {  // NOLINT
+    return os << D::Desc();
+  }
+
+  template <typename Tuple>
+  class Impl : public MatcherInterface<Tuple> {
+   public:
+    bool MatchAndExplain(Tuple args,
+                         MatchResultListener* /* listener */) const override {
+      return Op()(::std::get<0>(args), ::std::get<1>(args));
+    }
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "are " << GetDesc;
+    }
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "aren't " << GetDesc;
+    }
+  };
+};
+
+class Eq2Matcher : public PairMatchBase<Eq2Matcher, AnyEq> {
+ public:
+  static const char* Desc() { return "an equal pair"; }
+};
+class Ne2Matcher : public PairMatchBase<Ne2Matcher, AnyNe> {
+ public:
+  static const char* Desc() { return "an unequal pair"; }
+};
+class Lt2Matcher : public PairMatchBase<Lt2Matcher, AnyLt> {
+ public:
+  static const char* Desc() { return "a pair where the first < the second"; }
+};
+class Gt2Matcher : public PairMatchBase<Gt2Matcher, AnyGt> {
+ public:
+  static const char* Desc() { return "a pair where the first > the second"; }
+};
+class Le2Matcher : public PairMatchBase<Le2Matcher, AnyLe> {
+ public:
+  static const char* Desc() { return "a pair where the first <= the second"; }
+};
+class Ge2Matcher : public PairMatchBase<Ge2Matcher, AnyGe> {
+ public:
+  static const char* Desc() { return "a pair where the first >= the second"; }
+};
+
+// Implements the Not(...) matcher for a particular argument type T.
+// We do not nest it inside the NotMatcher class template, as that
+// will prevent different instantiations of NotMatcher from sharing
+// the same NotMatcherImpl<T> class.
+template <typename T>
+class NotMatcherImpl : public MatcherInterface<const T&> {
+ public:
+  explicit NotMatcherImpl(const Matcher<T>& matcher)
+      : matcher_(matcher) {}
+
+  bool MatchAndExplain(const T& x,
+                       MatchResultListener* listener) const override {
+    return !matcher_.MatchAndExplain(x, listener);
+  }
+
+  void DescribeTo(::std::ostream* os) const override {
+    matcher_.DescribeNegationTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    matcher_.DescribeTo(os);
+  }
+
+ private:
+  const Matcher<T> matcher_;
+};
+
+// Implements the Not(m) matcher, which matches a value that doesn't
+// match matcher m.
+template <typename InnerMatcher>
+class NotMatcher {
+ public:
+  explicit NotMatcher(InnerMatcher matcher) : matcher_(matcher) {}
+
+  // This template type conversion operator allows Not(m) to be used
+  // to match any type m can match.
+  template <typename T>
+  operator Matcher<T>() const {
+    return Matcher<T>(new NotMatcherImpl<T>(SafeMatcherCast<T>(matcher_)));
+  }
+
+ private:
+  InnerMatcher matcher_;
+};
+
+// Implements the AllOf(m1, m2) matcher for a particular argument type
+// T. We do not nest it inside the BothOfMatcher class template, as
+// that will prevent different instantiations of BothOfMatcher from
+// sharing the same BothOfMatcherImpl<T> class.
+template <typename T>
+class AllOfMatcherImpl : public MatcherInterface<const T&> {
+ public:
+  explicit AllOfMatcherImpl(std::vector<Matcher<T> > matchers)
+      : matchers_(std::move(matchers)) {}
+
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "(";
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      if (i != 0) *os << ") and (";
+      matchers_[i].DescribeTo(os);
+    }
+    *os << ")";
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "(";
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      if (i != 0) *os << ") or (";
+      matchers_[i].DescribeNegationTo(os);
+    }
+    *os << ")";
+  }
+
+  bool MatchAndExplain(const T& x,
+                       MatchResultListener* listener) const override {
+    // If either matcher1_ or matcher2_ doesn't match x, we only need
+    // to explain why one of them fails.
+    std::string all_match_result;
+
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      StringMatchResultListener slistener;
+      if (matchers_[i].MatchAndExplain(x, &slistener)) {
+        if (all_match_result.empty()) {
+          all_match_result = slistener.str();
+        } else {
+          std::string result = slistener.str();
+          if (!result.empty()) {
+            all_match_result += ", and ";
+            all_match_result += result;
+          }
+        }
+      } else {
+        *listener << slistener.str();
+        return false;
+      }
+    }
+
+    // Otherwise we need to explain why *both* of them match.
+    *listener << all_match_result;
+    return true;
+  }
+
+ private:
+  const std::vector<Matcher<T> > matchers_;
+};
+
+// VariadicMatcher is used for the variadic implementation of
+// AllOf(m_1, m_2, ...) and AnyOf(m_1, m_2, ...).
+// CombiningMatcher<T> is used to recursively combine the provided matchers
+// (of type Args...).
+template <template <typename T> class CombiningMatcher, typename... Args>
+class VariadicMatcher {
+ public:
+  VariadicMatcher(const Args&... matchers)  // NOLINT
+      : matchers_(matchers...) {
+    static_assert(sizeof...(Args) > 0, "Must have at least one matcher.");
+  }
+
+  VariadicMatcher(const VariadicMatcher&) = default;
+  VariadicMatcher& operator=(const VariadicMatcher&) = delete;
+
+  // This template type conversion operator allows an
+  // VariadicMatcher<Matcher1, Matcher2...> object to match any type that
+  // all of the provided matchers (Matcher1, Matcher2, ...) can match.
+  template <typename T>
+  operator Matcher<T>() const {
+    std::vector<Matcher<T> > values;
+    CreateVariadicMatcher<T>(&values, std::integral_constant<size_t, 0>());
+    return Matcher<T>(new CombiningMatcher<T>(std::move(values)));
+  }
+
+ private:
+  template <typename T, size_t I>
+  void CreateVariadicMatcher(std::vector<Matcher<T> >* values,
+                             std::integral_constant<size_t, I>) const {
+    values->push_back(SafeMatcherCast<T>(std::get<I>(matchers_)));
+    CreateVariadicMatcher<T>(values, std::integral_constant<size_t, I + 1>());
+  }
+
+  template <typename T>
+  void CreateVariadicMatcher(
+      std::vector<Matcher<T> >*,
+      std::integral_constant<size_t, sizeof...(Args)>) const {}
+
+  std::tuple<Args...> matchers_;
+};
+
+template <typename... Args>
+using AllOfMatcher = VariadicMatcher<AllOfMatcherImpl, Args...>;
+
+// Implements the AnyOf(m1, m2) matcher for a particular argument type
+// T.  We do not nest it inside the AnyOfMatcher class template, as
+// that will prevent different instantiations of AnyOfMatcher from
+// sharing the same EitherOfMatcherImpl<T> class.
+template <typename T>
+class AnyOfMatcherImpl : public MatcherInterface<const T&> {
+ public:
+  explicit AnyOfMatcherImpl(std::vector<Matcher<T> > matchers)
+      : matchers_(std::move(matchers)) {}
+
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "(";
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      if (i != 0) *os << ") or (";
+      matchers_[i].DescribeTo(os);
+    }
+    *os << ")";
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "(";
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      if (i != 0) *os << ") and (";
+      matchers_[i].DescribeNegationTo(os);
+    }
+    *os << ")";
+  }
+
+  bool MatchAndExplain(const T& x,
+                       MatchResultListener* listener) const override {
+    std::string no_match_result;
+
+    // If either matcher1_ or matcher2_ matches x, we just need to
+    // explain why *one* of them matches.
+    for (size_t i = 0; i < matchers_.size(); ++i) {
+      StringMatchResultListener slistener;
+      if (matchers_[i].MatchAndExplain(x, &slistener)) {
+        *listener << slistener.str();
+        return true;
+      } else {
+        if (no_match_result.empty()) {
+          no_match_result = slistener.str();
+        } else {
+          std::string result = slistener.str();
+          if (!result.empty()) {
+            no_match_result += ", and ";
+            no_match_result += result;
+          }
+        }
+      }
+    }
+
+    // Otherwise we need to explain why *both* of them fail.
+    *listener << no_match_result;
+    return false;
+  }
+
+ private:
+  const std::vector<Matcher<T> > matchers_;
+};
+
+// AnyOfMatcher is used for the variadic implementation of AnyOf(m_1, m_2, ...).
+template <typename... Args>
+using AnyOfMatcher = VariadicMatcher<AnyOfMatcherImpl, Args...>;
+
+// Wrapper for implementation of Any/AllOfArray().
+template <template <class> class MatcherImpl, typename T>
+class SomeOfArrayMatcher {
+ public:
+  // Constructs the matcher from a sequence of element values or
+  // element matchers.
+  template <typename Iter>
+  SomeOfArrayMatcher(Iter first, Iter last) : matchers_(first, last) {}
+
+  template <typename U>
+  operator Matcher<U>() const {  // NOLINT
+    using RawU = typename std::decay<U>::type;
+    std::vector<Matcher<RawU>> matchers;
+    for (const auto& matcher : matchers_) {
+      matchers.push_back(MatcherCast<RawU>(matcher));
+    }
+    return Matcher<U>(new MatcherImpl<RawU>(std::move(matchers)));
+  }
+
+ private:
+  const ::std::vector<T> matchers_;
+};
+
+template <typename T>
+using AllOfArrayMatcher = SomeOfArrayMatcher<AllOfMatcherImpl, T>;
+
+template <typename T>
+using AnyOfArrayMatcher = SomeOfArrayMatcher<AnyOfMatcherImpl, T>;
+
+// Used for implementing Truly(pred), which turns a predicate into a
+// matcher.
+template <typename Predicate>
+class TrulyMatcher {
+ public:
+  explicit TrulyMatcher(Predicate pred) : predicate_(pred) {}
+
+  // This method template allows Truly(pred) to be used as a matcher
+  // for type T where T is the argument type of predicate 'pred'.  The
+  // argument is passed by reference as the predicate may be
+  // interested in the address of the argument.
+  template <typename T>
+  bool MatchAndExplain(T& x,  // NOLINT
+                       MatchResultListener* listener) const {
+    // Without the if-statement, MSVC sometimes warns about converting
+    // a value to bool (warning 4800).
+    //
+    // We cannot write 'return !!predicate_(x);' as that doesn't work
+    // when predicate_(x) returns a class convertible to bool but
+    // having no operator!().
+    if (predicate_(x))
+      return true;
+    *listener << "didn't satisfy the given predicate";
+    return false;
+  }
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "satisfies the given predicate";
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "doesn't satisfy the given predicate";
+  }
+
+ private:
+  Predicate predicate_;
+};
+
+// Used for implementing Matches(matcher), which turns a matcher into
+// a predicate.
+template <typename M>
+class MatcherAsPredicate {
+ public:
+  explicit MatcherAsPredicate(M matcher) : matcher_(matcher) {}
+
+  // This template operator() allows Matches(m) to be used as a
+  // predicate on type T where m is a matcher on type T.
+  //
+  // The argument x is passed by reference instead of by value, as
+  // some matcher may be interested in its address (e.g. as in
+  // Matches(Ref(n))(x)).
+  template <typename T>
+  bool operator()(const T& x) const {
+    // We let matcher_ commit to a particular type here instead of
+    // when the MatcherAsPredicate object was constructed.  This
+    // allows us to write Matches(m) where m is a polymorphic matcher
+    // (e.g. Eq(5)).
+    //
+    // If we write Matcher<T>(matcher_).Matches(x) here, it won't
+    // compile when matcher_ has type Matcher<const T&>; if we write
+    // Matcher<const T&>(matcher_).Matches(x) here, it won't compile
+    // when matcher_ has type Matcher<T>; if we just write
+    // matcher_.Matches(x), it won't compile when matcher_ is
+    // polymorphic, e.g. Eq(5).
+    //
+    // MatcherCast<const T&>() is necessary for making the code work
+    // in all of the above situations.
+    return MatcherCast<const T&>(matcher_).Matches(x);
+  }
+
+ private:
+  M matcher_;
+};
+
+// For implementing ASSERT_THAT() and EXPECT_THAT().  The template
+// argument M must be a type that can be converted to a matcher.
+template <typename M>
+class PredicateFormatterFromMatcher {
+ public:
+  explicit PredicateFormatterFromMatcher(M m) : matcher_(std::move(m)) {}
+
+  // This template () operator allows a PredicateFormatterFromMatcher
+  // object to act as a predicate-formatter suitable for using with
+  // Google Test's EXPECT_PRED_FORMAT1() macro.
+  template <typename T>
+  AssertionResult operator()(const char* value_text, const T& x) const {
+    // We convert matcher_ to a Matcher<const T&> *now* instead of
+    // when the PredicateFormatterFromMatcher object was constructed,
+    // as matcher_ may be polymorphic (e.g. NotNull()) and we won't
+    // know which type to instantiate it to until we actually see the
+    // type of x here.
+    //
+    // We write SafeMatcherCast<const T&>(matcher_) instead of
+    // Matcher<const T&>(matcher_), as the latter won't compile when
+    // matcher_ has type Matcher<T> (e.g. An<int>()).
+    // We don't write MatcherCast<const T&> either, as that allows
+    // potentially unsafe downcasting of the matcher argument.
+    const Matcher<const T&> matcher = SafeMatcherCast<const T&>(matcher_);
+
+    // The expected path here is that the matcher should match (i.e. that most
+    // tests pass) so optimize for this case.
+    if (matcher.Matches(x)) {
+      return AssertionSuccess();
+    }
+
+    ::std::stringstream ss;
+    ss << "Value of: " << value_text << "\n"
+       << "Expected: ";
+    matcher.DescribeTo(&ss);
+
+    // Rerun the matcher to "PrintAndExplain" the failure.
+    StringMatchResultListener listener;
+    if (MatchPrintAndExplain(x, matcher, &listener)) {
+      ss << "\n  The matcher failed on the initial attempt; but passed when "
+            "rerun to generate the explanation.";
+    }
+    ss << "\n  Actual: " << listener.str();
+    return AssertionFailure() << ss.str();
+  }
+
+ private:
+  const M matcher_;
+};
+
+// A helper function for converting a matcher to a predicate-formatter
+// without the user needing to explicitly write the type.  This is
+// used for implementing ASSERT_THAT() and EXPECT_THAT().
+// Implementation detail: 'matcher' is received by-value to force decaying.
+template <typename M>
+inline PredicateFormatterFromMatcher<M>
+MakePredicateFormatterFromMatcher(M matcher) {
+  return PredicateFormatterFromMatcher<M>(std::move(matcher));
+}
+
+// Implements the polymorphic IsNan() matcher, which matches any floating type
+// value that is Nan.
+class IsNanMatcher {
+ public:
+  template <typename FloatType>
+  bool MatchAndExplain(const FloatType& f,
+                       MatchResultListener* /* listener */) const {
+    return (::std::isnan)(f);
+  }
+
+  void DescribeTo(::std::ostream* os) const { *os << "is NaN"; }
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "isn't NaN";
+  }
+};
+
+// Implements the polymorphic floating point equality matcher, which matches
+// two float values using ULP-based approximation or, optionally, a
+// user-specified epsilon.  The template is meant to be instantiated with
+// FloatType being either float or double.
+template <typename FloatType>
+class FloatingEqMatcher {
+ public:
+  // Constructor for FloatingEqMatcher.
+  // The matcher's input will be compared with expected.  The matcher treats two
+  // NANs as equal if nan_eq_nan is true.  Otherwise, under IEEE standards,
+  // equality comparisons between NANs will always return false.  We specify a
+  // negative max_abs_error_ term to indicate that ULP-based approximation will
+  // be used for comparison.
+  FloatingEqMatcher(FloatType expected, bool nan_eq_nan) :
+    expected_(expected), nan_eq_nan_(nan_eq_nan), max_abs_error_(-1) {
+  }
+
+  // Constructor that supports a user-specified max_abs_error that will be used
+  // for comparison instead of ULP-based approximation.  The max absolute
+  // should be non-negative.
+  FloatingEqMatcher(FloatType expected, bool nan_eq_nan,
+                    FloatType max_abs_error)
+      : expected_(expected),
+        nan_eq_nan_(nan_eq_nan),
+        max_abs_error_(max_abs_error) {
+    GTEST_CHECK_(max_abs_error >= 0)
+        << ", where max_abs_error is" << max_abs_error;
+  }
+
+  // Implements floating point equality matcher as a Matcher<T>.
+  template <typename T>
+  class Impl : public MatcherInterface<T> {
+   public:
+    Impl(FloatType expected, bool nan_eq_nan, FloatType max_abs_error)
+        : expected_(expected),
+          nan_eq_nan_(nan_eq_nan),
+          max_abs_error_(max_abs_error) {}
+
+    bool MatchAndExplain(T value,
+                         MatchResultListener* listener) const override {
+      const FloatingPoint<FloatType> actual(value), expected(expected_);
+
+      // Compares NaNs first, if nan_eq_nan_ is true.
+      if (actual.is_nan() || expected.is_nan()) {
+        if (actual.is_nan() && expected.is_nan()) {
+          return nan_eq_nan_;
+        }
+        // One is nan; the other is not nan.
+        return false;
+      }
+      if (HasMaxAbsError()) {
+        // We perform an equality check so that inf will match inf, regardless
+        // of error bounds.  If the result of value - expected_ would result in
+        // overflow or if either value is inf, the default result is infinity,
+        // which should only match if max_abs_error_ is also infinity.
+        if (value == expected_) {
+          return true;
+        }
+
+        const FloatType diff = value - expected_;
+        if (::std::fabs(diff) <= max_abs_error_) {
+          return true;
+        }
+
+        if (listener->IsInterested()) {
+          *listener << "which is " << diff << " from " << expected_;
+        }
+        return false;
+      } else {
+        return actual.AlmostEquals(expected);
+      }
+    }
+
+    void DescribeTo(::std::ostream* os) const override {
+      // os->precision() returns the previously set precision, which we
+      // store to restore the ostream to its original configuration
+      // after outputting.
+      const ::std::streamsize old_precision = os->precision(
+          ::std::numeric_limits<FloatType>::digits10 + 2);
+      if (FloatingPoint<FloatType>(expected_).is_nan()) {
+        if (nan_eq_nan_) {
+          *os << "is NaN";
+        } else {
+          *os << "never matches";
+        }
+      } else {
+        *os << "is approximately " << expected_;
+        if (HasMaxAbsError()) {
+          *os << " (absolute error <= " << max_abs_error_ << ")";
+        }
+      }
+      os->precision(old_precision);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      // As before, get original precision.
+      const ::std::streamsize old_precision = os->precision(
+          ::std::numeric_limits<FloatType>::digits10 + 2);
+      if (FloatingPoint<FloatType>(expected_).is_nan()) {
+        if (nan_eq_nan_) {
+          *os << "isn't NaN";
+        } else {
+          *os << "is anything";
+        }
+      } else {
+        *os << "isn't approximately " << expected_;
+        if (HasMaxAbsError()) {
+          *os << " (absolute error > " << max_abs_error_ << ")";
+        }
+      }
+      // Restore original precision.
+      os->precision(old_precision);
+    }
+
+   private:
+    bool HasMaxAbsError() const {
+      return max_abs_error_ >= 0;
+    }
+
+    const FloatType expected_;
+    const bool nan_eq_nan_;
+    // max_abs_error will be used for value comparison when >= 0.
+    const FloatType max_abs_error_;
+  };
+
+  // The following 3 type conversion operators allow FloatEq(expected) and
+  // NanSensitiveFloatEq(expected) to be used as a Matcher<float>, a
+  // Matcher<const float&>, or a Matcher<float&>, but nothing else.
+  operator Matcher<FloatType>() const {
+    return MakeMatcher(
+        new Impl<FloatType>(expected_, nan_eq_nan_, max_abs_error_));
+  }
+
+  operator Matcher<const FloatType&>() const {
+    return MakeMatcher(
+        new Impl<const FloatType&>(expected_, nan_eq_nan_, max_abs_error_));
+  }
+
+  operator Matcher<FloatType&>() const {
+    return MakeMatcher(
+        new Impl<FloatType&>(expected_, nan_eq_nan_, max_abs_error_));
+  }
+
+ private:
+  const FloatType expected_;
+  const bool nan_eq_nan_;
+  // max_abs_error will be used for value comparison when >= 0.
+  const FloatType max_abs_error_;
+};
+
+// A 2-tuple ("binary") wrapper around FloatingEqMatcher:
+// FloatingEq2Matcher() matches (x, y) by matching FloatingEqMatcher(x, false)
+// against y, and FloatingEq2Matcher(e) matches FloatingEqMatcher(x, false, e)
+// against y. The former implements "Eq", the latter "Near". At present, there
+// is no version that compares NaNs as equal.
+template <typename FloatType>
+class FloatingEq2Matcher {
+ public:
+  FloatingEq2Matcher() { Init(-1, false); }
+
+  explicit FloatingEq2Matcher(bool nan_eq_nan) { Init(-1, nan_eq_nan); }
+
+  explicit FloatingEq2Matcher(FloatType max_abs_error) {
+    Init(max_abs_error, false);
+  }
+
+  FloatingEq2Matcher(FloatType max_abs_error, bool nan_eq_nan) {
+    Init(max_abs_error, nan_eq_nan);
+  }
+
+  template <typename T1, typename T2>
+  operator Matcher<::std::tuple<T1, T2>>() const {
+    return MakeMatcher(
+        new Impl<::std::tuple<T1, T2>>(max_abs_error_, nan_eq_nan_));
+  }
+  template <typename T1, typename T2>
+  operator Matcher<const ::std::tuple<T1, T2>&>() const {
+    return MakeMatcher(
+        new Impl<const ::std::tuple<T1, T2>&>(max_abs_error_, nan_eq_nan_));
+  }
+
+ private:
+  static ::std::ostream& GetDesc(::std::ostream& os) {  // NOLINT
+    return os << "an almost-equal pair";
+  }
+
+  template <typename Tuple>
+  class Impl : public MatcherInterface<Tuple> {
+   public:
+    Impl(FloatType max_abs_error, bool nan_eq_nan) :
+        max_abs_error_(max_abs_error),
+        nan_eq_nan_(nan_eq_nan) {}
+
+    bool MatchAndExplain(Tuple args,
+                         MatchResultListener* listener) const override {
+      if (max_abs_error_ == -1) {
+        FloatingEqMatcher<FloatType> fm(::std::get<0>(args), nan_eq_nan_);
+        return static_cast<Matcher<FloatType>>(fm).MatchAndExplain(
+            ::std::get<1>(args), listener);
+      } else {
+        FloatingEqMatcher<FloatType> fm(::std::get<0>(args), nan_eq_nan_,
+                                        max_abs_error_);
+        return static_cast<Matcher<FloatType>>(fm).MatchAndExplain(
+            ::std::get<1>(args), listener);
+      }
+    }
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "are " << GetDesc;
+    }
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "aren't " << GetDesc;
+    }
+
+   private:
+    FloatType max_abs_error_;
+    const bool nan_eq_nan_;
+  };
+
+  void Init(FloatType max_abs_error_val, bool nan_eq_nan_val) {
+    max_abs_error_ = max_abs_error_val;
+    nan_eq_nan_ = nan_eq_nan_val;
+  }
+  FloatType max_abs_error_;
+  bool nan_eq_nan_;
+};
+
+// Implements the Pointee(m) matcher for matching a pointer whose
+// pointee matches matcher m.  The pointer can be either raw or smart.
+template <typename InnerMatcher>
+class PointeeMatcher {
+ public:
+  explicit PointeeMatcher(const InnerMatcher& matcher) : matcher_(matcher) {}
+
+  // This type conversion operator template allows Pointee(m) to be
+  // used as a matcher for any pointer type whose pointee type is
+  // compatible with the inner matcher, where type Pointer can be
+  // either a raw pointer or a smart pointer.
+  //
+  // The reason we do this instead of relying on
+  // MakePolymorphicMatcher() is that the latter is not flexible
+  // enough for implementing the DescribeTo() method of Pointee().
+  template <typename Pointer>
+  operator Matcher<Pointer>() const {
+    return Matcher<Pointer>(new Impl<const Pointer&>(matcher_));
+  }
+
+ private:
+  // The monomorphic implementation that works for a particular pointer type.
+  template <typename Pointer>
+  class Impl : public MatcherInterface<Pointer> {
+   public:
+    using Pointee =
+        typename std::pointer_traits<GTEST_REMOVE_REFERENCE_AND_CONST_(
+            Pointer)>::element_type;
+
+    explicit Impl(const InnerMatcher& matcher)
+        : matcher_(MatcherCast<const Pointee&>(matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "points to a value that ";
+      matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "does not point to a value that ";
+      matcher_.DescribeTo(os);
+    }
+
+    bool MatchAndExplain(Pointer pointer,
+                         MatchResultListener* listener) const override {
+      if (GetRawPointer(pointer) == nullptr) return false;
+
+      *listener << "which points to ";
+      return MatchPrintAndExplain(*pointer, matcher_, listener);
+    }
+
+   private:
+    const Matcher<const Pointee&> matcher_;
+  };
+
+  const InnerMatcher matcher_;
+};
+
+// Implements the Pointer(m) matcher
+// Implements the Pointer(m) matcher for matching a pointer that matches matcher
+// m.  The pointer can be either raw or smart, and will match `m` against the
+// raw pointer.
+template <typename InnerMatcher>
+class PointerMatcher {
+ public:
+  explicit PointerMatcher(const InnerMatcher& matcher) : matcher_(matcher) {}
+
+  // This type conversion operator template allows Pointer(m) to be
+  // used as a matcher for any pointer type whose pointer type is
+  // compatible with the inner matcher, where type PointerType can be
+  // either a raw pointer or a smart pointer.
+  //
+  // The reason we do this instead of relying on
+  // MakePolymorphicMatcher() is that the latter is not flexible
+  // enough for implementing the DescribeTo() method of Pointer().
+  template <typename PointerType>
+  operator Matcher<PointerType>() const {  // NOLINT
+    return Matcher<PointerType>(new Impl<const PointerType&>(matcher_));
+  }
+
+ private:
+  // The monomorphic implementation that works for a particular pointer type.
+  template <typename PointerType>
+  class Impl : public MatcherInterface<PointerType> {
+   public:
+    using Pointer =
+        const typename std::pointer_traits<GTEST_REMOVE_REFERENCE_AND_CONST_(
+            PointerType)>::element_type*;
+
+    explicit Impl(const InnerMatcher& matcher)
+        : matcher_(MatcherCast<Pointer>(matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "is a pointer that ";
+      matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "is not a pointer that ";
+      matcher_.DescribeTo(os);
+    }
+
+    bool MatchAndExplain(PointerType pointer,
+                         MatchResultListener* listener) const override {
+      *listener << "which is a pointer that ";
+      Pointer p = GetRawPointer(pointer);
+      return MatchPrintAndExplain(p, matcher_, listener);
+    }
+
+   private:
+    Matcher<Pointer> matcher_;
+  };
+
+  const InnerMatcher matcher_;
+};
+
+#if GTEST_HAS_RTTI
+// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or
+// reference that matches inner_matcher when dynamic_cast<T> is applied.
+// The result of dynamic_cast<To> is forwarded to the inner matcher.
+// If To is a pointer and the cast fails, the inner matcher will receive NULL.
+// If To is a reference and the cast fails, this matcher returns false
+// immediately.
+template <typename To>
+class WhenDynamicCastToMatcherBase {
+ public:
+  explicit WhenDynamicCastToMatcherBase(const Matcher<To>& matcher)
+      : matcher_(matcher) {}
+
+  void DescribeTo(::std::ostream* os) const {
+    GetCastTypeDescription(os);
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    GetCastTypeDescription(os);
+    matcher_.DescribeNegationTo(os);
+  }
+
+ protected:
+  const Matcher<To> matcher_;
+
+  static std::string GetToName() {
+    return GetTypeName<To>();
+  }
+
+ private:
+  static void GetCastTypeDescription(::std::ostream* os) {
+    *os << "when dynamic_cast to " << GetToName() << ", ";
+  }
+};
+
+// Primary template.
+// To is a pointer. Cast and forward the result.
+template <typename To>
+class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase<To> {
+ public:
+  explicit WhenDynamicCastToMatcher(const Matcher<To>& matcher)
+      : WhenDynamicCastToMatcherBase<To>(matcher) {}
+
+  template <typename From>
+  bool MatchAndExplain(From from, MatchResultListener* listener) const {
+    To to = dynamic_cast<To>(from);
+    return MatchPrintAndExplain(to, this->matcher_, listener);
+  }
+};
+
+// Specialize for references.
+// In this case we return false if the dynamic_cast fails.
+template <typename To>
+class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> {
+ public:
+  explicit WhenDynamicCastToMatcher(const Matcher<To&>& matcher)
+      : WhenDynamicCastToMatcherBase<To&>(matcher) {}
+
+  template <typename From>
+  bool MatchAndExplain(From& from, MatchResultListener* listener) const {
+    // We don't want an std::bad_cast here, so do the cast with pointers.
+    To* to = dynamic_cast<To*>(&from);
+    if (to == nullptr) {
+      *listener << "which cannot be dynamic_cast to " << this->GetToName();
+      return false;
+    }
+    return MatchPrintAndExplain(*to, this->matcher_, listener);
+  }
+};
+#endif  // GTEST_HAS_RTTI
+
+// Implements the Field() matcher for matching a field (i.e. member
+// variable) of an object.
+template <typename Class, typename FieldType>
+class FieldMatcher {
+ public:
+  FieldMatcher(FieldType Class::*field,
+               const Matcher<const FieldType&>& matcher)
+      : field_(field), matcher_(matcher), whose_field_("whose given field ") {}
+
+  FieldMatcher(const std::string& field_name, FieldType Class::*field,
+               const Matcher<const FieldType&>& matcher)
+      : field_(field),
+        matcher_(matcher),
+        whose_field_("whose field `" + field_name + "` ") {}
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "is an object " << whose_field_;
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "is an object " << whose_field_;
+    matcher_.DescribeNegationTo(os);
+  }
+
+  template <typename T>
+  bool MatchAndExplain(const T& value, MatchResultListener* listener) const {
+    // FIXME: The dispatch on std::is_pointer was introduced as a workaround for
+    // a compiler bug, and can now be removed.
+    return MatchAndExplainImpl(
+        typename std::is_pointer<typename std::remove_const<T>::type>::type(),
+        value, listener);
+  }
+
+ private:
+  bool MatchAndExplainImpl(std::false_type /* is_not_pointer */,
+                           const Class& obj,
+                           MatchResultListener* listener) const {
+    *listener << whose_field_ << "is ";
+    return MatchPrintAndExplain(obj.*field_, matcher_, listener);
+  }
+
+  bool MatchAndExplainImpl(std::true_type /* is_pointer */, const Class* p,
+                           MatchResultListener* listener) const {
+    if (p == nullptr) return false;
+
+    *listener << "which points to an object ";
+    // Since *p has a field, it must be a class/struct/union type and
+    // thus cannot be a pointer.  Therefore we pass false_type() as
+    // the first argument.
+    return MatchAndExplainImpl(std::false_type(), *p, listener);
+  }
+
+  const FieldType Class::*field_;
+  const Matcher<const FieldType&> matcher_;
+
+  // Contains either "whose given field " if the name of the field is unknown
+  // or "whose field `name_of_field` " if the name is known.
+  const std::string whose_field_;
+};
+
+// Implements the Property() matcher for matching a property
+// (i.e. return value of a getter method) of an object.
+//
+// Property is a const-qualified member function of Class returning
+// PropertyType.
+template <typename Class, typename PropertyType, typename Property>
+class PropertyMatcher {
+ public:
+  typedef const PropertyType& RefToConstProperty;
+
+  PropertyMatcher(Property property, const Matcher<RefToConstProperty>& matcher)
+      : property_(property),
+        matcher_(matcher),
+        whose_property_("whose given property ") {}
+
+  PropertyMatcher(const std::string& property_name, Property property,
+                  const Matcher<RefToConstProperty>& matcher)
+      : property_(property),
+        matcher_(matcher),
+        whose_property_("whose property `" + property_name + "` ") {}
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "is an object " << whose_property_;
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "is an object " << whose_property_;
+    matcher_.DescribeNegationTo(os);
+  }
+
+  template <typename T>
+  bool MatchAndExplain(const T&value, MatchResultListener* listener) const {
+    return MatchAndExplainImpl(
+        typename std::is_pointer<typename std::remove_const<T>::type>::type(),
+        value, listener);
+  }
+
+ private:
+  bool MatchAndExplainImpl(std::false_type /* is_not_pointer */,
+                           const Class& obj,
+                           MatchResultListener* listener) const {
+    *listener << whose_property_ << "is ";
+    // Cannot pass the return value (for example, int) to MatchPrintAndExplain,
+    // which takes a non-const reference as argument.
+    RefToConstProperty result = (obj.*property_)();
+    return MatchPrintAndExplain(result, matcher_, listener);
+  }
+
+  bool MatchAndExplainImpl(std::true_type /* is_pointer */, const Class* p,
+                           MatchResultListener* listener) const {
+    if (p == nullptr) return false;
+
+    *listener << "which points to an object ";
+    // Since *p has a property method, it must be a class/struct/union
+    // type and thus cannot be a pointer.  Therefore we pass
+    // false_type() as the first argument.
+    return MatchAndExplainImpl(std::false_type(), *p, listener);
+  }
+
+  Property property_;
+  const Matcher<RefToConstProperty> matcher_;
+
+  // Contains either "whose given property " if the name of the property is
+  // unknown or "whose property `name_of_property` " if the name is known.
+  const std::string whose_property_;
+};
+
+// Type traits specifying various features of different functors for ResultOf.
+// The default template specifies features for functor objects.
+template <typename Functor>
+struct CallableTraits {
+  typedef Functor StorageType;
+
+  static void CheckIsValid(Functor /* functor */) {}
+
+  template <typename T>
+  static auto Invoke(Functor f, const T& arg) -> decltype(f(arg)) {
+    return f(arg);
+  }
+};
+
+// Specialization for function pointers.
+template <typename ArgType, typename ResType>
+struct CallableTraits<ResType(*)(ArgType)> {
+  typedef ResType ResultType;
+  typedef ResType(*StorageType)(ArgType);
+
+  static void CheckIsValid(ResType(*f)(ArgType)) {
+    GTEST_CHECK_(f != nullptr)
+        << "NULL function pointer is passed into ResultOf().";
+  }
+  template <typename T>
+  static ResType Invoke(ResType(*f)(ArgType), T arg) {
+    return (*f)(arg);
+  }
+};
+
+// Implements the ResultOf() matcher for matching a return value of a
+// unary function of an object.
+template <typename Callable, typename InnerMatcher>
+class ResultOfMatcher {
+ public:
+  ResultOfMatcher(Callable callable, InnerMatcher matcher)
+      : callable_(std::move(callable)), matcher_(std::move(matcher)) {
+    CallableTraits<Callable>::CheckIsValid(callable_);
+  }
+
+  template <typename T>
+  operator Matcher<T>() const {
+    return Matcher<T>(new Impl<const T&>(callable_, matcher_));
+  }
+
+ private:
+  typedef typename CallableTraits<Callable>::StorageType CallableStorageType;
+
+  template <typename T>
+  class Impl : public MatcherInterface<T> {
+    using ResultType = decltype(CallableTraits<Callable>::template Invoke<T>(
+        std::declval<CallableStorageType>(), std::declval<T>()));
+
+   public:
+    template <typename M>
+    Impl(const CallableStorageType& callable, const M& matcher)
+        : callable_(callable), matcher_(MatcherCast<ResultType>(matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "is mapped by the given callable to a value that ";
+      matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "is mapped by the given callable to a value that ";
+      matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(T obj, MatchResultListener* listener) const override {
+      *listener << "which is mapped by the given callable to ";
+      // Cannot pass the return value directly to MatchPrintAndExplain, which
+      // takes a non-const reference as argument.
+      // Also, specifying template argument explicitly is needed because T could
+      // be a non-const reference (e.g. Matcher<Uncopyable&>).
+      ResultType result =
+          CallableTraits<Callable>::template Invoke<T>(callable_, obj);
+      return MatchPrintAndExplain(result, matcher_, listener);
+    }
+
+   private:
+    // Functors often define operator() as non-const method even though
+    // they are actually stateless. But we need to use them even when
+    // 'this' is a const pointer. It's the user's responsibility not to
+    // use stateful callables with ResultOf(), which doesn't guarantee
+    // how many times the callable will be invoked.
+    mutable CallableStorageType callable_;
+    const Matcher<ResultType> matcher_;
+  };  // class Impl
+
+  const CallableStorageType callable_;
+  const InnerMatcher matcher_;
+};
+
+// Implements a matcher that checks the size of an STL-style container.
+template <typename SizeMatcher>
+class SizeIsMatcher {
+ public:
+  explicit SizeIsMatcher(const SizeMatcher& size_matcher)
+       : size_matcher_(size_matcher) {
+  }
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    return Matcher<Container>(new Impl<const Container&>(size_matcher_));
+  }
+
+  template <typename Container>
+  class Impl : public MatcherInterface<Container> {
+   public:
+    using SizeType = decltype(std::declval<Container>().size());
+    explicit Impl(const SizeMatcher& size_matcher)
+        : size_matcher_(MatcherCast<SizeType>(size_matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "size ";
+      size_matcher_.DescribeTo(os);
+    }
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "size ";
+      size_matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(Container container,
+                         MatchResultListener* listener) const override {
+      SizeType size = container.size();
+      StringMatchResultListener size_listener;
+      const bool result = size_matcher_.MatchAndExplain(size, &size_listener);
+      *listener
+          << "whose size " << size << (result ? " matches" : " doesn't match");
+      PrintIfNotEmpty(size_listener.str(), listener->stream());
+      return result;
+    }
+
+   private:
+    const Matcher<SizeType> size_matcher_;
+  };
+
+ private:
+  const SizeMatcher size_matcher_;
+};
+
+// Implements a matcher that checks the begin()..end() distance of an STL-style
+// container.
+template <typename DistanceMatcher>
+class BeginEndDistanceIsMatcher {
+ public:
+  explicit BeginEndDistanceIsMatcher(const DistanceMatcher& distance_matcher)
+      : distance_matcher_(distance_matcher) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    return Matcher<Container>(new Impl<const Container&>(distance_matcher_));
+  }
+
+  template <typename Container>
+  class Impl : public MatcherInterface<Container> {
+   public:
+    typedef internal::StlContainerView<
+        GTEST_REMOVE_REFERENCE_AND_CONST_(Container)> ContainerView;
+    typedef typename std::iterator_traits<
+        typename ContainerView::type::const_iterator>::difference_type
+        DistanceType;
+    explicit Impl(const DistanceMatcher& distance_matcher)
+        : distance_matcher_(MatcherCast<DistanceType>(distance_matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "distance between begin() and end() ";
+      distance_matcher_.DescribeTo(os);
+    }
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "distance between begin() and end() ";
+      distance_matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(Container container,
+                         MatchResultListener* listener) const override {
+      using std::begin;
+      using std::end;
+      DistanceType distance = std::distance(begin(container), end(container));
+      StringMatchResultListener distance_listener;
+      const bool result =
+          distance_matcher_.MatchAndExplain(distance, &distance_listener);
+      *listener << "whose distance between begin() and end() " << distance
+                << (result ? " matches" : " doesn't match");
+      PrintIfNotEmpty(distance_listener.str(), listener->stream());
+      return result;
+    }
+
+   private:
+    const Matcher<DistanceType> distance_matcher_;
+  };
+
+ private:
+  const DistanceMatcher distance_matcher_;
+};
+
+// Implements an equality matcher for any STL-style container whose elements
+// support ==. This matcher is like Eq(), but its failure explanations provide
+// more detailed information that is useful when the container is used as a set.
+// The failure message reports elements that are in one of the operands but not
+// the other. The failure messages do not report duplicate or out-of-order
+// elements in the containers (which don't properly matter to sets, but can
+// occur if the containers are vectors or lists, for example).
+//
+// Uses the container's const_iterator, value_type, operator ==,
+// begin(), and end().
+template <typename Container>
+class ContainerEqMatcher {
+ public:
+  typedef internal::StlContainerView<Container> View;
+  typedef typename View::type StlContainer;
+  typedef typename View::const_reference StlContainerReference;
+
+  static_assert(!std::is_const<Container>::value,
+                "Container type must not be const");
+  static_assert(!std::is_reference<Container>::value,
+                "Container type must not be a reference");
+
+  // We make a copy of expected in case the elements in it are modified
+  // after this matcher is created.
+  explicit ContainerEqMatcher(const Container& expected)
+      : expected_(View::Copy(expected)) {}
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << "equals ";
+    UniversalPrint(expected_, os);
+  }
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "does not equal ";
+    UniversalPrint(expected_, os);
+  }
+
+  template <typename LhsContainer>
+  bool MatchAndExplain(const LhsContainer& lhs,
+                       MatchResultListener* listener) const {
+    typedef internal::StlContainerView<
+        typename std::remove_const<LhsContainer>::type>
+        LhsView;
+    typedef typename LhsView::type LhsStlContainer;
+    StlContainerReference lhs_stl_container = LhsView::ConstReference(lhs);
+    if (lhs_stl_container == expected_)
+      return true;
+
+    ::std::ostream* const os = listener->stream();
+    if (os != nullptr) {
+      // Something is different. Check for extra values first.
+      bool printed_header = false;
+      for (typename LhsStlContainer::const_iterator it =
+               lhs_stl_container.begin();
+           it != lhs_stl_container.end(); ++it) {
+        if (internal::ArrayAwareFind(expected_.begin(), expected_.end(), *it) ==
+            expected_.end()) {
+          if (printed_header) {
+            *os << ", ";
+          } else {
+            *os << "which has these unexpected elements: ";
+            printed_header = true;
+          }
+          UniversalPrint(*it, os);
+        }
+      }
+
+      // Now check for missing values.
+      bool printed_header2 = false;
+      for (typename StlContainer::const_iterator it = expected_.begin();
+           it != expected_.end(); ++it) {
+        if (internal::ArrayAwareFind(
+                lhs_stl_container.begin(), lhs_stl_container.end(), *it) ==
+            lhs_stl_container.end()) {
+          if (printed_header2) {
+            *os << ", ";
+          } else {
+            *os << (printed_header ? ",\nand" : "which")
+                << " doesn't have these expected elements: ";
+            printed_header2 = true;
+          }
+          UniversalPrint(*it, os);
+        }
+      }
+    }
+
+    return false;
+  }
+
+ private:
+  const StlContainer expected_;
+};
+
+// A comparator functor that uses the < operator to compare two values.
+struct LessComparator {
+  template <typename T, typename U>
+  bool operator()(const T& lhs, const U& rhs) const { return lhs < rhs; }
+};
+
+// Implements WhenSortedBy(comparator, container_matcher).
+template <typename Comparator, typename ContainerMatcher>
+class WhenSortedByMatcher {
+ public:
+  WhenSortedByMatcher(const Comparator& comparator,
+                      const ContainerMatcher& matcher)
+      : comparator_(comparator), matcher_(matcher) {}
+
+  template <typename LhsContainer>
+  operator Matcher<LhsContainer>() const {
+    return MakeMatcher(new Impl<LhsContainer>(comparator_, matcher_));
+  }
+
+  template <typename LhsContainer>
+  class Impl : public MatcherInterface<LhsContainer> {
+   public:
+    typedef internal::StlContainerView<
+         GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)> LhsView;
+    typedef typename LhsView::type LhsStlContainer;
+    typedef typename LhsView::const_reference LhsStlContainerReference;
+    // Transforms std::pair<const Key, Value> into std::pair<Key, Value>
+    // so that we can match associative containers.
+    typedef typename RemoveConstFromKey<
+        typename LhsStlContainer::value_type>::type LhsValue;
+
+    Impl(const Comparator& comparator, const ContainerMatcher& matcher)
+        : comparator_(comparator), matcher_(matcher) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "(when sorted) ";
+      matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "(when sorted) ";
+      matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(LhsContainer lhs,
+                         MatchResultListener* listener) const override {
+      LhsStlContainerReference lhs_stl_container = LhsView::ConstReference(lhs);
+      ::std::vector<LhsValue> sorted_container(lhs_stl_container.begin(),
+                                               lhs_stl_container.end());
+      ::std::sort(
+           sorted_container.begin(), sorted_container.end(), comparator_);
+
+      if (!listener->IsInterested()) {
+        // If the listener is not interested, we do not need to
+        // construct the inner explanation.
+        return matcher_.Matches(sorted_container);
+      }
+
+      *listener << "which is ";
+      UniversalPrint(sorted_container, listener->stream());
+      *listener << " when sorted";
+
+      StringMatchResultListener inner_listener;
+      const bool match = matcher_.MatchAndExplain(sorted_container,
+                                                  &inner_listener);
+      PrintIfNotEmpty(inner_listener.str(), listener->stream());
+      return match;
+    }
+
+   private:
+    const Comparator comparator_;
+    const Matcher<const ::std::vector<LhsValue>&> matcher_;
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl);
+  };
+
+ private:
+  const Comparator comparator_;
+  const ContainerMatcher matcher_;
+};
+
+// Implements Pointwise(tuple_matcher, rhs_container).  tuple_matcher
+// must be able to be safely cast to Matcher<std::tuple<const T1&, const
+// T2&> >, where T1 and T2 are the types of elements in the LHS
+// container and the RHS container respectively.
+template <typename TupleMatcher, typename RhsContainer>
+class PointwiseMatcher {
+  GTEST_COMPILE_ASSERT_(
+      !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(RhsContainer)>::value,
+      use_UnorderedPointwise_with_hash_tables);
+
+ public:
+  typedef internal::StlContainerView<RhsContainer> RhsView;
+  typedef typename RhsView::type RhsStlContainer;
+  typedef typename RhsStlContainer::value_type RhsValue;
+
+  static_assert(!std::is_const<RhsContainer>::value,
+                "RhsContainer type must not be const");
+  static_assert(!std::is_reference<RhsContainer>::value,
+                "RhsContainer type must not be a reference");
+
+  // Like ContainerEq, we make a copy of rhs in case the elements in
+  // it are modified after this matcher is created.
+  PointwiseMatcher(const TupleMatcher& tuple_matcher, const RhsContainer& rhs)
+      : tuple_matcher_(tuple_matcher), rhs_(RhsView::Copy(rhs)) {}
+
+  template <typename LhsContainer>
+  operator Matcher<LhsContainer>() const {
+    GTEST_COMPILE_ASSERT_(
+        !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)>::value,
+        use_UnorderedPointwise_with_hash_tables);
+
+    return Matcher<LhsContainer>(
+        new Impl<const LhsContainer&>(tuple_matcher_, rhs_));
+  }
+
+  template <typename LhsContainer>
+  class Impl : public MatcherInterface<LhsContainer> {
+   public:
+    typedef internal::StlContainerView<
+         GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)> LhsView;
+    typedef typename LhsView::type LhsStlContainer;
+    typedef typename LhsView::const_reference LhsStlContainerReference;
+    typedef typename LhsStlContainer::value_type LhsValue;
+    // We pass the LHS value and the RHS value to the inner matcher by
+    // reference, as they may be expensive to copy.  We must use tuple
+    // instead of pair here, as a pair cannot hold references (C++ 98,
+    // 20.2.2 [lib.pairs]).
+    typedef ::std::tuple<const LhsValue&, const RhsValue&> InnerMatcherArg;
+
+    Impl(const TupleMatcher& tuple_matcher, const RhsStlContainer& rhs)
+        // mono_tuple_matcher_ holds a monomorphic version of the tuple matcher.
+        : mono_tuple_matcher_(SafeMatcherCast<InnerMatcherArg>(tuple_matcher)),
+          rhs_(rhs) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "contains " << rhs_.size()
+          << " values, where each value and its corresponding value in ";
+      UniversalPrinter<RhsStlContainer>::Print(rhs_, os);
+      *os << " ";
+      mono_tuple_matcher_.DescribeTo(os);
+    }
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "doesn't contain exactly " << rhs_.size()
+          << " values, or contains a value x at some index i"
+          << " where x and the i-th value of ";
+      UniversalPrint(rhs_, os);
+      *os << " ";
+      mono_tuple_matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(LhsContainer lhs,
+                         MatchResultListener* listener) const override {
+      LhsStlContainerReference lhs_stl_container = LhsView::ConstReference(lhs);
+      const size_t actual_size = lhs_stl_container.size();
+      if (actual_size != rhs_.size()) {
+        *listener << "which contains " << actual_size << " values";
+        return false;
+      }
+
+      typename LhsStlContainer::const_iterator left = lhs_stl_container.begin();
+      typename RhsStlContainer::const_iterator right = rhs_.begin();
+      for (size_t i = 0; i != actual_size; ++i, ++left, ++right) {
+        if (listener->IsInterested()) {
+          StringMatchResultListener inner_listener;
+          // Create InnerMatcherArg as a temporarily object to avoid it outlives
+          // *left and *right. Dereference or the conversion to `const T&` may
+          // return temp objects, e.g for vector<bool>.
+          if (!mono_tuple_matcher_.MatchAndExplain(
+                  InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left),
+                                  ImplicitCast_<const RhsValue&>(*right)),
+                  &inner_listener)) {
+            *listener << "where the value pair (";
+            UniversalPrint(*left, listener->stream());
+            *listener << ", ";
+            UniversalPrint(*right, listener->stream());
+            *listener << ") at index #" << i << " don't match";
+            PrintIfNotEmpty(inner_listener.str(), listener->stream());
+            return false;
+          }
+        } else {
+          if (!mono_tuple_matcher_.Matches(
+                  InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left),
+                                  ImplicitCast_<const RhsValue&>(*right))))
+            return false;
+        }
+      }
+
+      return true;
+    }
+
+   private:
+    const Matcher<InnerMatcherArg> mono_tuple_matcher_;
+    const RhsStlContainer rhs_;
+  };
+
+ private:
+  const TupleMatcher tuple_matcher_;
+  const RhsStlContainer rhs_;
+};
+
+// Holds the logic common to ContainsMatcherImpl and EachMatcherImpl.
+template <typename Container>
+class QuantifierMatcherImpl : public MatcherInterface<Container> {
+ public:
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
+  typedef StlContainerView<RawContainer> View;
+  typedef typename View::type StlContainer;
+  typedef typename View::const_reference StlContainerReference;
+  typedef typename StlContainer::value_type Element;
+
+  template <typename InnerMatcher>
+  explicit QuantifierMatcherImpl(InnerMatcher inner_matcher)
+      : inner_matcher_(
+           testing::SafeMatcherCast<const Element&>(inner_matcher)) {}
+
+  // Checks whether:
+  // * All elements in the container match, if all_elements_should_match.
+  // * Any element in the container matches, if !all_elements_should_match.
+  bool MatchAndExplainImpl(bool all_elements_should_match,
+                           Container container,
+                           MatchResultListener* listener) const {
+    StlContainerReference stl_container = View::ConstReference(container);
+    size_t i = 0;
+    for (typename StlContainer::const_iterator it = stl_container.begin();
+         it != stl_container.end(); ++it, ++i) {
+      StringMatchResultListener inner_listener;
+      const bool matches = inner_matcher_.MatchAndExplain(*it, &inner_listener);
+
+      if (matches != all_elements_should_match) {
+        *listener << "whose element #" << i
+                  << (matches ? " matches" : " doesn't match");
+        PrintIfNotEmpty(inner_listener.str(), listener->stream());
+        return !all_elements_should_match;
+      }
+    }
+    return all_elements_should_match;
+  }
+
+ protected:
+  const Matcher<const Element&> inner_matcher_;
+};
+
+// Implements Contains(element_matcher) for the given argument type Container.
+// Symmetric to EachMatcherImpl.
+template <typename Container>
+class ContainsMatcherImpl : public QuantifierMatcherImpl<Container> {
+ public:
+  template <typename InnerMatcher>
+  explicit ContainsMatcherImpl(InnerMatcher inner_matcher)
+      : QuantifierMatcherImpl<Container>(inner_matcher) {}
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "contains at least one element that ";
+    this->inner_matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "doesn't contain any element that ";
+    this->inner_matcher_.DescribeTo(os);
+  }
+
+  bool MatchAndExplain(Container container,
+                       MatchResultListener* listener) const override {
+    return this->MatchAndExplainImpl(false, container, listener);
+  }
+};
+
+// Implements Each(element_matcher) for the given argument type Container.
+// Symmetric to ContainsMatcherImpl.
+template <typename Container>
+class EachMatcherImpl : public QuantifierMatcherImpl<Container> {
+ public:
+  template <typename InnerMatcher>
+  explicit EachMatcherImpl(InnerMatcher inner_matcher)
+      : QuantifierMatcherImpl<Container>(inner_matcher) {}
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "only contains elements that ";
+    this->inner_matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "contains some element that ";
+    this->inner_matcher_.DescribeNegationTo(os);
+  }
+
+  bool MatchAndExplain(Container container,
+                       MatchResultListener* listener) const override {
+    return this->MatchAndExplainImpl(true, container, listener);
+  }
+};
+
+// Implements polymorphic Contains(element_matcher).
+template <typename M>
+class ContainsMatcher {
+ public:
+  explicit ContainsMatcher(M m) : inner_matcher_(m) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    return Matcher<Container>(
+        new ContainsMatcherImpl<const Container&>(inner_matcher_));
+  }
+
+ private:
+  const M inner_matcher_;
+};
+
+// Implements polymorphic Each(element_matcher).
+template <typename M>
+class EachMatcher {
+ public:
+  explicit EachMatcher(M m) : inner_matcher_(m) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    return Matcher<Container>(
+        new EachMatcherImpl<const Container&>(inner_matcher_));
+  }
+
+ private:
+  const M inner_matcher_;
+};
+
+struct Rank1 {};
+struct Rank0 : Rank1 {};
+
+namespace pair_getters {
+using std::get;
+template <typename T>
+auto First(T& x, Rank1) -> decltype(get<0>(x)) {  // NOLINT
+  return get<0>(x);
+}
+template <typename T>
+auto First(T& x, Rank0) -> decltype((x.first)) {  // NOLINT
+  return x.first;
+}
+
+template <typename T>
+auto Second(T& x, Rank1) -> decltype(get<1>(x)) {  // NOLINT
+  return get<1>(x);
+}
+template <typename T>
+auto Second(T& x, Rank0) -> decltype((x.second)) {  // NOLINT
+  return x.second;
+}
+}  // namespace pair_getters
+
+// Implements Key(inner_matcher) for the given argument pair type.
+// Key(inner_matcher) matches an std::pair whose 'first' field matches
+// inner_matcher.  For example, Contains(Key(Ge(5))) can be used to match an
+// std::map that contains at least one element whose key is >= 5.
+template <typename PairType>
+class KeyMatcherImpl : public MatcherInterface<PairType> {
+ public:
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(PairType) RawPairType;
+  typedef typename RawPairType::first_type KeyType;
+
+  template <typename InnerMatcher>
+  explicit KeyMatcherImpl(InnerMatcher inner_matcher)
+      : inner_matcher_(
+          testing::SafeMatcherCast<const KeyType&>(inner_matcher)) {
+  }
+
+  // Returns true if and only if 'key_value.first' (the key) matches the inner
+  // matcher.
+  bool MatchAndExplain(PairType key_value,
+                       MatchResultListener* listener) const override {
+    StringMatchResultListener inner_listener;
+    const bool match = inner_matcher_.MatchAndExplain(
+        pair_getters::First(key_value, Rank0()), &inner_listener);
+    const std::string explanation = inner_listener.str();
+    if (explanation != "") {
+      *listener << "whose first field is a value " << explanation;
+    }
+    return match;
+  }
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "has a key that ";
+    inner_matcher_.DescribeTo(os);
+  }
+
+  // Describes what the negation of this matcher does.
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "doesn't have a key that ";
+    inner_matcher_.DescribeTo(os);
+  }
+
+ private:
+  const Matcher<const KeyType&> inner_matcher_;
+};
+
+// Implements polymorphic Key(matcher_for_key).
+template <typename M>
+class KeyMatcher {
+ public:
+  explicit KeyMatcher(M m) : matcher_for_key_(m) {}
+
+  template <typename PairType>
+  operator Matcher<PairType>() const {
+    return Matcher<PairType>(
+        new KeyMatcherImpl<const PairType&>(matcher_for_key_));
+  }
+
+ private:
+  const M matcher_for_key_;
+};
+
+// Implements polymorphic Address(matcher_for_address).
+template <typename InnerMatcher>
+class AddressMatcher {
+ public:
+  explicit AddressMatcher(InnerMatcher m) : matcher_(m) {}
+
+  template <typename Type>
+  operator Matcher<Type>() const {  // NOLINT
+    return Matcher<Type>(new Impl<const Type&>(matcher_));
+  }
+
+ private:
+  // The monomorphic implementation that works for a particular object type.
+  template <typename Type>
+  class Impl : public MatcherInterface<Type> {
+   public:
+    using Address = const GTEST_REMOVE_REFERENCE_AND_CONST_(Type) *;
+    explicit Impl(const InnerMatcher& matcher)
+        : matcher_(MatcherCast<Address>(matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "has address that ";
+      matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "does not have address that ";
+      matcher_.DescribeTo(os);
+    }
+
+    bool MatchAndExplain(Type object,
+                         MatchResultListener* listener) const override {
+      *listener << "which has address ";
+      Address address = std::addressof(object);
+      return MatchPrintAndExplain(address, matcher_, listener);
+    }
+
+   private:
+    const Matcher<Address> matcher_;
+  };
+  const InnerMatcher matcher_;
+};
+
+// Implements Pair(first_matcher, second_matcher) for the given argument pair
+// type with its two matchers. See Pair() function below.
+template <typename PairType>
+class PairMatcherImpl : public MatcherInterface<PairType> {
+ public:
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(PairType) RawPairType;
+  typedef typename RawPairType::first_type FirstType;
+  typedef typename RawPairType::second_type SecondType;
+
+  template <typename FirstMatcher, typename SecondMatcher>
+  PairMatcherImpl(FirstMatcher first_matcher, SecondMatcher second_matcher)
+      : first_matcher_(
+            testing::SafeMatcherCast<const FirstType&>(first_matcher)),
+        second_matcher_(
+            testing::SafeMatcherCast<const SecondType&>(second_matcher)) {
+  }
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "has a first field that ";
+    first_matcher_.DescribeTo(os);
+    *os << ", and has a second field that ";
+    second_matcher_.DescribeTo(os);
+  }
+
+  // Describes what the negation of this matcher does.
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "has a first field that ";
+    first_matcher_.DescribeNegationTo(os);
+    *os << ", or has a second field that ";
+    second_matcher_.DescribeNegationTo(os);
+  }
+
+  // Returns true if and only if 'a_pair.first' matches first_matcher and
+  // 'a_pair.second' matches second_matcher.
+  bool MatchAndExplain(PairType a_pair,
+                       MatchResultListener* listener) const override {
+    if (!listener->IsInterested()) {
+      // If the listener is not interested, we don't need to construct the
+      // explanation.
+      return first_matcher_.Matches(pair_getters::First(a_pair, Rank0())) &&
+             second_matcher_.Matches(pair_getters::Second(a_pair, Rank0()));
+    }
+    StringMatchResultListener first_inner_listener;
+    if (!first_matcher_.MatchAndExplain(pair_getters::First(a_pair, Rank0()),
+                                        &first_inner_listener)) {
+      *listener << "whose first field does not match";
+      PrintIfNotEmpty(first_inner_listener.str(), listener->stream());
+      return false;
+    }
+    StringMatchResultListener second_inner_listener;
+    if (!second_matcher_.MatchAndExplain(pair_getters::Second(a_pair, Rank0()),
+                                         &second_inner_listener)) {
+      *listener << "whose second field does not match";
+      PrintIfNotEmpty(second_inner_listener.str(), listener->stream());
+      return false;
+    }
+    ExplainSuccess(first_inner_listener.str(), second_inner_listener.str(),
+                   listener);
+    return true;
+  }
+
+ private:
+  void ExplainSuccess(const std::string& first_explanation,
+                      const std::string& second_explanation,
+                      MatchResultListener* listener) const {
+    *listener << "whose both fields match";
+    if (first_explanation != "") {
+      *listener << ", where the first field is a value " << first_explanation;
+    }
+    if (second_explanation != "") {
+      *listener << ", ";
+      if (first_explanation != "") {
+        *listener << "and ";
+      } else {
+        *listener << "where ";
+      }
+      *listener << "the second field is a value " << second_explanation;
+    }
+  }
+
+  const Matcher<const FirstType&> first_matcher_;
+  const Matcher<const SecondType&> second_matcher_;
+};
+
+// Implements polymorphic Pair(first_matcher, second_matcher).
+template <typename FirstMatcher, typename SecondMatcher>
+class PairMatcher {
+ public:
+  PairMatcher(FirstMatcher first_matcher, SecondMatcher second_matcher)
+      : first_matcher_(first_matcher), second_matcher_(second_matcher) {}
+
+  template <typename PairType>
+  operator Matcher<PairType> () const {
+    return Matcher<PairType>(
+        new PairMatcherImpl<const PairType&>(first_matcher_, second_matcher_));
+  }
+
+ private:
+  const FirstMatcher first_matcher_;
+  const SecondMatcher second_matcher_;
+};
+
+template <typename T, size_t... I>
+auto UnpackStructImpl(const T& t, IndexSequence<I...>, int)
+    -> decltype(std::tie(get<I>(t)...)) {
+  static_assert(std::tuple_size<T>::value == sizeof...(I),
+                "Number of arguments doesn't match the number of fields.");
+  return std::tie(get<I>(t)...);
+}
+
+#if defined(__cpp_structured_bindings) && __cpp_structured_bindings >= 201606
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<1>, char) {
+  const auto& [a] = t;
+  return std::tie(a);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<2>, char) {
+  const auto& [a, b] = t;
+  return std::tie(a, b);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<3>, char) {
+  const auto& [a, b, c] = t;
+  return std::tie(a, b, c);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<4>, char) {
+  const auto& [a, b, c, d] = t;
+  return std::tie(a, b, c, d);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<5>, char) {
+  const auto& [a, b, c, d, e] = t;
+  return std::tie(a, b, c, d, e);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<6>, char) {
+  const auto& [a, b, c, d, e, f] = t;
+  return std::tie(a, b, c, d, e, f);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<7>, char) {
+  const auto& [a, b, c, d, e, f, g] = t;
+  return std::tie(a, b, c, d, e, f, g);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<8>, char) {
+  const auto& [a, b, c, d, e, f, g, h] = t;
+  return std::tie(a, b, c, d, e, f, g, h);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<9>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<10>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<11>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<12>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k, l] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k, l);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<13>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<14>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<15>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
+}
+template <typename T>
+auto UnpackStructImpl(const T& t, MakeIndexSequence<16>, char) {
+  const auto& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = t;
+  return std::tie(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p);
+}
+#endif  // defined(__cpp_structured_bindings)
+
+template <size_t I, typename T>
+auto UnpackStruct(const T& t)
+    -> decltype((UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0)) {
+  return (UnpackStructImpl)(t, MakeIndexSequence<I>{}, 0);
+}
+
+// Helper function to do comma folding in C++11.
+// The array ensures left-to-right order of evaluation.
+// Usage: VariadicExpand({expr...});
+template <typename T, size_t N>
+void VariadicExpand(const T (&)[N]) {}
+
+template <typename Struct, typename StructSize>
+class FieldsAreMatcherImpl;
+
+template <typename Struct, size_t... I>
+class FieldsAreMatcherImpl<Struct, IndexSequence<I...>>
+    : public MatcherInterface<Struct> {
+  using UnpackedType =
+      decltype(UnpackStruct<sizeof...(I)>(std::declval<const Struct&>()));
+  using MatchersType = std::tuple<
+      Matcher<const typename std::tuple_element<I, UnpackedType>::type&>...>;
+
+ public:
+  template <typename Inner>
+  explicit FieldsAreMatcherImpl(const Inner& matchers)
+      : matchers_(testing::SafeMatcherCast<
+                  const typename std::tuple_element<I, UnpackedType>::type&>(
+            std::get<I>(matchers))...) {}
+
+  void DescribeTo(::std::ostream* os) const override {
+    const char* separator = "";
+    VariadicExpand(
+        {(*os << separator << "has field #" << I << " that ",
+          std::get<I>(matchers_).DescribeTo(os), separator = ", and ")...});
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    const char* separator = "";
+    VariadicExpand({(*os << separator << "has field #" << I << " that ",
+                     std::get<I>(matchers_).DescribeNegationTo(os),
+                     separator = ", or ")...});
+  }
+
+  bool MatchAndExplain(Struct t, MatchResultListener* listener) const override {
+    return MatchInternal((UnpackStruct<sizeof...(I)>)(t), listener);
+  }
+
+ private:
+  bool MatchInternal(UnpackedType tuple, MatchResultListener* listener) const {
+    if (!listener->IsInterested()) {
+      // If the listener is not interested, we don't need to construct the
+      // explanation.
+      bool good = true;
+      VariadicExpand({good = good && std::get<I>(matchers_).Matches(
+                                         std::get<I>(tuple))...});
+      return good;
+    }
+
+    size_t failed_pos = ~size_t{};
+
+    std::vector<StringMatchResultListener> inner_listener(sizeof...(I));
+
+    VariadicExpand(
+        {failed_pos == ~size_t{} && !std::get<I>(matchers_).MatchAndExplain(
+                                        std::get<I>(tuple), &inner_listener[I])
+             ? failed_pos = I
+             : 0 ...});
+    if (failed_pos != ~size_t{}) {
+      *listener << "whose field #" << failed_pos << " does not match";
+      PrintIfNotEmpty(inner_listener[failed_pos].str(), listener->stream());
+      return false;
+    }
+
+    *listener << "whose all elements match";
+    const char* separator = ", where";
+    for (size_t index = 0; index < sizeof...(I); ++index) {
+      const std::string str = inner_listener[index].str();
+      if (!str.empty()) {
+        *listener << separator << " field #" << index << " is a value " << str;
+        separator = ", and";
+      }
+    }
+
+    return true;
+  }
+
+  MatchersType matchers_;
+};
+
+template <typename... Inner>
+class FieldsAreMatcher {
+ public:
+  explicit FieldsAreMatcher(Inner... inner) : matchers_(std::move(inner)...) {}
+
+  template <typename Struct>
+  operator Matcher<Struct>() const {  // NOLINT
+    return Matcher<Struct>(
+        new FieldsAreMatcherImpl<const Struct&, IndexSequenceFor<Inner...>>(
+            matchers_));
+  }
+
+ private:
+  std::tuple<Inner...> matchers_;
+};
+
+// Implements ElementsAre() and ElementsAreArray().
+template <typename Container>
+class ElementsAreMatcherImpl : public MatcherInterface<Container> {
+ public:
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
+  typedef internal::StlContainerView<RawContainer> View;
+  typedef typename View::type StlContainer;
+  typedef typename View::const_reference StlContainerReference;
+  typedef typename StlContainer::value_type Element;
+
+  // Constructs the matcher from a sequence of element values or
+  // element matchers.
+  template <typename InputIter>
+  ElementsAreMatcherImpl(InputIter first, InputIter last) {
+    while (first != last) {
+      matchers_.push_back(MatcherCast<const Element&>(*first++));
+    }
+  }
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    if (count() == 0) {
+      *os << "is empty";
+    } else if (count() == 1) {
+      *os << "has 1 element that ";
+      matchers_[0].DescribeTo(os);
+    } else {
+      *os << "has " << Elements(count()) << " where\n";
+      for (size_t i = 0; i != count(); ++i) {
+        *os << "element #" << i << " ";
+        matchers_[i].DescribeTo(os);
+        if (i + 1 < count()) {
+          *os << ",\n";
+        }
+      }
+    }
+  }
+
+  // Describes what the negation of this matcher does.
+  void DescribeNegationTo(::std::ostream* os) const override {
+    if (count() == 0) {
+      *os << "isn't empty";
+      return;
+    }
+
+    *os << "doesn't have " << Elements(count()) << ", or\n";
+    for (size_t i = 0; i != count(); ++i) {
+      *os << "element #" << i << " ";
+      matchers_[i].DescribeNegationTo(os);
+      if (i + 1 < count()) {
+        *os << ", or\n";
+      }
+    }
+  }
+
+  bool MatchAndExplain(Container container,
+                       MatchResultListener* listener) const override {
+    // To work with stream-like "containers", we must only walk
+    // through the elements in one pass.
+
+    const bool listener_interested = listener->IsInterested();
+
+    // explanations[i] is the explanation of the element at index i.
+    ::std::vector<std::string> explanations(count());
+    StlContainerReference stl_container = View::ConstReference(container);
+    typename StlContainer::const_iterator it = stl_container.begin();
+    size_t exam_pos = 0;
+    bool mismatch_found = false;  // Have we found a mismatched element yet?
+
+    // Go through the elements and matchers in pairs, until we reach
+    // the end of either the elements or the matchers, or until we find a
+    // mismatch.
+    for (; it != stl_container.end() && exam_pos != count(); ++it, ++exam_pos) {
+      bool match;  // Does the current element match the current matcher?
+      if (listener_interested) {
+        StringMatchResultListener s;
+        match = matchers_[exam_pos].MatchAndExplain(*it, &s);
+        explanations[exam_pos] = s.str();
+      } else {
+        match = matchers_[exam_pos].Matches(*it);
+      }
+
+      if (!match) {
+        mismatch_found = true;
+        break;
+      }
+    }
+    // If mismatch_found is true, 'exam_pos' is the index of the mismatch.
+
+    // Find how many elements the actual container has.  We avoid
+    // calling size() s.t. this code works for stream-like "containers"
+    // that don't define size().
+    size_t actual_count = exam_pos;
+    for (; it != stl_container.end(); ++it) {
+      ++actual_count;
+    }
+
+    if (actual_count != count()) {
+      // The element count doesn't match.  If the container is empty,
+      // there's no need to explain anything as Google Mock already
+      // prints the empty container.  Otherwise we just need to show
+      // how many elements there actually are.
+      if (listener_interested && (actual_count != 0)) {
+        *listener << "which has " << Elements(actual_count);
+      }
+      return false;
+    }
+
+    if (mismatch_found) {
+      // The element count matches, but the exam_pos-th element doesn't match.
+      if (listener_interested) {
+        *listener << "whose element #" << exam_pos << " doesn't match";
+        PrintIfNotEmpty(explanations[exam_pos], listener->stream());
+      }
+      return false;
+    }
+
+    // Every element matches its expectation.  We need to explain why
+    // (the obvious ones can be skipped).
+    if (listener_interested) {
+      bool reason_printed = false;
+      for (size_t i = 0; i != count(); ++i) {
+        const std::string& s = explanations[i];
+        if (!s.empty()) {
+          if (reason_printed) {
+            *listener << ",\nand ";
+          }
+          *listener << "whose element #" << i << " matches, " << s;
+          reason_printed = true;
+        }
+      }
+    }
+    return true;
+  }
+
+ private:
+  static Message Elements(size_t count) {
+    return Message() << count << (count == 1 ? " element" : " elements");
+  }
+
+  size_t count() const { return matchers_.size(); }
+
+  ::std::vector<Matcher<const Element&> > matchers_;
+};
+
+// Connectivity matrix of (elements X matchers), in element-major order.
+// Initially, there are no edges.
+// Use NextGraph() to iterate over all possible edge configurations.
+// Use Randomize() to generate a random edge configuration.
+class GTEST_API_ MatchMatrix {
+ public:
+  MatchMatrix(size_t num_elements, size_t num_matchers)
+      : num_elements_(num_elements),
+        num_matchers_(num_matchers),
+        matched_(num_elements_* num_matchers_, 0) {
+  }
+
+  size_t LhsSize() const { return num_elements_; }
+  size_t RhsSize() const { return num_matchers_; }
+  bool HasEdge(size_t ilhs, size_t irhs) const {
+    return matched_[SpaceIndex(ilhs, irhs)] == 1;
+  }
+  void SetEdge(size_t ilhs, size_t irhs, bool b) {
+    matched_[SpaceIndex(ilhs, irhs)] = b ? 1 : 0;
+  }
+
+  // Treating the connectivity matrix as a (LhsSize()*RhsSize())-bit number,
+  // adds 1 to that number; returns false if incrementing the graph left it
+  // empty.
+  bool NextGraph();
+
+  void Randomize();
+
+  std::string DebugString() const;
+
+ private:
+  size_t SpaceIndex(size_t ilhs, size_t irhs) const {
+    return ilhs * num_matchers_ + irhs;
+  }
+
+  size_t num_elements_;
+  size_t num_matchers_;
+
+  // Each element is a char interpreted as bool. They are stored as a
+  // flattened array in lhs-major order, use 'SpaceIndex()' to translate
+  // a (ilhs, irhs) matrix coordinate into an offset.
+  ::std::vector<char> matched_;
+};
+
+typedef ::std::pair<size_t, size_t> ElementMatcherPair;
+typedef ::std::vector<ElementMatcherPair> ElementMatcherPairs;
+
+// Returns a maximum bipartite matching for the specified graph 'g'.
+// The matching is represented as a vector of {element, matcher} pairs.
+GTEST_API_ ElementMatcherPairs
+FindMaxBipartiteMatching(const MatchMatrix& g);
+
+struct UnorderedMatcherRequire {
+  enum Flags {
+    Superset = 1 << 0,
+    Subset = 1 << 1,
+    ExactMatch = Superset | Subset,
+  };
+};
+
+// Untyped base class for implementing UnorderedElementsAre.  By
+// putting logic that's not specific to the element type here, we
+// reduce binary bloat and increase compilation speed.
+class GTEST_API_ UnorderedElementsAreMatcherImplBase {
+ protected:
+  explicit UnorderedElementsAreMatcherImplBase(
+      UnorderedMatcherRequire::Flags matcher_flags)
+      : match_flags_(matcher_flags) {}
+
+  // A vector of matcher describers, one for each element matcher.
+  // Does not own the describers (and thus can be used only when the
+  // element matchers are alive).
+  typedef ::std::vector<const MatcherDescriberInterface*> MatcherDescriberVec;
+
+  // Describes this UnorderedElementsAre matcher.
+  void DescribeToImpl(::std::ostream* os) const;
+
+  // Describes the negation of this UnorderedElementsAre matcher.
+  void DescribeNegationToImpl(::std::ostream* os) const;
+
+  bool VerifyMatchMatrix(const ::std::vector<std::string>& element_printouts,
+                         const MatchMatrix& matrix,
+                         MatchResultListener* listener) const;
+
+  bool FindPairing(const MatchMatrix& matrix,
+                   MatchResultListener* listener) const;
+
+  MatcherDescriberVec& matcher_describers() {
+    return matcher_describers_;
+  }
+
+  static Message Elements(size_t n) {
+    return Message() << n << " element" << (n == 1 ? "" : "s");
+  }
+
+  UnorderedMatcherRequire::Flags match_flags() const { return match_flags_; }
+
+ private:
+  UnorderedMatcherRequire::Flags match_flags_;
+  MatcherDescriberVec matcher_describers_;
+};
+
+// Implements UnorderedElementsAre, UnorderedElementsAreArray, IsSubsetOf, and
+// IsSupersetOf.
+template <typename Container>
+class UnorderedElementsAreMatcherImpl
+    : public MatcherInterface<Container>,
+      public UnorderedElementsAreMatcherImplBase {
+ public:
+  typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
+  typedef internal::StlContainerView<RawContainer> View;
+  typedef typename View::type StlContainer;
+  typedef typename View::const_reference StlContainerReference;
+  typedef typename StlContainer::const_iterator StlContainerConstIterator;
+  typedef typename StlContainer::value_type Element;
+
+  template <typename InputIter>
+  UnorderedElementsAreMatcherImpl(UnorderedMatcherRequire::Flags matcher_flags,
+                                  InputIter first, InputIter last)
+      : UnorderedElementsAreMatcherImplBase(matcher_flags) {
+    for (; first != last; ++first) {
+      matchers_.push_back(MatcherCast<const Element&>(*first));
+    }
+    for (const auto& m : matchers_) {
+      matcher_describers().push_back(m.GetDescriber());
+    }
+  }
+
+  // Describes what this matcher does.
+  void DescribeTo(::std::ostream* os) const override {
+    return UnorderedElementsAreMatcherImplBase::DescribeToImpl(os);
+  }
+
+  // Describes what the negation of this matcher does.
+  void DescribeNegationTo(::std::ostream* os) const override {
+    return UnorderedElementsAreMatcherImplBase::DescribeNegationToImpl(os);
+  }
+
+  bool MatchAndExplain(Container container,
+                       MatchResultListener* listener) const override {
+    StlContainerReference stl_container = View::ConstReference(container);
+    ::std::vector<std::string> element_printouts;
+    MatchMatrix matrix =
+        AnalyzeElements(stl_container.begin(), stl_container.end(),
+                        &element_printouts, listener);
+
+    if (matrix.LhsSize() == 0 && matrix.RhsSize() == 0) {
+      return true;
+    }
+
+    if (match_flags() == UnorderedMatcherRequire::ExactMatch) {
+      if (matrix.LhsSize() != matrix.RhsSize()) {
+        // The element count doesn't match.  If the container is empty,
+        // there's no need to explain anything as Google Mock already
+        // prints the empty container. Otherwise we just need to show
+        // how many elements there actually are.
+        if (matrix.LhsSize() != 0 && listener->IsInterested()) {
+          *listener << "which has " << Elements(matrix.LhsSize());
+        }
+        return false;
+      }
+    }
+
+    return VerifyMatchMatrix(element_printouts, matrix, listener) &&
+           FindPairing(matrix, listener);
+  }
+
+ private:
+  template <typename ElementIter>
+  MatchMatrix AnalyzeElements(ElementIter elem_first, ElementIter elem_last,
+                              ::std::vector<std::string>* element_printouts,
+                              MatchResultListener* listener) const {
+    element_printouts->clear();
+    ::std::vector<char> did_match;
+    size_t num_elements = 0;
+    DummyMatchResultListener dummy;
+    for (; elem_first != elem_last; ++num_elements, ++elem_first) {
+      if (listener->IsInterested()) {
+        element_printouts->push_back(PrintToString(*elem_first));
+      }
+      for (size_t irhs = 0; irhs != matchers_.size(); ++irhs) {
+        did_match.push_back(
+            matchers_[irhs].MatchAndExplain(*elem_first, &dummy));
+      }
+    }
+
+    MatchMatrix matrix(num_elements, matchers_.size());
+    ::std::vector<char>::const_iterator did_match_iter = did_match.begin();
+    for (size_t ilhs = 0; ilhs != num_elements; ++ilhs) {
+      for (size_t irhs = 0; irhs != matchers_.size(); ++irhs) {
+        matrix.SetEdge(ilhs, irhs, *did_match_iter++ != 0);
+      }
+    }
+    return matrix;
+  }
+
+  ::std::vector<Matcher<const Element&> > matchers_;
+};
+
+// Functor for use in TransformTuple.
+// Performs MatcherCast<Target> on an input argument of any type.
+template <typename Target>
+struct CastAndAppendTransform {
+  template <typename Arg>
+  Matcher<Target> operator()(const Arg& a) const {
+    return MatcherCast<Target>(a);
+  }
+};
+
+// Implements UnorderedElementsAre.
+template <typename MatcherTuple>
+class UnorderedElementsAreMatcher {
+ public:
+  explicit UnorderedElementsAreMatcher(const MatcherTuple& args)
+      : matchers_(args) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
+    typedef typename internal::StlContainerView<RawContainer>::type View;
+    typedef typename View::value_type Element;
+    typedef ::std::vector<Matcher<const Element&> > MatcherVec;
+    MatcherVec matchers;
+    matchers.reserve(::std::tuple_size<MatcherTuple>::value);
+    TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_,
+                         ::std::back_inserter(matchers));
+    return Matcher<Container>(
+        new UnorderedElementsAreMatcherImpl<const Container&>(
+            UnorderedMatcherRequire::ExactMatch, matchers.begin(),
+            matchers.end()));
+  }
+
+ private:
+  const MatcherTuple matchers_;
+};
+
+// Implements ElementsAre.
+template <typename MatcherTuple>
+class ElementsAreMatcher {
+ public:
+  explicit ElementsAreMatcher(const MatcherTuple& args) : matchers_(args) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    GTEST_COMPILE_ASSERT_(
+        !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(Container)>::value ||
+            ::std::tuple_size<MatcherTuple>::value < 2,
+        use_UnorderedElementsAre_with_hash_tables);
+
+    typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Container) RawContainer;
+    typedef typename internal::StlContainerView<RawContainer>::type View;
+    typedef typename View::value_type Element;
+    typedef ::std::vector<Matcher<const Element&> > MatcherVec;
+    MatcherVec matchers;
+    matchers.reserve(::std::tuple_size<MatcherTuple>::value);
+    TransformTupleValues(CastAndAppendTransform<const Element&>(), matchers_,
+                         ::std::back_inserter(matchers));
+    return Matcher<Container>(new ElementsAreMatcherImpl<const Container&>(
+        matchers.begin(), matchers.end()));
+  }
+
+ private:
+  const MatcherTuple matchers_;
+};
+
+// Implements UnorderedElementsAreArray(), IsSubsetOf(), and IsSupersetOf().
+template <typename T>
+class UnorderedElementsAreArrayMatcher {
+ public:
+  template <typename Iter>
+  UnorderedElementsAreArrayMatcher(UnorderedMatcherRequire::Flags match_flags,
+                                   Iter first, Iter last)
+      : match_flags_(match_flags), matchers_(first, last) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    return Matcher<Container>(
+        new UnorderedElementsAreMatcherImpl<const Container&>(
+            match_flags_, matchers_.begin(), matchers_.end()));
+  }
+
+ private:
+  UnorderedMatcherRequire::Flags match_flags_;
+  ::std::vector<T> matchers_;
+};
+
+// Implements ElementsAreArray().
+template <typename T>
+class ElementsAreArrayMatcher {
+ public:
+  template <typename Iter>
+  ElementsAreArrayMatcher(Iter first, Iter last) : matchers_(first, last) {}
+
+  template <typename Container>
+  operator Matcher<Container>() const {
+    GTEST_COMPILE_ASSERT_(
+        !IsHashTable<GTEST_REMOVE_REFERENCE_AND_CONST_(Container)>::value,
+        use_UnorderedElementsAreArray_with_hash_tables);
+
+    return Matcher<Container>(new ElementsAreMatcherImpl<const Container&>(
+        matchers_.begin(), matchers_.end()));
+  }
+
+ private:
+  const ::std::vector<T> matchers_;
+};
+
+// Given a 2-tuple matcher tm of type Tuple2Matcher and a value second
+// of type Second, BoundSecondMatcher<Tuple2Matcher, Second>(tm,
+// second) is a polymorphic matcher that matches a value x if and only if
+// tm matches tuple (x, second).  Useful for implementing
+// UnorderedPointwise() in terms of UnorderedElementsAreArray().
+//
+// BoundSecondMatcher is copyable and assignable, as we need to put
+// instances of this class in a vector when implementing
+// UnorderedPointwise().
+template <typename Tuple2Matcher, typename Second>
+class BoundSecondMatcher {
+ public:
+  BoundSecondMatcher(const Tuple2Matcher& tm, const Second& second)
+      : tuple2_matcher_(tm), second_value_(second) {}
+
+  BoundSecondMatcher(const BoundSecondMatcher& other) = default;
+
+  template <typename T>
+  operator Matcher<T>() const {
+    return MakeMatcher(new Impl<T>(tuple2_matcher_, second_value_));
+  }
+
+  // We have to define this for UnorderedPointwise() to compile in
+  // C++98 mode, as it puts BoundSecondMatcher instances in a vector,
+  // which requires the elements to be assignable in C++98.  The
+  // compiler cannot generate the operator= for us, as Tuple2Matcher
+  // and Second may not be assignable.
+  //
+  // However, this should never be called, so the implementation just
+  // need to assert.
+  void operator=(const BoundSecondMatcher& /*rhs*/) {
+    GTEST_LOG_(FATAL) << "BoundSecondMatcher should never be assigned.";
+  }
+
+ private:
+  template <typename T>
+  class Impl : public MatcherInterface<T> {
+   public:
+    typedef ::std::tuple<T, Second> ArgTuple;
+
+    Impl(const Tuple2Matcher& tm, const Second& second)
+        : mono_tuple2_matcher_(SafeMatcherCast<const ArgTuple&>(tm)),
+          second_value_(second) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "and ";
+      UniversalPrint(second_value_, os);
+      *os << " ";
+      mono_tuple2_matcher_.DescribeTo(os);
+    }
+
+    bool MatchAndExplain(T x, MatchResultListener* listener) const override {
+      return mono_tuple2_matcher_.MatchAndExplain(ArgTuple(x, second_value_),
+                                                  listener);
+    }
+
+   private:
+    const Matcher<const ArgTuple&> mono_tuple2_matcher_;
+    const Second second_value_;
+  };
+
+  const Tuple2Matcher tuple2_matcher_;
+  const Second second_value_;
+};
+
+// Given a 2-tuple matcher tm and a value second,
+// MatcherBindSecond(tm, second) returns a matcher that matches a
+// value x if and only if tm matches tuple (x, second).  Useful for
+// implementing UnorderedPointwise() in terms of UnorderedElementsAreArray().
+template <typename Tuple2Matcher, typename Second>
+BoundSecondMatcher<Tuple2Matcher, Second> MatcherBindSecond(
+    const Tuple2Matcher& tm, const Second& second) {
+  return BoundSecondMatcher<Tuple2Matcher, Second>(tm, second);
+}
+
+// Returns the description for a matcher defined using the MATCHER*()
+// macro where the user-supplied description string is "", if
+// 'negation' is false; otherwise returns the description of the
+// negation of the matcher.  'param_values' contains a list of strings
+// that are the print-out of the matcher's parameters.
+GTEST_API_ std::string FormatMatcherDescription(bool negation,
+                                                const char* matcher_name,
+                                                const Strings& param_values);
+
+// Implements a matcher that checks the value of a optional<> type variable.
+template <typename ValueMatcher>
+class OptionalMatcher {
+ public:
+  explicit OptionalMatcher(const ValueMatcher& value_matcher)
+      : value_matcher_(value_matcher) {}
+
+  template <typename Optional>
+  operator Matcher<Optional>() const {
+    return Matcher<Optional>(new Impl<const Optional&>(value_matcher_));
+  }
+
+  template <typename Optional>
+  class Impl : public MatcherInterface<Optional> {
+   public:
+    typedef GTEST_REMOVE_REFERENCE_AND_CONST_(Optional) OptionalView;
+    typedef typename OptionalView::value_type ValueType;
+    explicit Impl(const ValueMatcher& value_matcher)
+        : value_matcher_(MatcherCast<ValueType>(value_matcher)) {}
+
+    void DescribeTo(::std::ostream* os) const override {
+      *os << "value ";
+      value_matcher_.DescribeTo(os);
+    }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      *os << "value ";
+      value_matcher_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(Optional optional,
+                         MatchResultListener* listener) const override {
+      if (!optional) {
+        *listener << "which is not engaged";
+        return false;
+      }
+      const ValueType& value = *optional;
+      StringMatchResultListener value_listener;
+      const bool match = value_matcher_.MatchAndExplain(value, &value_listener);
+      *listener << "whose value " << PrintToString(value)
+                << (match ? " matches" : " doesn't match");
+      PrintIfNotEmpty(value_listener.str(), listener->stream());
+      return match;
+    }
+
+   private:
+    const Matcher<ValueType> value_matcher_;
+  };
+
+ private:
+  const ValueMatcher value_matcher_;
+};
+
+namespace variant_matcher {
+// Overloads to allow VariantMatcher to do proper ADL lookup.
+template <typename T>
+void holds_alternative() {}
+template <typename T>
+void get() {}
+
+// Implements a matcher that checks the value of a variant<> type variable.
+template <typename T>
+class VariantMatcher {
+ public:
+  explicit VariantMatcher(::testing::Matcher<const T&> matcher)
+      : matcher_(std::move(matcher)) {}
+
+  template <typename Variant>
+  bool MatchAndExplain(const Variant& value,
+                       ::testing::MatchResultListener* listener) const {
+    using std::get;
+    if (!listener->IsInterested()) {
+      return holds_alternative<T>(value) && matcher_.Matches(get<T>(value));
+    }
+
+    if (!holds_alternative<T>(value)) {
+      *listener << "whose value is not of type '" << GetTypeName() << "'";
+      return false;
+    }
+
+    const T& elem = get<T>(value);
+    StringMatchResultListener elem_listener;
+    const bool match = matcher_.MatchAndExplain(elem, &elem_listener);
+    *listener << "whose value " << PrintToString(elem)
+              << (match ? " matches" : " doesn't match");
+    PrintIfNotEmpty(elem_listener.str(), listener->stream());
+    return match;
+  }
+
+  void DescribeTo(std::ostream* os) const {
+    *os << "is a variant<> with value of type '" << GetTypeName()
+        << "' and the value ";
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << "is a variant<> with value of type other than '" << GetTypeName()
+        << "' or the value ";
+    matcher_.DescribeNegationTo(os);
+  }
+
+ private:
+  static std::string GetTypeName() {
+#if GTEST_HAS_RTTI
+    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(
+        return internal::GetTypeName<T>());
+#endif
+    return "the element type";
+  }
+
+  const ::testing::Matcher<const T&> matcher_;
+};
+
+}  // namespace variant_matcher
+
+namespace any_cast_matcher {
+
+// Overloads to allow AnyCastMatcher to do proper ADL lookup.
+template <typename T>
+void any_cast() {}
+
+// Implements a matcher that any_casts the value.
+template <typename T>
+class AnyCastMatcher {
+ public:
+  explicit AnyCastMatcher(const ::testing::Matcher<const T&>& matcher)
+      : matcher_(matcher) {}
+
+  template <typename AnyType>
+  bool MatchAndExplain(const AnyType& value,
+                       ::testing::MatchResultListener* listener) const {
+    if (!listener->IsInterested()) {
+      const T* ptr = any_cast<T>(&value);
+      return ptr != nullptr && matcher_.Matches(*ptr);
+    }
+
+    const T* elem = any_cast<T>(&value);
+    if (elem == nullptr) {
+      *listener << "whose value is not of type '" << GetTypeName() << "'";
+      return false;
+    }
+
+    StringMatchResultListener elem_listener;
+    const bool match = matcher_.MatchAndExplain(*elem, &elem_listener);
+    *listener << "whose value " << PrintToString(*elem)
+              << (match ? " matches" : " doesn't match");
+    PrintIfNotEmpty(elem_listener.str(), listener->stream());
+    return match;
+  }
+
+  void DescribeTo(std::ostream* os) const {
+    *os << "is an 'any' type with value of type '" << GetTypeName()
+        << "' and the value ";
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << "is an 'any' type with value of type other than '" << GetTypeName()
+        << "' or the value ";
+    matcher_.DescribeNegationTo(os);
+  }
+
+ private:
+  static std::string GetTypeName() {
+#if GTEST_HAS_RTTI
+    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(
+        return internal::GetTypeName<T>());
+#endif
+    return "the element type";
+  }
+
+  const ::testing::Matcher<const T&> matcher_;
+};
+
+}  // namespace any_cast_matcher
+
+// Implements the Args() matcher.
+template <class ArgsTuple, size_t... k>
+class ArgsMatcherImpl : public MatcherInterface<ArgsTuple> {
+ public:
+  using RawArgsTuple = typename std::decay<ArgsTuple>::type;
+  using SelectedArgs =
+      std::tuple<typename std::tuple_element<k, RawArgsTuple>::type...>;
+  using MonomorphicInnerMatcher = Matcher<const SelectedArgs&>;
+
+  template <typename InnerMatcher>
+  explicit ArgsMatcherImpl(const InnerMatcher& inner_matcher)
+      : inner_matcher_(SafeMatcherCast<const SelectedArgs&>(inner_matcher)) {}
+
+  bool MatchAndExplain(ArgsTuple args,
+                       MatchResultListener* listener) const override {
+    // Workaround spurious C4100 on MSVC<=15.7 when k is empty.
+    (void)args;
+    const SelectedArgs& selected_args =
+        std::forward_as_tuple(std::get<k>(args)...);
+    if (!listener->IsInterested()) return inner_matcher_.Matches(selected_args);
+
+    PrintIndices(listener->stream());
+    *listener << "are " << PrintToString(selected_args);
+
+    StringMatchResultListener inner_listener;
+    const bool match =
+        inner_matcher_.MatchAndExplain(selected_args, &inner_listener);
+    PrintIfNotEmpty(inner_listener.str(), listener->stream());
+    return match;
+  }
+
+  void DescribeTo(::std::ostream* os) const override {
+    *os << "are a tuple ";
+    PrintIndices(os);
+    inner_matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const override {
+    *os << "are a tuple ";
+    PrintIndices(os);
+    inner_matcher_.DescribeNegationTo(os);
+  }
+
+ private:
+  // Prints the indices of the selected fields.
+  static void PrintIndices(::std::ostream* os) {
+    *os << "whose fields (";
+    const char* sep = "";
+    // Workaround spurious C4189 on MSVC<=15.7 when k is empty.
+    (void)sep;
+    const char* dummy[] = {"", (*os << sep << "#" << k, sep = ", ")...};
+    (void)dummy;
+    *os << ") ";
+  }
+
+  MonomorphicInnerMatcher inner_matcher_;
+};
+
+template <class InnerMatcher, size_t... k>
+class ArgsMatcher {
+ public:
+  explicit ArgsMatcher(InnerMatcher inner_matcher)
+      : inner_matcher_(std::move(inner_matcher)) {}
+
+  template <typename ArgsTuple>
+  operator Matcher<ArgsTuple>() const {  // NOLINT
+    return MakeMatcher(new ArgsMatcherImpl<ArgsTuple, k...>(inner_matcher_));
+  }
+
+ private:
+  InnerMatcher inner_matcher_;
+};
+
+}  // namespace internal
+
+// ElementsAreArray(iterator_first, iterator_last)
+// ElementsAreArray(pointer, count)
+// ElementsAreArray(array)
+// ElementsAreArray(container)
+// ElementsAreArray({ e1, e2, ..., en })
+//
+// The ElementsAreArray() functions are like ElementsAre(...), except
+// that they are given a homogeneous sequence rather than taking each
+// element as a function argument. The sequence can be specified as an
+// array, a pointer and count, a vector, an initializer list, or an
+// STL iterator range. In each of these cases, the underlying sequence
+// can be either a sequence of values or a sequence of matchers.
+//
+// All forms of ElementsAreArray() make a copy of the input matcher sequence.
+
+template <typename Iter>
+inline internal::ElementsAreArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+ElementsAreArray(Iter first, Iter last) {
+  typedef typename ::std::iterator_traits<Iter>::value_type T;
+  return internal::ElementsAreArrayMatcher<T>(first, last);
+}
+
+template <typename T>
+inline internal::ElementsAreArrayMatcher<T> ElementsAreArray(
+    const T* pointer, size_t count) {
+  return ElementsAreArray(pointer, pointer + count);
+}
+
+template <typename T, size_t N>
+inline internal::ElementsAreArrayMatcher<T> ElementsAreArray(
+    const T (&array)[N]) {
+  return ElementsAreArray(array, N);
+}
+
+template <typename Container>
+inline internal::ElementsAreArrayMatcher<typename Container::value_type>
+ElementsAreArray(const Container& container) {
+  return ElementsAreArray(container.begin(), container.end());
+}
+
+template <typename T>
+inline internal::ElementsAreArrayMatcher<T>
+ElementsAreArray(::std::initializer_list<T> xs) {
+  return ElementsAreArray(xs.begin(), xs.end());
+}
+
+// UnorderedElementsAreArray(iterator_first, iterator_last)
+// UnorderedElementsAreArray(pointer, count)
+// UnorderedElementsAreArray(array)
+// UnorderedElementsAreArray(container)
+// UnorderedElementsAreArray({ e1, e2, ..., en })
+//
+// UnorderedElementsAreArray() verifies that a bijective mapping onto a
+// collection of matchers exists.
+//
+// The matchers can be specified as an array, a pointer and count, a container,
+// an initializer list, or an STL iterator range. In each of these cases, the
+// underlying matchers can be either values or matchers.
+
+template <typename Iter>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+UnorderedElementsAreArray(Iter first, Iter last) {
+  typedef typename ::std::iterator_traits<Iter>::value_type T;
+  return internal::UnorderedElementsAreArrayMatcher<T>(
+      internal::UnorderedMatcherRequire::ExactMatch, first, last);
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T>
+UnorderedElementsAreArray(const T* pointer, size_t count) {
+  return UnorderedElementsAreArray(pointer, pointer + count);
+}
+
+template <typename T, size_t N>
+inline internal::UnorderedElementsAreArrayMatcher<T>
+UnorderedElementsAreArray(const T (&array)[N]) {
+  return UnorderedElementsAreArray(array, N);
+}
+
+template <typename Container>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename Container::value_type>
+UnorderedElementsAreArray(const Container& container) {
+  return UnorderedElementsAreArray(container.begin(), container.end());
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T>
+UnorderedElementsAreArray(::std::initializer_list<T> xs) {
+  return UnorderedElementsAreArray(xs.begin(), xs.end());
+}
+
+// _ is a matcher that matches anything of any type.
+//
+// This definition is fine as:
+//
+//   1. The C++ standard permits using the name _ in a namespace that
+//      is not the global namespace or ::std.
+//   2. The AnythingMatcher class has no data member or constructor,
+//      so it's OK to create global variables of this type.
+//   3. c-style has approved of using _ in this case.
+const internal::AnythingMatcher _ = {};
+// Creates a matcher that matches any value of the given type T.
+template <typename T>
+inline Matcher<T> A() {
+  return _;
+}
+
+// Creates a matcher that matches any value of the given type T.
+template <typename T>
+inline Matcher<T> An() {
+  return _;
+}
+
+template <typename T, typename M>
+Matcher<T> internal::MatcherCastImpl<T, M>::CastImpl(
+    const M& value, std::false_type /* convertible_to_matcher */,
+    std::false_type /* convertible_to_T */) {
+  return Eq(value);
+}
+
+// Creates a polymorphic matcher that matches any NULL pointer.
+inline PolymorphicMatcher<internal::IsNullMatcher > IsNull() {
+  return MakePolymorphicMatcher(internal::IsNullMatcher());
+}
+
+// Creates a polymorphic matcher that matches any non-NULL pointer.
+// This is convenient as Not(NULL) doesn't compile (the compiler
+// thinks that that expression is comparing a pointer with an integer).
+inline PolymorphicMatcher<internal::NotNullMatcher > NotNull() {
+  return MakePolymorphicMatcher(internal::NotNullMatcher());
+}
+
+// Creates a polymorphic matcher that matches any argument that
+// references variable x.
+template <typename T>
+inline internal::RefMatcher<T&> Ref(T& x) {  // NOLINT
+  return internal::RefMatcher<T&>(x);
+}
+
+// Creates a polymorphic matcher that matches any NaN floating point.
+inline PolymorphicMatcher<internal::IsNanMatcher> IsNan() {
+  return MakePolymorphicMatcher(internal::IsNanMatcher());
+}
+
+// Creates a matcher that matches any double argument approximately
+// equal to rhs, where two NANs are considered unequal.
+inline internal::FloatingEqMatcher<double> DoubleEq(double rhs) {
+  return internal::FloatingEqMatcher<double>(rhs, false);
+}
+
+// Creates a matcher that matches any double argument approximately
+// equal to rhs, including NaN values when rhs is NaN.
+inline internal::FloatingEqMatcher<double> NanSensitiveDoubleEq(double rhs) {
+  return internal::FloatingEqMatcher<double>(rhs, true);
+}
+
+// Creates a matcher that matches any double argument approximately equal to
+// rhs, up to the specified max absolute error bound, where two NANs are
+// considered unequal.  The max absolute error bound must be non-negative.
+inline internal::FloatingEqMatcher<double> DoubleNear(
+    double rhs, double max_abs_error) {
+  return internal::FloatingEqMatcher<double>(rhs, false, max_abs_error);
+}
+
+// Creates a matcher that matches any double argument approximately equal to
+// rhs, up to the specified max absolute error bound, including NaN values when
+// rhs is NaN.  The max absolute error bound must be non-negative.
+inline internal::FloatingEqMatcher<double> NanSensitiveDoubleNear(
+    double rhs, double max_abs_error) {
+  return internal::FloatingEqMatcher<double>(rhs, true, max_abs_error);
+}
+
+// Creates a matcher that matches any float argument approximately
+// equal to rhs, where two NANs are considered unequal.
+inline internal::FloatingEqMatcher<float> FloatEq(float rhs) {
+  return internal::FloatingEqMatcher<float>(rhs, false);
+}
+
+// Creates a matcher that matches any float argument approximately
+// equal to rhs, including NaN values when rhs is NaN.
+inline internal::FloatingEqMatcher<float> NanSensitiveFloatEq(float rhs) {
+  return internal::FloatingEqMatcher<float>(rhs, true);
+}
+
+// Creates a matcher that matches any float argument approximately equal to
+// rhs, up to the specified max absolute error bound, where two NANs are
+// considered unequal.  The max absolute error bound must be non-negative.
+inline internal::FloatingEqMatcher<float> FloatNear(
+    float rhs, float max_abs_error) {
+  return internal::FloatingEqMatcher<float>(rhs, false, max_abs_error);
+}
+
+// Creates a matcher that matches any float argument approximately equal to
+// rhs, up to the specified max absolute error bound, including NaN values when
+// rhs is NaN.  The max absolute error bound must be non-negative.
+inline internal::FloatingEqMatcher<float> NanSensitiveFloatNear(
+    float rhs, float max_abs_error) {
+  return internal::FloatingEqMatcher<float>(rhs, true, max_abs_error);
+}
+
+// Creates a matcher that matches a pointer (raw or smart) that points
+// to a value that matches inner_matcher.
+template <typename InnerMatcher>
+inline internal::PointeeMatcher<InnerMatcher> Pointee(
+    const InnerMatcher& inner_matcher) {
+  return internal::PointeeMatcher<InnerMatcher>(inner_matcher);
+}
+
+#if GTEST_HAS_RTTI
+// Creates a matcher that matches a pointer or reference that matches
+// inner_matcher when dynamic_cast<To> is applied.
+// The result of dynamic_cast<To> is forwarded to the inner matcher.
+// If To is a pointer and the cast fails, the inner matcher will receive NULL.
+// If To is a reference and the cast fails, this matcher returns false
+// immediately.
+template <typename To>
+inline PolymorphicMatcher<internal::WhenDynamicCastToMatcher<To> >
+WhenDynamicCastTo(const Matcher<To>& inner_matcher) {
+  return MakePolymorphicMatcher(
+      internal::WhenDynamicCastToMatcher<To>(inner_matcher));
+}
+#endif  // GTEST_HAS_RTTI
+
+// Creates a matcher that matches an object whose given field matches
+// 'matcher'.  For example,
+//   Field(&Foo::number, Ge(5))
+// matches a Foo object x if and only if x.number >= 5.
+template <typename Class, typename FieldType, typename FieldMatcher>
+inline PolymorphicMatcher<
+  internal::FieldMatcher<Class, FieldType> > Field(
+    FieldType Class::*field, const FieldMatcher& matcher) {
+  return MakePolymorphicMatcher(
+      internal::FieldMatcher<Class, FieldType>(
+          field, MatcherCast<const FieldType&>(matcher)));
+  // The call to MatcherCast() is required for supporting inner
+  // matchers of compatible types.  For example, it allows
+  //   Field(&Foo::bar, m)
+  // to compile where bar is an int32 and m is a matcher for int64.
+}
+
+// Same as Field() but also takes the name of the field to provide better error
+// messages.
+template <typename Class, typename FieldType, typename FieldMatcher>
+inline PolymorphicMatcher<internal::FieldMatcher<Class, FieldType> > Field(
+    const std::string& field_name, FieldType Class::*field,
+    const FieldMatcher& matcher) {
+  return MakePolymorphicMatcher(internal::FieldMatcher<Class, FieldType>(
+      field_name, field, MatcherCast<const FieldType&>(matcher)));
+}
+
+// Creates a matcher that matches an object whose given property
+// matches 'matcher'.  For example,
+//   Property(&Foo::str, StartsWith("hi"))
+// matches a Foo object x if and only if x.str() starts with "hi".
+template <typename Class, typename PropertyType, typename PropertyMatcher>
+inline PolymorphicMatcher<internal::PropertyMatcher<
+    Class, PropertyType, PropertyType (Class::*)() const> >
+Property(PropertyType (Class::*property)() const,
+         const PropertyMatcher& matcher) {
+  return MakePolymorphicMatcher(
+      internal::PropertyMatcher<Class, PropertyType,
+                                PropertyType (Class::*)() const>(
+          property, MatcherCast<const PropertyType&>(matcher)));
+  // The call to MatcherCast() is required for supporting inner
+  // matchers of compatible types.  For example, it allows
+  //   Property(&Foo::bar, m)
+  // to compile where bar() returns an int32 and m is a matcher for int64.
+}
+
+// Same as Property() above, but also takes the name of the property to provide
+// better error messages.
+template <typename Class, typename PropertyType, typename PropertyMatcher>
+inline PolymorphicMatcher<internal::PropertyMatcher<
+    Class, PropertyType, PropertyType (Class::*)() const> >
+Property(const std::string& property_name,
+         PropertyType (Class::*property)() const,
+         const PropertyMatcher& matcher) {
+  return MakePolymorphicMatcher(
+      internal::PropertyMatcher<Class, PropertyType,
+                                PropertyType (Class::*)() const>(
+          property_name, property, MatcherCast<const PropertyType&>(matcher)));
+}
+
+// The same as above but for reference-qualified member functions.
+template <typename Class, typename PropertyType, typename PropertyMatcher>
+inline PolymorphicMatcher<internal::PropertyMatcher<
+    Class, PropertyType, PropertyType (Class::*)() const &> >
+Property(PropertyType (Class::*property)() const &,
+         const PropertyMatcher& matcher) {
+  return MakePolymorphicMatcher(
+      internal::PropertyMatcher<Class, PropertyType,
+                                PropertyType (Class::*)() const&>(
+          property, MatcherCast<const PropertyType&>(matcher)));
+}
+
+// Three-argument form for reference-qualified member functions.
+template <typename Class, typename PropertyType, typename PropertyMatcher>
+inline PolymorphicMatcher<internal::PropertyMatcher<
+    Class, PropertyType, PropertyType (Class::*)() const &> >
+Property(const std::string& property_name,
+         PropertyType (Class::*property)() const &,
+         const PropertyMatcher& matcher) {
+  return MakePolymorphicMatcher(
+      internal::PropertyMatcher<Class, PropertyType,
+                                PropertyType (Class::*)() const&>(
+          property_name, property, MatcherCast<const PropertyType&>(matcher)));
+}
+
+// Creates a matcher that matches an object if and only if the result of
+// applying a callable to x matches 'matcher'. For example,
+//   ResultOf(f, StartsWith("hi"))
+// matches a Foo object x if and only if f(x) starts with "hi".
+// `callable` parameter can be a function, function pointer, or a functor. It is
+// required to keep no state affecting the results of the calls on it and make
+// no assumptions about how many calls will be made. Any state it keeps must be
+// protected from the concurrent access.
+template <typename Callable, typename InnerMatcher>
+internal::ResultOfMatcher<Callable, InnerMatcher> ResultOf(
+    Callable callable, InnerMatcher matcher) {
+  return internal::ResultOfMatcher<Callable, InnerMatcher>(
+      std::move(callable), std::move(matcher));
+}
+
+// String matchers.
+
+// Matches a string equal to str.
+template <typename T = std::string>
+PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrEq(
+    const internal::StringLike<T>& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::string>(std::string(str), true, true));
+}
+
+// Matches a string not equal to str.
+template <typename T = std::string>
+PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrNe(
+    const internal::StringLike<T>& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::string>(std::string(str), false, true));
+}
+
+// Matches a string equal to str, ignoring case.
+template <typename T = std::string>
+PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrCaseEq(
+    const internal::StringLike<T>& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::string>(std::string(str), true, false));
+}
+
+// Matches a string not equal to str, ignoring case.
+template <typename T = std::string>
+PolymorphicMatcher<internal::StrEqualityMatcher<std::string> > StrCaseNe(
+    const internal::StringLike<T>& str) {
+  return MakePolymorphicMatcher(internal::StrEqualityMatcher<std::string>(
+      std::string(str), false, false));
+}
+
+// Creates a matcher that matches any string, std::string, or C string
+// that contains the given substring.
+template <typename T = std::string>
+PolymorphicMatcher<internal::HasSubstrMatcher<std::string> > HasSubstr(
+    const internal::StringLike<T>& substring) {
+  return MakePolymorphicMatcher(
+      internal::HasSubstrMatcher<std::string>(std::string(substring)));
+}
+
+// Matches a string that starts with 'prefix' (case-sensitive).
+template <typename T = std::string>
+PolymorphicMatcher<internal::StartsWithMatcher<std::string> > StartsWith(
+    const internal::StringLike<T>& prefix) {
+  return MakePolymorphicMatcher(
+      internal::StartsWithMatcher<std::string>(std::string(prefix)));
+}
+
+// Matches a string that ends with 'suffix' (case-sensitive).
+template <typename T = std::string>
+PolymorphicMatcher<internal::EndsWithMatcher<std::string> > EndsWith(
+    const internal::StringLike<T>& suffix) {
+  return MakePolymorphicMatcher(
+      internal::EndsWithMatcher<std::string>(std::string(suffix)));
+}
+
+#if GTEST_HAS_STD_WSTRING
+// Wide string matchers.
+
+// Matches a string equal to str.
+inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > StrEq(
+    const std::wstring& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::wstring>(str, true, true));
+}
+
+// Matches a string not equal to str.
+inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> > StrNe(
+    const std::wstring& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::wstring>(str, false, true));
+}
+
+// Matches a string equal to str, ignoring case.
+inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> >
+StrCaseEq(const std::wstring& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::wstring>(str, true, false));
+}
+
+// Matches a string not equal to str, ignoring case.
+inline PolymorphicMatcher<internal::StrEqualityMatcher<std::wstring> >
+StrCaseNe(const std::wstring& str) {
+  return MakePolymorphicMatcher(
+      internal::StrEqualityMatcher<std::wstring>(str, false, false));
+}
+
+// Creates a matcher that matches any ::wstring, std::wstring, or C wide string
+// that contains the given substring.
+inline PolymorphicMatcher<internal::HasSubstrMatcher<std::wstring> > HasSubstr(
+    const std::wstring& substring) {
+  return MakePolymorphicMatcher(
+      internal::HasSubstrMatcher<std::wstring>(substring));
+}
+
+// Matches a string that starts with 'prefix' (case-sensitive).
+inline PolymorphicMatcher<internal::StartsWithMatcher<std::wstring> >
+StartsWith(const std::wstring& prefix) {
+  return MakePolymorphicMatcher(
+      internal::StartsWithMatcher<std::wstring>(prefix));
+}
+
+// Matches a string that ends with 'suffix' (case-sensitive).
+inline PolymorphicMatcher<internal::EndsWithMatcher<std::wstring> > EndsWith(
+    const std::wstring& suffix) {
+  return MakePolymorphicMatcher(
+      internal::EndsWithMatcher<std::wstring>(suffix));
+}
+
+#endif  // GTEST_HAS_STD_WSTRING
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field == the second field.
+inline internal::Eq2Matcher Eq() { return internal::Eq2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field >= the second field.
+inline internal::Ge2Matcher Ge() { return internal::Ge2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field > the second field.
+inline internal::Gt2Matcher Gt() { return internal::Gt2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field <= the second field.
+inline internal::Le2Matcher Le() { return internal::Le2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field < the second field.
+inline internal::Lt2Matcher Lt() { return internal::Lt2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where the
+// first field != the second field.
+inline internal::Ne2Matcher Ne() { return internal::Ne2Matcher(); }
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// FloatEq(first field) matches the second field.
+inline internal::FloatingEq2Matcher<float> FloatEq() {
+  return internal::FloatingEq2Matcher<float>();
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// DoubleEq(first field) matches the second field.
+inline internal::FloatingEq2Matcher<double> DoubleEq() {
+  return internal::FloatingEq2Matcher<double>();
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// FloatEq(first field) matches the second field with NaN equality.
+inline internal::FloatingEq2Matcher<float> NanSensitiveFloatEq() {
+  return internal::FloatingEq2Matcher<float>(true);
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// DoubleEq(first field) matches the second field with NaN equality.
+inline internal::FloatingEq2Matcher<double> NanSensitiveDoubleEq() {
+  return internal::FloatingEq2Matcher<double>(true);
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// FloatNear(first field, max_abs_error) matches the second field.
+inline internal::FloatingEq2Matcher<float> FloatNear(float max_abs_error) {
+  return internal::FloatingEq2Matcher<float>(max_abs_error);
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// DoubleNear(first field, max_abs_error) matches the second field.
+inline internal::FloatingEq2Matcher<double> DoubleNear(double max_abs_error) {
+  return internal::FloatingEq2Matcher<double>(max_abs_error);
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// FloatNear(first field, max_abs_error) matches the second field with NaN
+// equality.
+inline internal::FloatingEq2Matcher<float> NanSensitiveFloatNear(
+    float max_abs_error) {
+  return internal::FloatingEq2Matcher<float>(max_abs_error, true);
+}
+
+// Creates a polymorphic matcher that matches a 2-tuple where
+// DoubleNear(first field, max_abs_error) matches the second field with NaN
+// equality.
+inline internal::FloatingEq2Matcher<double> NanSensitiveDoubleNear(
+    double max_abs_error) {
+  return internal::FloatingEq2Matcher<double>(max_abs_error, true);
+}
+
+// Creates a matcher that matches any value of type T that m doesn't
+// match.
+template <typename InnerMatcher>
+inline internal::NotMatcher<InnerMatcher> Not(InnerMatcher m) {
+  return internal::NotMatcher<InnerMatcher>(m);
+}
+
+// Returns a matcher that matches anything that satisfies the given
+// predicate.  The predicate can be any unary function or functor
+// whose return type can be implicitly converted to bool.
+template <typename Predicate>
+inline PolymorphicMatcher<internal::TrulyMatcher<Predicate> >
+Truly(Predicate pred) {
+  return MakePolymorphicMatcher(internal::TrulyMatcher<Predicate>(pred));
+}
+
+// Returns a matcher that matches the container size. The container must
+// support both size() and size_type which all STL-like containers provide.
+// Note that the parameter 'size' can be a value of type size_type as well as
+// matcher. For instance:
+//   EXPECT_THAT(container, SizeIs(2));     // Checks container has 2 elements.
+//   EXPECT_THAT(container, SizeIs(Le(2));  // Checks container has at most 2.
+template <typename SizeMatcher>
+inline internal::SizeIsMatcher<SizeMatcher>
+SizeIs(const SizeMatcher& size_matcher) {
+  return internal::SizeIsMatcher<SizeMatcher>(size_matcher);
+}
+
+// Returns a matcher that matches the distance between the container's begin()
+// iterator and its end() iterator, i.e. the size of the container. This matcher
+// can be used instead of SizeIs with containers such as std::forward_list which
+// do not implement size(). The container must provide const_iterator (with
+// valid iterator_traits), begin() and end().
+template <typename DistanceMatcher>
+inline internal::BeginEndDistanceIsMatcher<DistanceMatcher>
+BeginEndDistanceIs(const DistanceMatcher& distance_matcher) {
+  return internal::BeginEndDistanceIsMatcher<DistanceMatcher>(distance_matcher);
+}
+
+// Returns a matcher that matches an equal container.
+// This matcher behaves like Eq(), but in the event of mismatch lists the
+// values that are included in one container but not the other. (Duplicate
+// values and order differences are not explained.)
+template <typename Container>
+inline PolymorphicMatcher<internal::ContainerEqMatcher<
+    typename std::remove_const<Container>::type>>
+ContainerEq(const Container& rhs) {
+  return MakePolymorphicMatcher(internal::ContainerEqMatcher<Container>(rhs));
+}
+
+// Returns a matcher that matches a container that, when sorted using
+// the given comparator, matches container_matcher.
+template <typename Comparator, typename ContainerMatcher>
+inline internal::WhenSortedByMatcher<Comparator, ContainerMatcher>
+WhenSortedBy(const Comparator& comparator,
+             const ContainerMatcher& container_matcher) {
+  return internal::WhenSortedByMatcher<Comparator, ContainerMatcher>(
+      comparator, container_matcher);
+}
+
+// Returns a matcher that matches a container that, when sorted using
+// the < operator, matches container_matcher.
+template <typename ContainerMatcher>
+inline internal::WhenSortedByMatcher<internal::LessComparator, ContainerMatcher>
+WhenSorted(const ContainerMatcher& container_matcher) {
+  return
+      internal::WhenSortedByMatcher<internal::LessComparator, ContainerMatcher>(
+          internal::LessComparator(), container_matcher);
+}
+
+// Matches an STL-style container or a native array that contains the
+// same number of elements as in rhs, where its i-th element and rhs's
+// i-th element (as a pair) satisfy the given pair matcher, for all i.
+// TupleMatcher must be able to be safely cast to Matcher<std::tuple<const
+// T1&, const T2&> >, where T1 and T2 are the types of elements in the
+// LHS container and the RHS container respectively.
+template <typename TupleMatcher, typename Container>
+inline internal::PointwiseMatcher<TupleMatcher,
+                                  typename std::remove_const<Container>::type>
+Pointwise(const TupleMatcher& tuple_matcher, const Container& rhs) {
+  return internal::PointwiseMatcher<TupleMatcher, Container>(tuple_matcher,
+                                                             rhs);
+}
+
+
+// Supports the Pointwise(m, {a, b, c}) syntax.
+template <typename TupleMatcher, typename T>
+inline internal::PointwiseMatcher<TupleMatcher, std::vector<T> > Pointwise(
+    const TupleMatcher& tuple_matcher, std::initializer_list<T> rhs) {
+  return Pointwise(tuple_matcher, std::vector<T>(rhs));
+}
+
+
+// UnorderedPointwise(pair_matcher, rhs) matches an STL-style
+// container or a native array that contains the same number of
+// elements as in rhs, where in some permutation of the container, its
+// i-th element and rhs's i-th element (as a pair) satisfy the given
+// pair matcher, for all i.  Tuple2Matcher must be able to be safely
+// cast to Matcher<std::tuple<const T1&, const T2&> >, where T1 and T2 are
+// the types of elements in the LHS container and the RHS container
+// respectively.
+//
+// This is like Pointwise(pair_matcher, rhs), except that the element
+// order doesn't matter.
+template <typename Tuple2Matcher, typename RhsContainer>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename internal::BoundSecondMatcher<
+        Tuple2Matcher,
+        typename internal::StlContainerView<
+            typename std::remove_const<RhsContainer>::type>::type::value_type>>
+UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
+                   const RhsContainer& rhs_container) {
+  // RhsView allows the same code to handle RhsContainer being a
+  // STL-style container and it being a native C-style array.
+  typedef typename internal::StlContainerView<RhsContainer> RhsView;
+  typedef typename RhsView::type RhsStlContainer;
+  typedef typename RhsStlContainer::value_type Second;
+  const RhsStlContainer& rhs_stl_container =
+      RhsView::ConstReference(rhs_container);
+
+  // Create a matcher for each element in rhs_container.
+  ::std::vector<internal::BoundSecondMatcher<Tuple2Matcher, Second> > matchers;
+  for (typename RhsStlContainer::const_iterator it = rhs_stl_container.begin();
+       it != rhs_stl_container.end(); ++it) {
+    matchers.push_back(
+        internal::MatcherBindSecond(tuple2_matcher, *it));
+  }
+
+  // Delegate the work to UnorderedElementsAreArray().
+  return UnorderedElementsAreArray(matchers);
+}
+
+
+// Supports the UnorderedPointwise(m, {a, b, c}) syntax.
+template <typename Tuple2Matcher, typename T>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename internal::BoundSecondMatcher<Tuple2Matcher, T> >
+UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
+                   std::initializer_list<T> rhs) {
+  return UnorderedPointwise(tuple2_matcher, std::vector<T>(rhs));
+}
+
+
+// Matches an STL-style container or a native array that contains at
+// least one element matching the given value or matcher.
+//
+// Examples:
+//   ::std::set<int> page_ids;
+//   page_ids.insert(3);
+//   page_ids.insert(1);
+//   EXPECT_THAT(page_ids, Contains(1));
+//   EXPECT_THAT(page_ids, Contains(Gt(2)));
+//   EXPECT_THAT(page_ids, Not(Contains(4)));
+//
+//   ::std::map<int, size_t> page_lengths;
+//   page_lengths[1] = 100;
+//   EXPECT_THAT(page_lengths,
+//               Contains(::std::pair<const int, size_t>(1, 100)));
+//
+//   const char* user_ids[] = { "joe", "mike", "tom" };
+//   EXPECT_THAT(user_ids, Contains(Eq(::std::string("tom"))));
+template <typename M>
+inline internal::ContainsMatcher<M> Contains(M matcher) {
+  return internal::ContainsMatcher<M>(matcher);
+}
+
+// IsSupersetOf(iterator_first, iterator_last)
+// IsSupersetOf(pointer, count)
+// IsSupersetOf(array)
+// IsSupersetOf(container)
+// IsSupersetOf({e1, e2, ..., en})
+//
+// IsSupersetOf() verifies that a surjective partial mapping onto a collection
+// of matchers exists. In other words, a container matches
+// IsSupersetOf({e1, ..., en}) if and only if there is a permutation
+// {y1, ..., yn} of some of the container's elements where y1 matches e1,
+// ..., and yn matches en. Obviously, the size of the container must be >= n
+// in order to have a match. Examples:
+//
+// - {1, 2, 3} matches IsSupersetOf({Ge(3), Ne(0)}), as 3 matches Ge(3) and
+//   1 matches Ne(0).
+// - {1, 2} doesn't match IsSupersetOf({Eq(1), Lt(2)}), even though 1 matches
+//   both Eq(1) and Lt(2). The reason is that different matchers must be used
+//   for elements in different slots of the container.
+// - {1, 1, 2} matches IsSupersetOf({Eq(1), Lt(2)}), as (the first) 1 matches
+//   Eq(1) and (the second) 1 matches Lt(2).
+// - {1, 2, 3} matches IsSupersetOf(Gt(1), Gt(1)), as 2 matches (the first)
+//   Gt(1) and 3 matches (the second) Gt(1).
+//
+// The matchers can be specified as an array, a pointer and count, a container,
+// an initializer list, or an STL iterator range. In each of these cases, the
+// underlying matchers can be either values or matchers.
+
+template <typename Iter>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+IsSupersetOf(Iter first, Iter last) {
+  typedef typename ::std::iterator_traits<Iter>::value_type T;
+  return internal::UnorderedElementsAreArrayMatcher<T>(
+      internal::UnorderedMatcherRequire::Superset, first, last);
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf(
+    const T* pointer, size_t count) {
+  return IsSupersetOf(pointer, pointer + count);
+}
+
+template <typename T, size_t N>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf(
+    const T (&array)[N]) {
+  return IsSupersetOf(array, N);
+}
+
+template <typename Container>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename Container::value_type>
+IsSupersetOf(const Container& container) {
+  return IsSupersetOf(container.begin(), container.end());
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSupersetOf(
+    ::std::initializer_list<T> xs) {
+  return IsSupersetOf(xs.begin(), xs.end());
+}
+
+// IsSubsetOf(iterator_first, iterator_last)
+// IsSubsetOf(pointer, count)
+// IsSubsetOf(array)
+// IsSubsetOf(container)
+// IsSubsetOf({e1, e2, ..., en})
+//
+// IsSubsetOf() verifies that an injective mapping onto a collection of matchers
+// exists.  In other words, a container matches IsSubsetOf({e1, ..., en}) if and
+// only if there is a subset of matchers {m1, ..., mk} which would match the
+// container using UnorderedElementsAre.  Obviously, the size of the container
+// must be <= n in order to have a match. Examples:
+//
+// - {1} matches IsSubsetOf({Gt(0), Lt(0)}), as 1 matches Gt(0).
+// - {1, -1} matches IsSubsetOf({Lt(0), Gt(0)}), as 1 matches Gt(0) and -1
+//   matches Lt(0).
+// - {1, 2} doesn't matches IsSubsetOf({Gt(0), Lt(0)}), even though 1 and 2 both
+//   match Gt(0). The reason is that different matchers must be used for
+//   elements in different slots of the container.
+//
+// The matchers can be specified as an array, a pointer and count, a container,
+// an initializer list, or an STL iterator range. In each of these cases, the
+// underlying matchers can be either values or matchers.
+
+template <typename Iter>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+IsSubsetOf(Iter first, Iter last) {
+  typedef typename ::std::iterator_traits<Iter>::value_type T;
+  return internal::UnorderedElementsAreArrayMatcher<T>(
+      internal::UnorderedMatcherRequire::Subset, first, last);
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf(
+    const T* pointer, size_t count) {
+  return IsSubsetOf(pointer, pointer + count);
+}
+
+template <typename T, size_t N>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf(
+    const T (&array)[N]) {
+  return IsSubsetOf(array, N);
+}
+
+template <typename Container>
+inline internal::UnorderedElementsAreArrayMatcher<
+    typename Container::value_type>
+IsSubsetOf(const Container& container) {
+  return IsSubsetOf(container.begin(), container.end());
+}
+
+template <typename T>
+inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf(
+    ::std::initializer_list<T> xs) {
+  return IsSubsetOf(xs.begin(), xs.end());
+}
+
+// Matches an STL-style container or a native array that contains only
+// elements matching the given value or matcher.
+//
+// Each(m) is semantically equivalent to Not(Contains(Not(m))). Only
+// the messages are different.
+//
+// Examples:
+//   ::std::set<int> page_ids;
+//   // Each(m) matches an empty container, regardless of what m is.
+//   EXPECT_THAT(page_ids, Each(Eq(1)));
+//   EXPECT_THAT(page_ids, Each(Eq(77)));
+//
+//   page_ids.insert(3);
+//   EXPECT_THAT(page_ids, Each(Gt(0)));
+//   EXPECT_THAT(page_ids, Not(Each(Gt(4))));
+//   page_ids.insert(1);
+//   EXPECT_THAT(page_ids, Not(Each(Lt(2))));
+//
+//   ::std::map<int, size_t> page_lengths;
+//   page_lengths[1] = 100;
+//   page_lengths[2] = 200;
+//   page_lengths[3] = 300;
+//   EXPECT_THAT(page_lengths, Not(Each(Pair(1, 100))));
+//   EXPECT_THAT(page_lengths, Each(Key(Le(3))));
+//
+//   const char* user_ids[] = { "joe", "mike", "tom" };
+//   EXPECT_THAT(user_ids, Not(Each(Eq(::std::string("tom")))));
+template <typename M>
+inline internal::EachMatcher<M> Each(M matcher) {
+  return internal::EachMatcher<M>(matcher);
+}
+
+// Key(inner_matcher) matches an std::pair whose 'first' field matches
+// inner_matcher.  For example, Contains(Key(Ge(5))) can be used to match an
+// std::map that contains at least one element whose key is >= 5.
+template <typename M>
+inline internal::KeyMatcher<M> Key(M inner_matcher) {
+  return internal::KeyMatcher<M>(inner_matcher);
+}
+
+// Pair(first_matcher, second_matcher) matches a std::pair whose 'first' field
+// matches first_matcher and whose 'second' field matches second_matcher.  For
+// example, EXPECT_THAT(map_type, ElementsAre(Pair(Ge(5), "foo"))) can be used
+// to match a std::map<int, string> that contains exactly one element whose key
+// is >= 5 and whose value equals "foo".
+template <typename FirstMatcher, typename SecondMatcher>
+inline internal::PairMatcher<FirstMatcher, SecondMatcher>
+Pair(FirstMatcher first_matcher, SecondMatcher second_matcher) {
+  return internal::PairMatcher<FirstMatcher, SecondMatcher>(
+      first_matcher, second_matcher);
+}
+
+namespace no_adl {
+// FieldsAre(matchers...) matches piecewise the fields of compatible structs.
+// These include those that support `get<I>(obj)`, and when structured bindings
+// are enabled any class that supports them.
+// In particular, `std::tuple`, `std::pair`, `std::array` and aggregate types.
+template <typename... M>
+internal::FieldsAreMatcher<typename std::decay<M>::type...> FieldsAre(
+    M&&... matchers) {
+  return internal::FieldsAreMatcher<typename std::decay<M>::type...>(
+      std::forward<M>(matchers)...);
+}
+
+// Creates a matcher that matches a pointer (raw or smart) that matches
+// inner_matcher.
+template <typename InnerMatcher>
+inline internal::PointerMatcher<InnerMatcher> Pointer(
+    const InnerMatcher& inner_matcher) {
+  return internal::PointerMatcher<InnerMatcher>(inner_matcher);
+}
+
+// Creates a matcher that matches an object that has an address that matches
+// inner_matcher.
+template <typename InnerMatcher>
+inline internal::AddressMatcher<InnerMatcher> Address(
+    const InnerMatcher& inner_matcher) {
+  return internal::AddressMatcher<InnerMatcher>(inner_matcher);
+}
+}  // namespace no_adl
+
+// Returns a predicate that is satisfied by anything that matches the
+// given matcher.
+template <typename M>
+inline internal::MatcherAsPredicate<M> Matches(M matcher) {
+  return internal::MatcherAsPredicate<M>(matcher);
+}
+
+// Returns true if and only if the value matches the matcher.
+template <typename T, typename M>
+inline bool Value(const T& value, M matcher) {
+  return testing::Matches(matcher)(value);
+}
+
+// Matches the value against the given matcher and explains the match
+// result to listener.
+template <typename T, typename M>
+inline bool ExplainMatchResult(
+    M matcher, const T& value, MatchResultListener* listener) {
+  return SafeMatcherCast<const T&>(matcher).MatchAndExplain(value, listener);
+}
+
+// Returns a string representation of the given matcher.  Useful for description
+// strings of matchers defined using MATCHER_P* macros that accept matchers as
+// their arguments.  For example:
+//
+// MATCHER_P(XAndYThat, matcher,
+//           "X that " + DescribeMatcher<int>(matcher, negation) +
+//               " and Y that " + DescribeMatcher<double>(matcher, negation)) {
+//   return ExplainMatchResult(matcher, arg.x(), result_listener) &&
+//          ExplainMatchResult(matcher, arg.y(), result_listener);
+// }
+template <typename T, typename M>
+std::string DescribeMatcher(const M& matcher, bool negation = false) {
+  ::std::stringstream ss;
+  Matcher<T> monomorphic_matcher = SafeMatcherCast<T>(matcher);
+  if (negation) {
+    monomorphic_matcher.DescribeNegationTo(&ss);
+  } else {
+    monomorphic_matcher.DescribeTo(&ss);
+  }
+  return ss.str();
+}
+
+template <typename... Args>
+internal::ElementsAreMatcher<
+    std::tuple<typename std::decay<const Args&>::type...>>
+ElementsAre(const Args&... matchers) {
+  return internal::ElementsAreMatcher<
+      std::tuple<typename std::decay<const Args&>::type...>>(
+      std::make_tuple(matchers...));
+}
+
+template <typename... Args>
+internal::UnorderedElementsAreMatcher<
+    std::tuple<typename std::decay<const Args&>::type...>>
+UnorderedElementsAre(const Args&... matchers) {
+  return internal::UnorderedElementsAreMatcher<
+      std::tuple<typename std::decay<const Args&>::type...>>(
+      std::make_tuple(matchers...));
+}
+
+// Define variadic matcher versions.
+template <typename... Args>
+internal::AllOfMatcher<typename std::decay<const Args&>::type...> AllOf(
+    const Args&... matchers) {
+  return internal::AllOfMatcher<typename std::decay<const Args&>::type...>(
+      matchers...);
+}
+
+template <typename... Args>
+internal::AnyOfMatcher<typename std::decay<const Args&>::type...> AnyOf(
+    const Args&... matchers) {
+  return internal::AnyOfMatcher<typename std::decay<const Args&>::type...>(
+      matchers...);
+}
+
+// AnyOfArray(array)
+// AnyOfArray(pointer, count)
+// AnyOfArray(container)
+// AnyOfArray({ e1, e2, ..., en })
+// AnyOfArray(iterator_first, iterator_last)
+//
+// AnyOfArray() verifies whether a given value matches any member of a
+// collection of matchers.
+//
+// AllOfArray(array)
+// AllOfArray(pointer, count)
+// AllOfArray(container)
+// AllOfArray({ e1, e2, ..., en })
+// AllOfArray(iterator_first, iterator_last)
+//
+// AllOfArray() verifies whether a given value matches all members of a
+// collection of matchers.
+//
+// The matchers can be specified as an array, a pointer and count, a container,
+// an initializer list, or an STL iterator range. In each of these cases, the
+// underlying matchers can be either values or matchers.
+
+template <typename Iter>
+inline internal::AnyOfArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+AnyOfArray(Iter first, Iter last) {
+  return internal::AnyOfArrayMatcher<
+      typename ::std::iterator_traits<Iter>::value_type>(first, last);
+}
+
+template <typename Iter>
+inline internal::AllOfArrayMatcher<
+    typename ::std::iterator_traits<Iter>::value_type>
+AllOfArray(Iter first, Iter last) {
+  return internal::AllOfArrayMatcher<
+      typename ::std::iterator_traits<Iter>::value_type>(first, last);
+}
+
+template <typename T>
+inline internal::AnyOfArrayMatcher<T> AnyOfArray(const T* ptr, size_t count) {
+  return AnyOfArray(ptr, ptr + count);
+}
+
+template <typename T>
+inline internal::AllOfArrayMatcher<T> AllOfArray(const T* ptr, size_t count) {
+  return AllOfArray(ptr, ptr + count);
+}
+
+template <typename T, size_t N>
+inline internal::AnyOfArrayMatcher<T> AnyOfArray(const T (&array)[N]) {
+  return AnyOfArray(array, N);
+}
+
+template <typename T, size_t N>
+inline internal::AllOfArrayMatcher<T> AllOfArray(const T (&array)[N]) {
+  return AllOfArray(array, N);
+}
+
+template <typename Container>
+inline internal::AnyOfArrayMatcher<typename Container::value_type> AnyOfArray(
+    const Container& container) {
+  return AnyOfArray(container.begin(), container.end());
+}
+
+template <typename Container>
+inline internal::AllOfArrayMatcher<typename Container::value_type> AllOfArray(
+    const Container& container) {
+  return AllOfArray(container.begin(), container.end());
+}
+
+template <typename T>
+inline internal::AnyOfArrayMatcher<T> AnyOfArray(
+    ::std::initializer_list<T> xs) {
+  return AnyOfArray(xs.begin(), xs.end());
+}
+
+template <typename T>
+inline internal::AllOfArrayMatcher<T> AllOfArray(
+    ::std::initializer_list<T> xs) {
+  return AllOfArray(xs.begin(), xs.end());
+}
+
+// Args<N1, N2, ..., Nk>(a_matcher) matches a tuple if the selected
+// fields of it matches a_matcher.  C++ doesn't support default
+// arguments for function templates, so we have to overload it.
+template <size_t... k, typename InnerMatcher>
+internal::ArgsMatcher<typename std::decay<InnerMatcher>::type, k...> Args(
+    InnerMatcher&& matcher) {
+  return internal::ArgsMatcher<typename std::decay<InnerMatcher>::type, k...>(
+      std::forward<InnerMatcher>(matcher));
+}
+
+// AllArgs(m) is a synonym of m.  This is useful in
+//
+//   EXPECT_CALL(foo, Bar(_, _)).With(AllArgs(Eq()));
+//
+// which is easier to read than
+//
+//   EXPECT_CALL(foo, Bar(_, _)).With(Eq());
+template <typename InnerMatcher>
+inline InnerMatcher AllArgs(const InnerMatcher& matcher) { return matcher; }
+
+// Returns a matcher that matches the value of an optional<> type variable.
+// The matcher implementation only uses '!arg' and requires that the optional<>
+// type has a 'value_type' member type and that '*arg' is of type 'value_type'
+// and is printable using 'PrintToString'. It is compatible with
+// std::optional/std::experimental::optional.
+// Note that to compare an optional type variable against nullopt you should
+// use Eq(nullopt) and not Eq(Optional(nullopt)). The latter implies that the
+// optional value contains an optional itself.
+template <typename ValueMatcher>
+inline internal::OptionalMatcher<ValueMatcher> Optional(
+    const ValueMatcher& value_matcher) {
+  return internal::OptionalMatcher<ValueMatcher>(value_matcher);
+}
+
+// Returns a matcher that matches the value of a absl::any type variable.
+template <typename T>
+PolymorphicMatcher<internal::any_cast_matcher::AnyCastMatcher<T> > AnyWith(
+    const Matcher<const T&>& matcher) {
+  return MakePolymorphicMatcher(
+      internal::any_cast_matcher::AnyCastMatcher<T>(matcher));
+}
+
+// Returns a matcher that matches the value of a variant<> type variable.
+// The matcher implementation uses ADL to find the holds_alternative and get
+// functions.
+// It is compatible with std::variant.
+template <typename T>
+PolymorphicMatcher<internal::variant_matcher::VariantMatcher<T> > VariantWith(
+    const Matcher<const T&>& matcher) {
+  return MakePolymorphicMatcher(
+      internal::variant_matcher::VariantMatcher<T>(matcher));
+}
+
+#if GTEST_HAS_EXCEPTIONS
+
+// Anything inside the `internal` namespace is internal to the implementation
+// and must not be used in user code!
+namespace internal {
+
+class WithWhatMatcherImpl {
+ public:
+  WithWhatMatcherImpl(Matcher<std::string> matcher)
+      : matcher_(std::move(matcher)) {}
+
+  void DescribeTo(std::ostream* os) const {
+    *os << "contains .what() that ";
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << "contains .what() that does not ";
+    matcher_.DescribeTo(os);
+  }
+
+  template <typename Err>
+  bool MatchAndExplain(const Err& err, MatchResultListener* listener) const {
+    *listener << "which contains .what() that ";
+    return matcher_.MatchAndExplain(err.what(), listener);
+  }
+
+ private:
+  const Matcher<std::string> matcher_;
+};
+
+inline PolymorphicMatcher<WithWhatMatcherImpl> WithWhat(
+    Matcher<std::string> m) {
+  return MakePolymorphicMatcher(WithWhatMatcherImpl(std::move(m)));
+}
+
+template <typename Err>
+class ExceptionMatcherImpl {
+  class NeverThrown {
+   public:
+    const char* what() const noexcept {
+      return "this exception should never be thrown";
+    }
+  };
+
+  // If the matchee raises an exception of a wrong type, we'd like to
+  // catch it and print its message and type. To do that, we add an additional
+  // catch clause:
+  //
+  //     try { ... }
+  //     catch (const Err&) { /* an expected exception */ }
+  //     catch (const std::exception&) { /* exception of a wrong type */ }
+  //
+  // However, if the `Err` itself is `std::exception`, we'd end up with two
+  // identical `catch` clauses:
+  //
+  //     try { ... }
+  //     catch (const std::exception&) { /* an expected exception */ }
+  //     catch (const std::exception&) { /* exception of a wrong type */ }
+  //
+  // This can cause a warning or an error in some compilers. To resolve
+  // the issue, we use a fake error type whenever `Err` is `std::exception`:
+  //
+  //     try { ... }
+  //     catch (const std::exception&) { /* an expected exception */ }
+  //     catch (const NeverThrown&) { /* exception of a wrong type */ }
+  using DefaultExceptionType = typename std::conditional<
+      std::is_same<typename std::remove_cv<
+                       typename std::remove_reference<Err>::type>::type,
+                   std::exception>::value,
+      const NeverThrown&, const std::exception&>::type;
+
+ public:
+  ExceptionMatcherImpl(Matcher<const Err&> matcher)
+      : matcher_(std::move(matcher)) {}
+
+  void DescribeTo(std::ostream* os) const {
+    *os << "throws an exception which is a " << GetTypeName<Err>();
+    *os << " which ";
+    matcher_.DescribeTo(os);
+  }
+
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << "throws an exception which is not a " << GetTypeName<Err>();
+    *os << " which ";
+    matcher_.DescribeNegationTo(os);
+  }
+
+  template <typename T>
+  bool MatchAndExplain(T&& x, MatchResultListener* listener) const {
+    try {
+      (void)(std::forward<T>(x)());
+    } catch (const Err& err) {
+      *listener << "throws an exception which is a " << GetTypeName<Err>();
+      *listener << " ";
+      return matcher_.MatchAndExplain(err, listener);
+    } catch (DefaultExceptionType err) {
+#if GTEST_HAS_RTTI
+      *listener << "throws an exception of type " << GetTypeName(typeid(err));
+      *listener << " ";
+#else
+      *listener << "throws an std::exception-derived type ";
+#endif
+      *listener << "with description \"" << err.what() << "\"";
+      return false;
+    } catch (...) {
+      *listener << "throws an exception of an unknown type";
+      return false;
+    }
+
+    *listener << "does not throw any exception";
+    return false;
+  }
+
+ private:
+  const Matcher<const Err&> matcher_;
+};
+
+}  // namespace internal
+
+// Throws()
+// Throws(exceptionMatcher)
+// ThrowsMessage(messageMatcher)
+//
+// This matcher accepts a callable and verifies that when invoked, it throws
+// an exception with the given type and properties.
+//
+// Examples:
+//
+//   EXPECT_THAT(
+//       []() { throw std::runtime_error("message"); },
+//       Throws<std::runtime_error>());
+//
+//   EXPECT_THAT(
+//       []() { throw std::runtime_error("message"); },
+//       ThrowsMessage<std::runtime_error>(HasSubstr("message")));
+//
+//   EXPECT_THAT(
+//       []() { throw std::runtime_error("message"); },
+//       Throws<std::runtime_error>(
+//           Property(&std::runtime_error::what, HasSubstr("message"))));
+
+template <typename Err>
+PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> Throws() {
+  return MakePolymorphicMatcher(
+      internal::ExceptionMatcherImpl<Err>(A<const Err&>()));
+}
+
+template <typename Err, typename ExceptionMatcher>
+PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> Throws(
+    const ExceptionMatcher& exception_matcher) {
+  // Using matcher cast allows users to pass a matcher of a more broad type.
+  // For example user may want to pass Matcher<std::exception>
+  // to Throws<std::runtime_error>, or Matcher<int64> to Throws<int32>.
+  return MakePolymorphicMatcher(internal::ExceptionMatcherImpl<Err>(
+      SafeMatcherCast<const Err&>(exception_matcher)));
+}
+
+template <typename Err, typename MessageMatcher>
+PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>> ThrowsMessage(
+    MessageMatcher&& message_matcher) {
+  static_assert(std::is_base_of<std::exception, Err>::value,
+                "expected an std::exception-derived type");
+  return Throws<Err>(internal::WithWhat(
+      MatcherCast<std::string>(std::forward<MessageMatcher>(message_matcher))));
+}
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+// These macros allow using matchers to check values in Google Test
+// tests.  ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher)
+// succeed if and only if the value matches the matcher.  If the assertion
+// fails, the value and the description of the matcher will be printed.
+#define ASSERT_THAT(value, matcher) ASSERT_PRED_FORMAT1(\
+    ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value)
+#define EXPECT_THAT(value, matcher) EXPECT_PRED_FORMAT1(\
+    ::testing::internal::MakePredicateFormatterFromMatcher(matcher), value)
+
+// MATCHER* macroses itself are listed below.
+#define MATCHER(name, description)                                             \
+  class name##Matcher                                                          \
+      : public ::testing::internal::MatcherBaseImpl<name##Matcher> {           \
+   public:                                                                     \
+    template <typename arg_type>                                               \
+    class gmock_Impl : public ::testing::MatcherInterface<const arg_type&> {   \
+     public:                                                                   \
+      gmock_Impl() {}                                                          \
+      bool MatchAndExplain(                                                    \
+          const arg_type& arg,                                                 \
+          ::testing::MatchResultListener* result_listener) const override;     \
+      void DescribeTo(::std::ostream* gmock_os) const override {               \
+        *gmock_os << FormatDescription(false);                                 \
+      }                                                                        \
+      void DescribeNegationTo(::std::ostream* gmock_os) const override {       \
+        *gmock_os << FormatDescription(true);                                  \
+      }                                                                        \
+                                                                               \
+     private:                                                                  \
+      ::std::string FormatDescription(bool negation) const {                   \
+        ::std::string gmock_description = (description);                       \
+        if (!gmock_description.empty()) {                                      \
+          return gmock_description;                                            \
+        }                                                                      \
+        return ::testing::internal::FormatMatcherDescription(negation, #name,  \
+                                                             {});              \
+      }                                                                        \
+    };                                                                         \
+  };                                                                           \
+  GTEST_ATTRIBUTE_UNUSED_ inline name##Matcher name() { return {}; }           \
+  template <typename arg_type>                                                 \
+  bool name##Matcher::gmock_Impl<arg_type>::MatchAndExplain(                   \
+      const arg_type& arg,                                                     \
+      ::testing::MatchResultListener* result_listener GTEST_ATTRIBUTE_UNUSED_) \
+      const
+
+#define MATCHER_P(name, p0, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP, description, (p0))
+#define MATCHER_P2(name, p0, p1, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP2, description, (p0, p1))
+#define MATCHER_P3(name, p0, p1, p2, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP3, description, (p0, p1, p2))
+#define MATCHER_P4(name, p0, p1, p2, p3, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP4, description, (p0, p1, p2, p3))
+#define MATCHER_P5(name, p0, p1, p2, p3, p4, description)    \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP5, description, \
+                         (p0, p1, p2, p3, p4))
+#define MATCHER_P6(name, p0, p1, p2, p3, p4, p5, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP6, description,  \
+                         (p0, p1, p2, p3, p4, p5))
+#define MATCHER_P7(name, p0, p1, p2, p3, p4, p5, p6, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP7, description,      \
+                         (p0, p1, p2, p3, p4, p5, p6))
+#define MATCHER_P8(name, p0, p1, p2, p3, p4, p5, p6, p7, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP8, description,          \
+                         (p0, p1, p2, p3, p4, p5, p6, p7))
+#define MATCHER_P9(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP9, description,              \
+                         (p0, p1, p2, p3, p4, p5, p6, p7, p8))
+#define MATCHER_P10(name, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, description) \
+  GMOCK_INTERNAL_MATCHER(name, name##MatcherP10, description,                  \
+                         (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9))
+
+#define GMOCK_INTERNAL_MATCHER(name, full_name, description, args)             \
+  template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)>                      \
+  class full_name : public ::testing::internal::MatcherBaseImpl<               \
+                        full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>> { \
+   public:                                                                     \
+    using full_name::MatcherBaseImpl::MatcherBaseImpl;                         \
+    template <typename arg_type>                                               \
+    class gmock_Impl : public ::testing::MatcherInterface<const arg_type&> {   \
+     public:                                                                   \
+      explicit gmock_Impl(GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args))          \
+          : GMOCK_INTERNAL_MATCHER_FORWARD_ARGS(args) {}                       \
+      bool MatchAndExplain(                                                    \
+          const arg_type& arg,                                                 \
+          ::testing::MatchResultListener* result_listener) const override;     \
+      void DescribeTo(::std::ostream* gmock_os) const override {               \
+        *gmock_os << FormatDescription(false);                                 \
+      }                                                                        \
+      void DescribeNegationTo(::std::ostream* gmock_os) const override {       \
+        *gmock_os << FormatDescription(true);                                  \
+      }                                                                        \
+      GMOCK_INTERNAL_MATCHER_MEMBERS(args)                                     \
+                                                                               \
+     private:                                                                  \
+      ::std::string FormatDescription(bool negation) const {                   \
+        ::std::string gmock_description = (description);                       \
+        if (!gmock_description.empty()) {                                      \
+          return gmock_description;                                            \
+        }                                                                      \
+        return ::testing::internal::FormatMatcherDescription(                  \
+            negation, #name,                                                   \
+            ::testing::internal::UniversalTersePrintTupleFieldsToStrings(      \
+                ::std::tuple<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>(        \
+                    GMOCK_INTERNAL_MATCHER_MEMBERS_USAGE(args))));             \
+      }                                                                        \
+    };                                                                         \
+  };                                                                           \
+  template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)>                      \
+  inline full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)> name(             \
+      GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args)) {                            \
+    return full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>(                \
+        GMOCK_INTERNAL_MATCHER_ARGS_USAGE(args));                              \
+  }                                                                            \
+  template <GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args)>                      \
+  template <typename arg_type>                                                 \
+  bool full_name<GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args)>::gmock_Impl<        \
+      arg_type>::MatchAndExplain(const arg_type& arg,                          \
+                                 ::testing::MatchResultListener*               \
+                                     result_listener GTEST_ATTRIBUTE_UNUSED_)  \
+      const
+
+#define GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAMS(args) \
+  GMOCK_PP_TAIL(                                     \
+      GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAM, , args))
+#define GMOCK_INTERNAL_MATCHER_TEMPLATE_PARAM(i_unused, data_unused, arg) \
+  , typename arg##_type
+
+#define GMOCK_INTERNAL_MATCHER_TYPE_PARAMS(args) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_TYPE_PARAM, , args))
+#define GMOCK_INTERNAL_MATCHER_TYPE_PARAM(i_unused, data_unused, arg) \
+  , arg##_type
+
+#define GMOCK_INTERNAL_MATCHER_FUNCTION_ARGS(args) \
+  GMOCK_PP_TAIL(dummy_first GMOCK_PP_FOR_EACH(     \
+      GMOCK_INTERNAL_MATCHER_FUNCTION_ARG, , args))
+#define GMOCK_INTERNAL_MATCHER_FUNCTION_ARG(i, data_unused, arg) \
+  , arg##_type gmock_p##i
+
+#define GMOCK_INTERNAL_MATCHER_FORWARD_ARGS(args) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_FORWARD_ARG, , args))
+#define GMOCK_INTERNAL_MATCHER_FORWARD_ARG(i, data_unused, arg) \
+  , arg(::std::forward<arg##_type>(gmock_p##i))
+
+#define GMOCK_INTERNAL_MATCHER_MEMBERS(args) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_MEMBER, , args)
+#define GMOCK_INTERNAL_MATCHER_MEMBER(i_unused, data_unused, arg) \
+  const arg##_type arg;
+
+#define GMOCK_INTERNAL_MATCHER_MEMBERS_USAGE(args) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_MEMBER_USAGE, , args))
+#define GMOCK_INTERNAL_MATCHER_MEMBER_USAGE(i_unused, data_unused, arg) , arg
+
+#define GMOCK_INTERNAL_MATCHER_ARGS_USAGE(args) \
+  GMOCK_PP_TAIL(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_MATCHER_ARG_USAGE, , args))
+#define GMOCK_INTERNAL_MATCHER_ARG_USAGE(i, data_unused, arg_unused) \
+  , gmock_p##i
+
+// To prevent ADL on certain functions we put them on a separate namespace.
+using namespace no_adl;  // NOLINT
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251 5046
+
+// Include any custom callback matchers added by the local installation.
+// We must include this header at the end to make sure it can use the
+// declarations from this file.
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MATCHERS_H_
+
+#if GTEST_HAS_EXCEPTIONS
+# include <stdexcept>  // NOLINT
+#endif
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// An abstract handle of an expectation.
+class Expectation;
+
+// A set of expectation handles.
+class ExpectationSet;
+
+// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION
+// and MUST NOT BE USED IN USER CODE!!!
+namespace internal {
+
+// Implements a mock function.
+template <typename F> class FunctionMocker;
+
+// Base class for expectations.
+class ExpectationBase;
+
+// Implements an expectation.
+template <typename F> class TypedExpectation;
+
+// Helper class for testing the Expectation class template.
+class ExpectationTester;
+
+// Helper classes for implementing NiceMock, StrictMock, and NaggyMock.
+template <typename MockClass>
+class NiceMockImpl;
+template <typename MockClass>
+class StrictMockImpl;
+template <typename MockClass>
+class NaggyMockImpl;
+
+// Protects the mock object registry (in class Mock), all function
+// mockers, and all expectations.
+//
+// The reason we don't use more fine-grained protection is: when a
+// mock function Foo() is called, it needs to consult its expectations
+// to see which one should be picked.  If another thread is allowed to
+// call a mock function (either Foo() or a different one) at the same
+// time, it could affect the "retired" attributes of Foo()'s
+// expectations when InSequence() is used, and thus affect which
+// expectation gets picked.  Therefore, we sequence all mock function
+// calls to ensure the integrity of the mock objects' states.
+GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_gmock_mutex);
+
+// Untyped base class for ActionResultHolder<R>.
+class UntypedActionResultHolderBase;
+
+// Abstract base class of FunctionMocker.  This is the
+// type-agnostic part of the function mocker interface.  Its pure
+// virtual methods are implemented by FunctionMocker.
+class GTEST_API_ UntypedFunctionMockerBase {
+ public:
+  UntypedFunctionMockerBase();
+  virtual ~UntypedFunctionMockerBase();
+
+  // Verifies that all expectations on this mock function have been
+  // satisfied.  Reports one or more Google Test non-fatal failures
+  // and returns false if not.
+  bool VerifyAndClearExpectationsLocked()
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex);
+
+  // Clears the ON_CALL()s set on this mock function.
+  virtual void ClearDefaultActionsLocked()
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) = 0;
+
+  // In all of the following Untyped* functions, it's the caller's
+  // responsibility to guarantee the correctness of the arguments'
+  // types.
+
+  // Performs the default action with the given arguments and returns
+  // the action's result.  The call description string will be used in
+  // the error message to describe the call in the case the default
+  // action fails.
+  // L = *
+  virtual UntypedActionResultHolderBase* UntypedPerformDefaultAction(
+      void* untyped_args, const std::string& call_description) const = 0;
+
+  // Performs the given action with the given arguments and returns
+  // the action's result.
+  // L = *
+  virtual UntypedActionResultHolderBase* UntypedPerformAction(
+      const void* untyped_action, void* untyped_args) const = 0;
+
+  // Writes a message that the call is uninteresting (i.e. neither
+  // explicitly expected nor explicitly unexpected) to the given
+  // ostream.
+  virtual void UntypedDescribeUninterestingCall(
+      const void* untyped_args,
+      ::std::ostream* os) const
+          GTEST_LOCK_EXCLUDED_(g_gmock_mutex) = 0;
+
+  // Returns the expectation that matches the given function arguments
+  // (or NULL is there's no match); when a match is found,
+  // untyped_action is set to point to the action that should be
+  // performed (or NULL if the action is "do default"), and
+  // is_excessive is modified to indicate whether the call exceeds the
+  // expected number.
+  virtual const ExpectationBase* UntypedFindMatchingExpectation(
+      const void* untyped_args,
+      const void** untyped_action, bool* is_excessive,
+      ::std::ostream* what, ::std::ostream* why)
+          GTEST_LOCK_EXCLUDED_(g_gmock_mutex) = 0;
+
+  // Prints the given function arguments to the ostream.
+  virtual void UntypedPrintArgs(const void* untyped_args,
+                                ::std::ostream* os) const = 0;
+
+  // Sets the mock object this mock method belongs to, and registers
+  // this information in the global mock registry.  Will be called
+  // whenever an EXPECT_CALL() or ON_CALL() is executed on this mock
+  // method.
+  void RegisterOwner(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex);
+
+  // Sets the mock object this mock method belongs to, and sets the
+  // name of the mock function.  Will be called upon each invocation
+  // of this mock function.
+  void SetOwnerAndName(const void* mock_obj, const char* name)
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex);
+
+  // Returns the mock object this mock method belongs to.  Must be
+  // called after RegisterOwner() or SetOwnerAndName() has been
+  // called.
+  const void* MockObject() const
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex);
+
+  // Returns the name of this mock method.  Must be called after
+  // SetOwnerAndName() has been called.
+  const char* Name() const
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex);
+
+  // Returns the result of invoking this mock function with the given
+  // arguments.  This function can be safely called from multiple
+  // threads concurrently.  The caller is responsible for deleting the
+  // result.
+  UntypedActionResultHolderBase* UntypedInvokeWith(void* untyped_args)
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex);
+
+ protected:
+  typedef std::vector<const void*> UntypedOnCallSpecs;
+
+  using UntypedExpectations = std::vector<std::shared_ptr<ExpectationBase>>;
+
+  // Returns an Expectation object that references and co-owns exp,
+  // which must be an expectation on this mock function.
+  Expectation GetHandleOf(ExpectationBase* exp);
+
+  // Address of the mock object this mock method belongs to.  Only
+  // valid after this mock method has been called or
+  // ON_CALL/EXPECT_CALL has been invoked on it.
+  const void* mock_obj_;  // Protected by g_gmock_mutex.
+
+  // Name of the function being mocked.  Only valid after this mock
+  // method has been called.
+  const char* name_;  // Protected by g_gmock_mutex.
+
+  // All default action specs for this function mocker.
+  UntypedOnCallSpecs untyped_on_call_specs_;
+
+  // All expectations for this function mocker.
+  //
+  // It's undefined behavior to interleave expectations (EXPECT_CALLs
+  // or ON_CALLs) and mock function calls.  Also, the order of
+  // expectations is important.  Therefore it's a logic race condition
+  // to read/write untyped_expectations_ concurrently.  In order for
+  // tools like tsan to catch concurrent read/write accesses to
+  // untyped_expectations, we deliberately leave accesses to it
+  // unprotected.
+  UntypedExpectations untyped_expectations_;
+};  // class UntypedFunctionMockerBase
+
+// Untyped base class for OnCallSpec<F>.
+class UntypedOnCallSpecBase {
+ public:
+  // The arguments are the location of the ON_CALL() statement.
+  UntypedOnCallSpecBase(const char* a_file, int a_line)
+      : file_(a_file), line_(a_line), last_clause_(kNone) {}
+
+  // Where in the source file was the default action spec defined?
+  const char* file() const { return file_; }
+  int line() const { return line_; }
+
+ protected:
+  // Gives each clause in the ON_CALL() statement a name.
+  enum Clause {
+    // Do not change the order of the enum members!  The run-time
+    // syntax checking relies on it.
+    kNone,
+    kWith,
+    kWillByDefault
+  };
+
+  // Asserts that the ON_CALL() statement has a certain property.
+  void AssertSpecProperty(bool property,
+                          const std::string& failure_message) const {
+    Assert(property, file_, line_, failure_message);
+  }
+
+  // Expects that the ON_CALL() statement has a certain property.
+  void ExpectSpecProperty(bool property,
+                          const std::string& failure_message) const {
+    Expect(property, file_, line_, failure_message);
+  }
+
+  const char* file_;
+  int line_;
+
+  // The last clause in the ON_CALL() statement as seen so far.
+  // Initially kNone and changes as the statement is parsed.
+  Clause last_clause_;
+};  // class UntypedOnCallSpecBase
+
+// This template class implements an ON_CALL spec.
+template <typename F>
+class OnCallSpec : public UntypedOnCallSpecBase {
+ public:
+  typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+  typedef typename Function<F>::ArgumentMatcherTuple ArgumentMatcherTuple;
+
+  // Constructs an OnCallSpec object from the information inside
+  // the parenthesis of an ON_CALL() statement.
+  OnCallSpec(const char* a_file, int a_line,
+             const ArgumentMatcherTuple& matchers)
+      : UntypedOnCallSpecBase(a_file, a_line),
+        matchers_(matchers),
+        // By default, extra_matcher_ should match anything.  However,
+        // we cannot initialize it with _ as that causes ambiguity between
+        // Matcher's copy and move constructor for some argument types.
+        extra_matcher_(A<const ArgumentTuple&>()) {}
+
+  // Implements the .With() clause.
+  OnCallSpec& With(const Matcher<const ArgumentTuple&>& m) {
+    // Makes sure this is called at most once.
+    ExpectSpecProperty(last_clause_ < kWith,
+                       ".With() cannot appear "
+                       "more than once in an ON_CALL().");
+    last_clause_ = kWith;
+
+    extra_matcher_ = m;
+    return *this;
+  }
+
+  // Implements the .WillByDefault() clause.
+  OnCallSpec& WillByDefault(const Action<F>& action) {
+    ExpectSpecProperty(last_clause_ < kWillByDefault,
+                       ".WillByDefault() must appear "
+                       "exactly once in an ON_CALL().");
+    last_clause_ = kWillByDefault;
+
+    ExpectSpecProperty(!action.IsDoDefault(),
+                       "DoDefault() cannot be used in ON_CALL().");
+    action_ = action;
+    return *this;
+  }
+
+  // Returns true if and only if the given arguments match the matchers.
+  bool Matches(const ArgumentTuple& args) const {
+    return TupleMatches(matchers_, args) && extra_matcher_.Matches(args);
+  }
+
+  // Returns the action specified by the user.
+  const Action<F>& GetAction() const {
+    AssertSpecProperty(last_clause_ == kWillByDefault,
+                       ".WillByDefault() must appear exactly "
+                       "once in an ON_CALL().");
+    return action_;
+  }
+
+ private:
+  // The information in statement
+  //
+  //   ON_CALL(mock_object, Method(matchers))
+  //       .With(multi-argument-matcher)
+  //       .WillByDefault(action);
+  //
+  // is recorded in the data members like this:
+  //
+  //   source file that contains the statement => file_
+  //   line number of the statement            => line_
+  //   matchers                                => matchers_
+  //   multi-argument-matcher                  => extra_matcher_
+  //   action                                  => action_
+  ArgumentMatcherTuple matchers_;
+  Matcher<const ArgumentTuple&> extra_matcher_;
+  Action<F> action_;
+};  // class OnCallSpec
+
+// Possible reactions on uninteresting calls.
+enum CallReaction {
+  kAllow,
+  kWarn,
+  kFail,
+};
+
+}  // namespace internal
+
+// Utilities for manipulating mock objects.
+class GTEST_API_ Mock {
+ public:
+  // The following public methods can be called concurrently.
+
+  // Tells Google Mock to ignore mock_obj when checking for leaked
+  // mock objects.
+  static void AllowLeak(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Verifies and clears all expectations on the given mock object.
+  // If the expectations aren't satisfied, generates one or more
+  // Google Test non-fatal failures and returns false.
+  static bool VerifyAndClearExpectations(void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Verifies all expectations on the given mock object and clears its
+  // default actions and expectations.  Returns true if and only if the
+  // verification was successful.
+  static bool VerifyAndClear(void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Returns whether the mock was created as a naggy mock (default)
+  static bool IsNaggy(void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+  // Returns whether the mock was created as a nice mock
+  static bool IsNice(void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+  // Returns whether the mock was created as a strict mock
+  static bool IsStrict(void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+ private:
+  friend class internal::UntypedFunctionMockerBase;
+
+  // Needed for a function mocker to register itself (so that we know
+  // how to clear a mock object).
+  template <typename F>
+  friend class internal::FunctionMocker;
+
+  template <typename MockClass>
+  friend class internal::NiceMockImpl;
+  template <typename MockClass>
+  friend class internal::NaggyMockImpl;
+  template <typename MockClass>
+  friend class internal::StrictMockImpl;
+
+  // Tells Google Mock to allow uninteresting calls on the given mock
+  // object.
+  static void AllowUninterestingCalls(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Tells Google Mock to warn the user about uninteresting calls on
+  // the given mock object.
+  static void WarnUninterestingCalls(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Tells Google Mock to fail uninteresting calls on the given mock
+  // object.
+  static void FailUninterestingCalls(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Tells Google Mock the given mock object is being destroyed and
+  // its entry in the call-reaction table should be removed.
+  static void UnregisterCallReaction(const void* mock_obj)
+      GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Returns the reaction Google Mock will have on uninteresting calls
+  // made on the given mock object.
+  static internal::CallReaction GetReactionOnUninterestingCalls(
+      const void* mock_obj)
+          GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Verifies that all expectations on the given mock object have been
+  // satisfied.  Reports one or more Google Test non-fatal failures
+  // and returns false if not.
+  static bool VerifyAndClearExpectationsLocked(void* mock_obj)
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex);
+
+  // Clears all ON_CALL()s set on the given mock object.
+  static void ClearDefaultActionsLocked(void* mock_obj)
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex);
+
+  // Registers a mock object and a mock method it owns.
+  static void Register(
+      const void* mock_obj,
+      internal::UntypedFunctionMockerBase* mocker)
+          GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Tells Google Mock where in the source code mock_obj is used in an
+  // ON_CALL or EXPECT_CALL.  In case mock_obj is leaked, this
+  // information helps the user identify which object it is.
+  static void RegisterUseByOnCallOrExpectCall(
+      const void* mock_obj, const char* file, int line)
+          GTEST_LOCK_EXCLUDED_(internal::g_gmock_mutex);
+
+  // Unregisters a mock method; removes the owning mock object from
+  // the registry when the last mock method associated with it has
+  // been unregistered.  This is called only in the destructor of
+  // FunctionMocker.
+  static void UnregisterLocked(internal::UntypedFunctionMockerBase* mocker)
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(internal::g_gmock_mutex);
+};  // class Mock
+
+// An abstract handle of an expectation.  Useful in the .After()
+// clause of EXPECT_CALL() for setting the (partial) order of
+// expectations.  The syntax:
+//
+//   Expectation e1 = EXPECT_CALL(...)...;
+//   EXPECT_CALL(...).After(e1)...;
+//
+// sets two expectations where the latter can only be matched after
+// the former has been satisfied.
+//
+// Notes:
+//   - This class is copyable and has value semantics.
+//   - Constness is shallow: a const Expectation object itself cannot
+//     be modified, but the mutable methods of the ExpectationBase
+//     object it references can be called via expectation_base().
+
+class GTEST_API_ Expectation {
+ public:
+  // Constructs a null object that doesn't reference any expectation.
+  Expectation();
+  Expectation(Expectation&&) = default;
+  Expectation(const Expectation&) = default;
+  Expectation& operator=(Expectation&&) = default;
+  Expectation& operator=(const Expectation&) = default;
+  ~Expectation();
+
+  // This single-argument ctor must not be explicit, in order to support the
+  //   Expectation e = EXPECT_CALL(...);
+  // syntax.
+  //
+  // A TypedExpectation object stores its pre-requisites as
+  // Expectation objects, and needs to call the non-const Retire()
+  // method on the ExpectationBase objects they reference.  Therefore
+  // Expectation must receive a *non-const* reference to the
+  // ExpectationBase object.
+  Expectation(internal::ExpectationBase& exp);  // NOLINT
+
+  // The compiler-generated copy ctor and operator= work exactly as
+  // intended, so we don't need to define our own.
+
+  // Returns true if and only if rhs references the same expectation as this
+  // object does.
+  bool operator==(const Expectation& rhs) const {
+    return expectation_base_ == rhs.expectation_base_;
+  }
+
+  bool operator!=(const Expectation& rhs) const { return !(*this == rhs); }
+
+ private:
+  friend class ExpectationSet;
+  friend class Sequence;
+  friend class ::testing::internal::ExpectationBase;
+  friend class ::testing::internal::UntypedFunctionMockerBase;
+
+  template <typename F>
+  friend class ::testing::internal::FunctionMocker;
+
+  template <typename F>
+  friend class ::testing::internal::TypedExpectation;
+
+  // This comparator is needed for putting Expectation objects into a set.
+  class Less {
+   public:
+    bool operator()(const Expectation& lhs, const Expectation& rhs) const {
+      return lhs.expectation_base_.get() < rhs.expectation_base_.get();
+    }
+  };
+
+  typedef ::std::set<Expectation, Less> Set;
+
+  Expectation(
+      const std::shared_ptr<internal::ExpectationBase>& expectation_base);
+
+  // Returns the expectation this object references.
+  const std::shared_ptr<internal::ExpectationBase>& expectation_base() const {
+    return expectation_base_;
+  }
+
+  // A shared_ptr that co-owns the expectation this handle references.
+  std::shared_ptr<internal::ExpectationBase> expectation_base_;
+};
+
+// A set of expectation handles.  Useful in the .After() clause of
+// EXPECT_CALL() for setting the (partial) order of expectations.  The
+// syntax:
+//
+//   ExpectationSet es;
+//   es += EXPECT_CALL(...)...;
+//   es += EXPECT_CALL(...)...;
+//   EXPECT_CALL(...).After(es)...;
+//
+// sets three expectations where the last one can only be matched
+// after the first two have both been satisfied.
+//
+// This class is copyable and has value semantics.
+class ExpectationSet {
+ public:
+  // A bidirectional iterator that can read a const element in the set.
+  typedef Expectation::Set::const_iterator const_iterator;
+
+  // An object stored in the set.  This is an alias of Expectation.
+  typedef Expectation::Set::value_type value_type;
+
+  // Constructs an empty set.
+  ExpectationSet() {}
+
+  // This single-argument ctor must not be explicit, in order to support the
+  //   ExpectationSet es = EXPECT_CALL(...);
+  // syntax.
+  ExpectationSet(internal::ExpectationBase& exp) {  // NOLINT
+    *this += Expectation(exp);
+  }
+
+  // This single-argument ctor implements implicit conversion from
+  // Expectation and thus must not be explicit.  This allows either an
+  // Expectation or an ExpectationSet to be used in .After().
+  ExpectationSet(const Expectation& e) {  // NOLINT
+    *this += e;
+  }
+
+  // The compiler-generator ctor and operator= works exactly as
+  // intended, so we don't need to define our own.
+
+  // Returns true if and only if rhs contains the same set of Expectation
+  // objects as this does.
+  bool operator==(const ExpectationSet& rhs) const {
+    return expectations_ == rhs.expectations_;
+  }
+
+  bool operator!=(const ExpectationSet& rhs) const { return !(*this == rhs); }
+
+  // Implements the syntax
+  //   expectation_set += EXPECT_CALL(...);
+  ExpectationSet& operator+=(const Expectation& e) {
+    expectations_.insert(e);
+    return *this;
+  }
+
+  int size() const { return static_cast<int>(expectations_.size()); }
+
+  const_iterator begin() const { return expectations_.begin(); }
+  const_iterator end() const { return expectations_.end(); }
+
+ private:
+  Expectation::Set expectations_;
+};
+
+
+// Sequence objects are used by a user to specify the relative order
+// in which the expectations should match.  They are copyable (we rely
+// on the compiler-defined copy constructor and assignment operator).
+class GTEST_API_ Sequence {
+ public:
+  // Constructs an empty sequence.
+  Sequence() : last_expectation_(new Expectation) {}
+
+  // Adds an expectation to this sequence.  The caller must ensure
+  // that no other thread is accessing this Sequence object.
+  void AddExpectation(const Expectation& expectation) const;
+
+ private:
+  // The last expectation in this sequence.
+  std::shared_ptr<Expectation> last_expectation_;
+};  // class Sequence
+
+// An object of this type causes all EXPECT_CALL() statements
+// encountered in its scope to be put in an anonymous sequence.  The
+// work is done in the constructor and destructor.  You should only
+// create an InSequence object on the stack.
+//
+// The sole purpose for this class is to support easy definition of
+// sequential expectations, e.g.
+//
+//   {
+//     InSequence dummy;  // The name of the object doesn't matter.
+//
+//     // The following expectations must match in the order they appear.
+//     EXPECT_CALL(a, Bar())...;
+//     EXPECT_CALL(a, Baz())...;
+//     ...
+//     EXPECT_CALL(b, Xyz())...;
+//   }
+//
+// You can create InSequence objects in multiple threads, as long as
+// they are used to affect different mock objects.  The idea is that
+// each thread can create and set up its own mocks as if it's the only
+// thread.  However, for clarity of your tests we recommend you to set
+// up mocks in the main thread unless you have a good reason not to do
+// so.
+class GTEST_API_ InSequence {
+ public:
+  InSequence();
+  ~InSequence();
+ private:
+  bool sequence_created_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(InSequence);  // NOLINT
+} GTEST_ATTRIBUTE_UNUSED_;
+
+namespace internal {
+
+// Points to the implicit sequence introduced by a living InSequence
+// object (if any) in the current thread or NULL.
+GTEST_API_ extern ThreadLocal<Sequence*> g_gmock_implicit_sequence;
+
+// Base class for implementing expectations.
+//
+// There are two reasons for having a type-agnostic base class for
+// Expectation:
+//
+//   1. We need to store collections of expectations of different
+//   types (e.g. all pre-requisites of a particular expectation, all
+//   expectations in a sequence).  Therefore these expectation objects
+//   must share a common base class.
+//
+//   2. We can avoid binary code bloat by moving methods not depending
+//   on the template argument of Expectation to the base class.
+//
+// This class is internal and mustn't be used by user code directly.
+class GTEST_API_ ExpectationBase {
+ public:
+  // source_text is the EXPECT_CALL(...) source that created this Expectation.
+  ExpectationBase(const char* file, int line, const std::string& source_text);
+
+  virtual ~ExpectationBase();
+
+  // Where in the source file was the expectation spec defined?
+  const char* file() const { return file_; }
+  int line() const { return line_; }
+  const char* source_text() const { return source_text_.c_str(); }
+  // Returns the cardinality specified in the expectation spec.
+  const Cardinality& cardinality() const { return cardinality_; }
+
+  // Describes the source file location of this expectation.
+  void DescribeLocationTo(::std::ostream* os) const {
+    *os << FormatFileLocation(file(), line()) << " ";
+  }
+
+  // Describes how many times a function call matching this
+  // expectation has occurred.
+  void DescribeCallCountTo(::std::ostream* os) const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex);
+
+  // If this mock method has an extra matcher (i.e. .With(matcher)),
+  // describes it to the ostream.
+  virtual void MaybeDescribeExtraMatcherTo(::std::ostream* os) = 0;
+
+ protected:
+  friend class ::testing::Expectation;
+  friend class UntypedFunctionMockerBase;
+
+  enum Clause {
+    // Don't change the order of the enum members!
+    kNone,
+    kWith,
+    kTimes,
+    kInSequence,
+    kAfter,
+    kWillOnce,
+    kWillRepeatedly,
+    kRetiresOnSaturation
+  };
+
+  typedef std::vector<const void*> UntypedActions;
+
+  // Returns an Expectation object that references and co-owns this
+  // expectation.
+  virtual Expectation GetHandle() = 0;
+
+  // Asserts that the EXPECT_CALL() statement has the given property.
+  void AssertSpecProperty(bool property,
+                          const std::string& failure_message) const {
+    Assert(property, file_, line_, failure_message);
+  }
+
+  // Expects that the EXPECT_CALL() statement has the given property.
+  void ExpectSpecProperty(bool property,
+                          const std::string& failure_message) const {
+    Expect(property, file_, line_, failure_message);
+  }
+
+  // Explicitly specifies the cardinality of this expectation.  Used
+  // by the subclasses to implement the .Times() clause.
+  void SpecifyCardinality(const Cardinality& cardinality);
+
+  // Returns true if and only if the user specified the cardinality
+  // explicitly using a .Times().
+  bool cardinality_specified() const { return cardinality_specified_; }
+
+  // Sets the cardinality of this expectation spec.
+  void set_cardinality(const Cardinality& a_cardinality) {
+    cardinality_ = a_cardinality;
+  }
+
+  // The following group of methods should only be called after the
+  // EXPECT_CALL() statement, and only when g_gmock_mutex is held by
+  // the current thread.
+
+  // Retires all pre-requisites of this expectation.
+  void RetireAllPreRequisites()
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex);
+
+  // Returns true if and only if this expectation is retired.
+  bool is_retired() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return retired_;
+  }
+
+  // Retires this expectation.
+  void Retire()
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    retired_ = true;
+  }
+
+  // Returns true if and only if this expectation is satisfied.
+  bool IsSatisfied() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return cardinality().IsSatisfiedByCallCount(call_count_);
+  }
+
+  // Returns true if and only if this expectation is saturated.
+  bool IsSaturated() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return cardinality().IsSaturatedByCallCount(call_count_);
+  }
+
+  // Returns true if and only if this expectation is over-saturated.
+  bool IsOverSaturated() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return cardinality().IsOverSaturatedByCallCount(call_count_);
+  }
+
+  // Returns true if and only if all pre-requisites of this expectation are
+  // satisfied.
+  bool AllPrerequisitesAreSatisfied() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex);
+
+  // Adds unsatisfied pre-requisites of this expectation to 'result'.
+  void FindUnsatisfiedPrerequisites(ExpectationSet* result) const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex);
+
+  // Returns the number this expectation has been invoked.
+  int call_count() const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return call_count_;
+  }
+
+  // Increments the number this expectation has been invoked.
+  void IncrementCallCount()
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    call_count_++;
+  }
+
+  // Checks the action count (i.e. the number of WillOnce() and
+  // WillRepeatedly() clauses) against the cardinality if this hasn't
+  // been done before.  Prints a warning if there are too many or too
+  // few actions.
+  void CheckActionCountIfNotDone() const
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  friend class ::testing::Sequence;
+  friend class ::testing::internal::ExpectationTester;
+
+  template <typename Function>
+  friend class TypedExpectation;
+
+  // Implements the .Times() clause.
+  void UntypedTimes(const Cardinality& a_cardinality);
+
+  // This group of fields are part of the spec and won't change after
+  // an EXPECT_CALL() statement finishes.
+  const char* file_;          // The file that contains the expectation.
+  int line_;                  // The line number of the expectation.
+  const std::string source_text_;  // The EXPECT_CALL(...) source text.
+  // True if and only if the cardinality is specified explicitly.
+  bool cardinality_specified_;
+  Cardinality cardinality_;            // The cardinality of the expectation.
+  // The immediate pre-requisites (i.e. expectations that must be
+  // satisfied before this expectation can be matched) of this
+  // expectation.  We use std::shared_ptr in the set because we want an
+  // Expectation object to be co-owned by its FunctionMocker and its
+  // successors.  This allows multiple mock objects to be deleted at
+  // different times.
+  ExpectationSet immediate_prerequisites_;
+
+  // This group of fields are the current state of the expectation,
+  // and can change as the mock function is called.
+  int call_count_;  // How many times this expectation has been invoked.
+  bool retired_;    // True if and only if this expectation has retired.
+  UntypedActions untyped_actions_;
+  bool extra_matcher_specified_;
+  bool repeated_action_specified_;  // True if a WillRepeatedly() was specified.
+  bool retires_on_saturation_;
+  Clause last_clause_;
+  mutable bool action_count_checked_;  // Under mutex_.
+  mutable Mutex mutex_;  // Protects action_count_checked_.
+};  // class ExpectationBase
+
+// Impements an expectation for the given function type.
+template <typename F>
+class TypedExpectation : public ExpectationBase {
+ public:
+  typedef typename Function<F>::ArgumentTuple ArgumentTuple;
+  typedef typename Function<F>::ArgumentMatcherTuple ArgumentMatcherTuple;
+  typedef typename Function<F>::Result Result;
+
+  TypedExpectation(FunctionMocker<F>* owner, const char* a_file, int a_line,
+                   const std::string& a_source_text,
+                   const ArgumentMatcherTuple& m)
+      : ExpectationBase(a_file, a_line, a_source_text),
+        owner_(owner),
+        matchers_(m),
+        // By default, extra_matcher_ should match anything.  However,
+        // we cannot initialize it with _ as that causes ambiguity between
+        // Matcher's copy and move constructor for some argument types.
+        extra_matcher_(A<const ArgumentTuple&>()),
+        repeated_action_(DoDefault()) {}
+
+  ~TypedExpectation() override {
+    // Check the validity of the action count if it hasn't been done
+    // yet (for example, if the expectation was never used).
+    CheckActionCountIfNotDone();
+    for (UntypedActions::const_iterator it = untyped_actions_.begin();
+         it != untyped_actions_.end(); ++it) {
+      delete static_cast<const Action<F>*>(*it);
+    }
+  }
+
+  // Implements the .With() clause.
+  TypedExpectation& With(const Matcher<const ArgumentTuple&>& m) {
+    if (last_clause_ == kWith) {
+      ExpectSpecProperty(false,
+                         ".With() cannot appear "
+                         "more than once in an EXPECT_CALL().");
+    } else {
+      ExpectSpecProperty(last_clause_ < kWith,
+                         ".With() must be the first "
+                         "clause in an EXPECT_CALL().");
+    }
+    last_clause_ = kWith;
+
+    extra_matcher_ = m;
+    extra_matcher_specified_ = true;
+    return *this;
+  }
+
+  // Implements the .Times() clause.
+  TypedExpectation& Times(const Cardinality& a_cardinality) {
+    ExpectationBase::UntypedTimes(a_cardinality);
+    return *this;
+  }
+
+  // Implements the .Times() clause.
+  TypedExpectation& Times(int n) {
+    return Times(Exactly(n));
+  }
+
+  // Implements the .InSequence() clause.
+  TypedExpectation& InSequence(const Sequence& s) {
+    ExpectSpecProperty(last_clause_ <= kInSequence,
+                       ".InSequence() cannot appear after .After(),"
+                       " .WillOnce(), .WillRepeatedly(), or "
+                       ".RetiresOnSaturation().");
+    last_clause_ = kInSequence;
+
+    s.AddExpectation(GetHandle());
+    return *this;
+  }
+  TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2) {
+    return InSequence(s1).InSequence(s2);
+  }
+  TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2,
+                               const Sequence& s3) {
+    return InSequence(s1, s2).InSequence(s3);
+  }
+  TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2,
+                               const Sequence& s3, const Sequence& s4) {
+    return InSequence(s1, s2, s3).InSequence(s4);
+  }
+  TypedExpectation& InSequence(const Sequence& s1, const Sequence& s2,
+                               const Sequence& s3, const Sequence& s4,
+                               const Sequence& s5) {
+    return InSequence(s1, s2, s3, s4).InSequence(s5);
+  }
+
+  // Implements that .After() clause.
+  TypedExpectation& After(const ExpectationSet& s) {
+    ExpectSpecProperty(last_clause_ <= kAfter,
+                       ".After() cannot appear after .WillOnce(),"
+                       " .WillRepeatedly(), or "
+                       ".RetiresOnSaturation().");
+    last_clause_ = kAfter;
+
+    for (ExpectationSet::const_iterator it = s.begin(); it != s.end(); ++it) {
+      immediate_prerequisites_ += *it;
+    }
+    return *this;
+  }
+  TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2) {
+    return After(s1).After(s2);
+  }
+  TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2,
+                          const ExpectationSet& s3) {
+    return After(s1, s2).After(s3);
+  }
+  TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2,
+                          const ExpectationSet& s3, const ExpectationSet& s4) {
+    return After(s1, s2, s3).After(s4);
+  }
+  TypedExpectation& After(const ExpectationSet& s1, const ExpectationSet& s2,
+                          const ExpectationSet& s3, const ExpectationSet& s4,
+                          const ExpectationSet& s5) {
+    return After(s1, s2, s3, s4).After(s5);
+  }
+
+  // Implements the .WillOnce() clause.
+  TypedExpectation& WillOnce(const Action<F>& action) {
+    ExpectSpecProperty(last_clause_ <= kWillOnce,
+                       ".WillOnce() cannot appear after "
+                       ".WillRepeatedly() or .RetiresOnSaturation().");
+    last_clause_ = kWillOnce;
+
+    untyped_actions_.push_back(new Action<F>(action));
+    if (!cardinality_specified()) {
+      set_cardinality(Exactly(static_cast<int>(untyped_actions_.size())));
+    }
+    return *this;
+  }
+
+  // Implements the .WillRepeatedly() clause.
+  TypedExpectation& WillRepeatedly(const Action<F>& action) {
+    if (last_clause_ == kWillRepeatedly) {
+      ExpectSpecProperty(false,
+                         ".WillRepeatedly() cannot appear "
+                         "more than once in an EXPECT_CALL().");
+    } else {
+      ExpectSpecProperty(last_clause_ < kWillRepeatedly,
+                         ".WillRepeatedly() cannot appear "
+                         "after .RetiresOnSaturation().");
+    }
+    last_clause_ = kWillRepeatedly;
+    repeated_action_specified_ = true;
+
+    repeated_action_ = action;
+    if (!cardinality_specified()) {
+      set_cardinality(AtLeast(static_cast<int>(untyped_actions_.size())));
+    }
+
+    // Now that no more action clauses can be specified, we check
+    // whether their count makes sense.
+    CheckActionCountIfNotDone();
+    return *this;
+  }
+
+  // Implements the .RetiresOnSaturation() clause.
+  TypedExpectation& RetiresOnSaturation() {
+    ExpectSpecProperty(last_clause_ < kRetiresOnSaturation,
+                       ".RetiresOnSaturation() cannot appear "
+                       "more than once.");
+    last_clause_ = kRetiresOnSaturation;
+    retires_on_saturation_ = true;
+
+    // Now that no more action clauses can be specified, we check
+    // whether their count makes sense.
+    CheckActionCountIfNotDone();
+    return *this;
+  }
+
+  // Returns the matchers for the arguments as specified inside the
+  // EXPECT_CALL() macro.
+  const ArgumentMatcherTuple& matchers() const {
+    return matchers_;
+  }
+
+  // Returns the matcher specified by the .With() clause.
+  const Matcher<const ArgumentTuple&>& extra_matcher() const {
+    return extra_matcher_;
+  }
+
+  // Returns the action specified by the .WillRepeatedly() clause.
+  const Action<F>& repeated_action() const { return repeated_action_; }
+
+  // If this mock method has an extra matcher (i.e. .With(matcher)),
+  // describes it to the ostream.
+  void MaybeDescribeExtraMatcherTo(::std::ostream* os) override {
+    if (extra_matcher_specified_) {
+      *os << "    Expected args: ";
+      extra_matcher_.DescribeTo(os);
+      *os << "\n";
+    }
+  }
+
+ private:
+  template <typename Function>
+  friend class FunctionMocker;
+
+  // Returns an Expectation object that references and co-owns this
+  // expectation.
+  Expectation GetHandle() override { return owner_->GetHandleOf(this); }
+
+  // The following methods will be called only after the EXPECT_CALL()
+  // statement finishes and when the current thread holds
+  // g_gmock_mutex.
+
+  // Returns true if and only if this expectation matches the given arguments.
+  bool Matches(const ArgumentTuple& args) const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    return TupleMatches(matchers_, args) && extra_matcher_.Matches(args);
+  }
+
+  // Returns true if and only if this expectation should handle the given
+  // arguments.
+  bool ShouldHandleArguments(const ArgumentTuple& args) const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+
+    // In case the action count wasn't checked when the expectation
+    // was defined (e.g. if this expectation has no WillRepeatedly()
+    // or RetiresOnSaturation() clause), we check it when the
+    // expectation is used for the first time.
+    CheckActionCountIfNotDone();
+    return !is_retired() && AllPrerequisitesAreSatisfied() && Matches(args);
+  }
+
+  // Describes the result of matching the arguments against this
+  // expectation to the given ostream.
+  void ExplainMatchResultTo(
+      const ArgumentTuple& args,
+      ::std::ostream* os) const
+          GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+
+    if (is_retired()) {
+      *os << "         Expected: the expectation is active\n"
+          << "           Actual: it is retired\n";
+    } else if (!Matches(args)) {
+      if (!TupleMatches(matchers_, args)) {
+        ExplainMatchFailureTupleTo(matchers_, args, os);
+      }
+      StringMatchResultListener listener;
+      if (!extra_matcher_.MatchAndExplain(args, &listener)) {
+        *os << "    Expected args: ";
+        extra_matcher_.DescribeTo(os);
+        *os << "\n           Actual: don't match";
+
+        internal::PrintIfNotEmpty(listener.str(), os);
+        *os << "\n";
+      }
+    } else if (!AllPrerequisitesAreSatisfied()) {
+      *os << "         Expected: all pre-requisites are satisfied\n"
+          << "           Actual: the following immediate pre-requisites "
+          << "are not satisfied:\n";
+      ExpectationSet unsatisfied_prereqs;
+      FindUnsatisfiedPrerequisites(&unsatisfied_prereqs);
+      int i = 0;
+      for (ExpectationSet::const_iterator it = unsatisfied_prereqs.begin();
+           it != unsatisfied_prereqs.end(); ++it) {
+        it->expectation_base()->DescribeLocationTo(os);
+        *os << "pre-requisite #" << i++ << "\n";
+      }
+      *os << "                   (end of pre-requisites)\n";
+    } else {
+      // This line is here just for completeness' sake.  It will never
+      // be executed as currently the ExplainMatchResultTo() function
+      // is called only when the mock function call does NOT match the
+      // expectation.
+      *os << "The call matches the expectation.\n";
+    }
+  }
+
+  // Returns the action that should be taken for the current invocation.
+  const Action<F>& GetCurrentAction(const FunctionMocker<F>* mocker,
+                                    const ArgumentTuple& args) const
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    const int count = call_count();
+    Assert(count >= 1, __FILE__, __LINE__,
+           "call_count() is <= 0 when GetCurrentAction() is "
+           "called - this should never happen.");
+
+    const int action_count = static_cast<int>(untyped_actions_.size());
+    if (action_count > 0 && !repeated_action_specified_ &&
+        count > action_count) {
+      // If there is at least one WillOnce() and no WillRepeatedly(),
+      // we warn the user when the WillOnce() clauses ran out.
+      ::std::stringstream ss;
+      DescribeLocationTo(&ss);
+      ss << "Actions ran out in " << source_text() << "...\n"
+         << "Called " << count << " times, but only "
+         << action_count << " WillOnce()"
+         << (action_count == 1 ? " is" : "s are") << " specified - ";
+      mocker->DescribeDefaultActionTo(args, &ss);
+      Log(kWarning, ss.str(), 1);
+    }
+
+    return count <= action_count
+               ? *static_cast<const Action<F>*>(
+                     untyped_actions_[static_cast<size_t>(count - 1)])
+               : repeated_action();
+  }
+
+  // Given the arguments of a mock function call, if the call will
+  // over-saturate this expectation, returns the default action;
+  // otherwise, returns the next action in this expectation.  Also
+  // describes *what* happened to 'what', and explains *why* Google
+  // Mock does it to 'why'.  This method is not const as it calls
+  // IncrementCallCount().  A return value of NULL means the default
+  // action.
+  const Action<F>* GetActionForArguments(const FunctionMocker<F>* mocker,
+                                         const ArgumentTuple& args,
+                                         ::std::ostream* what,
+                                         ::std::ostream* why)
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    if (IsSaturated()) {
+      // We have an excessive call.
+      IncrementCallCount();
+      *what << "Mock function called more times than expected - ";
+      mocker->DescribeDefaultActionTo(args, what);
+      DescribeCallCountTo(why);
+
+      return nullptr;
+    }
+
+    IncrementCallCount();
+    RetireAllPreRequisites();
+
+    if (retires_on_saturation_ && IsSaturated()) {
+      Retire();
+    }
+
+    // Must be done after IncrementCount()!
+    *what << "Mock function call matches " << source_text() <<"...\n";
+    return &(GetCurrentAction(mocker, args));
+  }
+
+  // All the fields below won't change once the EXPECT_CALL()
+  // statement finishes.
+  FunctionMocker<F>* const owner_;
+  ArgumentMatcherTuple matchers_;
+  Matcher<const ArgumentTuple&> extra_matcher_;
+  Action<F> repeated_action_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TypedExpectation);
+};  // class TypedExpectation
+
+// A MockSpec object is used by ON_CALL() or EXPECT_CALL() for
+// specifying the default behavior of, or expectation on, a mock
+// function.
+
+// Note: class MockSpec really belongs to the ::testing namespace.
+// However if we define it in ::testing, MSVC will complain when
+// classes in ::testing::internal declare it as a friend class
+// template.  To workaround this compiler bug, we define MockSpec in
+// ::testing::internal and import it into ::testing.
+
+// Logs a message including file and line number information.
+GTEST_API_ void LogWithLocation(testing::internal::LogSeverity severity,
+                                const char* file, int line,
+                                const std::string& message);
+
+template <typename F>
+class MockSpec {
+ public:
+  typedef typename internal::Function<F>::ArgumentTuple ArgumentTuple;
+  typedef typename internal::Function<F>::ArgumentMatcherTuple
+      ArgumentMatcherTuple;
+
+  // Constructs a MockSpec object, given the function mocker object
+  // that the spec is associated with.
+  MockSpec(internal::FunctionMocker<F>* function_mocker,
+           const ArgumentMatcherTuple& matchers)
+      : function_mocker_(function_mocker), matchers_(matchers) {}
+
+  // Adds a new default action spec to the function mocker and returns
+  // the newly created spec.
+  internal::OnCallSpec<F>& InternalDefaultActionSetAt(
+      const char* file, int line, const char* obj, const char* call) {
+    LogWithLocation(internal::kInfo, file, line,
+                    std::string("ON_CALL(") + obj + ", " + call + ") invoked");
+    return function_mocker_->AddNewOnCallSpec(file, line, matchers_);
+  }
+
+  // Adds a new expectation spec to the function mocker and returns
+  // the newly created spec.
+  internal::TypedExpectation<F>& InternalExpectedAt(
+      const char* file, int line, const char* obj, const char* call) {
+    const std::string source_text(std::string("EXPECT_CALL(") + obj + ", " +
+                                  call + ")");
+    LogWithLocation(internal::kInfo, file, line, source_text + " invoked");
+    return function_mocker_->AddNewExpectation(
+        file, line, source_text, matchers_);
+  }
+
+  // This operator overload is used to swallow the superfluous parameter list
+  // introduced by the ON/EXPECT_CALL macros. See the macro comments for more
+  // explanation.
+  MockSpec<F>& operator()(const internal::WithoutMatchers&, void* const) {
+    return *this;
+  }
+
+ private:
+  template <typename Function>
+  friend class internal::FunctionMocker;
+
+  // The function mocker that owns this spec.
+  internal::FunctionMocker<F>* const function_mocker_;
+  // The argument matchers specified in the spec.
+  ArgumentMatcherTuple matchers_;
+};  // class MockSpec
+
+// Wrapper type for generically holding an ordinary value or lvalue reference.
+// If T is not a reference type, it must be copyable or movable.
+// ReferenceOrValueWrapper<T> is movable, and will also be copyable unless
+// T is a move-only value type (which means that it will always be copyable
+// if the current platform does not support move semantics).
+//
+// The primary template defines handling for values, but function header
+// comments describe the contract for the whole template (including
+// specializations).
+template <typename T>
+class ReferenceOrValueWrapper {
+ public:
+  // Constructs a wrapper from the given value/reference.
+  explicit ReferenceOrValueWrapper(T value)
+      : value_(std::move(value)) {
+  }
+
+  // Unwraps and returns the underlying value/reference, exactly as
+  // originally passed. The behavior of calling this more than once on
+  // the same object is unspecified.
+  T Unwrap() { return std::move(value_); }
+
+  // Provides nondestructive access to the underlying value/reference.
+  // Always returns a const reference (more precisely,
+  // const std::add_lvalue_reference<T>::type). The behavior of calling this
+  // after calling Unwrap on the same object is unspecified.
+  const T& Peek() const {
+    return value_;
+  }
+
+ private:
+  T value_;
+};
+
+// Specialization for lvalue reference types. See primary template
+// for documentation.
+template <typename T>
+class ReferenceOrValueWrapper<T&> {
+ public:
+  // Workaround for debatable pass-by-reference lint warning (c-library-team
+  // policy precludes NOLINT in this context)
+  typedef T& reference;
+  explicit ReferenceOrValueWrapper(reference ref)
+      : value_ptr_(&ref) {}
+  T& Unwrap() { return *value_ptr_; }
+  const T& Peek() const { return *value_ptr_; }
+
+ private:
+  T* value_ptr_;
+};
+
+// C++ treats the void type specially.  For example, you cannot define
+// a void-typed variable or pass a void value to a function.
+// ActionResultHolder<T> holds a value of type T, where T must be a
+// copyable type or void (T doesn't need to be default-constructable).
+// It hides the syntactic difference between void and other types, and
+// is used to unify the code for invoking both void-returning and
+// non-void-returning mock functions.
+
+// Untyped base class for ActionResultHolder<T>.
+class UntypedActionResultHolderBase {
+ public:
+  virtual ~UntypedActionResultHolderBase() {}
+
+  // Prints the held value as an action's result to os.
+  virtual void PrintAsActionResult(::std::ostream* os) const = 0;
+};
+
+// This generic definition is used when T is not void.
+template <typename T>
+class ActionResultHolder : public UntypedActionResultHolderBase {
+ public:
+  // Returns the held value. Must not be called more than once.
+  T Unwrap() {
+    return result_.Unwrap();
+  }
+
+  // Prints the held value as an action's result to os.
+  void PrintAsActionResult(::std::ostream* os) const override {
+    *os << "\n          Returns: ";
+    // T may be a reference type, so we don't use UniversalPrint().
+    UniversalPrinter<T>::Print(result_.Peek(), os);
+  }
+
+  // Performs the given mock function's default action and returns the
+  // result in a new-ed ActionResultHolder.
+  template <typename F>
+  static ActionResultHolder* PerformDefaultAction(
+      const FunctionMocker<F>* func_mocker,
+      typename Function<F>::ArgumentTuple&& args,
+      const std::string& call_description) {
+    return new ActionResultHolder(Wrapper(func_mocker->PerformDefaultAction(
+        std::move(args), call_description)));
+  }
+
+  // Performs the given action and returns the result in a new-ed
+  // ActionResultHolder.
+  template <typename F>
+  static ActionResultHolder* PerformAction(
+      const Action<F>& action, typename Function<F>::ArgumentTuple&& args) {
+    return new ActionResultHolder(
+        Wrapper(action.Perform(std::move(args))));
+  }
+
+ private:
+  typedef ReferenceOrValueWrapper<T> Wrapper;
+
+  explicit ActionResultHolder(Wrapper result)
+      : result_(std::move(result)) {
+  }
+
+  Wrapper result_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionResultHolder);
+};
+
+// Specialization for T = void.
+template <>
+class ActionResultHolder<void> : public UntypedActionResultHolderBase {
+ public:
+  void Unwrap() { }
+
+  void PrintAsActionResult(::std::ostream* /* os */) const override {}
+
+  // Performs the given mock function's default action and returns ownership
+  // of an empty ActionResultHolder*.
+  template <typename F>
+  static ActionResultHolder* PerformDefaultAction(
+      const FunctionMocker<F>* func_mocker,
+      typename Function<F>::ArgumentTuple&& args,
+      const std::string& call_description) {
+    func_mocker->PerformDefaultAction(std::move(args), call_description);
+    return new ActionResultHolder;
+  }
+
+  // Performs the given action and returns ownership of an empty
+  // ActionResultHolder*.
+  template <typename F>
+  static ActionResultHolder* PerformAction(
+      const Action<F>& action, typename Function<F>::ArgumentTuple&& args) {
+    action.Perform(std::move(args));
+    return new ActionResultHolder;
+  }
+
+ private:
+  ActionResultHolder() {}
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ActionResultHolder);
+};
+
+template <typename F>
+class FunctionMocker;
+
+template <typename R, typename... Args>
+class FunctionMocker<R(Args...)> final : public UntypedFunctionMockerBase {
+  using F = R(Args...);
+
+ public:
+  using Result = R;
+  using ArgumentTuple = std::tuple<Args...>;
+  using ArgumentMatcherTuple = std::tuple<Matcher<Args>...>;
+
+  FunctionMocker() {}
+
+  // There is no generally useful and implementable semantics of
+  // copying a mock object, so copying a mock is usually a user error.
+  // Thus we disallow copying function mockers.  If the user really
+  // wants to copy a mock object, they should implement their own copy
+  // operation, for example:
+  //
+  //   class MockFoo : public Foo {
+  //    public:
+  //     // Defines a copy constructor explicitly.
+  //     MockFoo(const MockFoo& src) {}
+  //     ...
+  //   };
+  FunctionMocker(const FunctionMocker&) = delete;
+  FunctionMocker& operator=(const FunctionMocker&) = delete;
+
+  // The destructor verifies that all expectations on this mock
+  // function have been satisfied.  If not, it will report Google Test
+  // non-fatal failures for the violations.
+  ~FunctionMocker() override GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    MutexLock l(&g_gmock_mutex);
+    VerifyAndClearExpectationsLocked();
+    Mock::UnregisterLocked(this);
+    ClearDefaultActionsLocked();
+  }
+
+  // Returns the ON_CALL spec that matches this mock function with the
+  // given arguments; returns NULL if no matching ON_CALL is found.
+  // L = *
+  const OnCallSpec<F>* FindOnCallSpec(
+      const ArgumentTuple& args) const {
+    for (UntypedOnCallSpecs::const_reverse_iterator it
+             = untyped_on_call_specs_.rbegin();
+         it != untyped_on_call_specs_.rend(); ++it) {
+      const OnCallSpec<F>* spec = static_cast<const OnCallSpec<F>*>(*it);
+      if (spec->Matches(args))
+        return spec;
+    }
+
+    return nullptr;
+  }
+
+  // Performs the default action of this mock function on the given
+  // arguments and returns the result. Asserts (or throws if
+  // exceptions are enabled) with a helpful call descrption if there
+  // is no valid return value. This method doesn't depend on the
+  // mutable state of this object, and thus can be called concurrently
+  // without locking.
+  // L = *
+  Result PerformDefaultAction(ArgumentTuple&& args,
+                              const std::string& call_description) const {
+    const OnCallSpec<F>* const spec =
+        this->FindOnCallSpec(args);
+    if (spec != nullptr) {
+      return spec->GetAction().Perform(std::move(args));
+    }
+    const std::string message =
+        call_description +
+        "\n    The mock function has no default action "
+        "set, and its return type has no default value set.";
+#if GTEST_HAS_EXCEPTIONS
+    if (!DefaultValue<Result>::Exists()) {
+      throw std::runtime_error(message);
+    }
+#else
+    Assert(DefaultValue<Result>::Exists(), "", -1, message);
+#endif
+    return DefaultValue<Result>::Get();
+  }
+
+  // Performs the default action with the given arguments and returns
+  // the action's result.  The call description string will be used in
+  // the error message to describe the call in the case the default
+  // action fails.  The caller is responsible for deleting the result.
+  // L = *
+  UntypedActionResultHolderBase* UntypedPerformDefaultAction(
+      void* untyped_args,  // must point to an ArgumentTuple
+      const std::string& call_description) const override {
+    ArgumentTuple* args = static_cast<ArgumentTuple*>(untyped_args);
+    return ResultHolder::PerformDefaultAction(this, std::move(*args),
+                                              call_description);
+  }
+
+  // Performs the given action with the given arguments and returns
+  // the action's result.  The caller is responsible for deleting the
+  // result.
+  // L = *
+  UntypedActionResultHolderBase* UntypedPerformAction(
+      const void* untyped_action, void* untyped_args) const override {
+    // Make a copy of the action before performing it, in case the
+    // action deletes the mock object (and thus deletes itself).
+    const Action<F> action = *static_cast<const Action<F>*>(untyped_action);
+    ArgumentTuple* args = static_cast<ArgumentTuple*>(untyped_args);
+    return ResultHolder::PerformAction(action, std::move(*args));
+  }
+
+  // Implements UntypedFunctionMockerBase::ClearDefaultActionsLocked():
+  // clears the ON_CALL()s set on this mock function.
+  void ClearDefaultActionsLocked() override
+      GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+
+    // Deleting our default actions may trigger other mock objects to be
+    // deleted, for example if an action contains a reference counted smart
+    // pointer to that mock object, and that is the last reference. So if we
+    // delete our actions within the context of the global mutex we may deadlock
+    // when this method is called again. Instead, make a copy of the set of
+    // actions to delete, clear our set within the mutex, and then delete the
+    // actions outside of the mutex.
+    UntypedOnCallSpecs specs_to_delete;
+    untyped_on_call_specs_.swap(specs_to_delete);
+
+    g_gmock_mutex.Unlock();
+    for (UntypedOnCallSpecs::const_iterator it =
+             specs_to_delete.begin();
+         it != specs_to_delete.end(); ++it) {
+      delete static_cast<const OnCallSpec<F>*>(*it);
+    }
+
+    // Lock the mutex again, since the caller expects it to be locked when we
+    // return.
+    g_gmock_mutex.Lock();
+  }
+
+  // Returns the result of invoking this mock function with the given
+  // arguments.  This function can be safely called from multiple
+  // threads concurrently.
+  Result Invoke(Args... args) GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    ArgumentTuple tuple(std::forward<Args>(args)...);
+    std::unique_ptr<ResultHolder> holder(DownCast_<ResultHolder*>(
+        this->UntypedInvokeWith(static_cast<void*>(&tuple))));
+    return holder->Unwrap();
+  }
+
+  MockSpec<F> With(Matcher<Args>... m) {
+    return MockSpec<F>(this, ::std::make_tuple(std::move(m)...));
+  }
+
+ protected:
+  template <typename Function>
+  friend class MockSpec;
+
+  typedef ActionResultHolder<Result> ResultHolder;
+
+  // Adds and returns a default action spec for this mock function.
+  OnCallSpec<F>& AddNewOnCallSpec(
+      const char* file, int line,
+      const ArgumentMatcherTuple& m)
+          GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line);
+    OnCallSpec<F>* const on_call_spec = new OnCallSpec<F>(file, line, m);
+    untyped_on_call_specs_.push_back(on_call_spec);
+    return *on_call_spec;
+  }
+
+  // Adds and returns an expectation spec for this mock function.
+  TypedExpectation<F>& AddNewExpectation(const char* file, int line,
+                                         const std::string& source_text,
+                                         const ArgumentMatcherTuple& m)
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    Mock::RegisterUseByOnCallOrExpectCall(MockObject(), file, line);
+    TypedExpectation<F>* const expectation =
+        new TypedExpectation<F>(this, file, line, source_text, m);
+    const std::shared_ptr<ExpectationBase> untyped_expectation(expectation);
+    // See the definition of untyped_expectations_ for why access to
+    // it is unprotected here.
+    untyped_expectations_.push_back(untyped_expectation);
+
+    // Adds this expectation into the implicit sequence if there is one.
+    Sequence* const implicit_sequence = g_gmock_implicit_sequence.get();
+    if (implicit_sequence != nullptr) {
+      implicit_sequence->AddExpectation(Expectation(untyped_expectation));
+    }
+
+    return *expectation;
+  }
+
+ private:
+  template <typename Func> friend class TypedExpectation;
+
+  // Some utilities needed for implementing UntypedInvokeWith().
+
+  // Describes what default action will be performed for the given
+  // arguments.
+  // L = *
+  void DescribeDefaultActionTo(const ArgumentTuple& args,
+                               ::std::ostream* os) const {
+    const OnCallSpec<F>* const spec = FindOnCallSpec(args);
+
+    if (spec == nullptr) {
+      *os << (std::is_void<Result>::value ? "returning directly.\n"
+                                          : "returning default value.\n");
+    } else {
+      *os << "taking default action specified at:\n"
+          << FormatFileLocation(spec->file(), spec->line()) << "\n";
+    }
+  }
+
+  // Writes a message that the call is uninteresting (i.e. neither
+  // explicitly expected nor explicitly unexpected) to the given
+  // ostream.
+  void UntypedDescribeUninterestingCall(const void* untyped_args,
+                                        ::std::ostream* os) const override
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    const ArgumentTuple& args =
+        *static_cast<const ArgumentTuple*>(untyped_args);
+    *os << "Uninteresting mock function call - ";
+    DescribeDefaultActionTo(args, os);
+    *os << "    Function call: " << Name();
+    UniversalPrint(args, os);
+  }
+
+  // Returns the expectation that matches the given function arguments
+  // (or NULL is there's no match); when a match is found,
+  // untyped_action is set to point to the action that should be
+  // performed (or NULL if the action is "do default"), and
+  // is_excessive is modified to indicate whether the call exceeds the
+  // expected number.
+  //
+  // Critical section: We must find the matching expectation and the
+  // corresponding action that needs to be taken in an ATOMIC
+  // transaction.  Otherwise another thread may call this mock
+  // method in the middle and mess up the state.
+  //
+  // However, performing the action has to be left out of the critical
+  // section.  The reason is that we have no control on what the
+  // action does (it can invoke an arbitrary user function or even a
+  // mock function) and excessive locking could cause a dead lock.
+  const ExpectationBase* UntypedFindMatchingExpectation(
+      const void* untyped_args, const void** untyped_action, bool* is_excessive,
+      ::std::ostream* what, ::std::ostream* why) override
+      GTEST_LOCK_EXCLUDED_(g_gmock_mutex) {
+    const ArgumentTuple& args =
+        *static_cast<const ArgumentTuple*>(untyped_args);
+    MutexLock l(&g_gmock_mutex);
+    TypedExpectation<F>* exp = this->FindMatchingExpectationLocked(args);
+    if (exp == nullptr) {  // A match wasn't found.
+      this->FormatUnexpectedCallMessageLocked(args, what, why);
+      return nullptr;
+    }
+
+    // This line must be done before calling GetActionForArguments(),
+    // which will increment the call count for *exp and thus affect
+    // its saturation status.
+    *is_excessive = exp->IsSaturated();
+    const Action<F>* action = exp->GetActionForArguments(this, args, what, why);
+    if (action != nullptr && action->IsDoDefault())
+      action = nullptr;  // Normalize "do default" to NULL.
+    *untyped_action = action;
+    return exp;
+  }
+
+  // Prints the given function arguments to the ostream.
+  void UntypedPrintArgs(const void* untyped_args,
+                        ::std::ostream* os) const override {
+    const ArgumentTuple& args =
+        *static_cast<const ArgumentTuple*>(untyped_args);
+    UniversalPrint(args, os);
+  }
+
+  // Returns the expectation that matches the arguments, or NULL if no
+  // expectation matches them.
+  TypedExpectation<F>* FindMatchingExpectationLocked(
+      const ArgumentTuple& args) const
+          GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    // See the definition of untyped_expectations_ for why access to
+    // it is unprotected here.
+    for (typename UntypedExpectations::const_reverse_iterator it =
+             untyped_expectations_.rbegin();
+         it != untyped_expectations_.rend(); ++it) {
+      TypedExpectation<F>* const exp =
+          static_cast<TypedExpectation<F>*>(it->get());
+      if (exp->ShouldHandleArguments(args)) {
+        return exp;
+      }
+    }
+    return nullptr;
+  }
+
+  // Returns a message that the arguments don't match any expectation.
+  void FormatUnexpectedCallMessageLocked(
+      const ArgumentTuple& args,
+      ::std::ostream* os,
+      ::std::ostream* why) const
+          GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    *os << "\nUnexpected mock function call - ";
+    DescribeDefaultActionTo(args, os);
+    PrintTriedExpectationsLocked(args, why);
+  }
+
+  // Prints a list of expectations that have been tried against the
+  // current mock function call.
+  void PrintTriedExpectationsLocked(
+      const ArgumentTuple& args,
+      ::std::ostream* why) const
+          GTEST_EXCLUSIVE_LOCK_REQUIRED_(g_gmock_mutex) {
+    g_gmock_mutex.AssertHeld();
+    const size_t count = untyped_expectations_.size();
+    *why << "Google Mock tried the following " << count << " "
+         << (count == 1 ? "expectation, but it didn't match" :
+             "expectations, but none matched")
+         << ":\n";
+    for (size_t i = 0; i < count; i++) {
+      TypedExpectation<F>* const expectation =
+          static_cast<TypedExpectation<F>*>(untyped_expectations_[i].get());
+      *why << "\n";
+      expectation->DescribeLocationTo(why);
+      if (count > 1) {
+        *why << "tried expectation #" << i << ": ";
+      }
+      *why << expectation->source_text() << "...\n";
+      expectation->ExplainMatchResultTo(args, why);
+      expectation->DescribeCallCountTo(why);
+    }
+  }
+};  // class FunctionMocker
+
+// Reports an uninteresting call (whose description is in msg) in the
+// manner specified by 'reaction'.
+void ReportUninterestingCall(CallReaction reaction, const std::string& msg);
+
+}  // namespace internal
+
+namespace internal {
+
+template <typename F>
+class MockFunction;
+
+template <typename R, typename... Args>
+class MockFunction<R(Args...)> {
+ public:
+  MockFunction(const MockFunction&) = delete;
+  MockFunction& operator=(const MockFunction&) = delete;
+
+  std::function<R(Args...)> AsStdFunction() {
+    return [this](Args... args) -> R {
+      return this->Call(std::forward<Args>(args)...);
+    };
+  }
+
+  // Implementation detail: the expansion of the MOCK_METHOD macro.
+  R Call(Args... args) {
+    mock_.SetOwnerAndName(this, "Call");
+    return mock_.Invoke(std::forward<Args>(args)...);
+  }
+
+  MockSpec<R(Args...)> gmock_Call(Matcher<Args>... m) {
+    mock_.RegisterOwner(this);
+    return mock_.With(std::move(m)...);
+  }
+
+  MockSpec<R(Args...)> gmock_Call(const WithoutMatchers&, R (*)(Args...)) {
+    return this->gmock_Call(::testing::A<Args>()...);
+  }
+
+ protected:
+  MockFunction() = default;
+  ~MockFunction() = default;
+
+ private:
+  FunctionMocker<R(Args...)> mock_;
+};
+
+/*
+The SignatureOf<F> struct is a meta-function returning function signature
+corresponding to the provided F argument.
+
+It makes use of MockFunction easier by allowing it to accept more F arguments
+than just function signatures.
+
+Specializations provided here cover only a signature type itself and
+std::function. However, if need be it can be easily extended to cover also other
+types (like for example boost::function).
+*/
+
+template <typename F>
+struct SignatureOf;
+
+template <typename R, typename... Args>
+struct SignatureOf<R(Args...)> {
+  using type = R(Args...);
+};
+
+template <typename F>
+struct SignatureOf<std::function<F>> : SignatureOf<F> {};
+
+template <typename F>
+using SignatureOfT = typename SignatureOf<F>::type;
+
+}  // namespace internal
+
+// A MockFunction<F> type has one mock method whose type is
+// internal::SignatureOfT<F>.  It is useful when you just want your
+// test code to emit some messages and have Google Mock verify the
+// right messages are sent (and perhaps at the right times).  For
+// example, if you are exercising code:
+//
+//   Foo(1);
+//   Foo(2);
+//   Foo(3);
+//
+// and want to verify that Foo(1) and Foo(3) both invoke
+// mock.Bar("a"), but Foo(2) doesn't invoke anything, you can write:
+//
+// TEST(FooTest, InvokesBarCorrectly) {
+//   MyMock mock;
+//   MockFunction<void(string check_point_name)> check;
+//   {
+//     InSequence s;
+//
+//     EXPECT_CALL(mock, Bar("a"));
+//     EXPECT_CALL(check, Call("1"));
+//     EXPECT_CALL(check, Call("2"));
+//     EXPECT_CALL(mock, Bar("a"));
+//   }
+//   Foo(1);
+//   check.Call("1");
+//   Foo(2);
+//   check.Call("2");
+//   Foo(3);
+// }
+//
+// The expectation spec says that the first Bar("a") must happen
+// before check point "1", the second Bar("a") must happen after check
+// point "2", and nothing should happen between the two check
+// points. The explicit check points make it easy to tell which
+// Bar("a") is called by which call to Foo().
+//
+// MockFunction<F> can also be used to exercise code that accepts
+// std::function<internal::SignatureOfT<F>> callbacks. To do so, use
+// AsStdFunction() method to create std::function proxy forwarding to
+// original object's Call. Example:
+//
+// TEST(FooTest, RunsCallbackWithBarArgument) {
+//   MockFunction<int(string)> callback;
+//   EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1));
+//   Foo(callback.AsStdFunction());
+// }
+//
+// The internal::SignatureOfT<F> indirection allows to use other types
+// than just function signature type. This is typically useful when
+// providing a mock for a predefined std::function type. Example:
+//
+// using FilterPredicate = std::function<bool(string)>;
+// void MyFilterAlgorithm(FilterPredicate predicate);
+//
+// TEST(FooTest, FilterPredicateAlwaysAccepts) {
+//   MockFunction<FilterPredicate> predicateMock;
+//   EXPECT_CALL(predicateMock, Call(_)).WillRepeatedly(Return(true));
+//   MyFilterAlgorithm(predicateMock.AsStdFunction());
+// }
+template <typename F>
+class MockFunction : public internal::MockFunction<internal::SignatureOfT<F>> {
+  using Base = internal::MockFunction<internal::SignatureOfT<F>>;
+
+ public:
+  using Base::Base;
+};
+
+// The style guide prohibits "using" statements in a namespace scope
+// inside a header file.  However, the MockSpec class template is
+// meant to be defined in the ::testing namespace.  The following line
+// is just a trick for working around a bug in MSVC 8.0, which cannot
+// handle it if we define MockSpec in ::testing.
+using internal::MockSpec;
+
+// Const(x) is a convenient function for obtaining a const reference
+// to x.  This is useful for setting expectations on an overloaded
+// const mock method, e.g.
+//
+//   class MockFoo : public FooInterface {
+//    public:
+//     MOCK_METHOD0(Bar, int());
+//     MOCK_CONST_METHOD0(Bar, int&());
+//   };
+//
+//   MockFoo foo;
+//   // Expects a call to non-const MockFoo::Bar().
+//   EXPECT_CALL(foo, Bar());
+//   // Expects a call to const MockFoo::Bar().
+//   EXPECT_CALL(Const(foo), Bar());
+template <typename T>
+inline const T& Const(const T& x) { return x; }
+
+// Constructs an Expectation object that references and co-owns exp.
+inline Expectation::Expectation(internal::ExpectationBase& exp)  // NOLINT
+    : expectation_base_(exp.GetHandle().expectation_base()) {}
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+// Implementation for ON_CALL and EXPECT_CALL macros. A separate macro is
+// required to avoid compile errors when the name of the method used in call is
+// a result of macro expansion. See CompilesWithMethodNameExpandedFromMacro
+// tests in internal/gmock-spec-builders_test.cc for more details.
+//
+// This macro supports statements both with and without parameter matchers. If
+// the parameter list is omitted, gMock will accept any parameters, which allows
+// tests to be written that don't need to encode the number of method
+// parameter. This technique may only be used for non-overloaded methods.
+//
+//   // These are the same:
+//   ON_CALL(mock, NoArgsMethod()).WillByDefault(...);
+//   ON_CALL(mock, NoArgsMethod).WillByDefault(...);
+//
+//   // As are these:
+//   ON_CALL(mock, TwoArgsMethod(_, _)).WillByDefault(...);
+//   ON_CALL(mock, TwoArgsMethod).WillByDefault(...);
+//
+//   // Can also specify args if you want, of course:
+//   ON_CALL(mock, TwoArgsMethod(_, 45)).WillByDefault(...);
+//
+//   // Overloads work as long as you specify parameters:
+//   ON_CALL(mock, OverloadedMethod(_)).WillByDefault(...);
+//   ON_CALL(mock, OverloadedMethod(_, _)).WillByDefault(...);
+//
+//   // Oops! Which overload did you want?
+//   ON_CALL(mock, OverloadedMethod).WillByDefault(...);
+//     => ERROR: call to member function 'gmock_OverloadedMethod' is ambiguous
+//
+// How this works: The mock class uses two overloads of the gmock_Method
+// expectation setter method plus an operator() overload on the MockSpec object.
+// In the matcher list form, the macro expands to:
+//
+//   // This statement:
+//   ON_CALL(mock, TwoArgsMethod(_, 45))...
+//
+//   // ...expands to:
+//   mock.gmock_TwoArgsMethod(_, 45)(WithoutMatchers(), nullptr)...
+//   |-------------v---------------||------------v-------------|
+//       invokes first overload        swallowed by operator()
+//
+//   // ...which is essentially:
+//   mock.gmock_TwoArgsMethod(_, 45)...
+//
+// Whereas the form without a matcher list:
+//
+//   // This statement:
+//   ON_CALL(mock, TwoArgsMethod)...
+//
+//   // ...expands to:
+//   mock.gmock_TwoArgsMethod(WithoutMatchers(), nullptr)...
+//   |-----------------------v--------------------------|
+//                 invokes second overload
+//
+//   // ...which is essentially:
+//   mock.gmock_TwoArgsMethod(_, _)...
+//
+// The WithoutMatchers() argument is used to disambiguate overloads and to
+// block the caller from accidentally invoking the second overload directly. The
+// second argument is an internal type derived from the method signature. The
+// failure to disambiguate two overloads of this method in the ON_CALL statement
+// is how we block callers from setting expectations on overloaded methods.
+#define GMOCK_ON_CALL_IMPL_(mock_expr, Setter, call)                    \
+  ((mock_expr).gmock_##call)(::testing::internal::GetWithoutMatchers(), \
+                             nullptr)                                   \
+      .Setter(__FILE__, __LINE__, #mock_expr, #call)
+
+#define ON_CALL(obj, call) \
+  GMOCK_ON_CALL_IMPL_(obj, InternalDefaultActionSetAt, call)
+
+#define EXPECT_CALL(obj, call) \
+  GMOCK_ON_CALL_IMPL_(obj, InternalExpectedAt, call)
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_SPEC_BUILDERS_H_
+
+namespace testing {
+namespace internal {
+template <typename T>
+using identity_t = T;
+
+template <typename Pattern>
+struct ThisRefAdjuster {
+  template <typename T>
+  using AdjustT = typename std::conditional<
+      std::is_const<typename std::remove_reference<Pattern>::type>::value,
+      typename std::conditional<std::is_lvalue_reference<Pattern>::value,
+                                const T&, const T&&>::type,
+      typename std::conditional<std::is_lvalue_reference<Pattern>::value, T&,
+                                T&&>::type>::type;
+
+  template <typename MockType>
+  static AdjustT<MockType> Adjust(const MockType& mock) {
+    return static_cast<AdjustT<MockType>>(const_cast<MockType&>(mock));
+  }
+};
+
+}  // namespace internal
+
+// The style guide prohibits "using" statements in a namespace scope
+// inside a header file.  However, the FunctionMocker class template
+// is meant to be defined in the ::testing namespace.  The following
+// line is just a trick for working around a bug in MSVC 8.0, which
+// cannot handle it if we define FunctionMocker in ::testing.
+using internal::FunctionMocker;
+}  // namespace testing
+
+#define MOCK_METHOD(...) \
+  GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_1(...) \
+  GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_2(...) \
+  GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \
+  GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ())
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec)     \
+  GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args);                                   \
+  GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec);                                   \
+  GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(                                      \
+      GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args));           \
+  GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec)                                     \
+  GMOCK_INTERNAL_MOCK_METHOD_IMPL(                                            \
+      GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec),     \
+      GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec),    \
+      GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec),                                \
+      GMOCK_INTERNAL_GET_CALLTYPE(_Spec), GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \
+      (GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)))
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \
+  GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_6(...) \
+  GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_ARG_7(...) \
+  GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
+
+#define GMOCK_INTERNAL_WRONG_ARITY(...)                                      \
+  static_assert(                                                             \
+      false,                                                                 \
+      "MOCK_METHOD must be called with 3 or 4 arguments. _Ret, "             \
+      "_MethodName, _Args and optionally _Spec. _Args and _Spec must be "    \
+      "enclosed in parentheses. If _Ret is a type with unprotected commas, " \
+      "it must also be enclosed in parentheses.")
+
+#define GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Tuple) \
+  static_assert(                                  \
+      GMOCK_PP_IS_ENCLOSED_PARENS(_Tuple),        \
+      GMOCK_PP_STRINGIZE(_Tuple) " should be enclosed in parentheses.")
+
+#define GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(_N, ...)                 \
+  static_assert(                                                       \
+      std::is_function<__VA_ARGS__>::value,                            \
+      "Signature must be a function type, maybe return type contains " \
+      "unprotected comma.");                                           \
+  static_assert(                                                       \
+      ::testing::tuple_size<typename ::testing::internal::Function<    \
+              __VA_ARGS__>::ArgumentTuple>::value == _N,               \
+      "This method does not take " GMOCK_PP_STRINGIZE(                 \
+          _N) " arguments. Parenthesize all types with unprotected commas.")
+
+#define GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT, ~, _Spec)
+
+#define GMOCK_INTERNAL_MOCK_METHOD_IMPL(_N, _MethodName, _Constness,           \
+                                        _Override, _Final, _NoexceptSpec,      \
+                                        _CallType, _RefSpec, _Signature)       \
+  typename ::testing::internal::Function<GMOCK_PP_REMOVE_PARENS(               \
+      _Signature)>::Result                                                     \
+  GMOCK_INTERNAL_EXPAND(_CallType)                                             \
+      _MethodName(GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, _Signature, _N))   \
+          GMOCK_PP_IF(_Constness, const, ) _RefSpec _NoexceptSpec              \
+          GMOCK_PP_IF(_Override, override, ) GMOCK_PP_IF(_Final, final, ) {    \
+    GMOCK_MOCKER_(_N, _Constness, _MethodName)                                 \
+        .SetOwnerAndName(this, #_MethodName);                                  \
+    return GMOCK_MOCKER_(_N, _Constness, _MethodName)                          \
+        .Invoke(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, _Signature, _N));  \
+  }                                                                            \
+  ::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \
+      GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_PARAMETER, _Signature, _N))       \
+      GMOCK_PP_IF(_Constness, const, ) _RefSpec {                              \
+    GMOCK_MOCKER_(_N, _Constness, _MethodName).RegisterOwner(this);            \
+    return GMOCK_MOCKER_(_N, _Constness, _MethodName)                          \
+        .With(GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_ARGUMENT, , _N));         \
+  }                                                                            \
+  ::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \
+      const ::testing::internal::WithoutMatchers&,                             \
+      GMOCK_PP_IF(_Constness, const, )::testing::internal::Function<           \
+          GMOCK_PP_REMOVE_PARENS(_Signature)>*) const _RefSpec _NoexceptSpec { \
+    return ::testing::internal::ThisRefAdjuster<GMOCK_PP_IF(                   \
+        _Constness, const, ) int _RefSpec>::Adjust(*this)                      \
+        .gmock_##_MethodName(GMOCK_PP_REPEAT(                                  \
+            GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N));               \
+  }                                                                            \
+  mutable ::testing::FunctionMocker<GMOCK_PP_REMOVE_PARENS(_Signature)>        \
+      GMOCK_MOCKER_(_N, _Constness, _MethodName)
+
+#define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__
+
+// Five Valid modifiers.
+#define GMOCK_INTERNAL_HAS_CONST(_Tuple) \
+  GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple))
+
+#define GMOCK_INTERNAL_HAS_OVERRIDE(_Tuple) \
+  GMOCK_PP_HAS_COMMA(                       \
+      GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_OVERRIDE, ~, _Tuple))
+
+#define GMOCK_INTERNAL_HAS_FINAL(_Tuple) \
+  GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_FINAL, ~, _Tuple))
+
+#define GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Tuple) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT, ~, _Tuple)
+
+#define GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT(_i, _, _elem)          \
+  GMOCK_PP_IF(                                                          \
+      GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \
+      _elem, )
+
+#define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple)
+
+#define GMOCK_INTERNAL_REF_SPEC_IF_REF(_i, _, _elem)                       \
+  GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \
+              GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), )
+
+#define GMOCK_INTERNAL_GET_CALLTYPE(_Tuple) \
+  GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_CALLTYPE_IMPL, ~, _Tuple)
+
+#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem)            \
+  static_assert(                                                          \
+      (GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) +    \
+       GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \
+       GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) +    \
+       GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \
+       GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) +      \
+       GMOCK_INTERNAL_IS_CALLTYPE(_elem)) == 1,                           \
+      GMOCK_PP_STRINGIZE(                                                 \
+          _elem) " cannot be recognized as a valid specification modifier.");
+
+// Modifiers implementation.
+#define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \
+  GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CONST_I_, _elem)
+
+#define GMOCK_INTERNAL_DETECT_CONST_I_const ,
+
+#define GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem) \
+  GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_OVERRIDE_I_, _elem)
+
+#define GMOCK_INTERNAL_DETECT_OVERRIDE_I_override ,
+
+#define GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem) \
+  GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_FINAL_I_, _elem)
+
+#define GMOCK_INTERNAL_DETECT_FINAL_I_final ,
+
+#define GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem) \
+  GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_NOEXCEPT_I_, _elem)
+
+#define GMOCK_INTERNAL_DETECT_NOEXCEPT_I_noexcept ,
+
+#define GMOCK_INTERNAL_DETECT_REF(_i, _, _elem) \
+  GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_REF_I_, _elem)
+
+#define GMOCK_INTERNAL_DETECT_REF_I_ref ,
+
+#define GMOCK_INTERNAL_UNPACK_ref(x) x
+
+#define GMOCK_INTERNAL_GET_CALLTYPE_IMPL(_i, _, _elem)           \
+  GMOCK_PP_IF(GMOCK_INTERNAL_IS_CALLTYPE(_elem),                 \
+              GMOCK_INTERNAL_GET_VALUE_CALLTYPE, GMOCK_PP_EMPTY) \
+  (_elem)
+
+// TODO(iserna): GMOCK_INTERNAL_IS_CALLTYPE and
+// GMOCK_INTERNAL_GET_VALUE_CALLTYPE needed more expansions to work on windows
+// maybe they can be simplified somehow.
+#define GMOCK_INTERNAL_IS_CALLTYPE(_arg) \
+  GMOCK_INTERNAL_IS_CALLTYPE_I(          \
+      GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg))
+#define GMOCK_INTERNAL_IS_CALLTYPE_I(_arg) GMOCK_PP_IS_ENCLOSED_PARENS(_arg)
+
+#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE(_arg) \
+  GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I(          \
+      GMOCK_PP_CAT(GMOCK_INTERNAL_IS_CALLTYPE_HELPER_, _arg))
+#define GMOCK_INTERNAL_GET_VALUE_CALLTYPE_I(_arg) \
+  GMOCK_PP_IDENTITY _arg
+
+#define GMOCK_INTERNAL_IS_CALLTYPE_HELPER_Calltype
+
+// Note: The use of `identity_t` here allows _Ret to represent return types that
+// would normally need to be specified in a different way. For example, a method
+// returning a function pointer must be written as
+//
+// fn_ptr_return_t (*method(method_args_t...))(fn_ptr_args_t...)
+//
+// But we only support placing the return type at the beginning. To handle this,
+// we wrap all calls in identity_t, so that a declaration will be expanded to
+//
+// identity_t<fn_ptr_return_t (*)(fn_ptr_args_t...)> method(method_args_t...)
+//
+// This allows us to work around the syntactic oddities of function/method
+// types.
+#define GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)                                 \
+  ::testing::internal::identity_t<GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_Ret), \
+                                              GMOCK_PP_REMOVE_PARENS,         \
+                                              GMOCK_PP_IDENTITY)(_Ret)>(      \
+      GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_TYPE, _, _Args))
+
+#define GMOCK_INTERNAL_GET_TYPE(_i, _, _elem)                          \
+  GMOCK_PP_COMMA_IF(_i)                                                \
+  GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_elem), GMOCK_PP_REMOVE_PARENS, \
+              GMOCK_PP_IDENTITY)                                       \
+  (_elem)
+
+#define GMOCK_INTERNAL_PARAMETER(_i, _Signature, _)            \
+  GMOCK_PP_COMMA_IF(_i)                                        \
+  GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \
+  gmock_a##_i
+
+#define GMOCK_INTERNAL_FORWARD_ARG(_i, _Signature, _) \
+  GMOCK_PP_COMMA_IF(_i)                               \
+  ::std::forward<GMOCK_INTERNAL_ARG_O(                \
+      _i, GMOCK_PP_REMOVE_PARENS(_Signature))>(gmock_a##_i)
+
+#define GMOCK_INTERNAL_MATCHER_PARAMETER(_i, _Signature, _)        \
+  GMOCK_PP_COMMA_IF(_i)                                            \
+  GMOCK_INTERNAL_MATCHER_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \
+  gmock_a##_i
+
+#define GMOCK_INTERNAL_MATCHER_ARGUMENT(_i, _1, _2) \
+  GMOCK_PP_COMMA_IF(_i)                             \
+  gmock_a##_i
+
+#define GMOCK_INTERNAL_A_MATCHER_ARGUMENT(_i, _Signature, _) \
+  GMOCK_PP_COMMA_IF(_i)                                      \
+  ::testing::A<GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature))>()
+
+#define GMOCK_INTERNAL_ARG_O(_i, ...) \
+  typename ::testing::internal::Function<__VA_ARGS__>::template Arg<_i>::type
+
+#define GMOCK_INTERNAL_MATCHER_O(_i, ...)                          \
+  const ::testing::Matcher<typename ::testing::internal::Function< \
+      __VA_ARGS__>::template Arg<_i>::type>&
+
+#define MOCK_METHOD0(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 0, __VA_ARGS__)
+#define MOCK_METHOD1(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 1, __VA_ARGS__)
+#define MOCK_METHOD2(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 2, __VA_ARGS__)
+#define MOCK_METHOD3(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 3, __VA_ARGS__)
+#define MOCK_METHOD4(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 4, __VA_ARGS__)
+#define MOCK_METHOD5(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 5, __VA_ARGS__)
+#define MOCK_METHOD6(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 6, __VA_ARGS__)
+#define MOCK_METHOD7(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 7, __VA_ARGS__)
+#define MOCK_METHOD8(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 8, __VA_ARGS__)
+#define MOCK_METHOD9(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 9, __VA_ARGS__)
+#define MOCK_METHOD10(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, , m, 10, __VA_ARGS__)
+
+#define MOCK_CONST_METHOD0(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 0, __VA_ARGS__)
+#define MOCK_CONST_METHOD1(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 1, __VA_ARGS__)
+#define MOCK_CONST_METHOD2(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 2, __VA_ARGS__)
+#define MOCK_CONST_METHOD3(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 3, __VA_ARGS__)
+#define MOCK_CONST_METHOD4(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 4, __VA_ARGS__)
+#define MOCK_CONST_METHOD5(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 5, __VA_ARGS__)
+#define MOCK_CONST_METHOD6(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 6, __VA_ARGS__)
+#define MOCK_CONST_METHOD7(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 7, __VA_ARGS__)
+#define MOCK_CONST_METHOD8(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 8, __VA_ARGS__)
+#define MOCK_CONST_METHOD9(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 9, __VA_ARGS__)
+#define MOCK_CONST_METHOD10(m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, , m, 10, __VA_ARGS__)
+
+#define MOCK_METHOD0_T(m, ...) MOCK_METHOD0(m, __VA_ARGS__)
+#define MOCK_METHOD1_T(m, ...) MOCK_METHOD1(m, __VA_ARGS__)
+#define MOCK_METHOD2_T(m, ...) MOCK_METHOD2(m, __VA_ARGS__)
+#define MOCK_METHOD3_T(m, ...) MOCK_METHOD3(m, __VA_ARGS__)
+#define MOCK_METHOD4_T(m, ...) MOCK_METHOD4(m, __VA_ARGS__)
+#define MOCK_METHOD5_T(m, ...) MOCK_METHOD5(m, __VA_ARGS__)
+#define MOCK_METHOD6_T(m, ...) MOCK_METHOD6(m, __VA_ARGS__)
+#define MOCK_METHOD7_T(m, ...) MOCK_METHOD7(m, __VA_ARGS__)
+#define MOCK_METHOD8_T(m, ...) MOCK_METHOD8(m, __VA_ARGS__)
+#define MOCK_METHOD9_T(m, ...) MOCK_METHOD9(m, __VA_ARGS__)
+#define MOCK_METHOD10_T(m, ...) MOCK_METHOD10(m, __VA_ARGS__)
+
+#define MOCK_CONST_METHOD0_T(m, ...) MOCK_CONST_METHOD0(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD1_T(m, ...) MOCK_CONST_METHOD1(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD2_T(m, ...) MOCK_CONST_METHOD2(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD3_T(m, ...) MOCK_CONST_METHOD3(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD4_T(m, ...) MOCK_CONST_METHOD4(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD5_T(m, ...) MOCK_CONST_METHOD5(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD6_T(m, ...) MOCK_CONST_METHOD6(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD7_T(m, ...) MOCK_CONST_METHOD7(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD8_T(m, ...) MOCK_CONST_METHOD8(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD9_T(m, ...) MOCK_CONST_METHOD9(m, __VA_ARGS__)
+#define MOCK_CONST_METHOD10_T(m, ...) MOCK_CONST_METHOD10(m, __VA_ARGS__)
+
+#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 0, __VA_ARGS__)
+#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 1, __VA_ARGS__)
+#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 2, __VA_ARGS__)
+#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 3, __VA_ARGS__)
+#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 4, __VA_ARGS__)
+#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 5, __VA_ARGS__)
+#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 6, __VA_ARGS__)
+#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 7, __VA_ARGS__)
+#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 8, __VA_ARGS__)
+#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 9, __VA_ARGS__)
+#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 10, __VA_ARGS__)
+
+#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 0, __VA_ARGS__)
+#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 1, __VA_ARGS__)
+#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 2, __VA_ARGS__)
+#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 3, __VA_ARGS__)
+#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 4, __VA_ARGS__)
+#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 5, __VA_ARGS__)
+#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 6, __VA_ARGS__)
+#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 7, __VA_ARGS__)
+#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 8, __VA_ARGS__)
+#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 9, __VA_ARGS__)
+#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \
+  GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 10, __VA_ARGS__)
+
+#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+
+#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \
+  MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__)
+
+#define GMOCK_INTERNAL_MOCK_METHODN(constness, ct, Method, args_num, ...) \
+  GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(                                  \
+      args_num, ::testing::internal::identity_t<__VA_ARGS__>);            \
+  GMOCK_INTERNAL_MOCK_METHOD_IMPL(                                        \
+      args_num, Method, GMOCK_PP_NARG0(constness), 0, 0, , ct, ,          \
+      (::testing::internal::identity_t<__VA_ARGS__>))
+
+#define GMOCK_MOCKER_(arity, constness, Method) \
+  GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__)
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_FUNCTION_MOCKER_H_
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements some commonly used variadic actions.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
+
+#include <memory>
+#include <utility>
+
+
+// Include any custom callback actions added by the local installation.
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
+
+// Sometimes you want to give an action explicit template parameters
+// that cannot be inferred from its value parameters.  ACTION() and
+// ACTION_P*() don't support that.  ACTION_TEMPLATE() remedies that
+// and can be viewed as an extension to ACTION() and ACTION_P*().
+//
+// The syntax:
+//
+//   ACTION_TEMPLATE(ActionName,
+//                   HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m),
+//                   AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; }
+//
+// defines an action template that takes m explicit template
+// parameters and n value parameters.  name_i is the name of the i-th
+// template parameter, and kind_i specifies whether it's a typename,
+// an integral constant, or a template.  p_i is the name of the i-th
+// value parameter.
+//
+// Example:
+//
+//   // DuplicateArg<k, T>(output) converts the k-th argument of the mock
+//   // function to type T and copies it to *output.
+//   ACTION_TEMPLATE(DuplicateArg,
+//                   HAS_2_TEMPLATE_PARAMS(int, k, typename, T),
+//                   AND_1_VALUE_PARAMS(output)) {
+//     *output = T(::std::get<k>(args));
+//   }
+//   ...
+//     int n;
+//     EXPECT_CALL(mock, Foo(_, _))
+//         .WillOnce(DuplicateArg<1, unsigned char>(&n));
+//
+// To create an instance of an action template, write:
+//
+//   ActionName<t1, ..., t_m>(v1, ..., v_n)
+//
+// where the ts are the template arguments and the vs are the value
+// arguments.  The value argument types are inferred by the compiler.
+// If you want to explicitly specify the value argument types, you can
+// provide additional template arguments:
+//
+//   ActionName<t1, ..., t_m, u1, ..., u_k>(v1, ..., v_n)
+//
+// where u_i is the desired type of v_i.
+//
+// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the
+// number of value parameters, but not on the number of template
+// parameters.  Without the restriction, the meaning of the following
+// is unclear:
+//
+//   OverloadedAction<int, bool>(x);
+//
+// Are we using a single-template-parameter action where 'bool' refers
+// to the type of x, or are we using a two-template-parameter action
+// where the compiler is asked to infer the type of x?
+//
+// Implementation notes:
+//
+// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and
+// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for
+// implementing ACTION_TEMPLATE.  The main trick we use is to create
+// new macro invocations when expanding a macro.  For example, we have
+//
+//   #define ACTION_TEMPLATE(name, template_params, value_params)
+//       ... GMOCK_INTERNAL_DECL_##template_params ...
+//
+// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...)
+// to expand to
+//
+//       ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ...
+//
+// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the
+// preprocessor will continue to expand it to
+//
+//       ... typename T ...
+//
+// This technique conforms to the C++ standard and is portable.  It
+// allows us to implement action templates using O(N) code, where N is
+// the maximum number of template/value parameters supported.  Without
+// using it, we'd have to devote O(N^2) amount of code to implement all
+// combinations of m and n.
+
+// Declares the template parameters.
+#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0
+#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \
+    name1) kind0 name0, kind1 name1
+#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2) kind0 name0, kind1 name1, kind2 name2
+#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3) kind0 name0, kind1 name1, kind2 name2, \
+    kind3 name3
+#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4) kind0 name0, kind1 name1, \
+    kind2 name2, kind3 name3, kind4 name4
+#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5) kind0 name0, \
+    kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5
+#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \
+    name6) kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \
+    kind5 name5, kind6 name6
+#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \
+    kind7, name7) kind0 name0, kind1 name1, kind2 name2, kind3 name3, \
+    kind4 name4, kind5 name5, kind6 name6, kind7 name7
+#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \
+    kind7, name7, kind8, name8) kind0 name0, kind1 name1, kind2 name2, \
+    kind3 name3, kind4 name4, kind5 name5, kind6 name6, kind7 name7, \
+    kind8 name8
+#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \
+    name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \
+    name6, kind7, name7, kind8, name8, kind9, name9) kind0 name0, \
+    kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5, \
+    kind6 name6, kind7 name7, kind8 name8, kind9 name9
+
+// Lists the template parameters.
+#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0
+#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, \
+    name1) name0, name1
+#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2) name0, name1, name2
+#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3) name0, name1, name2, name3
+#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4) name0, name1, name2, name3, \
+    name4
+#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5) name0, name1, \
+    name2, name3, name4, name5
+#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \
+    name6) name0, name1, name2, name3, name4, name5, name6
+#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \
+    kind7, name7) name0, name1, name2, name3, name4, name5, name6, name7
+#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
+    kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, name6, \
+    kind7, name7, kind8, name8) name0, name1, name2, name3, name4, name5, \
+    name6, name7, name8
+#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS(kind0, name0, kind1, \
+    name1, kind2, name2, kind3, name3, kind4, name4, kind5, name5, kind6, \
+    name6, kind7, name7, kind8, name8, kind9, name9) name0, name1, name2, \
+    name3, name4, name5, name6, name7, name8, name9
+
+// Declares the types of value parameters.
+#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) , \
+    typename p0##_type, typename p1##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , \
+    typename p0##_type, typename p1##_type, typename p2##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \
+    typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \
+    typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type, typename p4##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \
+    typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type, typename p4##_type, typename p5##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6) , typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type, typename p4##_type, typename p5##_type, \
+    typename p6##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7) , typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type, typename p4##_type, typename p5##_type, \
+    typename p6##_type, typename p7##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7, p8) , typename p0##_type, typename p1##_type, typename p2##_type, \
+    typename p3##_type, typename p4##_type, typename p5##_type, \
+    typename p6##_type, typename p7##_type, typename p8##_type
+#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7, p8, p9) , typename p0##_type, typename p1##_type, \
+    typename p2##_type, typename p3##_type, typename p4##_type, \
+    typename p5##_type, typename p6##_type, typename p7##_type, \
+    typename p8##_type, typename p9##_type
+
+// Initializes the value parameters.
+#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS()\
+    ()
+#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0)\
+    (p0##_type gmock_p0) : p0(::std::move(gmock_p0))
+#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1)\
+    (p0##_type gmock_p0, p1##_type gmock_p1) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1))
+#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, \
+        p2##_type gmock_p2) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2))
+#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3))
+#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4))
+#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4, \
+        p5##_type gmock_p5) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \
+        p5(::std::move(gmock_p5))
+#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
+        p6##_type gmock_p6) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \
+        p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6))
+#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
+        p6##_type gmock_p6, p7##_type gmock_p7) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \
+        p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \
+        p7(::std::move(gmock_p7))
+#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
+        p6##_type gmock_p6, p7##_type gmock_p7, \
+        p8##_type gmock_p8) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \
+        p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \
+        p7(::std::move(gmock_p7)), p8(::std::move(gmock_p8))
+#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8, p9)\
+    (p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
+        p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
+        p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \
+        p9##_type gmock_p9) : p0(::std::move(gmock_p0)), \
+        p1(::std::move(gmock_p1)), p2(::std::move(gmock_p2)), \
+        p3(::std::move(gmock_p3)), p4(::std::move(gmock_p4)), \
+        p5(::std::move(gmock_p5)), p6(::std::move(gmock_p6)), \
+        p7(::std::move(gmock_p7)), p8(::std::move(gmock_p8)), \
+        p9(::std::move(gmock_p9))
+
+// Defines the copy constructor
+#define GMOCK_INTERNAL_DEFN_COPY_AND_0_VALUE_PARAMS() \
+    {}  // Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82134
+#define GMOCK_INTERNAL_DEFN_COPY_AND_1_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_2_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_3_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_4_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_5_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_6_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_7_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_8_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_9_VALUE_PARAMS(...) = default;
+#define GMOCK_INTERNAL_DEFN_COPY_AND_10_VALUE_PARAMS(...) = default;
+
+// Declares the fields for storing the value parameters.
+#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0;
+#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0; \
+    p1##_type p1;
+#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0; \
+    p1##_type p1; p2##_type p2;
+#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0; \
+    p1##_type p1; p2##_type p2; p3##_type p3;
+#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \
+    p4) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4;
+#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \
+    p5) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \
+    p5##_type p5;
+#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \
+    p5##_type p5; p6##_type p6;
+#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; p4##_type p4; \
+    p5##_type p5; p6##_type p6; p7##_type p7;
+#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \
+    p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8;
+#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8, p9) p0##_type p0; p1##_type p1; p2##_type p2; p3##_type p3; \
+    p4##_type p4; p5##_type p5; p6##_type p6; p7##_type p7; p8##_type p8; \
+    p9##_type p9;
+
+// Lists the value parameters.
+#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0
+#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1
+#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2
+#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3
+#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) p0, p1, \
+    p2, p3, p4
+#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) p0, \
+    p1, p2, p3, p4, p5
+#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6) p0, p1, p2, p3, p4, p5, p6
+#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7) p0, p1, p2, p3, p4, p5, p6, p7
+#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8) p0, p1, p2, p3, p4, p5, p6, p7, p8
+#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8, p9) p0, p1, p2, p3, p4, p5, p6, p7, p8, p9
+
+// Lists the value parameter types.
+#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) , p0##_type, \
+    p1##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) , p0##_type, \
+    p1##_type, p2##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) , \
+    p0##_type, p1##_type, p2##_type, p3##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) , \
+    p0##_type, p1##_type, p2##_type, p3##_type, p4##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) , \
+    p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \
+    p6##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \
+    p5##_type, p6##_type, p7##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7, p8) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \
+    p5##_type, p6##_type, p7##_type, p8##_type
+#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6, p7, p8, p9) , p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, \
+    p5##_type, p6##_type, p7##_type, p8##_type, p9##_type
+
+// Declares the value parameters.
+#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0
+#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) p0##_type p0, \
+    p1##_type p1
+#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) p0##_type p0, \
+    p1##_type p1, p2##_type p2
+#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0##_type p0, \
+    p1##_type p1, p2##_type p2, p3##_type p3
+#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, \
+    p4) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4
+#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, \
+    p5) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
+    p5##_type p5
+#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
+    p6) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
+    p5##_type p5, p6##_type p6
+#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
+    p5##_type p5, p6##_type p6, p7##_type p7
+#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \
+    p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8
+#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8, p9) p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, \
+    p4##_type p4, p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, \
+    p9##_type p9
+
+// The suffix of the class template implementing the action template.
+#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS()
+#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P
+#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2
+#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3
+#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4
+#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5
+#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6
+#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7
+#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7) P8
+#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8) P9
+#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
+    p7, p8, p9) P10
+
+// The name of the class template implementing the action template.
+#define GMOCK_ACTION_CLASS_(name, value_params)\
+    GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params)
+
+#define ACTION_TEMPLATE(name, template_params, value_params)                   \
+  template <GMOCK_INTERNAL_DECL_##template_params                              \
+            GMOCK_INTERNAL_DECL_TYPE_##value_params>                           \
+  class GMOCK_ACTION_CLASS_(name, value_params) {                              \
+   public:                                                                     \
+    explicit GMOCK_ACTION_CLASS_(name, value_params)(                          \
+        GMOCK_INTERNAL_DECL_##value_params)                                    \
+        GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params),    \
+                    = default; ,                                               \
+                    : impl_(std::make_shared<gmock_Impl>(                      \
+                                GMOCK_INTERNAL_LIST_##value_params)) { })      \
+    GMOCK_ACTION_CLASS_(name, value_params)(                                   \
+        const GMOCK_ACTION_CLASS_(name, value_params)&) noexcept               \
+        GMOCK_INTERNAL_DEFN_COPY_##value_params                                \
+    GMOCK_ACTION_CLASS_(name, value_params)(                                   \
+        GMOCK_ACTION_CLASS_(name, value_params)&&) noexcept                    \
+        GMOCK_INTERNAL_DEFN_COPY_##value_params                                \
+    template <typename F>                                                      \
+    operator ::testing::Action<F>() const {                                    \
+      return GMOCK_PP_IF(                                                      \
+          GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params),              \
+                      (::testing::internal::MakeAction<F, gmock_Impl>()),      \
+                      (::testing::internal::MakeAction<F>(impl_)));            \
+    }                                                                          \
+   private:                                                                    \
+    class gmock_Impl {                                                         \
+     public:                                                                   \
+      explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {}                \
+      template <typename function_type, typename return_type,                  \
+                typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>         \
+      return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const;  \
+      GMOCK_INTERNAL_DEFN_##value_params                                       \
+    };                                                                         \
+    GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params),        \
+                , std::shared_ptr<const gmock_Impl> impl_;)                    \
+  };                                                                           \
+  template <GMOCK_INTERNAL_DECL_##template_params                              \
+            GMOCK_INTERNAL_DECL_TYPE_##value_params>                           \
+  GMOCK_ACTION_CLASS_(name, value_params)<                                     \
+      GMOCK_INTERNAL_LIST_##template_params                                    \
+      GMOCK_INTERNAL_LIST_TYPE_##value_params> name(                           \
+          GMOCK_INTERNAL_DECL_##value_params) GTEST_MUST_USE_RESULT_;          \
+  template <GMOCK_INTERNAL_DECL_##template_params                              \
+            GMOCK_INTERNAL_DECL_TYPE_##value_params>                           \
+  inline GMOCK_ACTION_CLASS_(name, value_params)<                              \
+      GMOCK_INTERNAL_LIST_##template_params                                    \
+      GMOCK_INTERNAL_LIST_TYPE_##value_params> name(                           \
+          GMOCK_INTERNAL_DECL_##value_params) {                                \
+    return GMOCK_ACTION_CLASS_(name, value_params)<                            \
+        GMOCK_INTERNAL_LIST_##template_params                                  \
+        GMOCK_INTERNAL_LIST_TYPE_##value_params>(                              \
+            GMOCK_INTERNAL_LIST_##value_params);                               \
+  }                                                                            \
+  template <GMOCK_INTERNAL_DECL_##template_params                              \
+            GMOCK_INTERNAL_DECL_TYPE_##value_params>                           \
+  template <typename function_type, typename return_type, typename args_type,  \
+            GMOCK_ACTION_TEMPLATE_ARGS_NAMES_>                                 \
+  return_type GMOCK_ACTION_CLASS_(name, value_params)<                         \
+      GMOCK_INTERNAL_LIST_##template_params                                    \
+      GMOCK_INTERNAL_LIST_TYPE_##value_params>::gmock_Impl::gmock_PerformImpl( \
+          GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) const
+
+namespace testing {
+
+// The ACTION*() macros trigger warning C4100 (unreferenced formal
+// parameter) in MSVC with -W4.  Unfortunately they cannot be fixed in
+// the macro definition, as the warnings are generated when the macro
+// is expanded and macro expansion cannot contain #pragma.  Therefore
+// we suppress them here.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4100)
+#endif
+
+namespace internal {
+
+// internal::InvokeArgument - a helper for InvokeArgument action.
+// The basic overloads are provided here for generic functors.
+// Overloads for other custom-callables are provided in the
+// internal/custom/gmock-generated-actions.h header.
+template <typename F, typename... Args>
+auto InvokeArgument(F f, Args... args) -> decltype(f(args...)) {
+  return f(args...);
+}
+
+template <std::size_t index, typename... Params>
+struct InvokeArgumentAction {
+  template <typename... Args>
+  auto operator()(Args&&... args) const -> decltype(internal::InvokeArgument(
+      std::get<index>(std::forward_as_tuple(std::forward<Args>(args)...)),
+      std::declval<const Params&>()...)) {
+    internal::FlatTuple<Args&&...> args_tuple(FlatTupleConstructTag{},
+                                              std::forward<Args>(args)...);
+    return params.Apply([&](const Params&... unpacked_params) {
+      auto&& callable = args_tuple.template Get<index>();
+      return internal::InvokeArgument(
+          std::forward<decltype(callable)>(callable), unpacked_params...);
+    });
+  }
+
+  internal::FlatTuple<Params...> params;
+};
+
+}  // namespace internal
+
+// The InvokeArgument<N>(a1, a2, ..., a_k) action invokes the N-th
+// (0-based) argument, which must be a k-ary callable, of the mock
+// function, with arguments a1, a2, ..., a_k.
+//
+// Notes:
+//
+//   1. The arguments are passed by value by default.  If you need to
+//   pass an argument by reference, wrap it inside std::ref().  For
+//   example,
+//
+//     InvokeArgument<1>(5, string("Hello"), std::ref(foo))
+//
+//   passes 5 and string("Hello") by value, and passes foo by
+//   reference.
+//
+//   2. If the callable takes an argument by reference but std::ref() is
+//   not used, it will receive the reference to a copy of the value,
+//   instead of the original value.  For example, when the 0-th
+//   argument of the mock function takes a const string&, the action
+//
+//     InvokeArgument<0>(string("Hello"))
+//
+//   makes a copy of the temporary string("Hello") object and passes a
+//   reference of the copy, instead of the original temporary object,
+//   to the callable.  This makes it easy for a user to define an
+//   InvokeArgument action from temporary values and have it performed
+//   later.
+template <std::size_t index, typename... Params>
+internal::InvokeArgumentAction<index, typename std::decay<Params>::type...>
+InvokeArgument(Params&&... params) {
+  return {internal::FlatTuple<typename std::decay<Params>::type...>(
+      internal::FlatTupleConstructTag{}, std::forward<Params>(params)...)};
+}
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+}  // namespace testing
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
+// Copyright 2013, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Mock - a framework for writing C++ mock classes.
+//
+// This file implements some matchers that depend on gmock-matchers.h.
+//
+// Note that tests are implemented in gmock-matchers_test.cc rather than
+// gmock-more-matchers-test.cc.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
+
+
+namespace testing {
+
+// Silence C4100 (unreferenced formal
+// parameter) for MSVC
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4100)
+#if (_MSC_VER == 1900)
+// and silence C4800 (C4800: 'int *const ': forcing value
+// to bool 'true' or 'false') for MSVC 14
+# pragma warning(disable:4800)
+  #endif
+#endif
+
+// Defines a matcher that matches an empty container. The container must
+// support both size() and empty(), which all STL-like containers provide.
+MATCHER(IsEmpty, negation ? "isn't empty" : "is empty") {
+  if (arg.empty()) {
+    return true;
+  }
+  *result_listener << "whose size is " << arg.size();
+  return false;
+}
+
+// Define a matcher that matches a value that evaluates in boolean
+// context to true.  Useful for types that define "explicit operator
+// bool" operators and so can't be compared for equality with true
+// and false.
+MATCHER(IsTrue, negation ? "is false" : "is true") {
+  return static_cast<bool>(arg);
+}
+
+// Define a matcher that matches a value that evaluates in boolean
+// context to false.  Useful for types that define "explicit operator
+// bool" operators and so can't be compared for equality with true
+// and false.
+MATCHER(IsFalse, negation ? "is true" : "is false") {
+  return !static_cast<bool>(arg);
+}
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+
+}  // namespace testing
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Implements class templates NiceMock, NaggyMock, and StrictMock.
+//
+// Given a mock class MockFoo that is created using Google Mock,
+// NiceMock<MockFoo> is a subclass of MockFoo that allows
+// uninteresting calls (i.e. calls to mock methods that have no
+// EXPECT_CALL specs), NaggyMock<MockFoo> is a subclass of MockFoo
+// that prints a warning when an uninteresting call occurs, and
+// StrictMock<MockFoo> is a subclass of MockFoo that treats all
+// uninteresting calls as errors.
+//
+// Currently a mock is naggy by default, so MockFoo and
+// NaggyMock<MockFoo> behave like the same.  However, we will soon
+// switch the default behavior of mocks to be nice, as that in general
+// leads to more maintainable tests.  When that happens, MockFoo will
+// stop behaving like NaggyMock<MockFoo> and start behaving like
+// NiceMock<MockFoo>.
+//
+// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of
+// their respective base class.  Therefore you can write
+// NiceMock<MockFoo>(5, "a") to construct a nice mock where MockFoo
+// has a constructor that accepts (int, const char*), for example.
+//
+// A known limitation is that NiceMock<MockFoo>, NaggyMock<MockFoo>,
+// and StrictMock<MockFoo> only works for mock methods defined using
+// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class.
+// If a mock method is defined in a base class of MockFoo, the "nice"
+// or "strict" modifier may not affect it, depending on the compiler.
+// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT
+// supported.
+
+// GOOGLETEST_CM0002 DO NOT DELETE
+
+#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
+#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
+
+#include <type_traits>
+
+
+namespace testing {
+template <class MockClass>
+class NiceMock;
+template <class MockClass>
+class NaggyMock;
+template <class MockClass>
+class StrictMock;
+
+namespace internal {
+template <typename T>
+std::true_type StrictnessModifierProbe(const NiceMock<T>&);
+template <typename T>
+std::true_type StrictnessModifierProbe(const NaggyMock<T>&);
+template <typename T>
+std::true_type StrictnessModifierProbe(const StrictMock<T>&);
+std::false_type StrictnessModifierProbe(...);
+
+template <typename T>
+constexpr bool HasStrictnessModifier() {
+  return decltype(StrictnessModifierProbe(std::declval<const T&>()))::value;
+}
+
+// Base classes that register and deregister with testing::Mock to alter the
+// default behavior around uninteresting calls. Inheriting from one of these
+// classes first and then MockClass ensures the MockClass constructor is run
+// after registration, and that the MockClass destructor runs before
+// deregistration. This guarantees that MockClass's constructor and destructor
+// run with the same level of strictness as its instance methods.
+
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW && \
+    (defined(_MSC_VER) || defined(__clang__))
+// We need to mark these classes with this declspec to ensure that
+// the empty base class optimization is performed.
+#define GTEST_INTERNAL_EMPTY_BASE_CLASS __declspec(empty_bases)
+#else
+#define GTEST_INTERNAL_EMPTY_BASE_CLASS
+#endif
+
+template <typename Base>
+class NiceMockImpl {
+ public:
+  NiceMockImpl() { ::testing::Mock::AllowUninterestingCalls(this); }
+
+  ~NiceMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
+};
+
+template <typename Base>
+class NaggyMockImpl {
+ public:
+  NaggyMockImpl() { ::testing::Mock::WarnUninterestingCalls(this); }
+
+  ~NaggyMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
+};
+
+template <typename Base>
+class StrictMockImpl {
+ public:
+  StrictMockImpl() { ::testing::Mock::FailUninterestingCalls(this); }
+
+  ~StrictMockImpl() { ::testing::Mock::UnregisterCallReaction(this); }
+};
+
+}  // namespace internal
+
+template <class MockClass>
+class GTEST_INTERNAL_EMPTY_BASE_CLASS NiceMock
+    : private internal::NiceMockImpl<MockClass>,
+      public MockClass {
+ public:
+  static_assert(!internal::HasStrictnessModifier<MockClass>(),
+                "Can't apply NiceMock to a class hierarchy that already has a "
+                "strictness modifier. See "
+                "https://google.github.io/googletest/"
+                "gmock_cook_book.html#NiceStrictNaggy");
+  NiceMock() : MockClass() {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  // Ideally, we would inherit base class's constructors through a using
+  // declaration, which would preserve their visibility. However, many existing
+  // tests rely on the fact that current implementation reexports protected
+  // constructors as public. These tests would need to be cleaned up first.
+
+  // Single argument constructor is special-cased so that it can be
+  // made explicit.
+  template <typename A>
+  explicit NiceMock(A&& arg) : MockClass(std::forward<A>(arg)) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  template <typename TArg1, typename TArg2, typename... An>
+  NiceMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
+      : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
+                  std::forward<An>(args)...) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(NiceMock);
+};
+
+template <class MockClass>
+class GTEST_INTERNAL_EMPTY_BASE_CLASS NaggyMock
+    : private internal::NaggyMockImpl<MockClass>,
+      public MockClass {
+  static_assert(!internal::HasStrictnessModifier<MockClass>(),
+                "Can't apply NaggyMock to a class hierarchy that already has a "
+                "strictness modifier. See "
+                "https://google.github.io/googletest/"
+                "gmock_cook_book.html#NiceStrictNaggy");
+
+ public:
+  NaggyMock() : MockClass() {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  // Ideally, we would inherit base class's constructors through a using
+  // declaration, which would preserve their visibility. However, many existing
+  // tests rely on the fact that current implementation reexports protected
+  // constructors as public. These tests would need to be cleaned up first.
+
+  // Single argument constructor is special-cased so that it can be
+  // made explicit.
+  template <typename A>
+  explicit NaggyMock(A&& arg) : MockClass(std::forward<A>(arg)) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  template <typename TArg1, typename TArg2, typename... An>
+  NaggyMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
+      : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
+                  std::forward<An>(args)...) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(NaggyMock);
+};
+
+template <class MockClass>
+class GTEST_INTERNAL_EMPTY_BASE_CLASS StrictMock
+    : private internal::StrictMockImpl<MockClass>,
+      public MockClass {
+ public:
+  static_assert(
+      !internal::HasStrictnessModifier<MockClass>(),
+      "Can't apply StrictMock to a class hierarchy that already has a "
+      "strictness modifier. See "
+      "https://google.github.io/googletest/"
+      "gmock_cook_book.html#NiceStrictNaggy");
+  StrictMock() : MockClass() {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  // Ideally, we would inherit base class's constructors through a using
+  // declaration, which would preserve their visibility. However, many existing
+  // tests rely on the fact that current implementation reexports protected
+  // constructors as public. These tests would need to be cleaned up first.
+
+  // Single argument constructor is special-cased so that it can be
+  // made explicit.
+  template <typename A>
+  explicit StrictMock(A&& arg) : MockClass(std::forward<A>(arg)) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+  template <typename TArg1, typename TArg2, typename... An>
+  StrictMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
+      : MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
+                  std::forward<An>(args)...) {
+    static_assert(sizeof(*this) == sizeof(MockClass),
+                  "The impl subclass shouldn't introduce any padding");
+  }
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(StrictMock);
+};
+
+#undef GTEST_INTERNAL_EMPTY_BASE_CLASS
+
+}  // namespace testing
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
+
+namespace testing {
+
+// Declares Google Mock flags that we want a user to use programmatically.
+GMOCK_DECLARE_bool_(catch_leaked_mocks);
+GMOCK_DECLARE_string_(verbose);
+GMOCK_DECLARE_int32_(default_mock_behavior);
+
+// Initializes Google Mock.  This must be called before running the
+// tests.  In particular, it parses the command line for the flags
+// that Google Mock recognizes.  Whenever a Google Mock flag is seen,
+// it is removed from argv, and *argc is decremented.
+//
+// No value is returned.  Instead, the Google Mock flag variables are
+// updated.
+//
+// Since Google Test is needed for Google Mock to work, this function
+// also initializes Google Test and parses its flags, if that hasn't
+// been done.
+GTEST_API_ void InitGoogleMock(int* argc, char** argv);
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+GTEST_API_ void InitGoogleMock(int* argc, wchar_t** argv);
+
+// This overloaded version can be used on Arduino/embedded platforms where
+// there is no argc/argv.
+GTEST_API_ void InitGoogleMock();
+
+}  // namespace testing
+
+#endif  // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
diff --git a/libs/fmt/test/gtest/gtest/gtest-spi.h b/libs/fmt/test/gtest/gtest/gtest-spi.h
new file mode 100644
index 0000000000000000000000000000000000000000..eacef44669e8104a3d28daa054edadcdeb23c548
--- /dev/null
+++ b/libs/fmt/test/gtest/gtest/gtest-spi.h
@@ -0,0 +1,238 @@
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Utilities for testing Google Test itself and code that uses Google Test
+// (e.g. frameworks built on top of Google Test).
+
+// GOOGLETEST_CM0004 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
+
+#include "gtest/gtest.h"
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// This helper class can be used to mock out Google Test failure reporting
+// so that we can test Google Test or code that builds on Google Test.
+//
+// An object of this class appends a TestPartResult object to the
+// TestPartResultArray object given in the constructor whenever a Google Test
+// failure is reported. It can either intercept only failures that are
+// generated in the same thread that created this object or it can intercept
+// all generated failures. The scope of this mock object can be controlled with
+// the second argument to the two arguments constructor.
+class GTEST_API_ ScopedFakeTestPartResultReporter
+    : public TestPartResultReporterInterface {
+ public:
+  // The two possible mocking modes of this object.
+  enum InterceptMode {
+    INTERCEPT_ONLY_CURRENT_THREAD,  // Intercepts only thread local failures.
+    INTERCEPT_ALL_THREADS           // Intercepts all failures.
+  };
+
+  // The c'tor sets this object as the test part result reporter used
+  // by Google Test.  The 'result' parameter specifies where to report the
+  // results. This reporter will only catch failures generated in the current
+  // thread. DEPRECATED
+  explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
+
+  // Same as above, but you can choose the interception scope of this object.
+  ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
+                                   TestPartResultArray* result);
+
+  // The d'tor restores the previous test part result reporter.
+  ~ScopedFakeTestPartResultReporter() override;
+
+  // Appends the TestPartResult object to the TestPartResultArray
+  // received in the constructor.
+  //
+  // This method is from the TestPartResultReporterInterface
+  // interface.
+  void ReportTestPartResult(const TestPartResult& result) override;
+
+ private:
+  void Init();
+
+  const InterceptMode intercept_mode_;
+  TestPartResultReporterInterface* old_reporter_;
+  TestPartResultArray* const result_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter);
+};
+
+namespace internal {
+
+// A helper class for implementing EXPECT_FATAL_FAILURE() and
+// EXPECT_NONFATAL_FAILURE().  Its destructor verifies that the given
+// TestPartResultArray contains exactly one failure that has the given
+// type and contains the given substring.  If that's not the case, a
+// non-fatal failure will be generated.
+class GTEST_API_ SingleFailureChecker {
+ public:
+  // The constructor remembers the arguments.
+  SingleFailureChecker(const TestPartResultArray* results,
+                       TestPartResult::Type type, const std::string& substr);
+  ~SingleFailureChecker();
+ private:
+  const TestPartResultArray* const results_;
+  const TestPartResult::Type type_;
+  const std::string substr_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
+};
+
+}  // namespace internal
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+// A set of macros for testing Google Test assertions or code that's expected
+// to generate Google Test fatal failures.  It verifies that the given
+// statement will cause exactly one fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+//   - 'statement' cannot reference local non-static variables or
+//     non-static members of the current object.
+//   - 'statement' cannot return a value.
+//   - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works.  The AcceptsMacroThatExpandsToUnprotectedComma test in
+// gtest_unittest.cc will fail to compile if we do that.
+#define EXPECT_FATAL_FAILURE(statement, substr) \
+  do { \
+    class GTestExpectFatalFailureHelper {\
+     public:\
+      static void Execute() { statement; }\
+    };\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+      GTestExpectFatalFailureHelper::Execute();\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+  do { \
+    class GTestExpectFatalFailureHelper {\
+     public:\
+      static void Execute() { statement; }\
+    };\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ALL_THREADS, &gtest_failures);\
+      GTestExpectFatalFailureHelper::Execute();\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+// A macro for testing Google Test assertions or code that's expected to
+// generate Google Test non-fatal failures.  It asserts that the given
+// statement will cause exactly one non-fatal Google Test failure with 'substr'
+// being part of the failure message.
+//
+// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
+// affects and considers failures generated in the current thread and
+// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
+//
+// 'statement' is allowed to reference local variables and members of
+// the current object.
+//
+// The verification of the assertion is done correctly even when the statement
+// throws an exception or aborts the current function.
+//
+// Known restrictions:
+//   - You cannot stream a failure message to this macro.
+//
+// Note that even though the implementations of the following two
+// macros are much alike, we cannot refactor them to use a common
+// helper macro, due to some peculiarity in how the preprocessor
+// works.  If we do that, the code won't compile when the user gives
+// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
+// expands to code containing an unprotected comma.  The
+// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
+// catches that.
+//
+// For the same reason, we have to write
+//   if (::testing::internal::AlwaysTrue()) { statement; }
+// instead of
+//   GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
+// to avoid an MSVC warning on unreachable code.
+#define EXPECT_NONFATAL_FAILURE(statement, substr) \
+  do {\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+        (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter:: \
+          INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
+      if (::testing::internal::AlwaysTrue()) { statement; }\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
+  do {\
+    ::testing::TestPartResultArray gtest_failures;\
+    ::testing::internal::SingleFailureChecker gtest_checker(\
+        &gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
+        (substr));\
+    {\
+      ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
+          ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
+          &gtest_failures);\
+      if (::testing::internal::AlwaysTrue()) { statement; }\
+    }\
+  } while (::testing::internal::AlwaysFalse())
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
diff --git a/libs/fmt/test/gtest/gtest/gtest.h b/libs/fmt/test/gtest/gtest/gtest.h
new file mode 100644
index 0000000000000000000000000000000000000000..55a31d0a87dc788d2d10d7db17751632a5e4f4a6
--- /dev/null
+++ b/libs/fmt/test/gtest/gtest/gtest.h
@@ -0,0 +1,12398 @@
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the public API for Google Test.  It should be
+// included by any test program that uses Google Test.
+//
+// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
+// leave some internal implementation details in this header file.
+// They are clearly marked by comments like this:
+//
+//   // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+//
+// Such code is NOT meant to be used by a user directly, and is subject
+// to CHANGE WITHOUT NOTICE.  Therefore DO NOT DEPEND ON IT in a user
+// program!
+//
+// Acknowledgment: Google Test borrowed the idea of automatic test
+// registration from Barthelemy Dagenais' (barthelemy@prologique.com)
+// easyUnit framework.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_H_
+
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <type_traits>
+#include <vector>
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file declares functions and macros used internally by
+// Google Test.  They are subject to change without notice.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Low-level types and utilities for porting Google Test to various
+// platforms.  All macros ending with _ and symbols defined in an
+// internal namespace are subject to change without notice.  Code
+// outside Google Test MUST NOT USE THEM DIRECTLY.  Macros that don't
+// end with _ are part of Google Test's public API and can be used by
+// code outside Google Test.
+//
+// This file is fundamental to Google Test.  All other Google Test source
+// files are expected to #include this.  Therefore, it cannot #include
+// any other Google Test header.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+
+// Environment-describing macros
+// -----------------------------
+//
+// Google Test can be used in many different environments.  Macros in
+// this section tell Google Test what kind of environment it is being
+// used in, such that Google Test can provide environment-specific
+// features and implementations.
+//
+// Google Test tries to automatically detect the properties of its
+// environment, so users usually don't need to worry about these
+// macros.  However, the automatic detection is not perfect.
+// Sometimes it's necessary for a user to define some of the following
+// macros in the build script to override Google Test's decisions.
+//
+// If the user doesn't define a macro in the list, Google Test will
+// provide a default definition.  After this header is #included, all
+// macros in this list will be defined to either 1 or 0.
+//
+// Notes to maintainers:
+//   - Each macro here is a user-tweakable knob; do not grow the list
+//     lightly.
+//   - Use #if to key off these macros.  Don't use #ifdef or "#if
+//     defined(...)", which will not work as these macros are ALWAYS
+//     defined.
+//
+//   GTEST_HAS_CLONE          - Define it to 1/0 to indicate that clone(2)
+//                              is/isn't available.
+//   GTEST_HAS_EXCEPTIONS     - Define it to 1/0 to indicate that exceptions
+//                              are enabled.
+//   GTEST_HAS_POSIX_RE       - Define it to 1/0 to indicate that POSIX regular
+//                              expressions are/aren't available.
+//   GTEST_HAS_PTHREAD        - Define it to 1/0 to indicate that <pthread.h>
+//                              is/isn't available.
+//   GTEST_HAS_RTTI           - Define it to 1/0 to indicate that RTTI is/isn't
+//                              enabled.
+//   GTEST_HAS_STD_WSTRING    - Define it to 1/0 to indicate that
+//                              std::wstring does/doesn't work (Google Test can
+//                              be used where std::wstring is unavailable).
+//   GTEST_HAS_SEH            - Define it to 1/0 to indicate whether the
+//                              compiler supports Microsoft's "Structured
+//                              Exception Handling".
+//   GTEST_HAS_STREAM_REDIRECTION
+//                            - Define it to 1/0 to indicate whether the
+//                              platform supports I/O stream redirection using
+//                              dup() and dup2().
+//   GTEST_LINKED_AS_SHARED_LIBRARY
+//                            - Define to 1 when compiling tests that use
+//                              Google Test as a shared library (known as
+//                              DLL on Windows).
+//   GTEST_CREATE_SHARED_LIBRARY
+//                            - Define to 1 when compiling Google Test itself
+//                              as a shared library.
+//   GTEST_DEFAULT_DEATH_TEST_STYLE
+//                            - The default value of --gtest_death_test_style.
+//                              The legacy default has been "fast" in the open
+//                              source version since 2008. The recommended value
+//                              is "threadsafe", and can be set in
+//                              custom/gtest-port.h.
+
+// Platform-indicating macros
+// --------------------------
+//
+// Macros indicating the platform on which Google Test is being used
+// (a macro is defined to 1 if compiled on the given platform;
+// otherwise UNDEFINED -- it's never defined to 0.).  Google Test
+// defines these macros automatically.  Code outside Google Test MUST
+// NOT define them.
+//
+//   GTEST_OS_AIX      - IBM AIX
+//   GTEST_OS_CYGWIN   - Cygwin
+//   GTEST_OS_DRAGONFLY - DragonFlyBSD
+//   GTEST_OS_FREEBSD  - FreeBSD
+//   GTEST_OS_FUCHSIA  - Fuchsia
+//   GTEST_OS_GNU_KFREEBSD - GNU/kFreeBSD
+//   GTEST_OS_HAIKU    - Haiku
+//   GTEST_OS_HPUX     - HP-UX
+//   GTEST_OS_LINUX    - Linux
+//     GTEST_OS_LINUX_ANDROID - Google Android
+//   GTEST_OS_MAC      - Mac OS X
+//     GTEST_OS_IOS    - iOS
+//   GTEST_OS_NACL     - Google Native Client (NaCl)
+//   GTEST_OS_NETBSD   - NetBSD
+//   GTEST_OS_OPENBSD  - OpenBSD
+//   GTEST_OS_OS2      - OS/2
+//   GTEST_OS_QNX      - QNX
+//   GTEST_OS_SOLARIS  - Sun Solaris
+//   GTEST_OS_WINDOWS  - Windows (Desktop, MinGW, or Mobile)
+//     GTEST_OS_WINDOWS_DESKTOP  - Windows Desktop
+//     GTEST_OS_WINDOWS_MINGW    - MinGW
+//     GTEST_OS_WINDOWS_MOBILE   - Windows Mobile
+//     GTEST_OS_WINDOWS_PHONE    - Windows Phone
+//     GTEST_OS_WINDOWS_RT       - Windows Store App/WinRT
+//   GTEST_OS_ZOS      - z/OS
+//
+// Among the platforms, Cygwin, Linux, Mac OS X, and Windows have the
+// most stable support.  Since core members of the Google Test project
+// don't have access to other platforms, support for them may be less
+// stable.  If you notice any problems on your platform, please notify
+// googletestframework@googlegroups.com (patches for fixing them are
+// even more welcome!).
+//
+// It is possible that none of the GTEST_OS_* macros are defined.
+
+// Feature-indicating macros
+// -------------------------
+//
+// Macros indicating which Google Test features are available (a macro
+// is defined to 1 if the corresponding feature is supported;
+// otherwise UNDEFINED -- it's never defined to 0.).  Google Test
+// defines these macros automatically.  Code outside Google Test MUST
+// NOT define them.
+//
+// These macros are public so that portable tests can be written.
+// Such tests typically surround code using a feature with an #if
+// which controls that code.  For example:
+//
+// #if GTEST_HAS_DEATH_TEST
+//   EXPECT_DEATH(DoSomethingDeadly());
+// #endif
+//
+//   GTEST_HAS_DEATH_TEST   - death tests
+//   GTEST_HAS_TYPED_TEST   - typed tests
+//   GTEST_HAS_TYPED_TEST_P - type-parameterized tests
+//   GTEST_IS_THREADSAFE    - Google Test is thread-safe.
+//   GOOGLETEST_CM0007 DO NOT DELETE
+//   GTEST_USES_POSIX_RE    - enhanced POSIX regex is used. Do not confuse with
+//                            GTEST_HAS_POSIX_RE (see above) which users can
+//                            define themselves.
+//   GTEST_USES_SIMPLE_RE   - our own simple regex is used;
+//                            the above RE\b(s) are mutually exclusive.
+
+// Misc public macros
+// ------------------
+//
+//   GTEST_FLAG(flag_name)  - references the variable corresponding to
+//                            the given Google Test flag.
+
+// Internal utilities
+// ------------------
+//
+// The following macros and utilities are for Google Test's INTERNAL
+// use only.  Code outside Google Test MUST NOT USE THEM DIRECTLY.
+//
+// Macros for basic C++ coding:
+//   GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning.
+//   GTEST_ATTRIBUTE_UNUSED_  - declares that a class' instances or a
+//                              variable don't have to be used.
+//   GTEST_DISALLOW_ASSIGN_   - disables copy operator=.
+//   GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=.
+//   GTEST_DISALLOW_MOVE_ASSIGN_   - disables move operator=.
+//   GTEST_DISALLOW_MOVE_AND_ASSIGN_ - disables move ctor and operator=.
+//   GTEST_MUST_USE_RESULT_   - declares that a function's result must be used.
+//   GTEST_INTENTIONAL_CONST_COND_PUSH_ - start code section where MSVC C4127 is
+//                                        suppressed (constant conditional).
+//   GTEST_INTENTIONAL_CONST_COND_POP_  - finish code section where MSVC C4127
+//                                        is suppressed.
+//   GTEST_INTERNAL_HAS_ANY - for enabling UniversalPrinter<std::any> or
+//                            UniversalPrinter<absl::any> specializations.
+//   GTEST_INTERNAL_HAS_OPTIONAL - for enabling UniversalPrinter<std::optional>
+//   or
+//                                 UniversalPrinter<absl::optional>
+//                                 specializations.
+//   GTEST_INTERNAL_HAS_STRING_VIEW - for enabling Matcher<std::string_view> or
+//                                    Matcher<absl::string_view>
+//                                    specializations.
+//   GTEST_INTERNAL_HAS_VARIANT - for enabling UniversalPrinter<std::variant> or
+//                                UniversalPrinter<absl::variant>
+//                                specializations.
+//
+// Synchronization:
+//   Mutex, MutexLock, ThreadLocal, GetThreadCount()
+//                            - synchronization primitives.
+//
+// Regular expressions:
+//   RE             - a simple regular expression class using the POSIX
+//                    Extended Regular Expression syntax on UNIX-like platforms
+//                    GOOGLETEST_CM0008 DO NOT DELETE
+//                    or a reduced regular exception syntax on other
+//                    platforms, including Windows.
+// Logging:
+//   GTEST_LOG_()   - logs messages at the specified severity level.
+//   LogToStderr()  - directs all log messages to stderr.
+//   FlushInfoLog() - flushes informational log messages.
+//
+// Stdout and stderr capturing:
+//   CaptureStdout()     - starts capturing stdout.
+//   GetCapturedStdout() - stops capturing stdout and returns the captured
+//                         string.
+//   CaptureStderr()     - starts capturing stderr.
+//   GetCapturedStderr() - stops capturing stderr and returns the captured
+//                         string.
+//
+// Integer types:
+//   TypeWithSize   - maps an integer to a int type.
+//   TimeInMillis   - integers of known sizes.
+//   BiggestInt     - the biggest signed integer type.
+//
+// Command-line utilities:
+//   GTEST_DECLARE_*()  - declares a flag.
+//   GTEST_DEFINE_*()   - defines a flag.
+//   GetInjectableArgvs() - returns the command line as a vector of strings.
+//
+// Environment variable utilities:
+//   GetEnv()             - gets the value of an environment variable.
+//   BoolFromGTestEnv()   - parses a bool environment variable.
+//   Int32FromGTestEnv()  - parses an int32_t environment variable.
+//   StringFromGTestEnv() - parses a string environment variable.
+//
+// Deprecation warnings:
+//   GTEST_INTERNAL_DEPRECATED(message) - attribute marking a function as
+//                                        deprecated; calling a marked function
+//                                        should generate a compiler warning
+
+#include <ctype.h>   // for isspace, etc
+#include <stddef.h>  // for ptrdiff_t
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cerrno>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#ifndef _WIN32_WCE
+# include <sys/types.h>
+# include <sys/stat.h>
+#endif  // !_WIN32_WCE
+
+#if defined __APPLE__
+# include <AvailabilityMacros.h>
+# include <TargetConditionals.h>
+#endif
+
+#include <iostream>  // NOLINT
+#include <locale>
+#include <memory>
+#include <string>  // NOLINT
+#include <tuple>
+#include <vector>  // NOLINT
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the GTEST_OS_* macro.
+// It is separate from gtest-port.h so that custom/gtest-port.h can include it.
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+
+// Determines the platform on which Google Test is compiled.
+#ifdef __CYGWIN__
+# define GTEST_OS_CYGWIN 1
+# elif defined(__MINGW__) || defined(__MINGW32__) || defined(__MINGW64__)
+#  define GTEST_OS_WINDOWS_MINGW 1
+#  define GTEST_OS_WINDOWS 1
+#elif defined _WIN32
+# define GTEST_OS_WINDOWS 1
+# ifdef _WIN32_WCE
+#  define GTEST_OS_WINDOWS_MOBILE 1
+# elif defined(WINAPI_FAMILY)
+#  include <winapifamily.h>
+#  if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+#   define GTEST_OS_WINDOWS_DESKTOP 1
+#  elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP)
+#   define GTEST_OS_WINDOWS_PHONE 1
+#  elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP)
+#   define GTEST_OS_WINDOWS_RT 1
+#  elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_TV_TITLE)
+#   define GTEST_OS_WINDOWS_PHONE 1
+#   define GTEST_OS_WINDOWS_TV_TITLE 1
+#  else
+    // WINAPI_FAMILY defined but no known partition matched.
+    // Default to desktop.
+#   define GTEST_OS_WINDOWS_DESKTOP 1
+#  endif
+# else
+#  define GTEST_OS_WINDOWS_DESKTOP 1
+# endif  // _WIN32_WCE
+#elif defined __OS2__
+# define GTEST_OS_OS2 1
+#elif defined __APPLE__
+# define GTEST_OS_MAC 1
+# include <TargetConditionals.h>
+# if TARGET_OS_IPHONE
+#  define GTEST_OS_IOS 1
+# endif
+#elif defined __DragonFly__
+# define GTEST_OS_DRAGONFLY 1
+#elif defined __FreeBSD__
+# define GTEST_OS_FREEBSD 1
+#elif defined __Fuchsia__
+# define GTEST_OS_FUCHSIA 1
+#elif defined(__GLIBC__) && defined(__FreeBSD_kernel__)
+# define GTEST_OS_GNU_KFREEBSD 1
+#elif defined __linux__
+# define GTEST_OS_LINUX 1
+# if defined __ANDROID__
+#  define GTEST_OS_LINUX_ANDROID 1
+# endif
+#elif defined __MVS__
+# define GTEST_OS_ZOS 1
+#elif defined(__sun) && defined(__SVR4)
+# define GTEST_OS_SOLARIS 1
+#elif defined(_AIX)
+# define GTEST_OS_AIX 1
+#elif defined(__hpux)
+# define GTEST_OS_HPUX 1
+#elif defined __native_client__
+# define GTEST_OS_NACL 1
+#elif defined __NetBSD__
+# define GTEST_OS_NETBSD 1
+#elif defined __OpenBSD__
+# define GTEST_OS_OPENBSD 1
+#elif defined __QNX__
+# define GTEST_OS_QNX 1
+#elif defined(__HAIKU__)
+#define GTEST_OS_HAIKU 1
+#elif defined ESP8266
+#define GTEST_OS_ESP8266 1
+#elif defined ESP32
+#define GTEST_OS_ESP32 1
+#elif defined(__XTENSA__)
+#define GTEST_OS_XTENSA 1
+#endif  // __CYGWIN__
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_ARCH_H_
+
+#if !defined(GTEST_DEV_EMAIL_)
+# define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com"
+# define GTEST_FLAG_PREFIX_ "gtest_"
+# define GTEST_FLAG_PREFIX_DASH_ "gtest-"
+# define GTEST_FLAG_PREFIX_UPPER_ "GTEST_"
+# define GTEST_NAME_ "Google Test"
+# define GTEST_PROJECT_URL_ "https://github.com/google/googletest/"
+#endif  // !defined(GTEST_DEV_EMAIL_)
+
+#if !defined(GTEST_INIT_GOOGLE_TEST_NAME_)
+# define GTEST_INIT_GOOGLE_TEST_NAME_ "testing::InitGoogleTest"
+#endif  // !defined(GTEST_INIT_GOOGLE_TEST_NAME_)
+
+// Determines the version of gcc that is used to compile this.
+#ifdef __GNUC__
+// 40302 means version 4.3.2.
+# define GTEST_GCC_VER_ \
+    (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__)
+#endif  // __GNUC__
+
+// Macros for disabling Microsoft Visual C++ warnings.
+//
+//   GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 4385)
+//   /* code that triggers warnings C4800 and C4385 */
+//   GTEST_DISABLE_MSC_WARNINGS_POP_()
+#if defined(_MSC_VER)
+# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings) \
+    __pragma(warning(push))                        \
+    __pragma(warning(disable: warnings))
+# define GTEST_DISABLE_MSC_WARNINGS_POP_()          \
+    __pragma(warning(pop))
+#else
+// Not all compilers are MSVC
+# define GTEST_DISABLE_MSC_WARNINGS_PUSH_(warnings)
+# define GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+// Clang on Windows does not understand MSVC's pragma warning.
+// We need clang-specific way to disable function deprecation warning.
+#ifdef __clang__
+# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_()                         \
+    _Pragma("clang diagnostic push")                                  \
+    _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") \
+    _Pragma("clang diagnostic ignored \"-Wdeprecated-implementations\"")
+#define GTEST_DISABLE_MSC_DEPRECATED_POP_() \
+    _Pragma("clang diagnostic pop")
+#else
+# define GTEST_DISABLE_MSC_DEPRECATED_PUSH_() \
+    GTEST_DISABLE_MSC_WARNINGS_PUSH_(4996)
+# define GTEST_DISABLE_MSC_DEPRECATED_POP_() \
+    GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+// Brings in definitions for functions used in the testing::internal::posix
+// namespace (read, write, close, chdir, isatty, stat). We do not currently
+// use them on Windows Mobile.
+#if GTEST_OS_WINDOWS
+# if !GTEST_OS_WINDOWS_MOBILE
+#  include <direct.h>
+#  include <io.h>
+# endif
+// In order to avoid having to include <windows.h>, use forward declaration
+#if GTEST_OS_WINDOWS_MINGW && !defined(__MINGW64_VERSION_MAJOR)
+// MinGW defined _CRITICAL_SECTION and _RTL_CRITICAL_SECTION as two
+// separate (equivalent) structs, instead of using typedef
+typedef struct _CRITICAL_SECTION GTEST_CRITICAL_SECTION;
+#else
+// Assume CRITICAL_SECTION is a typedef of _RTL_CRITICAL_SECTION.
+// This assumption is verified by
+// WindowsTypesTest.CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION.
+typedef struct _RTL_CRITICAL_SECTION GTEST_CRITICAL_SECTION;
+#endif
+#elif GTEST_OS_XTENSA
+#include <unistd.h>
+// Xtensa toolchains define strcasecmp in the string.h header instead of
+// strings.h. string.h is already included.
+#else
+// This assumes that non-Windows OSes provide unistd.h. For OSes where this
+// is not the case, we need to include headers that provide the functions
+// mentioned above.
+# include <unistd.h>
+# include <strings.h>
+#endif  // GTEST_OS_WINDOWS
+
+#if GTEST_OS_LINUX_ANDROID
+// Used to define __ANDROID_API__ matching the target NDK API level.
+#  include <android/api-level.h>  // NOLINT
+#endif
+
+// Defines this to true if and only if Google Test can use POSIX regular
+// expressions.
+#ifndef GTEST_HAS_POSIX_RE
+# if GTEST_OS_LINUX_ANDROID
+// On Android, <regex.h> is only available starting with Gingerbread.
+#  define GTEST_HAS_POSIX_RE (__ANDROID_API__ >= 9)
+# else
+#define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS && !GTEST_OS_XTENSA)
+# endif
+#endif
+
+#if GTEST_USES_PCRE
+// The appropriate headers have already been included.
+
+#elif GTEST_HAS_POSIX_RE
+
+// On some platforms, <regex.h> needs someone to define size_t, and
+// won't compile otherwise.  We can #include it here as we already
+// included <stdlib.h>, which is guaranteed to define size_t through
+// <stddef.h>.
+# include <regex.h>  // NOLINT
+
+# define GTEST_USES_POSIX_RE 1
+
+#elif GTEST_OS_WINDOWS
+
+// <regex.h> is not available on Windows.  Use our own simple regex
+// implementation instead.
+# define GTEST_USES_SIMPLE_RE 1
+
+#else
+
+// <regex.h> may not be available on this platform.  Use our own
+// simple regex implementation instead.
+# define GTEST_USES_SIMPLE_RE 1
+
+#endif  // GTEST_USES_PCRE
+
+#ifndef GTEST_HAS_EXCEPTIONS
+// The user didn't tell us whether exceptions are enabled, so we need
+// to figure it out.
+# if defined(_MSC_VER) && defined(_CPPUNWIND)
+// MSVC defines _CPPUNWIND to 1 if and only if exceptions are enabled.
+#  define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__BORLANDC__)
+// C++Builder's implementation of the STL uses the _HAS_EXCEPTIONS
+// macro to enable exceptions, so we'll do the same.
+// Assumes that exceptions are enabled by default.
+#  ifndef _HAS_EXCEPTIONS
+#   define _HAS_EXCEPTIONS 1
+#  endif  // _HAS_EXCEPTIONS
+#  define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS
+# elif defined(__clang__)
+// clang defines __EXCEPTIONS if and only if exceptions are enabled before clang
+// 220714, but if and only if cleanups are enabled after that. In Obj-C++ files,
+// there can be cleanups for ObjC exceptions which also need cleanups, even if
+// C++ exceptions are disabled. clang has __has_feature(cxx_exceptions) which
+// checks for C++ exceptions starting at clang r206352, but which checked for
+// cleanups prior to that. To reliably check for C++ exception availability with
+// clang, check for
+// __EXCEPTIONS && __has_feature(cxx_exceptions).
+#  define GTEST_HAS_EXCEPTIONS (__EXCEPTIONS && __has_feature(cxx_exceptions))
+# elif defined(__GNUC__) && __EXCEPTIONS
+// gcc defines __EXCEPTIONS to 1 if and only if exceptions are enabled.
+#  define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__SUNPRO_CC)
+// Sun Pro CC supports exceptions.  However, there is no compile-time way of
+// detecting whether they are enabled or not.  Therefore, we assume that
+// they are enabled unless the user tells us otherwise.
+#  define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__IBMCPP__) && __EXCEPTIONS
+// xlC defines __EXCEPTIONS to 1 if and only if exceptions are enabled.
+#  define GTEST_HAS_EXCEPTIONS 1
+# elif defined(__HP_aCC)
+// Exception handling is in effect by default in HP aCC compiler. It has to
+// be turned of by +noeh compiler option if desired.
+#  define GTEST_HAS_EXCEPTIONS 1
+# else
+// For other compilers, we assume exceptions are disabled to be
+// conservative.
+#  define GTEST_HAS_EXCEPTIONS 0
+# endif  // defined(_MSC_VER) || defined(__BORLANDC__)
+#endif  // GTEST_HAS_EXCEPTIONS
+
+#ifndef GTEST_HAS_STD_WSTRING
+// The user didn't tell us whether ::std::wstring is available, so we need
+// to figure it out.
+// Cygwin 1.7 and below doesn't support ::std::wstring.
+// Solaris' libc++ doesn't support it either.  Android has
+// no support for it at least as recent as Froyo (2.2).
+#define GTEST_HAS_STD_WSTRING                                         \
+  (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \
+     GTEST_OS_HAIKU || GTEST_OS_ESP32 || GTEST_OS_ESP8266 || GTEST_OS_XTENSA))
+
+#endif  // GTEST_HAS_STD_WSTRING
+
+// Determines whether RTTI is available.
+#ifndef GTEST_HAS_RTTI
+// The user didn't tell us whether RTTI is enabled, so we need to
+// figure it out.
+
+# ifdef _MSC_VER
+
+#ifdef _CPPRTTI  // MSVC defines this macro if and only if RTTI is enabled.
+#   define GTEST_HAS_RTTI 1
+#  else
+#   define GTEST_HAS_RTTI 0
+#  endif
+
+// Starting with version 4.3.2, gcc defines __GXX_RTTI if and only if RTTI is
+// enabled.
+# elif defined(__GNUC__)
+
+#  ifdef __GXX_RTTI
+// When building against STLport with the Android NDK and with
+// -frtti -fno-exceptions, the build fails at link time with undefined
+// references to __cxa_bad_typeid. Note sure if STL or toolchain bug,
+// so disable RTTI when detected.
+#   if GTEST_OS_LINUX_ANDROID && defined(_STLPORT_MAJOR) && \
+       !defined(__EXCEPTIONS)
+#    define GTEST_HAS_RTTI 0
+#   else
+#    define GTEST_HAS_RTTI 1
+#   endif  // GTEST_OS_LINUX_ANDROID && __STLPORT_MAJOR && !__EXCEPTIONS
+#  else
+#   define GTEST_HAS_RTTI 0
+#  endif  // __GXX_RTTI
+
+// Clang defines __GXX_RTTI starting with version 3.0, but its manual recommends
+// using has_feature instead. has_feature(cxx_rtti) is supported since 2.7, the
+// first version with C++ support.
+# elif defined(__clang__)
+
+#  define GTEST_HAS_RTTI __has_feature(cxx_rtti)
+
+// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if
+// both the typeid and dynamic_cast features are present.
+# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900)
+
+#  ifdef __RTTI_ALL__
+#   define GTEST_HAS_RTTI 1
+#  else
+#   define GTEST_HAS_RTTI 0
+#  endif
+
+# else
+
+// For all other compilers, we assume RTTI is enabled.
+#  define GTEST_HAS_RTTI 1
+
+# endif  // _MSC_VER
+
+#endif  // GTEST_HAS_RTTI
+
+// It's this header's responsibility to #include <typeinfo> when RTTI
+// is enabled.
+#if GTEST_HAS_RTTI
+# include <typeinfo>
+#endif
+
+// Determines whether Google Test can use the pthreads library.
+#ifndef GTEST_HAS_PTHREAD
+// The user didn't tell us explicitly, so we make reasonable assumptions about
+// which platforms have pthreads support.
+//
+// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0
+// to your compiler flags.
+#define GTEST_HAS_PTHREAD                                                      \
+  (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX || GTEST_OS_QNX ||          \
+   GTEST_OS_FREEBSD || GTEST_OS_NACL || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA || \
+   GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_OPENBSD ||          \
+   GTEST_OS_HAIKU)
+#endif  // GTEST_HAS_PTHREAD
+
+#if GTEST_HAS_PTHREAD
+// gtest-port.h guarantees to #include <pthread.h> when GTEST_HAS_PTHREAD is
+// true.
+# include <pthread.h>  // NOLINT
+
+// For timespec and nanosleep, used below.
+# include <time.h>  // NOLINT
+#endif
+
+// Determines whether clone(2) is supported.
+// Usually it will only be available on Linux, excluding
+// Linux on the Itanium architecture.
+// Also see http://linux.die.net/man/2/clone.
+#ifndef GTEST_HAS_CLONE
+// The user didn't tell us, so we need to figure it out.
+
+# if GTEST_OS_LINUX && !defined(__ia64__)
+#  if GTEST_OS_LINUX_ANDROID
+// On Android, clone() became available at different API levels for each 32-bit
+// architecture.
+#    if defined(__LP64__) || \
+        (defined(__arm__) && __ANDROID_API__ >= 9) || \
+        (defined(__mips__) && __ANDROID_API__ >= 12) || \
+        (defined(__i386__) && __ANDROID_API__ >= 17)
+#     define GTEST_HAS_CLONE 1
+#    else
+#     define GTEST_HAS_CLONE 0
+#    endif
+#  else
+#   define GTEST_HAS_CLONE 1
+#  endif
+# else
+#  define GTEST_HAS_CLONE 0
+# endif  // GTEST_OS_LINUX && !defined(__ia64__)
+
+#endif  // GTEST_HAS_CLONE
+
+// Determines whether to support stream redirection. This is used to test
+// output correctness and to implement death tests.
+#ifndef GTEST_HAS_STREAM_REDIRECTION
+// By default, we assume that stream redirection is supported on all
+// platforms except known mobile ones.
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \
+    GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_XTENSA
+#  define GTEST_HAS_STREAM_REDIRECTION 0
+# else
+#  define GTEST_HAS_STREAM_REDIRECTION 1
+# endif  // !GTEST_OS_WINDOWS_MOBILE
+#endif  // GTEST_HAS_STREAM_REDIRECTION
+
+// Determines whether to support death tests.
+// pops up a dialog window that cannot be suppressed programmatically.
+#if (GTEST_OS_LINUX || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS ||             \
+     (GTEST_OS_MAC && !GTEST_OS_IOS) ||                                   \
+     (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER) || GTEST_OS_WINDOWS_MINGW ||  \
+     GTEST_OS_AIX || GTEST_OS_HPUX || GTEST_OS_OPENBSD || GTEST_OS_QNX || \
+     GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_FUCHSIA ||           \
+     GTEST_OS_DRAGONFLY || GTEST_OS_GNU_KFREEBSD || GTEST_OS_HAIKU)
+# define GTEST_HAS_DEATH_TEST 1
+#endif
+
+// Determines whether to support type-driven tests.
+
+// Typed tests need <typeinfo> and variadic macros, which GCC, VC++ 8.0,
+// Sun Pro CC, IBM Visual Age, and HP aCC support.
+#if defined(__GNUC__) || defined(_MSC_VER) || defined(__SUNPRO_CC) || \
+    defined(__IBMCPP__) || defined(__HP_aCC)
+# define GTEST_HAS_TYPED_TEST 1
+# define GTEST_HAS_TYPED_TEST_P 1
+#endif
+
+// Determines whether the system compiler uses UTF-16 for encoding wide strings.
+#define GTEST_WIDE_STRING_USES_UTF16_ \
+  (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_AIX || GTEST_OS_OS2)
+
+// Determines whether test results can be streamed to a socket.
+#if GTEST_OS_LINUX || GTEST_OS_GNU_KFREEBSD || GTEST_OS_DRAGONFLY || \
+    GTEST_OS_FREEBSD || GTEST_OS_NETBSD || GTEST_OS_OPENBSD
+# define GTEST_CAN_STREAM_RESULTS_ 1
+#endif
+
+// Defines some utility macros.
+
+// The GNU compiler emits a warning if nested "if" statements are followed by
+// an "else" statement and braces are not used to explicitly disambiguate the
+// "else" binding.  This leads to problems with code like:
+//
+//   if (gate)
+//     ASSERT_*(condition) << "Some message";
+//
+// The "switch (0) case 0:" idiom is used to suppress this.
+#ifdef __INTEL_COMPILER
+# define GTEST_AMBIGUOUS_ELSE_BLOCKER_
+#else
+# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default:  // NOLINT
+#endif
+
+// Use this annotation at the end of a struct/class definition to
+// prevent the compiler from optimizing away instances that are never
+// used.  This is useful when all interesting logic happens inside the
+// c'tor and / or d'tor.  Example:
+//
+//   struct Foo {
+//     Foo() { ... }
+//   } GTEST_ATTRIBUTE_UNUSED_;
+//
+// Also use it after a variable or parameter declaration to tell the
+// compiler the variable/parameter does not have to be used.
+#if defined(__GNUC__) && !defined(COMPILER_ICC)
+# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused))
+#elif defined(__clang__)
+# if __has_attribute(unused)
+#  define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused))
+# endif
+#endif
+#ifndef GTEST_ATTRIBUTE_UNUSED_
+# define GTEST_ATTRIBUTE_UNUSED_
+#endif
+
+// Use this annotation before a function that takes a printf format string.
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(COMPILER_ICC)
+# if defined(__MINGW_PRINTF_FORMAT)
+// MinGW has two different printf implementations. Ensure the format macro
+// matches the selected implementation. See
+// https://sourceforge.net/p/mingw-w64/wiki2/gnu%20printf/.
+#  define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \
+       __attribute__((__format__(__MINGW_PRINTF_FORMAT, string_index, \
+                                 first_to_check)))
+# else
+#  define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check) \
+       __attribute__((__format__(__printf__, string_index, first_to_check)))
+# endif
+#else
+# define GTEST_ATTRIBUTE_PRINTF_(string_index, first_to_check)
+#endif
+
+
+// A macro to disallow copy operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_ASSIGN_(type) \
+  type& operator=(type const &) = delete
+
+// A macro to disallow copy constructor and operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type) \
+  type(type const&) = delete;                 \
+  type& operator=(type const&) = delete
+
+// A macro to disallow move operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_MOVE_ASSIGN_(type) \
+  type& operator=(type &&) noexcept = delete
+
+// A macro to disallow move constructor and operator=
+// This should be used in the private: declarations for a class.
+#define GTEST_DISALLOW_MOVE_AND_ASSIGN_(type) \
+  type(type&&) noexcept = delete;             \
+  type& operator=(type&&) noexcept = delete
+
+// Tell the compiler to warn about unused return values for functions declared
+// with this macro.  The macro should be used on function declarations
+// following the argument list:
+//
+//   Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_;
+#if defined(__GNUC__) && !defined(COMPILER_ICC)
+# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result))
+#else
+# define GTEST_MUST_USE_RESULT_
+#endif  // __GNUC__ && !COMPILER_ICC
+
+// MS C++ compiler emits warning when a conditional expression is compile time
+// constant. In some contexts this warning is false positive and needs to be
+// suppressed. Use the following two macros in such cases:
+//
+// GTEST_INTENTIONAL_CONST_COND_PUSH_()
+// while (true) {
+// GTEST_INTENTIONAL_CONST_COND_POP_()
+// }
+# define GTEST_INTENTIONAL_CONST_COND_PUSH_() \
+    GTEST_DISABLE_MSC_WARNINGS_PUSH_(4127)
+# define GTEST_INTENTIONAL_CONST_COND_POP_() \
+    GTEST_DISABLE_MSC_WARNINGS_POP_()
+
+// Determine whether the compiler supports Microsoft's Structured Exception
+// Handling.  This is supported by several Windows compilers but generally
+// does not exist on any other system.
+#ifndef GTEST_HAS_SEH
+// The user didn't tell us, so we need to figure it out.
+
+# if defined(_MSC_VER) || defined(__BORLANDC__)
+// These two compilers are known to support SEH.
+#  define GTEST_HAS_SEH 1
+# else
+// Assume no SEH.
+#  define GTEST_HAS_SEH 0
+# endif
+
+#endif  // GTEST_HAS_SEH
+
+#ifndef GTEST_IS_THREADSAFE
+
+#define GTEST_IS_THREADSAFE                                                 \
+  (GTEST_HAS_MUTEX_AND_THREAD_LOCAL_ ||                                     \
+   (GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT) || \
+   GTEST_HAS_PTHREAD)
+
+#endif  // GTEST_IS_THREADSAFE
+
+// GTEST_API_ qualifies all symbols that must be exported. The definitions below
+// are guarded by #ifndef to give embedders a chance to define GTEST_API_ in
+// gtest/internal/custom/gtest-port.h
+#ifndef GTEST_API_
+
+#ifdef _MSC_VER
+# if GTEST_LINKED_AS_SHARED_LIBRARY
+#  define GTEST_API_ __declspec(dllimport)
+# elif GTEST_CREATE_SHARED_LIBRARY
+#  define GTEST_API_ __declspec(dllexport)
+# endif
+#elif __GNUC__ >= 4 || defined(__clang__)
+# define GTEST_API_ __attribute__((visibility ("default")))
+#endif  // _MSC_VER
+
+#endif  // GTEST_API_
+
+#ifndef GTEST_API_
+# define GTEST_API_
+#endif  // GTEST_API_
+
+#ifndef GTEST_DEFAULT_DEATH_TEST_STYLE
+# define GTEST_DEFAULT_DEATH_TEST_STYLE  "fast"
+#endif  // GTEST_DEFAULT_DEATH_TEST_STYLE
+
+#ifdef __GNUC__
+// Ask the compiler to never inline a given function.
+# define GTEST_NO_INLINE_ __attribute__((noinline))
+#else
+# define GTEST_NO_INLINE_
+#endif
+
+// _LIBCPP_VERSION is defined by the libc++ library from the LLVM project.
+#if !defined(GTEST_HAS_CXXABI_H_)
+# if defined(__GLIBCXX__) || (defined(_LIBCPP_VERSION) && !defined(_MSC_VER))
+#  define GTEST_HAS_CXXABI_H_ 1
+# else
+#  define GTEST_HAS_CXXABI_H_ 0
+# endif
+#endif
+
+// A function level attribute to disable checking for use of uninitialized
+// memory when built with MemorySanitizer.
+#if defined(__clang__)
+# if __has_feature(memory_sanitizer)
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_ \
+       __attribute__((no_sanitize_memory))
+# else
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+# endif  // __has_feature(memory_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_MEMORY_
+#endif  // __clang__
+
+// A function level attribute to disable AddressSanitizer instrumentation.
+#if defined(__clang__)
+# if __has_feature(address_sanitizer)
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ \
+       __attribute__((no_sanitize_address))
+# else
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+# endif  // __has_feature(address_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_
+#endif  // __clang__
+
+// A function level attribute to disable HWAddressSanitizer instrumentation.
+#if defined(__clang__)
+# if __has_feature(hwaddress_sanitizer)
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ \
+       __attribute__((no_sanitize("hwaddress")))
+# else
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+# endif  // __has_feature(hwaddress_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_
+#endif  // __clang__
+
+// A function level attribute to disable ThreadSanitizer instrumentation.
+#if defined(__clang__)
+# if __has_feature(thread_sanitizer)
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_ \
+       __attribute__((no_sanitize_thread))
+# else
+#  define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+# endif  // __has_feature(thread_sanitizer)
+#else
+# define GTEST_ATTRIBUTE_NO_SANITIZE_THREAD_
+#endif  // __clang__
+
+namespace testing {
+
+class Message;
+
+// Legacy imports for backwards compatibility.
+// New code should use std:: names directly.
+using std::get;
+using std::make_tuple;
+using std::tuple;
+using std::tuple_element;
+using std::tuple_size;
+
+namespace internal {
+
+// A secret type that Google Test users don't know about.  It has no
+// definition on purpose.  Therefore it's impossible to create a
+// Secret object, which is what we want.
+class Secret;
+
+// The GTEST_COMPILE_ASSERT_ is a legacy macro used to verify that a compile
+// time expression is true (in new code, use static_assert instead). For
+// example, you could use it to verify the size of a static array:
+//
+//   GTEST_COMPILE_ASSERT_(GTEST_ARRAY_SIZE_(names) == NUM_NAMES,
+//                         names_incorrect_size);
+//
+// The second argument to the macro must be a valid C++ identifier. If the
+// expression is false, compiler will issue an error containing this identifier.
+#define GTEST_COMPILE_ASSERT_(expr, msg) static_assert(expr, #msg)
+
+// A helper for suppressing warnings on constant condition.  It just
+// returns 'condition'.
+GTEST_API_ bool IsTrue(bool condition);
+
+// Defines RE.
+
+#if GTEST_USES_PCRE
+// if used, PCRE is injected by custom/gtest-port.h
+#elif GTEST_USES_POSIX_RE || GTEST_USES_SIMPLE_RE
+
+// A simple C++ wrapper for <regex.h>.  It uses the POSIX Extended
+// Regular Expression syntax.
+class GTEST_API_ RE {
+ public:
+  // A copy constructor is required by the Standard to initialize object
+  // references from r-values.
+  RE(const RE& other) { Init(other.pattern()); }
+
+  // Constructs an RE from a string.
+  RE(const ::std::string& regex) { Init(regex.c_str()); }  // NOLINT
+
+  RE(const char* regex) { Init(regex); }  // NOLINT
+  ~RE();
+
+  // Returns the string representation of the regex.
+  const char* pattern() const { return pattern_; }
+
+  // FullMatch(str, re) returns true if and only if regular expression re
+  // matches the entire str.
+  // PartialMatch(str, re) returns true if and only if regular expression re
+  // matches a substring of str (including str itself).
+  static bool FullMatch(const ::std::string& str, const RE& re) {
+    return FullMatch(str.c_str(), re);
+  }
+  static bool PartialMatch(const ::std::string& str, const RE& re) {
+    return PartialMatch(str.c_str(), re);
+  }
+
+  static bool FullMatch(const char* str, const RE& re);
+  static bool PartialMatch(const char* str, const RE& re);
+
+ private:
+  void Init(const char* regex);
+  const char* pattern_;
+  bool is_valid_;
+
+# if GTEST_USES_POSIX_RE
+
+  regex_t full_regex_;     // For FullMatch().
+  regex_t partial_regex_;  // For PartialMatch().
+
+# else  // GTEST_USES_SIMPLE_RE
+
+  const char* full_pattern_;  // For FullMatch();
+
+# endif
+};
+
+#endif  // GTEST_USES_PCRE
+
+// Formats a source file path and a line number as they would appear
+// in an error message from the compiler used to compile this code.
+GTEST_API_ ::std::string FormatFileLocation(const char* file, int line);
+
+// Formats a file location for compiler-independent XML output.
+// Although this function is not platform dependent, we put it next to
+// FormatFileLocation in order to contrast the two functions.
+GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file,
+                                                               int line);
+
+// Defines logging utilities:
+//   GTEST_LOG_(severity) - logs messages at the specified severity level. The
+//                          message itself is streamed into the macro.
+//   LogToStderr()  - directs all log messages to stderr.
+//   FlushInfoLog() - flushes informational log messages.
+
+enum GTestLogSeverity {
+  GTEST_INFO,
+  GTEST_WARNING,
+  GTEST_ERROR,
+  GTEST_FATAL
+};
+
+// Formats log entry severity, provides a stream object for streaming the
+// log message, and terminates the message with a newline when going out of
+// scope.
+class GTEST_API_ GTestLog {
+ public:
+  GTestLog(GTestLogSeverity severity, const char* file, int line);
+
+  // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program.
+  ~GTestLog();
+
+  ::std::ostream& GetStream() { return ::std::cerr; }
+
+ private:
+  const GTestLogSeverity severity_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog);
+};
+
+#if !defined(GTEST_LOG_)
+
+# define GTEST_LOG_(severity) \
+    ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \
+                                  __FILE__, __LINE__).GetStream()
+
+inline void LogToStderr() {}
+inline void FlushInfoLog() { fflush(nullptr); }
+
+#endif  // !defined(GTEST_LOG_)
+
+#if !defined(GTEST_CHECK_)
+// INTERNAL IMPLEMENTATION - DO NOT USE.
+//
+// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition
+// is not satisfied.
+//  Synopsys:
+//    GTEST_CHECK_(boolean_condition);
+//     or
+//    GTEST_CHECK_(boolean_condition) << "Additional message";
+//
+//    This checks the condition and if the condition is not satisfied
+//    it prints message about the condition violation, including the
+//    condition itself, plus additional message streamed into it, if any,
+//    and then it aborts the program. It aborts the program irrespective of
+//    whether it is built in the debug mode or not.
+# define GTEST_CHECK_(condition) \
+    GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+    if (::testing::internal::IsTrue(condition)) \
+      ; \
+    else \
+      GTEST_LOG_(FATAL) << "Condition " #condition " failed. "
+#endif  // !defined(GTEST_CHECK_)
+
+// An all-mode assert to verify that the given POSIX-style function
+// call returns 0 (indicating success).  Known limitation: this
+// doesn't expand to a balanced 'if' statement, so enclose the macro
+// in {} if you need to use it as the only statement in an 'if'
+// branch.
+#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \
+  if (const int gtest_error = (posix_call)) \
+    GTEST_LOG_(FATAL) << #posix_call << "failed with error " \
+                      << gtest_error
+
+// Transforms "T" into "const T&" according to standard reference collapsing
+// rules (this is only needed as a backport for C++98 compilers that do not
+// support reference collapsing). Specifically, it transforms:
+//
+//   char         ==> const char&
+//   const char   ==> const char&
+//   char&        ==> char&
+//   const char&  ==> const char&
+//
+// Note that the non-const reference will not have "const" added. This is
+// standard, and necessary so that "T" can always bind to "const T&".
+template <typename T>
+struct ConstRef { typedef const T& type; };
+template <typename T>
+struct ConstRef<T&> { typedef T& type; };
+
+// The argument T must depend on some template parameters.
+#define GTEST_REFERENCE_TO_CONST_(T) \
+  typename ::testing::internal::ConstRef<T>::type
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Use ImplicitCast_ as a safe version of static_cast for upcasting in
+// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a
+// const Foo*).  When you use ImplicitCast_, the compiler checks that
+// the cast is safe.  Such explicit ImplicitCast_s are necessary in
+// surprisingly many situations where C++ demands an exact type match
+// instead of an argument type convertable to a target type.
+//
+// The syntax for using ImplicitCast_ is the same as for static_cast:
+//
+//   ImplicitCast_<ToType>(expr)
+//
+// ImplicitCast_ would have been part of the C++ standard library,
+// but the proposal was submitted too late.  It will probably make
+// its way into the language in the future.
+//
+// This relatively ugly name is intentional. It prevents clashes with
+// similar functions users may have (e.g., implicit_cast). The internal
+// namespace alone is not enough because the function can be found by ADL.
+template<typename To>
+inline To ImplicitCast_(To x) { return x; }
+
+// When you upcast (that is, cast a pointer from type Foo to type
+// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts
+// always succeed.  When you downcast (that is, cast a pointer from
+// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because
+// how do you know the pointer is really of type SubclassOfFoo?  It
+// could be a bare Foo, or of type DifferentSubclassOfFoo.  Thus,
+// when you downcast, you should use this macro.  In debug mode, we
+// use dynamic_cast<> to double-check the downcast is legal (we die
+// if it's not).  In normal mode, we do the efficient static_cast<>
+// instead.  Thus, it's important to test in debug mode to make sure
+// the cast is legal!
+//    This is the only place in the code we should use dynamic_cast<>.
+// In particular, you SHOULDN'T be using dynamic_cast<> in order to
+// do RTTI (eg code like this:
+//    if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo);
+//    if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo);
+// You should design the code some other way not to need this.
+//
+// This relatively ugly name is intentional. It prevents clashes with
+// similar functions users may have (e.g., down_cast). The internal
+// namespace alone is not enough because the function can be found by ADL.
+template<typename To, typename From>  // use like this: DownCast_<T*>(foo);
+inline To DownCast_(From* f) {  // so we only accept pointers
+  // Ensures that To is a sub-type of From *.  This test is here only
+  // for compile-time type checking, and has no overhead in an
+  // optimized build at run-time, as it will be optimized away
+  // completely.
+  GTEST_INTENTIONAL_CONST_COND_PUSH_()
+  if (false) {
+  GTEST_INTENTIONAL_CONST_COND_POP_()
+  const To to = nullptr;
+  ::testing::internal::ImplicitCast_<From*>(to);
+  }
+
+#if GTEST_HAS_RTTI
+  // RTTI: debug mode only!
+  GTEST_CHECK_(f == nullptr || dynamic_cast<To>(f) != nullptr);
+#endif
+  return static_cast<To>(f);
+}
+
+// Downcasts the pointer of type Base to Derived.
+// Derived must be a subclass of Base. The parameter MUST
+// point to a class of type Derived, not any subclass of it.
+// When RTTI is available, the function performs a runtime
+// check to enforce this.
+template <class Derived, class Base>
+Derived* CheckedDowncastToActualType(Base* base) {
+#if GTEST_HAS_RTTI
+  GTEST_CHECK_(typeid(*base) == typeid(Derived));
+#endif
+
+#if GTEST_HAS_DOWNCAST_
+  return ::down_cast<Derived*>(base);
+#elif GTEST_HAS_RTTI
+  return dynamic_cast<Derived*>(base);  // NOLINT
+#else
+  return static_cast<Derived*>(base);  // Poor man's downcast.
+#endif
+}
+
+#if GTEST_HAS_STREAM_REDIRECTION
+
+// Defines the stderr capturer:
+//   CaptureStdout     - starts capturing stdout.
+//   GetCapturedStdout - stops capturing stdout and returns the captured string.
+//   CaptureStderr     - starts capturing stderr.
+//   GetCapturedStderr - stops capturing stderr and returns the captured string.
+//
+GTEST_API_ void CaptureStdout();
+GTEST_API_ std::string GetCapturedStdout();
+GTEST_API_ void CaptureStderr();
+GTEST_API_ std::string GetCapturedStderr();
+
+#endif  // GTEST_HAS_STREAM_REDIRECTION
+// Returns the size (in bytes) of a file.
+GTEST_API_ size_t GetFileSize(FILE* file);
+
+// Reads the entire content of a file as a string.
+GTEST_API_ std::string ReadEntireFile(FILE* file);
+
+// All command line arguments.
+GTEST_API_ std::vector<std::string> GetArgvs();
+
+#if GTEST_HAS_DEATH_TEST
+
+std::vector<std::string> GetInjectableArgvs();
+// Deprecated: pass the args vector by value instead.
+void SetInjectableArgvs(const std::vector<std::string>* new_argvs);
+void SetInjectableArgvs(const std::vector<std::string>& new_argvs);
+void ClearInjectableArgvs();
+
+#endif  // GTEST_HAS_DEATH_TEST
+
+// Defines synchronization primitives.
+#if GTEST_IS_THREADSAFE
+# if GTEST_HAS_PTHREAD
+// Sleeps for (roughly) n milliseconds.  This function is only for testing
+// Google Test's own constructs.  Don't use it in user tests, either
+// directly or indirectly.
+inline void SleepMilliseconds(int n) {
+  const timespec time = {
+    0,                  // 0 seconds.
+    n * 1000L * 1000L,  // And n ms.
+  };
+  nanosleep(&time, nullptr);
+}
+# endif  // GTEST_HAS_PTHREAD
+
+# if GTEST_HAS_NOTIFICATION_
+// Notification has already been imported into the namespace.
+// Nothing to do here.
+
+# elif GTEST_HAS_PTHREAD
+// Allows a controller thread to pause execution of newly created
+// threads until notified.  Instances of this class must be created
+// and destroyed in the controller thread.
+//
+// This class is only for testing Google Test's own constructs. Do not
+// use it in user tests, either directly or indirectly.
+class Notification {
+ public:
+  Notification() : notified_(false) {
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr));
+  }
+  ~Notification() {
+    pthread_mutex_destroy(&mutex_);
+  }
+
+  // Notifies all threads created with this notification to start. Must
+  // be called from the controller thread.
+  void Notify() {
+    pthread_mutex_lock(&mutex_);
+    notified_ = true;
+    pthread_mutex_unlock(&mutex_);
+  }
+
+  // Blocks until the controller thread notifies. Must be called from a test
+  // thread.
+  void WaitForNotification() {
+    for (;;) {
+      pthread_mutex_lock(&mutex_);
+      const bool notified = notified_;
+      pthread_mutex_unlock(&mutex_);
+      if (notified)
+        break;
+      SleepMilliseconds(10);
+    }
+  }
+
+ private:
+  pthread_mutex_t mutex_;
+  bool notified_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification);
+};
+
+# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+
+GTEST_API_ void SleepMilliseconds(int n);
+
+// Provides leak-safe Windows kernel handle ownership.
+// Used in death tests and in threading support.
+class GTEST_API_ AutoHandle {
+ public:
+  // Assume that Win32 HANDLE type is equivalent to void*. Doing so allows us to
+  // avoid including <windows.h> in this header file. Including <windows.h> is
+  // undesirable because it defines a lot of symbols and macros that tend to
+  // conflict with client code. This assumption is verified by
+  // WindowsTypesTest.HANDLEIsVoidStar.
+  typedef void* Handle;
+  AutoHandle();
+  explicit AutoHandle(Handle handle);
+
+  ~AutoHandle();
+
+  Handle Get() const;
+  void Reset();
+  void Reset(Handle handle);
+
+ private:
+  // Returns true if and only if the handle is a valid handle object that can be
+  // closed.
+  bool IsCloseable() const;
+
+  Handle handle_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle);
+};
+
+// Allows a controller thread to pause execution of newly created
+// threads until notified.  Instances of this class must be created
+// and destroyed in the controller thread.
+//
+// This class is only for testing Google Test's own constructs. Do not
+// use it in user tests, either directly or indirectly.
+class GTEST_API_ Notification {
+ public:
+  Notification();
+  void Notify();
+  void WaitForNotification();
+
+ private:
+  AutoHandle event_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification);
+};
+# endif  // GTEST_HAS_NOTIFICATION_
+
+// On MinGW, we can have both GTEST_OS_WINDOWS and GTEST_HAS_PTHREAD
+// defined, but we don't want to use MinGW's pthreads implementation, which
+// has conformance problems with some versions of the POSIX standard.
+# if GTEST_HAS_PTHREAD && !GTEST_OS_WINDOWS_MINGW
+
+// As a C-function, ThreadFuncWithCLinkage cannot be templated itself.
+// Consequently, it cannot select a correct instantiation of ThreadWithParam
+// in order to call its Run(). Introducing ThreadWithParamBase as a
+// non-templated base class for ThreadWithParam allows us to bypass this
+// problem.
+class ThreadWithParamBase {
+ public:
+  virtual ~ThreadWithParamBase() {}
+  virtual void Run() = 0;
+};
+
+// pthread_create() accepts a pointer to a function type with the C linkage.
+// According to the Standard (7.5/1), function types with different linkages
+// are different even if they are otherwise identical.  Some compilers (for
+// example, SunStudio) treat them as different types.  Since class methods
+// cannot be defined with C-linkage we need to define a free C-function to
+// pass into pthread_create().
+extern "C" inline void* ThreadFuncWithCLinkage(void* thread) {
+  static_cast<ThreadWithParamBase*>(thread)->Run();
+  return nullptr;
+}
+
+// Helper class for testing Google Test's multi-threading constructs.
+// To use it, write:
+//
+//   void ThreadFunc(int param) { /* Do things with param */ }
+//   Notification thread_can_start;
+//   ...
+//   // The thread_can_start parameter is optional; you can supply NULL.
+//   ThreadWithParam<int> thread(&ThreadFunc, 5, &thread_can_start);
+//   thread_can_start.Notify();
+//
+// These classes are only for testing Google Test's own constructs. Do
+// not use them in user tests, either directly or indirectly.
+template <typename T>
+class ThreadWithParam : public ThreadWithParamBase {
+ public:
+  typedef void UserThreadFunc(T);
+
+  ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start)
+      : func_(func),
+        param_(param),
+        thread_can_start_(thread_can_start),
+        finished_(false) {
+    ThreadWithParamBase* const base = this;
+    // The thread can be created only after all fields except thread_
+    // have been initialized.
+    GTEST_CHECK_POSIX_SUCCESS_(
+        pthread_create(&thread_, nullptr, &ThreadFuncWithCLinkage, base));
+  }
+  ~ThreadWithParam() override { Join(); }
+
+  void Join() {
+    if (!finished_) {
+      GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, nullptr));
+      finished_ = true;
+    }
+  }
+
+  void Run() override {
+    if (thread_can_start_ != nullptr) thread_can_start_->WaitForNotification();
+    func_(param_);
+  }
+
+ private:
+  UserThreadFunc* const func_;  // User-supplied thread function.
+  const T param_;  // User-supplied parameter to the thread function.
+  // When non-NULL, used to block execution until the controller thread
+  // notifies.
+  Notification* const thread_can_start_;
+  bool finished_;  // true if and only if we know that the thread function has
+                   // finished.
+  pthread_t thread_;  // The native thread object.
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam);
+};
+# endif  // !GTEST_OS_WINDOWS && GTEST_HAS_PTHREAD ||
+         // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+
+# if GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+// Mutex and ThreadLocal have already been imported into the namespace.
+// Nothing to do here.
+
+# elif GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_PHONE && !GTEST_OS_WINDOWS_RT
+
+// Mutex implements mutex on Windows platforms.  It is used in conjunction
+// with class MutexLock:
+//
+//   Mutex mutex;
+//   ...
+//   MutexLock lock(&mutex);  // Acquires the mutex and releases it at the
+//                            // end of the current scope.
+//
+// A static Mutex *must* be defined or declared using one of the following
+// macros:
+//   GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex);
+//   GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex);
+//
+// (A non-static Mutex is defined/declared in the usual way).
+class GTEST_API_ Mutex {
+ public:
+  enum MutexType { kStatic = 0, kDynamic = 1 };
+  // We rely on kStaticMutex being 0 as it is to what the linker initializes
+  // type_ in static mutexes.  critical_section_ will be initialized lazily
+  // in ThreadSafeLazyInit().
+  enum StaticConstructorSelector { kStaticMutex = 0 };
+
+  // This constructor intentionally does nothing.  It relies on type_ being
+  // statically initialized to 0 (effectively setting it to kStatic) and on
+  // ThreadSafeLazyInit() to lazily initialize the rest of the members.
+  explicit Mutex(StaticConstructorSelector /*dummy*/) {}
+
+  Mutex();
+  ~Mutex();
+
+  void Lock();
+
+  void Unlock();
+
+  // Does nothing if the current thread holds the mutex. Otherwise, crashes
+  // with high probability.
+  void AssertHeld();
+
+ private:
+  // Initializes owner_thread_id_ and critical_section_ in static mutexes.
+  void ThreadSafeLazyInit();
+
+  // Per https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503,
+  // we assume that 0 is an invalid value for thread IDs.
+  unsigned int owner_thread_id_;
+
+  // For static mutexes, we rely on these members being initialized to zeros
+  // by the linker.
+  MutexType type_;
+  long critical_section_init_phase_;  // NOLINT
+  GTEST_CRITICAL_SECTION* critical_section_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex);
+};
+
+# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+    extern ::testing::internal::Mutex mutex
+
+# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \
+    ::testing::internal::Mutex mutex(::testing::internal::Mutex::kStaticMutex)
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)".  Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+  explicit GTestMutexLock(Mutex* mutex)
+      : mutex_(mutex) { mutex_->Lock(); }
+
+  ~GTestMutexLock() { mutex_->Unlock(); }
+
+ private:
+  Mutex* const mutex_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock);
+};
+
+typedef GTestMutexLock MutexLock;
+
+// Base class for ValueHolder<T>.  Allows a caller to hold and delete a value
+// without knowing its type.
+class ThreadLocalValueHolderBase {
+ public:
+  virtual ~ThreadLocalValueHolderBase() {}
+};
+
+// Provides a way for a thread to send notifications to a ThreadLocal
+// regardless of its parameter type.
+class ThreadLocalBase {
+ public:
+  // Creates a new ValueHolder<T> object holding a default value passed to
+  // this ThreadLocal<T>'s constructor and returns it.  It is the caller's
+  // responsibility not to call this when the ThreadLocal<T> instance already
+  // has a value on the current thread.
+  virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const = 0;
+
+ protected:
+  ThreadLocalBase() {}
+  virtual ~ThreadLocalBase() {}
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocalBase);
+};
+
+// Maps a thread to a set of ThreadLocals that have values instantiated on that
+// thread and notifies them when the thread exits.  A ThreadLocal instance is
+// expected to persist until all threads it has values on have terminated.
+class GTEST_API_ ThreadLocalRegistry {
+ public:
+  // Registers thread_local_instance as having value on the current thread.
+  // Returns a value that can be used to identify the thread from other threads.
+  static ThreadLocalValueHolderBase* GetValueOnCurrentThread(
+      const ThreadLocalBase* thread_local_instance);
+
+  // Invoked when a ThreadLocal instance is destroyed.
+  static void OnThreadLocalDestroyed(
+      const ThreadLocalBase* thread_local_instance);
+};
+
+class GTEST_API_ ThreadWithParamBase {
+ public:
+  void Join();
+
+ protected:
+  class Runnable {
+   public:
+    virtual ~Runnable() {}
+    virtual void Run() = 0;
+  };
+
+  ThreadWithParamBase(Runnable *runnable, Notification* thread_can_start);
+  virtual ~ThreadWithParamBase();
+
+ private:
+  AutoHandle thread_;
+};
+
+// Helper class for testing Google Test's multi-threading constructs.
+template <typename T>
+class ThreadWithParam : public ThreadWithParamBase {
+ public:
+  typedef void UserThreadFunc(T);
+
+  ThreadWithParam(UserThreadFunc* func, T param, Notification* thread_can_start)
+      : ThreadWithParamBase(new RunnableImpl(func, param), thread_can_start) {
+  }
+  virtual ~ThreadWithParam() {}
+
+ private:
+  class RunnableImpl : public Runnable {
+   public:
+    RunnableImpl(UserThreadFunc* func, T param)
+        : func_(func),
+          param_(param) {
+    }
+    virtual ~RunnableImpl() {}
+    virtual void Run() {
+      func_(param_);
+    }
+
+   private:
+    UserThreadFunc* const func_;
+    const T param_;
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(RunnableImpl);
+  };
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam);
+};
+
+// Implements thread-local storage on Windows systems.
+//
+//   // Thread 1
+//   ThreadLocal<int> tl(100);  // 100 is the default value for each thread.
+//
+//   // Thread 2
+//   tl.set(150);  // Changes the value for thread 2 only.
+//   EXPECT_EQ(150, tl.get());
+//
+//   // Thread 1
+//   EXPECT_EQ(100, tl.get());  // In thread 1, tl has the original value.
+//   tl.set(200);
+//   EXPECT_EQ(200, tl.get());
+//
+// The template type argument T must have a public copy constructor.
+// In addition, the default ThreadLocal constructor requires T to have
+// a public default constructor.
+//
+// The users of a TheadLocal instance have to make sure that all but one
+// threads (including the main one) using that instance have exited before
+// destroying it. Otherwise, the per-thread objects managed for them by the
+// ThreadLocal instance are not guaranteed to be destroyed on all platforms.
+//
+// Google Test only uses global ThreadLocal objects.  That means they
+// will die after main() has returned.  Therefore, no per-thread
+// object managed by Google Test will be leaked as long as all threads
+// using Google Test have exited when main() returns.
+template <typename T>
+class ThreadLocal : public ThreadLocalBase {
+ public:
+  ThreadLocal() : default_factory_(new DefaultValueHolderFactory()) {}
+  explicit ThreadLocal(const T& value)
+      : default_factory_(new InstanceValueHolderFactory(value)) {}
+
+  ~ThreadLocal() { ThreadLocalRegistry::OnThreadLocalDestroyed(this); }
+
+  T* pointer() { return GetOrCreateValue(); }
+  const T* pointer() const { return GetOrCreateValue(); }
+  const T& get() const { return *pointer(); }
+  void set(const T& value) { *pointer() = value; }
+
+ private:
+  // Holds a value of T.  Can be deleted via its base class without the caller
+  // knowing the type of T.
+  class ValueHolder : public ThreadLocalValueHolderBase {
+   public:
+    ValueHolder() : value_() {}
+    explicit ValueHolder(const T& value) : value_(value) {}
+
+    T* pointer() { return &value_; }
+
+   private:
+    T value_;
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder);
+  };
+
+
+  T* GetOrCreateValue() const {
+    return static_cast<ValueHolder*>(
+        ThreadLocalRegistry::GetValueOnCurrentThread(this))->pointer();
+  }
+
+  virtual ThreadLocalValueHolderBase* NewValueForCurrentThread() const {
+    return default_factory_->MakeNewHolder();
+  }
+
+  class ValueHolderFactory {
+   public:
+    ValueHolderFactory() {}
+    virtual ~ValueHolderFactory() {}
+    virtual ValueHolder* MakeNewHolder() const = 0;
+
+   private:
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory);
+  };
+
+  class DefaultValueHolderFactory : public ValueHolderFactory {
+   public:
+    DefaultValueHolderFactory() {}
+    ValueHolder* MakeNewHolder() const override { return new ValueHolder(); }
+
+   private:
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory);
+  };
+
+  class InstanceValueHolderFactory : public ValueHolderFactory {
+   public:
+    explicit InstanceValueHolderFactory(const T& value) : value_(value) {}
+    ValueHolder* MakeNewHolder() const override {
+      return new ValueHolder(value_);
+    }
+
+   private:
+    const T value_;  // The value for each thread.
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory);
+  };
+
+  std::unique_ptr<ValueHolderFactory> default_factory_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal);
+};
+
+# elif GTEST_HAS_PTHREAD
+
+// MutexBase and Mutex implement mutex on pthreads-based platforms.
+class MutexBase {
+ public:
+  // Acquires this mutex.
+  void Lock() {
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_));
+    owner_ = pthread_self();
+    has_owner_ = true;
+  }
+
+  // Releases this mutex.
+  void Unlock() {
+    // Since the lock is being released the owner_ field should no longer be
+    // considered valid. We don't protect writing to has_owner_ here, as it's
+    // the caller's responsibility to ensure that the current thread holds the
+    // mutex when this is called.
+    has_owner_ = false;
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_));
+  }
+
+  // Does nothing if the current thread holds the mutex. Otherwise, crashes
+  // with high probability.
+  void AssertHeld() const {
+    GTEST_CHECK_(has_owner_ && pthread_equal(owner_, pthread_self()))
+        << "The current thread is not holding the mutex @" << this;
+  }
+
+  // A static mutex may be used before main() is entered.  It may even
+  // be used before the dynamic initialization stage.  Therefore we
+  // must be able to initialize a static mutex object at link time.
+  // This means MutexBase has to be a POD and its member variables
+  // have to be public.
+ public:
+  pthread_mutex_t mutex_;  // The underlying pthread mutex.
+  // has_owner_ indicates whether the owner_ field below contains a valid thread
+  // ID and is therefore safe to inspect (e.g., to use in pthread_equal()). All
+  // accesses to the owner_ field should be protected by a check of this field.
+  // An alternative might be to memset() owner_ to all zeros, but there's no
+  // guarantee that a zero'd pthread_t is necessarily invalid or even different
+  // from pthread_self().
+  bool has_owner_;
+  pthread_t owner_;  // The thread holding the mutex.
+};
+
+// Forward-declares a static mutex.
+#  define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+     extern ::testing::internal::MutexBase mutex
+
+// Defines and statically (i.e. at link time) initializes a static mutex.
+// The initialization list here does not explicitly initialize each field,
+// instead relying on default initialization for the unspecified fields. In
+// particular, the owner_ field (a pthread_t) is not explicitly initialized.
+// This allows initialization to work whether pthread_t is a scalar or struct.
+// The flag -Wmissing-field-initializers must not be specified for this to work.
+#define GTEST_DEFINE_STATIC_MUTEX_(mutex) \
+  ::testing::internal::MutexBase mutex = {PTHREAD_MUTEX_INITIALIZER, false, 0}
+
+// The Mutex class can only be used for mutexes created at runtime. It
+// shares its API with MutexBase otherwise.
+class Mutex : public MutexBase {
+ public:
+  Mutex() {
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, nullptr));
+    has_owner_ = false;
+  }
+  ~Mutex() {
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_));
+  }
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex);
+};
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)".  Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+  explicit GTestMutexLock(MutexBase* mutex)
+      : mutex_(mutex) { mutex_->Lock(); }
+
+  ~GTestMutexLock() { mutex_->Unlock(); }
+
+ private:
+  MutexBase* const mutex_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock);
+};
+
+typedef GTestMutexLock MutexLock;
+
+// Helpers for ThreadLocal.
+
+// pthread_key_create() requires DeleteThreadLocalValue() to have
+// C-linkage.  Therefore it cannot be templatized to access
+// ThreadLocal<T>.  Hence the need for class
+// ThreadLocalValueHolderBase.
+class ThreadLocalValueHolderBase {
+ public:
+  virtual ~ThreadLocalValueHolderBase() {}
+};
+
+// Called by pthread to delete thread-local data stored by
+// pthread_setspecific().
+extern "C" inline void DeleteThreadLocalValue(void* value_holder) {
+  delete static_cast<ThreadLocalValueHolderBase*>(value_holder);
+}
+
+// Implements thread-local storage on pthreads-based systems.
+template <typename T>
+class GTEST_API_ ThreadLocal {
+ public:
+  ThreadLocal()
+      : key_(CreateKey()), default_factory_(new DefaultValueHolderFactory()) {}
+  explicit ThreadLocal(const T& value)
+      : key_(CreateKey()),
+        default_factory_(new InstanceValueHolderFactory(value)) {}
+
+  ~ThreadLocal() {
+    // Destroys the managed object for the current thread, if any.
+    DeleteThreadLocalValue(pthread_getspecific(key_));
+
+    // Releases resources associated with the key.  This will *not*
+    // delete managed objects for other threads.
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_));
+  }
+
+  T* pointer() { return GetOrCreateValue(); }
+  const T* pointer() const { return GetOrCreateValue(); }
+  const T& get() const { return *pointer(); }
+  void set(const T& value) { *pointer() = value; }
+
+ private:
+  // Holds a value of type T.
+  class ValueHolder : public ThreadLocalValueHolderBase {
+   public:
+    ValueHolder() : value_() {}
+    explicit ValueHolder(const T& value) : value_(value) {}
+
+    T* pointer() { return &value_; }
+
+   private:
+    T value_;
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder);
+  };
+
+  static pthread_key_t CreateKey() {
+    pthread_key_t key;
+    // When a thread exits, DeleteThreadLocalValue() will be called on
+    // the object managed for that thread.
+    GTEST_CHECK_POSIX_SUCCESS_(
+        pthread_key_create(&key, &DeleteThreadLocalValue));
+    return key;
+  }
+
+  T* GetOrCreateValue() const {
+    ThreadLocalValueHolderBase* const holder =
+        static_cast<ThreadLocalValueHolderBase*>(pthread_getspecific(key_));
+    if (holder != nullptr) {
+      return CheckedDowncastToActualType<ValueHolder>(holder)->pointer();
+    }
+
+    ValueHolder* const new_holder = default_factory_->MakeNewHolder();
+    ThreadLocalValueHolderBase* const holder_base = new_holder;
+    GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base));
+    return new_holder->pointer();
+  }
+
+  class ValueHolderFactory {
+   public:
+    ValueHolderFactory() {}
+    virtual ~ValueHolderFactory() {}
+    virtual ValueHolder* MakeNewHolder() const = 0;
+
+   private:
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolderFactory);
+  };
+
+  class DefaultValueHolderFactory : public ValueHolderFactory {
+   public:
+    DefaultValueHolderFactory() {}
+    ValueHolder* MakeNewHolder() const override { return new ValueHolder(); }
+
+   private:
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultValueHolderFactory);
+  };
+
+  class InstanceValueHolderFactory : public ValueHolderFactory {
+   public:
+    explicit InstanceValueHolderFactory(const T& value) : value_(value) {}
+    ValueHolder* MakeNewHolder() const override {
+      return new ValueHolder(value_);
+    }
+
+   private:
+    const T value_;  // The value for each thread.
+
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(InstanceValueHolderFactory);
+  };
+
+  // A key pthreads uses for looking up per-thread values.
+  const pthread_key_t key_;
+  std::unique_ptr<ValueHolderFactory> default_factory_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal);
+};
+
+# endif  // GTEST_HAS_MUTEX_AND_THREAD_LOCAL_
+
+#else  // GTEST_IS_THREADSAFE
+
+// A dummy implementation of synchronization primitives (mutex, lock,
+// and thread-local variable).  Necessary for compiling Google Test where
+// mutex is not supported - using Google Test in multiple threads is not
+// supported on such platforms.
+
+class Mutex {
+ public:
+  Mutex() {}
+  void Lock() {}
+  void Unlock() {}
+  void AssertHeld() const {}
+};
+
+# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \
+  extern ::testing::internal::Mutex mutex
+
+# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex
+
+// We cannot name this class MutexLock because the ctor declaration would
+// conflict with a macro named MutexLock, which is defined on some
+// platforms. That macro is used as a defensive measure to prevent against
+// inadvertent misuses of MutexLock like "MutexLock(&mu)" rather than
+// "MutexLock l(&mu)".  Hence the typedef trick below.
+class GTestMutexLock {
+ public:
+  explicit GTestMutexLock(Mutex*) {}  // NOLINT
+};
+
+typedef GTestMutexLock MutexLock;
+
+template <typename T>
+class GTEST_API_ ThreadLocal {
+ public:
+  ThreadLocal() : value_() {}
+  explicit ThreadLocal(const T& value) : value_(value) {}
+  T* pointer() { return &value_; }
+  const T* pointer() const { return &value_; }
+  const T& get() const { return value_; }
+  void set(const T& value) { value_ = value; }
+ private:
+  T value_;
+};
+
+#endif  // GTEST_IS_THREADSAFE
+
+// Returns the number of threads running in the process, or 0 to indicate that
+// we cannot detect it.
+GTEST_API_ size_t GetThreadCount();
+
+#if GTEST_OS_WINDOWS
+# define GTEST_PATH_SEP_ "\\"
+# define GTEST_HAS_ALT_PATH_SEP_ 1
+#else
+# define GTEST_PATH_SEP_ "/"
+# define GTEST_HAS_ALT_PATH_SEP_ 0
+#endif  // GTEST_OS_WINDOWS
+
+// Utilities for char.
+
+// isspace(int ch) and friends accept an unsigned char or EOF.  char
+// may be signed, depending on the compiler (or compiler flags).
+// Therefore we need to cast a char to unsigned char before calling
+// isspace(), etc.
+
+inline bool IsAlpha(char ch) {
+  return isalpha(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsAlNum(char ch) {
+  return isalnum(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsDigit(char ch) {
+  return isdigit(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsLower(char ch) {
+  return islower(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsSpace(char ch) {
+  return isspace(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsUpper(char ch) {
+  return isupper(static_cast<unsigned char>(ch)) != 0;
+}
+inline bool IsXDigit(char ch) {
+  return isxdigit(static_cast<unsigned char>(ch)) != 0;
+}
+#ifdef __cpp_char8_t
+inline bool IsXDigit(char8_t ch) {
+  return isxdigit(static_cast<unsigned char>(ch)) != 0;
+}
+#endif
+inline bool IsXDigit(char16_t ch) {
+  const unsigned char low_byte = static_cast<unsigned char>(ch);
+  return ch == low_byte && isxdigit(low_byte) != 0;
+}
+inline bool IsXDigit(char32_t ch) {
+  const unsigned char low_byte = static_cast<unsigned char>(ch);
+  return ch == low_byte && isxdigit(low_byte) != 0;
+}
+inline bool IsXDigit(wchar_t ch) {
+  const unsigned char low_byte = static_cast<unsigned char>(ch);
+  return ch == low_byte && isxdigit(low_byte) != 0;
+}
+
+inline char ToLower(char ch) {
+  return static_cast<char>(tolower(static_cast<unsigned char>(ch)));
+}
+inline char ToUpper(char ch) {
+  return static_cast<char>(toupper(static_cast<unsigned char>(ch)));
+}
+
+inline std::string StripTrailingSpaces(std::string str) {
+  std::string::iterator it = str.end();
+  while (it != str.begin() && IsSpace(*--it))
+    it = str.erase(it);
+  return str;
+}
+
+// The testing::internal::posix namespace holds wrappers for common
+// POSIX functions.  These wrappers hide the differences between
+// Windows/MSVC and POSIX systems.  Since some compilers define these
+// standard functions as macros, the wrapper cannot have the same name
+// as the wrapped function.
+
+namespace posix {
+
+// Functions with a different name on Windows.
+
+#if GTEST_OS_WINDOWS
+
+typedef struct _stat StatStruct;
+
+# ifdef __BORLANDC__
+inline int DoIsATTY(int fd) { return isatty(fd); }
+inline int StrCaseCmp(const char* s1, const char* s2) {
+  return stricmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return strdup(src); }
+# else  // !__BORLANDC__
+#  if GTEST_OS_WINDOWS_MOBILE
+inline int DoIsATTY(int /* fd */) { return 0; }
+#  else
+inline int DoIsATTY(int fd) { return _isatty(fd); }
+#  endif  // GTEST_OS_WINDOWS_MOBILE
+inline int StrCaseCmp(const char* s1, const char* s2) {
+  return _stricmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return _strdup(src); }
+# endif  // __BORLANDC__
+
+# if GTEST_OS_WINDOWS_MOBILE
+inline int FileNo(FILE* file) { return reinterpret_cast<int>(_fileno(file)); }
+// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this
+// time and thus not defined there.
+# else
+inline int FileNo(FILE* file) { return _fileno(file); }
+inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); }
+inline int RmDir(const char* dir) { return _rmdir(dir); }
+inline bool IsDir(const StatStruct& st) {
+  return (_S_IFDIR & st.st_mode) != 0;
+}
+# endif  // GTEST_OS_WINDOWS_MOBILE
+
+#elif GTEST_OS_ESP8266
+typedef struct stat StatStruct;
+
+inline int FileNo(FILE* file) { return fileno(file); }
+inline int DoIsATTY(int fd) { return isatty(fd); }
+inline int Stat(const char* path, StatStruct* buf) {
+  // stat function not implemented on ESP8266
+  return 0;
+}
+inline int StrCaseCmp(const char* s1, const char* s2) {
+  return strcasecmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return strdup(src); }
+inline int RmDir(const char* dir) { return rmdir(dir); }
+inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); }
+
+#else
+
+typedef struct stat StatStruct;
+
+inline int FileNo(FILE* file) { return fileno(file); }
+inline int DoIsATTY(int fd) { return isatty(fd); }
+inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); }
+inline int StrCaseCmp(const char* s1, const char* s2) {
+  return strcasecmp(s1, s2);
+}
+inline char* StrDup(const char* src) { return strdup(src); }
+inline int RmDir(const char* dir) { return rmdir(dir); }
+inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); }
+
+#endif  // GTEST_OS_WINDOWS
+
+inline int IsATTY(int fd) {
+  // DoIsATTY might change errno (for example ENOTTY in case you redirect stdout
+  // to a file on Linux), which is unexpected, so save the previous value, and
+  // restore it after the call.
+  int savedErrno = errno;
+  int isAttyValue = DoIsATTY(fd);
+  errno = savedErrno;
+
+  return isAttyValue;
+}
+
+// Functions deprecated by MSVC 8.0.
+
+GTEST_DISABLE_MSC_DEPRECATED_PUSH_()
+
+// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and
+// StrError() aren't needed on Windows CE at this time and thus not
+// defined there.
+
+#if !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_WINDOWS_PHONE && \
+    !GTEST_OS_WINDOWS_RT && !GTEST_OS_ESP8266 && !GTEST_OS_XTENSA
+inline int ChDir(const char* dir) { return chdir(dir); }
+#endif
+inline FILE* FOpen(const char* path, const char* mode) {
+#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW
+  struct wchar_codecvt : public std::codecvt<wchar_t, char, std::mbstate_t> {};
+  std::wstring_convert<wchar_codecvt> converter;
+  std::wstring wide_path = converter.from_bytes(path);
+  std::wstring wide_mode = converter.from_bytes(mode);
+  return _wfopen(wide_path.c_str(), wide_mode.c_str());
+#else  // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW
+  return fopen(path, mode);
+#endif  // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MINGW
+}
+#if !GTEST_OS_WINDOWS_MOBILE
+inline FILE *FReopen(const char* path, const char* mode, FILE* stream) {
+  return freopen(path, mode, stream);
+}
+inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); }
+#endif
+inline int FClose(FILE* fp) { return fclose(fp); }
+#if !GTEST_OS_WINDOWS_MOBILE
+inline int Read(int fd, void* buf, unsigned int count) {
+  return static_cast<int>(read(fd, buf, count));
+}
+inline int Write(int fd, const void* buf, unsigned int count) {
+  return static_cast<int>(write(fd, buf, count));
+}
+inline int Close(int fd) { return close(fd); }
+inline const char* StrError(int errnum) { return strerror(errnum); }
+#endif
+inline const char* GetEnv(const char* name) {
+#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_WINDOWS_PHONE || \
+    GTEST_OS_WINDOWS_RT || GTEST_OS_ESP8266 || GTEST_OS_XTENSA
+  // We are on an embedded platform, which has no environment variables.
+  static_cast<void>(name);  // To prevent 'unused argument' warning.
+  return nullptr;
+#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9)
+  // Environment variables which we programmatically clear will be set to the
+  // empty string rather than unset (NULL).  Handle that case.
+  const char* const env = getenv(name);
+  return (env != nullptr && env[0] != '\0') ? env : nullptr;
+#else
+  return getenv(name);
+#endif
+}
+
+GTEST_DISABLE_MSC_DEPRECATED_POP_()
+
+#if GTEST_OS_WINDOWS_MOBILE
+// Windows CE has no C library. The abort() function is used in
+// several places in Google Test. This implementation provides a reasonable
+// imitation of standard behaviour.
+[[noreturn]] void Abort();
+#else
+[[noreturn]] inline void Abort() { abort(); }
+#endif  // GTEST_OS_WINDOWS_MOBILE
+
+}  // namespace posix
+
+// MSVC "deprecates" snprintf and issues warnings wherever it is used.  In
+// order to avoid these warnings, we need to use _snprintf or _snprintf_s on
+// MSVC-based platforms.  We map the GTEST_SNPRINTF_ macro to the appropriate
+// function in order to achieve that.  We use macro definition here because
+// snprintf is a variadic function.
+#if _MSC_VER && !GTEST_OS_WINDOWS_MOBILE
+// MSVC 2005 and above support variadic macros.
+# define GTEST_SNPRINTF_(buffer, size, format, ...) \
+     _snprintf_s(buffer, size, size, format, __VA_ARGS__)
+#elif defined(_MSC_VER)
+// Windows CE does not define _snprintf_s
+# define GTEST_SNPRINTF_ _snprintf
+#else
+# define GTEST_SNPRINTF_ snprintf
+#endif
+
+// The biggest signed integer type the compiler supports.
+//
+// long long is guaranteed to be at least 64-bits in C++11.
+using BiggestInt = long long;  // NOLINT
+
+// The maximum number a BiggestInt can represent.
+constexpr BiggestInt kMaxBiggestInt = (std::numeric_limits<BiggestInt>::max)();
+
+// This template class serves as a compile-time function from size to
+// type.  It maps a size in bytes to a primitive type with that
+// size. e.g.
+//
+//   TypeWithSize<4>::UInt
+//
+// is typedef-ed to be unsigned int (unsigned integer made up of 4
+// bytes).
+//
+// Such functionality should belong to STL, but I cannot find it
+// there.
+//
+// Google Test uses this class in the implementation of floating-point
+// comparison.
+//
+// For now it only handles UInt (unsigned int) as that's all Google Test
+// needs.  Other types can be easily added in the future if need
+// arises.
+template <size_t size>
+class TypeWithSize {
+ public:
+  // This prevents the user from using TypeWithSize<N> with incorrect
+  // values of N.
+  using UInt = void;
+};
+
+// The specialization for size 4.
+template <>
+class TypeWithSize<4> {
+ public:
+  using Int = std::int32_t;
+  using UInt = std::uint32_t;
+};
+
+// The specialization for size 8.
+template <>
+class TypeWithSize<8> {
+ public:
+  using Int = std::int64_t;
+  using UInt = std::uint64_t;
+};
+
+// Integer types of known sizes.
+using TimeInMillis = int64_t;  // Represents time in milliseconds.
+
+// Utilities for command line flags and environment variables.
+
+// Macro for referencing flags.
+#if !defined(GTEST_FLAG)
+# define GTEST_FLAG(name) FLAGS_gtest_##name
+#endif  // !defined(GTEST_FLAG)
+
+#if !defined(GTEST_USE_OWN_FLAGFILE_FLAG_)
+# define GTEST_USE_OWN_FLAGFILE_FLAG_ 1
+#endif  // !defined(GTEST_USE_OWN_FLAGFILE_FLAG_)
+
+#if !defined(GTEST_DECLARE_bool_)
+# define GTEST_FLAG_SAVER_ ::testing::internal::GTestFlagSaver
+
+// Macros for declaring flags.
+# define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name)
+# define GTEST_DECLARE_int32_(name) \
+    GTEST_API_ extern std::int32_t GTEST_FLAG(name)
+# define GTEST_DECLARE_string_(name) \
+    GTEST_API_ extern ::std::string GTEST_FLAG(name)
+
+// Macros for defining flags.
+# define GTEST_DEFINE_bool_(name, default_val, doc) \
+    GTEST_API_ bool GTEST_FLAG(name) = (default_val)
+# define GTEST_DEFINE_int32_(name, default_val, doc) \
+    GTEST_API_ std::int32_t GTEST_FLAG(name) = (default_val)
+# define GTEST_DEFINE_string_(name, default_val, doc) \
+    GTEST_API_ ::std::string GTEST_FLAG(name) = (default_val)
+
+#endif  // !defined(GTEST_DECLARE_bool_)
+
+// Thread annotations
+#if !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_)
+# define GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)
+# define GTEST_LOCK_EXCLUDED_(locks)
+#endif  // !defined(GTEST_EXCLUSIVE_LOCK_REQUIRED_)
+
+// Parses 'str' for a 32-bit signed integer.  If successful, writes the result
+// to *value and returns true; otherwise leaves *value unchanged and returns
+// false.
+GTEST_API_ bool ParseInt32(const Message& src_text, const char* str,
+                           int32_t* value);
+
+// Parses a bool/int32_t/string from the environment variable
+// corresponding to the given Google Test flag.
+bool BoolFromGTestEnv(const char* flag, bool default_val);
+GTEST_API_ int32_t Int32FromGTestEnv(const char* flag, int32_t default_val);
+std::string OutputFlagAlsoCheckEnvVar();
+const char* StringFromGTestEnv(const char* flag, const char* default_val);
+
+}  // namespace internal
+}  // namespace testing
+
+#if !defined(GTEST_INTERNAL_DEPRECATED)
+
+// Internal Macro to mark an API deprecated, for googletest usage only
+// Usage: class GTEST_INTERNAL_DEPRECATED(message) MyClass or
+// GTEST_INTERNAL_DEPRECATED(message) <return_type> myFunction(); Every usage of
+// a deprecated entity will trigger a warning when compiled with
+// `-Wdeprecated-declarations` option (clang, gcc, any __GNUC__ compiler).
+// For msvc /W3 option will need to be used
+// Note that for 'other' compilers this macro evaluates to nothing to prevent
+// compilations errors.
+#if defined(_MSC_VER)
+#define GTEST_INTERNAL_DEPRECATED(message) __declspec(deprecated(message))
+#elif defined(__GNUC__)
+#define GTEST_INTERNAL_DEPRECATED(message) __attribute__((deprecated(message)))
+#else
+#define GTEST_INTERNAL_DEPRECATED(message)
+#endif
+
+#endif  // !defined(GTEST_INTERNAL_DEPRECATED)
+
+#if GTEST_HAS_ABSL
+// Always use absl::any for UniversalPrinter<> specializations if googletest
+// is built with absl support.
+#define GTEST_INTERNAL_HAS_ANY 1
+#include "absl/types/any.h"
+namespace testing {
+namespace internal {
+using Any = ::absl::any;
+}  // namespace internal
+}  // namespace testing
+#else
+#ifdef __has_include
+#if __has_include(<any>) && __cplusplus >= 201703L
+// Otherwise for C++17 and higher use std::any for UniversalPrinter<>
+// specializations.
+#define GTEST_INTERNAL_HAS_ANY 1
+#include <any>
+namespace testing {
+namespace internal {
+using Any = ::std::any;
+}  // namespace internal
+}  // namespace testing
+// The case where absl is configured NOT to alias std::any is not
+// supported.
+#endif  // __has_include(<any>) && __cplusplus >= 201703L
+#endif  // __has_include
+#endif  // GTEST_HAS_ABSL
+
+#if GTEST_HAS_ABSL
+// Always use absl::optional for UniversalPrinter<> specializations if
+// googletest is built with absl support.
+#define GTEST_INTERNAL_HAS_OPTIONAL 1
+#include "absl/types/optional.h"
+namespace testing {
+namespace internal {
+template <typename T>
+using Optional = ::absl::optional<T>;
+}  // namespace internal
+}  // namespace testing
+#else
+#ifdef __has_include
+#if __has_include(<optional>) && __cplusplus >= 201703L
+// Otherwise for C++17 and higher use std::optional for UniversalPrinter<>
+// specializations.
+#define GTEST_INTERNAL_HAS_OPTIONAL 1
+#include <optional>
+namespace testing {
+namespace internal {
+template <typename T>
+using Optional = ::std::optional<T>;
+}  // namespace internal
+}  // namespace testing
+// The case where absl is configured NOT to alias std::optional is not
+// supported.
+#endif  // __has_include(<optional>) && __cplusplus >= 201703L
+#endif  // __has_include
+#endif  // GTEST_HAS_ABSL
+
+#if GTEST_HAS_ABSL
+// Always use absl::string_view for Matcher<> specializations if googletest
+// is built with absl support.
+# define GTEST_INTERNAL_HAS_STRING_VIEW 1
+#include "absl/strings/string_view.h"
+namespace testing {
+namespace internal {
+using StringView = ::absl::string_view;
+}  // namespace internal
+}  // namespace testing
+#else
+# ifdef __has_include
+#   if __has_include(<string_view>) && __cplusplus >= 201703L
+// Otherwise for C++17 and higher use std::string_view for Matcher<>
+// specializations.
+#   define GTEST_INTERNAL_HAS_STRING_VIEW 1
+#include <string_view>
+namespace testing {
+namespace internal {
+using StringView = ::std::string_view;
+}  // namespace internal
+}  // namespace testing
+// The case where absl is configured NOT to alias std::string_view is not
+// supported.
+#  endif  // __has_include(<string_view>) && __cplusplus >= 201703L
+# endif  // __has_include
+#endif  // GTEST_HAS_ABSL
+
+#if GTEST_HAS_ABSL
+// Always use absl::variant for UniversalPrinter<> specializations if googletest
+// is built with absl support.
+#define GTEST_INTERNAL_HAS_VARIANT 1
+#include "absl/types/variant.h"
+namespace testing {
+namespace internal {
+template <typename... T>
+using Variant = ::absl::variant<T...>;
+}  // namespace internal
+}  // namespace testing
+#else
+#ifdef __has_include
+#if __has_include(<variant>) && __cplusplus >= 201703L
+// Otherwise for C++17 and higher use std::variant for UniversalPrinter<>
+// specializations.
+#define GTEST_INTERNAL_HAS_VARIANT 1
+#include <variant>
+namespace testing {
+namespace internal {
+template <typename... T>
+using Variant = ::std::variant<T...>;
+}  // namespace internal
+}  // namespace testing
+// The case where absl is configured NOT to alias std::variant is not supported.
+#endif  // __has_include(<variant>) && __cplusplus >= 201703L
+#endif  // __has_include
+#endif  // GTEST_HAS_ABSL
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_
+
+#if GTEST_OS_LINUX
+# include <stdlib.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <unistd.h>
+#endif  // GTEST_OS_LINUX
+
+#if GTEST_HAS_EXCEPTIONS
+# include <stdexcept>
+#endif
+
+#include <ctype.h>
+#include <float.h>
+#include <string.h>
+#include <cstdint>
+#include <iomanip>
+#include <limits>
+#include <map>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the Message class.
+//
+// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
+// leave some internal implementation details in this header file.
+// They are clearly marked by comments like this:
+//
+//   // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+//
+// Such code is NOT meant to be used by a user directly, and is subject
+// to CHANGE WITHOUT NOTICE.  Therefore DO NOT DEPEND ON IT in a user
+// program!
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+
+#include <limits>
+#include <memory>
+#include <sstream>
+
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// Ensures that there is at least one operator<< in the global namespace.
+// See Message& operator<<(...) below for why.
+void operator<<(const testing::internal::Secret&, int);
+
+namespace testing {
+
+// The Message class works like an ostream repeater.
+//
+// Typical usage:
+//
+//   1. You stream a bunch of values to a Message object.
+//      It will remember the text in a stringstream.
+//   2. Then you stream the Message object to an ostream.
+//      This causes the text in the Message to be streamed
+//      to the ostream.
+//
+// For example;
+//
+//   testing::Message foo;
+//   foo << 1 << " != " << 2;
+//   std::cout << foo;
+//
+// will print "1 != 2".
+//
+// Message is not intended to be inherited from.  In particular, its
+// destructor is not virtual.
+//
+// Note that stringstream behaves differently in gcc and in MSVC.  You
+// can stream a NULL char pointer to it in the former, but not in the
+// latter (it causes an access violation if you do).  The Message
+// class hides this difference by treating a NULL char pointer as
+// "(null)".
+class GTEST_API_ Message {
+ private:
+  // The type of basic IO manipulators (endl, ends, and flush) for
+  // narrow streams.
+  typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&);
+
+ public:
+  // Constructs an empty Message.
+  Message();
+
+  // Copy constructor.
+  Message(const Message& msg) : ss_(new ::std::stringstream) {  // NOLINT
+    *ss_ << msg.GetString();
+  }
+
+  // Constructs a Message from a C-string.
+  explicit Message(const char* str) : ss_(new ::std::stringstream) {
+    *ss_ << str;
+  }
+
+  // Streams a non-pointer value to this object.
+  template <typename T>
+  inline Message& operator <<(const T& val) {
+    // Some libraries overload << for STL containers.  These
+    // overloads are defined in the global namespace instead of ::std.
+    //
+    // C++'s symbol lookup rule (i.e. Koenig lookup) says that these
+    // overloads are visible in either the std namespace or the global
+    // namespace, but not other namespaces, including the testing
+    // namespace which Google Test's Message class is in.
+    //
+    // To allow STL containers (and other types that has a << operator
+    // defined in the global namespace) to be used in Google Test
+    // assertions, testing::Message must access the custom << operator
+    // from the global namespace.  With this using declaration,
+    // overloads of << defined in the global namespace and those
+    // visible via Koenig lookup are both exposed in this function.
+    using ::operator <<;
+    *ss_ << val;
+    return *this;
+  }
+
+  // Streams a pointer value to this object.
+  //
+  // This function is an overload of the previous one.  When you
+  // stream a pointer to a Message, this definition will be used as it
+  // is more specialized.  (The C++ Standard, section
+  // [temp.func.order].)  If you stream a non-pointer, then the
+  // previous definition will be used.
+  //
+  // The reason for this overload is that streaming a NULL pointer to
+  // ostream is undefined behavior.  Depending on the compiler, you
+  // may get "0", "(nil)", "(null)", or an access violation.  To
+  // ensure consistent result across compilers, we always treat NULL
+  // as "(null)".
+  template <typename T>
+  inline Message& operator <<(T* const& pointer) {  // NOLINT
+    if (pointer == nullptr) {
+      *ss_ << "(null)";
+    } else {
+      *ss_ << pointer;
+    }
+    return *this;
+  }
+
+  // Since the basic IO manipulators are overloaded for both narrow
+  // and wide streams, we have to provide this specialized definition
+  // of operator <<, even though its body is the same as the
+  // templatized version above.  Without this definition, streaming
+  // endl or other basic IO manipulators to Message will confuse the
+  // compiler.
+  Message& operator <<(BasicNarrowIoManip val) {
+    *ss_ << val;
+    return *this;
+  }
+
+  // Instead of 1/0, we want to see true/false for bool values.
+  Message& operator <<(bool b) {
+    return *this << (b ? "true" : "false");
+  }
+
+  // These two overloads allow streaming a wide C string to a Message
+  // using the UTF-8 encoding.
+  Message& operator <<(const wchar_t* wide_c_str);
+  Message& operator <<(wchar_t* wide_c_str);
+
+#if GTEST_HAS_STD_WSTRING
+  // Converts the given wide string to a narrow string using the UTF-8
+  // encoding, and streams the result to this Message object.
+  Message& operator <<(const ::std::wstring& wstr);
+#endif  // GTEST_HAS_STD_WSTRING
+
+  // Gets the text streamed to this object so far as an std::string.
+  // Each '\0' character in the buffer is replaced with "\\0".
+  //
+  // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+  std::string GetString() const;
+
+ private:
+  // We'll hold the text streamed to this object here.
+  const std::unique_ptr< ::std::stringstream> ss_;
+
+  // We declare (but don't implement) this to prevent the compiler
+  // from implementing the assignment operator.
+  void operator=(const Message&);
+};
+
+// Streams a Message to an ostream.
+inline std::ostream& operator <<(std::ostream& os, const Message& sb) {
+  return os << sb.GetString();
+}
+
+namespace internal {
+
+// Converts a streamable value to an std::string.  A NULL pointer is
+// converted to "(null)".  When the input value is a ::string,
+// ::std::string, ::wstring, or ::std::wstring object, each NUL
+// character in it is replaced with "\\0".
+template <typename T>
+std::string StreamableToString(const T& streamable) {
+  return (Message() << streamable).GetString();
+}
+
+}  // namespace internal
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Google Test filepath utilities
+//
+// This header file declares classes and functions used internally by
+// Google Test.  They are subject to change without notice.
+//
+// This file is #included in gtest/internal/gtest-internal.h.
+// Do not include this header file separately!
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file declares the String class and functions used internally by
+// Google Test.  They are subject to change without notice. They should not used
+// by code external to Google Test.
+//
+// This header file is #included by gtest-internal.h.
+// It should not be #included by other files.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+
+#ifdef __BORLANDC__
+// string.h is not guaranteed to provide strcpy on C++ Builder.
+# include <mem.h>
+#endif
+
+#include <string.h>
+#include <cstdint>
+#include <string>
+
+
+namespace testing {
+namespace internal {
+
+// String - an abstract class holding static string utilities.
+class GTEST_API_ String {
+ public:
+  // Static utility methods
+
+  // Clones a 0-terminated C string, allocating memory using new.  The
+  // caller is responsible for deleting the return value using
+  // delete[].  Returns the cloned string, or NULL if the input is
+  // NULL.
+  //
+  // This is different from strdup() in string.h, which allocates
+  // memory using malloc().
+  static const char* CloneCString(const char* c_str);
+
+#if GTEST_OS_WINDOWS_MOBILE
+  // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be
+  // able to pass strings to Win32 APIs on CE we need to convert them
+  // to 'Unicode', UTF-16.
+
+  // Creates a UTF-16 wide string from the given ANSI string, allocating
+  // memory using new. The caller is responsible for deleting the return
+  // value using delete[]. Returns the wide string, or NULL if the
+  // input is NULL.
+  //
+  // The wide string is created using the ANSI codepage (CP_ACP) to
+  // match the behaviour of the ANSI versions of Win32 calls and the
+  // C runtime.
+  static LPCWSTR AnsiToUtf16(const char* c_str);
+
+  // Creates an ANSI string from the given wide string, allocating
+  // memory using new. The caller is responsible for deleting the return
+  // value using delete[]. Returns the ANSI string, or NULL if the
+  // input is NULL.
+  //
+  // The returned string is created using the ANSI codepage (CP_ACP) to
+  // match the behaviour of the ANSI versions of Win32 calls and the
+  // C runtime.
+  static const char* Utf16ToAnsi(LPCWSTR utf16_str);
+#endif
+
+  // Compares two C strings.  Returns true if and only if they have the same
+  // content.
+  //
+  // Unlike strcmp(), this function can handle NULL argument(s).  A
+  // NULL C string is considered different to any non-NULL C string,
+  // including the empty string.
+  static bool CStringEquals(const char* lhs, const char* rhs);
+
+  // Converts a wide C string to a String using the UTF-8 encoding.
+  // NULL will be converted to "(null)".  If an error occurred during
+  // the conversion, "(failed to convert from wide string)" is
+  // returned.
+  static std::string ShowWideCString(const wchar_t* wide_c_str);
+
+  // Compares two wide C strings.  Returns true if and only if they have the
+  // same content.
+  //
+  // Unlike wcscmp(), this function can handle NULL argument(s).  A
+  // NULL C string is considered different to any non-NULL C string,
+  // including the empty string.
+  static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs);
+
+  // Compares two C strings, ignoring case.  Returns true if and only if
+  // they have the same content.
+  //
+  // Unlike strcasecmp(), this function can handle NULL argument(s).
+  // A NULL C string is considered different to any non-NULL C string,
+  // including the empty string.
+  static bool CaseInsensitiveCStringEquals(const char* lhs,
+                                           const char* rhs);
+
+  // Compares two wide C strings, ignoring case.  Returns true if and only if
+  // they have the same content.
+  //
+  // Unlike wcscasecmp(), this function can handle NULL argument(s).
+  // A NULL C string is considered different to any non-NULL wide C string,
+  // including the empty string.
+  // NB: The implementations on different platforms slightly differ.
+  // On windows, this method uses _wcsicmp which compares according to LC_CTYPE
+  // environment variable. On GNU platform this method uses wcscasecmp
+  // which compares according to LC_CTYPE category of the current locale.
+  // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the
+  // current locale.
+  static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs,
+                                               const wchar_t* rhs);
+
+  // Returns true if and only if the given string ends with the given suffix,
+  // ignoring case. Any string is considered to end with an empty suffix.
+  static bool EndsWithCaseInsensitive(
+      const std::string& str, const std::string& suffix);
+
+  // Formats an int value as "%02d".
+  static std::string FormatIntWidth2(int value);  // "%02d" for width == 2
+
+  // Formats an int value to given width with leading zeros.
+  static std::string FormatIntWidthN(int value, int width);
+
+  // Formats an int value as "%X".
+  static std::string FormatHexInt(int value);
+
+  // Formats an int value as "%X".
+  static std::string FormatHexUInt32(uint32_t value);
+
+  // Formats a byte as "%02X".
+  static std::string FormatByte(unsigned char value);
+
+ private:
+  String();  // Not meant to be instantiated.
+};  // class String
+
+// Gets the content of the stringstream's buffer as an std::string.  Each '\0'
+// character in the buffer is replaced with "\\0".
+GTEST_API_ std::string StringStreamToString(::std::stringstream* stream);
+
+}  // namespace internal
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+namespace internal {
+
+// FilePath - a class for file and directory pathname manipulation which
+// handles platform-specific conventions (like the pathname separator).
+// Used for helper functions for naming files in a directory for xml output.
+// Except for Set methods, all methods are const or static, which provides an
+// "immutable value object" -- useful for peace of mind.
+// A FilePath with a value ending in a path separator ("like/this/") represents
+// a directory, otherwise it is assumed to represent a file. In either case,
+// it may or may not represent an actual file or directory in the file system.
+// Names are NOT checked for syntax correctness -- no checking for illegal
+// characters, malformed paths, etc.
+
+class GTEST_API_ FilePath {
+ public:
+  FilePath() : pathname_("") { }
+  FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { }
+
+  explicit FilePath(const std::string& pathname) : pathname_(pathname) {
+    Normalize();
+  }
+
+  FilePath& operator=(const FilePath& rhs) {
+    Set(rhs);
+    return *this;
+  }
+
+  void Set(const FilePath& rhs) {
+    pathname_ = rhs.pathname_;
+  }
+
+  const std::string& string() const { return pathname_; }
+  const char* c_str() const { return pathname_.c_str(); }
+
+  // Returns the current working directory, or "" if unsuccessful.
+  static FilePath GetCurrentDir();
+
+  // Given directory = "dir", base_name = "test", number = 0,
+  // extension = "xml", returns "dir/test.xml". If number is greater
+  // than zero (e.g., 12), returns "dir/test_12.xml".
+  // On Windows platform, uses \ as the separator rather than /.
+  static FilePath MakeFileName(const FilePath& directory,
+                               const FilePath& base_name,
+                               int number,
+                               const char* extension);
+
+  // Given directory = "dir", relative_path = "test.xml",
+  // returns "dir/test.xml".
+  // On Windows, uses \ as the separator rather than /.
+  static FilePath ConcatPaths(const FilePath& directory,
+                              const FilePath& relative_path);
+
+  // Returns a pathname for a file that does not currently exist. The pathname
+  // will be directory/base_name.extension or
+  // directory/base_name_<number>.extension if directory/base_name.extension
+  // already exists. The number will be incremented until a pathname is found
+  // that does not already exist.
+  // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'.
+  // There could be a race condition if two or more processes are calling this
+  // function at the same time -- they could both pick the same filename.
+  static FilePath GenerateUniqueFileName(const FilePath& directory,
+                                         const FilePath& base_name,
+                                         const char* extension);
+
+  // Returns true if and only if the path is "".
+  bool IsEmpty() const { return pathname_.empty(); }
+
+  // If input name has a trailing separator character, removes it and returns
+  // the name, otherwise return the name string unmodified.
+  // On Windows platform, uses \ as the separator, other platforms use /.
+  FilePath RemoveTrailingPathSeparator() const;
+
+  // Returns a copy of the FilePath with the directory part removed.
+  // Example: FilePath("path/to/file").RemoveDirectoryName() returns
+  // FilePath("file"). If there is no directory part ("just_a_file"), it returns
+  // the FilePath unmodified. If there is no file part ("just_a_dir/") it
+  // returns an empty FilePath ("").
+  // On Windows platform, '\' is the path separator, otherwise it is '/'.
+  FilePath RemoveDirectoryName() const;
+
+  // RemoveFileName returns the directory path with the filename removed.
+  // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/".
+  // If the FilePath is "a_file" or "/a_file", RemoveFileName returns
+  // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does
+  // not have a file, like "just/a/dir/", it returns the FilePath unmodified.
+  // On Windows platform, '\' is the path separator, otherwise it is '/'.
+  FilePath RemoveFileName() const;
+
+  // Returns a copy of the FilePath with the case-insensitive extension removed.
+  // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns
+  // FilePath("dir/file"). If a case-insensitive extension is not
+  // found, returns a copy of the original FilePath.
+  FilePath RemoveExtension(const char* extension) const;
+
+  // Creates directories so that path exists. Returns true if successful or if
+  // the directories already exist; returns false if unable to create
+  // directories for any reason. Will also return false if the FilePath does
+  // not represent a directory (that is, it doesn't end with a path separator).
+  bool CreateDirectoriesRecursively() const;
+
+  // Create the directory so that path exists. Returns true if successful or
+  // if the directory already exists; returns false if unable to create the
+  // directory for any reason, including if the parent directory does not
+  // exist. Not named "CreateDirectory" because that's a macro on Windows.
+  bool CreateFolder() const;
+
+  // Returns true if FilePath describes something in the file-system,
+  // either a file, directory, or whatever, and that something exists.
+  bool FileOrDirectoryExists() const;
+
+  // Returns true if pathname describes a directory in the file-system
+  // that exists.
+  bool DirectoryExists() const;
+
+  // Returns true if FilePath ends with a path separator, which indicates that
+  // it is intended to represent a directory. Returns false otherwise.
+  // This does NOT check that a directory (or file) actually exists.
+  bool IsDirectory() const;
+
+  // Returns true if pathname describes a root directory. (Windows has one
+  // root directory per disk drive.)
+  bool IsRootDirectory() const;
+
+  // Returns true if pathname describes an absolute path.
+  bool IsAbsolutePath() const;
+
+ private:
+  // Replaces multiple consecutive separators with a single separator.
+  // For example, "bar///foo" becomes "bar/foo". Does not eliminate other
+  // redundancies that might be in a pathname involving "." or "..".
+  //
+  // A pathname with multiple consecutive separators may occur either through
+  // user error or as a result of some scripts or APIs that generate a pathname
+  // with a trailing separator. On other platforms the same API or script
+  // may NOT generate a pathname with a trailing "/". Then elsewhere that
+  // pathname may have another "/" and pathname components added to it,
+  // without checking for the separator already being there.
+  // The script language and operating system may allow paths like "foo//bar"
+  // but some of the functions in FilePath will not handle that correctly. In
+  // particular, RemoveTrailingPathSeparator() only removes one separator, and
+  // it is called in CreateDirectoriesRecursively() assuming that it will change
+  // a pathname from directory syntax (trailing separator) to filename syntax.
+  //
+  // On Windows this method also replaces the alternate path separator '/' with
+  // the primary path separator '\\', so that for example "bar\\/\\foo" becomes
+  // "bar\\foo".
+
+  void Normalize();
+
+  // Returns a pointer to the last occurrence of a valid path separator in
+  // the FilePath. On Windows, for example, both '/' and '\' are valid path
+  // separators. Returns NULL if no path separator was found.
+  const char* FindLastPathSeparator() const;
+
+  std::string pathname_;
+};  // class FilePath
+
+}  // namespace internal
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Type utilities needed for implementing typed and type-parameterized
+// tests.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+
+
+// #ifdef __GNUC__ is too general here.  It is possible to use gcc without using
+// libstdc++ (which is where cxxabi.h comes from).
+# if GTEST_HAS_CXXABI_H_
+#  include <cxxabi.h>
+# elif defined(__HP_aCC)
+#  include <acxx_demangle.h>
+# endif  // GTEST_HASH_CXXABI_H_
+
+namespace testing {
+namespace internal {
+
+// Canonicalizes a given name with respect to the Standard C++ Library.
+// This handles removing the inline namespace within `std` that is
+// used by various standard libraries (e.g., `std::__1`).  Names outside
+// of namespace std are returned unmodified.
+inline std::string CanonicalizeForStdLibVersioning(std::string s) {
+  static const char prefix[] = "std::__";
+  if (s.compare(0, strlen(prefix), prefix) == 0) {
+    std::string::size_type end = s.find("::", strlen(prefix));
+    if (end != s.npos) {
+      // Erase everything between the initial `std` and the second `::`.
+      s.erase(strlen("std"), end - strlen("std"));
+    }
+  }
+  return s;
+}
+
+#if GTEST_HAS_RTTI
+// GetTypeName(const std::type_info&) returns a human-readable name of type T.
+inline std::string GetTypeName(const std::type_info& type) {
+  const char* const name = type.name();
+#if GTEST_HAS_CXXABI_H_ || defined(__HP_aCC)
+  int status = 0;
+  // gcc's implementation of typeid(T).name() mangles the type name,
+  // so we have to demangle it.
+#if GTEST_HAS_CXXABI_H_
+  using abi::__cxa_demangle;
+#endif  // GTEST_HAS_CXXABI_H_
+  char* const readable_name = __cxa_demangle(name, nullptr, nullptr, &status);
+  const std::string name_str(status == 0 ? readable_name : name);
+  free(readable_name);
+  return CanonicalizeForStdLibVersioning(name_str);
+#else
+  return name;
+#endif  // GTEST_HAS_CXXABI_H_ || __HP_aCC
+}
+#endif  // GTEST_HAS_RTTI
+
+// GetTypeName<T>() returns a human-readable name of type T if and only if
+// RTTI is enabled, otherwise it returns a dummy type name.
+// NB: This function is also used in Google Mock, so don't move it inside of
+// the typed-test-only section below.
+template <typename T>
+std::string GetTypeName() {
+#if GTEST_HAS_RTTI
+  return GetTypeName(typeid(T));
+#else
+  return "<type>";
+#endif  // GTEST_HAS_RTTI
+}
+
+// A unique type indicating an empty node
+struct None {};
+
+# define GTEST_TEMPLATE_ template <typename T> class
+
+// The template "selector" struct TemplateSel<Tmpl> is used to
+// represent Tmpl, which must be a class template with one type
+// parameter, as a type.  TemplateSel<Tmpl>::Bind<T>::type is defined
+// as the type Tmpl<T>.  This allows us to actually instantiate the
+// template "selected" by TemplateSel<Tmpl>.
+//
+// This trick is necessary for simulating typedef for class templates,
+// which C++ doesn't support directly.
+template <GTEST_TEMPLATE_ Tmpl>
+struct TemplateSel {
+  template <typename T>
+  struct Bind {
+    typedef Tmpl<T> type;
+  };
+};
+
+# define GTEST_BIND_(TmplSel, T) \
+  TmplSel::template Bind<T>::type
+
+template <GTEST_TEMPLATE_ Head_, GTEST_TEMPLATE_... Tail_>
+struct Templates {
+  using Head = TemplateSel<Head_>;
+  using Tail = Templates<Tail_...>;
+};
+
+template <GTEST_TEMPLATE_ Head_>
+struct Templates<Head_> {
+  using Head = TemplateSel<Head_>;
+  using Tail = None;
+};
+
+// Tuple-like type lists
+template <typename Head_, typename... Tail_>
+struct Types {
+  using Head = Head_;
+  using Tail = Types<Tail_...>;
+};
+
+template <typename Head_>
+struct Types<Head_> {
+  using Head = Head_;
+  using Tail = None;
+};
+
+// Helper metafunctions to tell apart a single type from types
+// generated by ::testing::Types
+template <typename... Ts>
+struct ProxyTypeList {
+  using type = Types<Ts...>;
+};
+
+template <typename>
+struct is_proxy_type_list : std::false_type {};
+
+template <typename... Ts>
+struct is_proxy_type_list<ProxyTypeList<Ts...>> : std::true_type {};
+
+// Generator which conditionally creates type lists.
+// It recognizes if a requested type list should be created
+// and prevents creating a new type list nested within another one.
+template <typename T>
+struct GenerateTypeList {
+ private:
+  using proxy = typename std::conditional<is_proxy_type_list<T>::value, T,
+                                          ProxyTypeList<T>>::type;
+
+ public:
+  using type = typename proxy::type;
+};
+
+}  // namespace internal
+
+template <typename... Ts>
+using Types = internal::ProxyTypeList<Ts...>;
+
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_
+
+// Due to C++ preprocessor weirdness, we need double indirection to
+// concatenate two tokens when one of them is __LINE__.  Writing
+//
+//   foo ## __LINE__
+//
+// will result in the token foo__LINE__, instead of foo followed by
+// the current line number.  For more details, see
+// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6
+#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar)
+#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar
+
+// Stringifies its argument.
+// Work around a bug in visual studio which doesn't accept code like this:
+//
+//   #define GTEST_STRINGIFY_(name) #name
+//   #define MACRO(a, b, c) ... GTEST_STRINGIFY_(a) ...
+//   MACRO(, x, y)
+//
+// Complaining about the argument to GTEST_STRINGIFY_ being empty.
+// This is allowed by the spec.
+#define GTEST_STRINGIFY_HELPER_(name, ...) #name
+#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )
+
+namespace proto2 {
+class MessageLite;
+}
+
+namespace testing {
+
+// Forward declarations.
+
+class AssertionResult;                 // Result of an assertion.
+class Message;                         // Represents a failure message.
+class Test;                            // Represents a test.
+class TestInfo;                        // Information about a test.
+class TestPartResult;                  // Result of a test part.
+class UnitTest;                        // A collection of test suites.
+
+template <typename T>
+::std::string PrintToString(const T& value);
+
+namespace internal {
+
+struct TraceInfo;                      // Information about a trace point.
+class TestInfoImpl;                    // Opaque implementation of TestInfo
+class UnitTestImpl;                    // Opaque implementation of UnitTest
+
+// The text used in failure messages to indicate the start of the
+// stack trace.
+GTEST_API_ extern const char kStackTraceMarker[];
+
+// An IgnoredValue object can be implicitly constructed from ANY value.
+class IgnoredValue {
+  struct Sink {};
+ public:
+  // This constructor template allows any value to be implicitly
+  // converted to IgnoredValue.  The object has no data member and
+  // doesn't try to remember anything about the argument.  We
+  // deliberately omit the 'explicit' keyword in order to allow the
+  // conversion to be implicit.
+  // Disable the conversion if T already has a magical conversion operator.
+  // Otherwise we get ambiguity.
+  template <typename T,
+            typename std::enable_if<!std::is_convertible<T, Sink>::value,
+                                    int>::type = 0>
+  IgnoredValue(const T& /* ignored */) {}  // NOLINT(runtime/explicit)
+};
+
+// Appends the user-supplied message to the Google-Test-generated message.
+GTEST_API_ std::string AppendUserMessage(
+    const std::string& gtest_msg, const Message& user_msg);
+
+#if GTEST_HAS_EXCEPTIONS
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4275 \
+/* an exported class was derived from a class that was not exported */)
+
+// This exception is thrown by (and only by) a failed Google Test
+// assertion when GTEST_FLAG(throw_on_failure) is true (if exceptions
+// are enabled).  We derive it from std::runtime_error, which is for
+// errors presumably detectable only at run time.  Since
+// std::runtime_error inherits from std::exception, many testing
+// frameworks know how to extract and print the message inside it.
+class GTEST_API_ GoogleTestFailureException : public ::std::runtime_error {
+ public:
+  explicit GoogleTestFailureException(const TestPartResult& failure);
+};
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4275
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+namespace edit_distance {
+// Returns the optimal edits to go from 'left' to 'right'.
+// All edits cost the same, with replace having lower priority than
+// add/remove.
+// Simple implementation of the Wagner-Fischer algorithm.
+// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm
+enum EditType { kMatch, kAdd, kRemove, kReplace };
+GTEST_API_ std::vector<EditType> CalculateOptimalEdits(
+    const std::vector<size_t>& left, const std::vector<size_t>& right);
+
+// Same as above, but the input is represented as strings.
+GTEST_API_ std::vector<EditType> CalculateOptimalEdits(
+    const std::vector<std::string>& left,
+    const std::vector<std::string>& right);
+
+// Create a diff of the input strings in Unified diff format.
+GTEST_API_ std::string CreateUnifiedDiff(const std::vector<std::string>& left,
+                                         const std::vector<std::string>& right,
+                                         size_t context = 2);
+
+}  // namespace edit_distance
+
+// Calculate the diff between 'left' and 'right' and return it in unified diff
+// format.
+// If not null, stores in 'total_line_count' the total number of lines found
+// in left + right.
+GTEST_API_ std::string DiffStrings(const std::string& left,
+                                   const std::string& right,
+                                   size_t* total_line_count);
+
+// Constructs and returns the message for an equality assertion
+// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure.
+//
+// The first four parameters are the expressions used in the assertion
+// and their values, as strings.  For example, for ASSERT_EQ(foo, bar)
+// where foo is 5 and bar is 6, we have:
+//
+//   expected_expression: "foo"
+//   actual_expression:   "bar"
+//   expected_value:      "5"
+//   actual_value:        "6"
+//
+// The ignoring_case parameter is true if and only if the assertion is a
+// *_STRCASEEQ*.  When it's true, the string " (ignoring case)" will
+// be inserted into the message.
+GTEST_API_ AssertionResult EqFailure(const char* expected_expression,
+                                     const char* actual_expression,
+                                     const std::string& expected_value,
+                                     const std::string& actual_value,
+                                     bool ignoring_case);
+
+// Constructs a failure message for Boolean assertions such as EXPECT_TRUE.
+GTEST_API_ std::string GetBoolAssertionFailureMessage(
+    const AssertionResult& assertion_result,
+    const char* expression_text,
+    const char* actual_predicate_value,
+    const char* expected_predicate_value);
+
+// This template class represents an IEEE floating-point number
+// (either single-precision or double-precision, depending on the
+// template parameters).
+//
+// The purpose of this class is to do more sophisticated number
+// comparison.  (Due to round-off error, etc, it's very unlikely that
+// two floating-points will be equal exactly.  Hence a naive
+// comparison by the == operation often doesn't work.)
+//
+// Format of IEEE floating-point:
+//
+//   The most-significant bit being the leftmost, an IEEE
+//   floating-point looks like
+//
+//     sign_bit exponent_bits fraction_bits
+//
+//   Here, sign_bit is a single bit that designates the sign of the
+//   number.
+//
+//   For float, there are 8 exponent bits and 23 fraction bits.
+//
+//   For double, there are 11 exponent bits and 52 fraction bits.
+//
+//   More details can be found at
+//   http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
+//
+// Template parameter:
+//
+//   RawType: the raw floating-point type (either float or double)
+template <typename RawType>
+class FloatingPoint {
+ public:
+  // Defines the unsigned integer type that has the same size as the
+  // floating point number.
+  typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;
+
+  // Constants.
+
+  // # of bits in a number.
+  static const size_t kBitCount = 8*sizeof(RawType);
+
+  // # of fraction bits in a number.
+  static const size_t kFractionBitCount =
+    std::numeric_limits<RawType>::digits - 1;
+
+  // # of exponent bits in a number.
+  static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
+
+  // The mask for the sign bit.
+  static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
+
+  // The mask for the fraction bits.
+  static const Bits kFractionBitMask =
+    ~static_cast<Bits>(0) >> (kExponentBitCount + 1);
+
+  // The mask for the exponent bits.
+  static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
+
+  // How many ULP's (Units in the Last Place) we want to tolerate when
+  // comparing two numbers.  The larger the value, the more error we
+  // allow.  A 0 value means that two numbers must be exactly the same
+  // to be considered equal.
+  //
+  // The maximum error of a single floating-point operation is 0.5
+  // units in the last place.  On Intel CPU's, all floating-point
+  // calculations are done with 80-bit precision, while double has 64
+  // bits.  Therefore, 4 should be enough for ordinary use.
+  //
+  // See the following article for more details on ULP:
+  // http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+  static const uint32_t kMaxUlps = 4;
+
+  // Constructs a FloatingPoint from a raw floating-point number.
+  //
+  // On an Intel CPU, passing a non-normalized NAN (Not a Number)
+  // around may change its bits, although the new value is guaranteed
+  // to be also a NAN.  Therefore, don't expect this constructor to
+  // preserve the bits in x when x is a NAN.
+  explicit FloatingPoint(const RawType& x) { u_.value_ = x; }
+
+  // Static methods
+
+  // Reinterprets a bit pattern as a floating-point number.
+  //
+  // This function is needed to test the AlmostEquals() method.
+  static RawType ReinterpretBits(const Bits bits) {
+    FloatingPoint fp(0);
+    fp.u_.bits_ = bits;
+    return fp.u_.value_;
+  }
+
+  // Returns the floating-point number that represent positive infinity.
+  static RawType Infinity() {
+    return ReinterpretBits(kExponentBitMask);
+  }
+
+  // Returns the maximum representable finite floating-point number.
+  static RawType Max();
+
+  // Non-static methods
+
+  // Returns the bits that represents this number.
+  const Bits &bits() const { return u_.bits_; }
+
+  // Returns the exponent bits of this number.
+  Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }
+
+  // Returns the fraction bits of this number.
+  Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }
+
+  // Returns the sign bit of this number.
+  Bits sign_bit() const { return kSignBitMask & u_.bits_; }
+
+  // Returns true if and only if this is NAN (not a number).
+  bool is_nan() const {
+    // It's a NAN if the exponent bits are all ones and the fraction
+    // bits are not entirely zeros.
+    return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
+  }
+
+  // Returns true if and only if this number is at most kMaxUlps ULP's away
+  // from rhs.  In particular, this function:
+  //
+  //   - returns false if either number is (or both are) NAN.
+  //   - treats really large numbers as almost equal to infinity.
+  //   - thinks +0.0 and -0.0 are 0 DLP's apart.
+  bool AlmostEquals(const FloatingPoint& rhs) const {
+    // The IEEE standard says that any comparison operation involving
+    // a NAN must return false.
+    if (is_nan() || rhs.is_nan()) return false;
+
+    return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
+        <= kMaxUlps;
+  }
+
+ private:
+  // The data type used to store the actual floating-point number.
+  union FloatingPointUnion {
+    RawType value_;  // The raw floating-point number.
+    Bits bits_;      // The bits that represent the number.
+  };
+
+  // Converts an integer from the sign-and-magnitude representation to
+  // the biased representation.  More precisely, let N be 2 to the
+  // power of (kBitCount - 1), an integer x is represented by the
+  // unsigned number x + N.
+  //
+  // For instance,
+  //
+  //   -N + 1 (the most negative number representable using
+  //          sign-and-magnitude) is represented by 1;
+  //   0      is represented by N; and
+  //   N - 1  (the biggest number representable using
+  //          sign-and-magnitude) is represented by 2N - 1.
+  //
+  // Read http://en.wikipedia.org/wiki/Signed_number_representations
+  // for more details on signed number representations.
+  static Bits SignAndMagnitudeToBiased(const Bits &sam) {
+    if (kSignBitMask & sam) {
+      // sam represents a negative number.
+      return ~sam + 1;
+    } else {
+      // sam represents a positive number.
+      return kSignBitMask | sam;
+    }
+  }
+
+  // Given two numbers in the sign-and-magnitude representation,
+  // returns the distance between them as an unsigned number.
+  static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
+                                                     const Bits &sam2) {
+    const Bits biased1 = SignAndMagnitudeToBiased(sam1);
+    const Bits biased2 = SignAndMagnitudeToBiased(sam2);
+    return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
+  }
+
+  FloatingPointUnion u_;
+};
+
+// We cannot use std::numeric_limits<T>::max() as it clashes with the max()
+// macro defined by <windows.h>.
+template <>
+inline float FloatingPoint<float>::Max() { return FLT_MAX; }
+template <>
+inline double FloatingPoint<double>::Max() { return DBL_MAX; }
+
+// Typedefs the instances of the FloatingPoint template class that we
+// care to use.
+typedef FloatingPoint<float> Float;
+typedef FloatingPoint<double> Double;
+
+// In order to catch the mistake of putting tests that use different
+// test fixture classes in the same test suite, we need to assign
+// unique IDs to fixture classes and compare them.  The TypeId type is
+// used to hold such IDs.  The user should treat TypeId as an opaque
+// type: the only operation allowed on TypeId values is to compare
+// them for equality using the == operator.
+typedef const void* TypeId;
+
+template <typename T>
+class TypeIdHelper {
+ public:
+  // dummy_ must not have a const type.  Otherwise an overly eager
+  // compiler (e.g. MSVC 7.1 & 8.0) may try to merge
+  // TypeIdHelper<T>::dummy_ for different Ts as an "optimization".
+  static bool dummy_;
+};
+
+template <typename T>
+bool TypeIdHelper<T>::dummy_ = false;
+
+// GetTypeId<T>() returns the ID of type T.  Different values will be
+// returned for different types.  Calling the function twice with the
+// same type argument is guaranteed to return the same ID.
+template <typename T>
+TypeId GetTypeId() {
+  // The compiler is required to allocate a different
+  // TypeIdHelper<T>::dummy_ variable for each T used to instantiate
+  // the template.  Therefore, the address of dummy_ is guaranteed to
+  // be unique.
+  return &(TypeIdHelper<T>::dummy_);
+}
+
+// Returns the type ID of ::testing::Test.  Always call this instead
+// of GetTypeId< ::testing::Test>() to get the type ID of
+// ::testing::Test, as the latter may give the wrong result due to a
+// suspected linker bug when compiling Google Test as a Mac OS X
+// framework.
+GTEST_API_ TypeId GetTestTypeId();
+
+// Defines the abstract factory interface that creates instances
+// of a Test object.
+class TestFactoryBase {
+ public:
+  virtual ~TestFactoryBase() {}
+
+  // Creates a test instance to run. The instance is both created and destroyed
+  // within TestInfoImpl::Run()
+  virtual Test* CreateTest() = 0;
+
+ protected:
+  TestFactoryBase() {}
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase);
+};
+
+// This class provides implementation of TeastFactoryBase interface.
+// It is used in TEST and TEST_F macros.
+template <class TestClass>
+class TestFactoryImpl : public TestFactoryBase {
+ public:
+  Test* CreateTest() override { return new TestClass; }
+};
+
+#if GTEST_OS_WINDOWS
+
+// Predicate-formatters for implementing the HRESULT checking macros
+// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}
+// We pass a long instead of HRESULT to avoid causing an
+// include dependency for the HRESULT type.
+GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr,
+                                            long hr);  // NOLINT
+GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr,
+                                            long hr);  // NOLINT
+
+#endif  // GTEST_OS_WINDOWS
+
+// Types of SetUpTestSuite() and TearDownTestSuite() functions.
+using SetUpTestSuiteFunc = void (*)();
+using TearDownTestSuiteFunc = void (*)();
+
+struct CodeLocation {
+  CodeLocation(const std::string& a_file, int a_line)
+      : file(a_file), line(a_line) {}
+
+  std::string file;
+  int line;
+};
+
+//  Helper to identify which setup function for TestCase / TestSuite to call.
+//  Only one function is allowed, either TestCase or TestSute but not both.
+
+// Utility functions to help SuiteApiResolver
+using SetUpTearDownSuiteFuncType = void (*)();
+
+inline SetUpTearDownSuiteFuncType GetNotDefaultOrNull(
+    SetUpTearDownSuiteFuncType a, SetUpTearDownSuiteFuncType def) {
+  return a == def ? nullptr : a;
+}
+
+template <typename T>
+//  Note that SuiteApiResolver inherits from T because
+//  SetUpTestSuite()/TearDownTestSuite() could be protected. Ths way
+//  SuiteApiResolver can access them.
+struct SuiteApiResolver : T {
+  // testing::Test is only forward declared at this point. So we make it a
+  // dependend class for the compiler to be OK with it.
+  using Test =
+      typename std::conditional<sizeof(T) != 0, ::testing::Test, void>::type;
+
+  static SetUpTearDownSuiteFuncType GetSetUpCaseOrSuite(const char* filename,
+                                                        int line_num) {
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+    SetUpTearDownSuiteFuncType test_case_fp =
+        GetNotDefaultOrNull(&T::SetUpTestCase, &Test::SetUpTestCase);
+    SetUpTearDownSuiteFuncType test_suite_fp =
+        GetNotDefaultOrNull(&T::SetUpTestSuite, &Test::SetUpTestSuite);
+
+    GTEST_CHECK_(!test_case_fp || !test_suite_fp)
+        << "Test can not provide both SetUpTestSuite and SetUpTestCase, please "
+           "make sure there is only one present at "
+        << filename << ":" << line_num;
+
+    return test_case_fp != nullptr ? test_case_fp : test_suite_fp;
+#else
+    (void)(filename);
+    (void)(line_num);
+    return &T::SetUpTestSuite;
+#endif
+  }
+
+  static SetUpTearDownSuiteFuncType GetTearDownCaseOrSuite(const char* filename,
+                                                           int line_num) {
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+    SetUpTearDownSuiteFuncType test_case_fp =
+        GetNotDefaultOrNull(&T::TearDownTestCase, &Test::TearDownTestCase);
+    SetUpTearDownSuiteFuncType test_suite_fp =
+        GetNotDefaultOrNull(&T::TearDownTestSuite, &Test::TearDownTestSuite);
+
+    GTEST_CHECK_(!test_case_fp || !test_suite_fp)
+        << "Test can not provide both TearDownTestSuite and TearDownTestCase,"
+           " please make sure there is only one present at"
+        << filename << ":" << line_num;
+
+    return test_case_fp != nullptr ? test_case_fp : test_suite_fp;
+#else
+    (void)(filename);
+    (void)(line_num);
+    return &T::TearDownTestSuite;
+#endif
+  }
+};
+
+// Creates a new TestInfo object and registers it with Google Test;
+// returns the created object.
+//
+// Arguments:
+//
+//   test_suite_name:  name of the test suite
+//   name:             name of the test
+//   type_param:       the name of the test's type parameter, or NULL if
+//                     this is not a typed or a type-parameterized test.
+//   value_param:      text representation of the test's value parameter,
+//                     or NULL if this is not a type-parameterized test.
+//   code_location:    code location where the test is defined
+//   fixture_class_id: ID of the test fixture class
+//   set_up_tc:        pointer to the function that sets up the test suite
+//   tear_down_tc:     pointer to the function that tears down the test suite
+//   factory:          pointer to the factory that creates a test object.
+//                     The newly created TestInfo instance will assume
+//                     ownership of the factory object.
+GTEST_API_ TestInfo* MakeAndRegisterTestInfo(
+    const char* test_suite_name, const char* name, const char* type_param,
+    const char* value_param, CodeLocation code_location,
+    TypeId fixture_class_id, SetUpTestSuiteFunc set_up_tc,
+    TearDownTestSuiteFunc tear_down_tc, TestFactoryBase* factory);
+
+// If *pstr starts with the given prefix, modifies *pstr to be right
+// past the prefix and returns true; otherwise leaves *pstr unchanged
+// and returns false.  None of pstr, *pstr, and prefix can be NULL.
+GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr);
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// State of the definition of a type-parameterized test suite.
+class GTEST_API_ TypedTestSuitePState {
+ public:
+  TypedTestSuitePState() : registered_(false) {}
+
+  // Adds the given test name to defined_test_names_ and return true
+  // if the test suite hasn't been registered; otherwise aborts the
+  // program.
+  bool AddTestName(const char* file, int line, const char* case_name,
+                   const char* test_name) {
+    if (registered_) {
+      fprintf(stderr,
+              "%s Test %s must be defined before "
+              "REGISTER_TYPED_TEST_SUITE_P(%s, ...).\n",
+              FormatFileLocation(file, line).c_str(), test_name, case_name);
+      fflush(stderr);
+      posix::Abort();
+    }
+    registered_tests_.insert(
+        ::std::make_pair(test_name, CodeLocation(file, line)));
+    return true;
+  }
+
+  bool TestExists(const std::string& test_name) const {
+    return registered_tests_.count(test_name) > 0;
+  }
+
+  const CodeLocation& GetCodeLocation(const std::string& test_name) const {
+    RegisteredTestsMap::const_iterator it = registered_tests_.find(test_name);
+    GTEST_CHECK_(it != registered_tests_.end());
+    return it->second;
+  }
+
+  // Verifies that registered_tests match the test names in
+  // defined_test_names_; returns registered_tests if successful, or
+  // aborts the program otherwise.
+  const char* VerifyRegisteredTestNames(const char* test_suite_name,
+                                        const char* file, int line,
+                                        const char* registered_tests);
+
+ private:
+  typedef ::std::map<std::string, CodeLocation> RegisteredTestsMap;
+
+  bool registered_;
+  RegisteredTestsMap registered_tests_;
+};
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+using TypedTestCasePState = TypedTestSuitePState;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+// Skips to the first non-space char after the first comma in 'str';
+// returns NULL if no comma is found in 'str'.
+inline const char* SkipComma(const char* str) {
+  const char* comma = strchr(str, ',');
+  if (comma == nullptr) {
+    return nullptr;
+  }
+  while (IsSpace(*(++comma))) {}
+  return comma;
+}
+
+// Returns the prefix of 'str' before the first comma in it; returns
+// the entire string if it contains no comma.
+inline std::string GetPrefixUntilComma(const char* str) {
+  const char* comma = strchr(str, ',');
+  return comma == nullptr ? str : std::string(str, comma);
+}
+
+// Splits a given string on a given delimiter, populating a given
+// vector with the fields.
+void SplitString(const ::std::string& str, char delimiter,
+                 ::std::vector< ::std::string>* dest);
+
+// The default argument to the template below for the case when the user does
+// not provide a name generator.
+struct DefaultNameGenerator {
+  template <typename T>
+  static std::string GetName(int i) {
+    return StreamableToString(i);
+  }
+};
+
+template <typename Provided = DefaultNameGenerator>
+struct NameGeneratorSelector {
+  typedef Provided type;
+};
+
+template <typename NameGenerator>
+void GenerateNamesRecursively(internal::None, std::vector<std::string>*, int) {}
+
+template <typename NameGenerator, typename Types>
+void GenerateNamesRecursively(Types, std::vector<std::string>* result, int i) {
+  result->push_back(NameGenerator::template GetName<typename Types::Head>(i));
+  GenerateNamesRecursively<NameGenerator>(typename Types::Tail(), result,
+                                          i + 1);
+}
+
+template <typename NameGenerator, typename Types>
+std::vector<std::string> GenerateNames() {
+  std::vector<std::string> result;
+  GenerateNamesRecursively<NameGenerator>(Types(), &result, 0);
+  return result;
+}
+
+// TypeParameterizedTest<Fixture, TestSel, Types>::Register()
+// registers a list of type-parameterized tests with Google Test.  The
+// return value is insignificant - we just need to return something
+// such that we can call this function in a namespace scope.
+//
+// Implementation note: The GTEST_TEMPLATE_ macro declares a template
+// template parameter.  It's defined in gtest-type-util.h.
+template <GTEST_TEMPLATE_ Fixture, class TestSel, typename Types>
+class TypeParameterizedTest {
+ public:
+  // 'index' is the index of the test in the type list 'Types'
+  // specified in INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, TestSuite,
+  // Types).  Valid values for 'index' are [0, N - 1] where N is the
+  // length of Types.
+  static bool Register(const char* prefix, const CodeLocation& code_location,
+                       const char* case_name, const char* test_names, int index,
+                       const std::vector<std::string>& type_names =
+                           GenerateNames<DefaultNameGenerator, Types>()) {
+    typedef typename Types::Head Type;
+    typedef Fixture<Type> FixtureClass;
+    typedef typename GTEST_BIND_(TestSel, Type) TestClass;
+
+    // First, registers the first type-parameterized test in the type
+    // list.
+    MakeAndRegisterTestInfo(
+        (std::string(prefix) + (prefix[0] == '\0' ? "" : "/") + case_name +
+         "/" + type_names[static_cast<size_t>(index)])
+            .c_str(),
+        StripTrailingSpaces(GetPrefixUntilComma(test_names)).c_str(),
+        GetTypeName<Type>().c_str(),
+        nullptr,  // No value parameter.
+        code_location, GetTypeId<FixtureClass>(),
+        SuiteApiResolver<TestClass>::GetSetUpCaseOrSuite(
+            code_location.file.c_str(), code_location.line),
+        SuiteApiResolver<TestClass>::GetTearDownCaseOrSuite(
+            code_location.file.c_str(), code_location.line),
+        new TestFactoryImpl<TestClass>);
+
+    // Next, recurses (at compile time) with the tail of the type list.
+    return TypeParameterizedTest<Fixture, TestSel,
+                                 typename Types::Tail>::Register(prefix,
+                                                                 code_location,
+                                                                 case_name,
+                                                                 test_names,
+                                                                 index + 1,
+                                                                 type_names);
+  }
+};
+
+// The base case for the compile time recursion.
+template <GTEST_TEMPLATE_ Fixture, class TestSel>
+class TypeParameterizedTest<Fixture, TestSel, internal::None> {
+ public:
+  static bool Register(const char* /*prefix*/, const CodeLocation&,
+                       const char* /*case_name*/, const char* /*test_names*/,
+                       int /*index*/,
+                       const std::vector<std::string>& =
+                           std::vector<std::string>() /*type_names*/) {
+    return true;
+  }
+};
+
+GTEST_API_ void RegisterTypeParameterizedTestSuite(const char* test_suite_name,
+                                                   CodeLocation code_location);
+GTEST_API_ void RegisterTypeParameterizedTestSuiteInstantiation(
+    const char* case_name);
+
+// TypeParameterizedTestSuite<Fixture, Tests, Types>::Register()
+// registers *all combinations* of 'Tests' and 'Types' with Google
+// Test.  The return value is insignificant - we just need to return
+// something such that we can call this function in a namespace scope.
+template <GTEST_TEMPLATE_ Fixture, typename Tests, typename Types>
+class TypeParameterizedTestSuite {
+ public:
+  static bool Register(const char* prefix, CodeLocation code_location,
+                       const TypedTestSuitePState* state, const char* case_name,
+                       const char* test_names,
+                       const std::vector<std::string>& type_names =
+                           GenerateNames<DefaultNameGenerator, Types>()) {
+    RegisterTypeParameterizedTestSuiteInstantiation(case_name);
+    std::string test_name = StripTrailingSpaces(
+        GetPrefixUntilComma(test_names));
+    if (!state->TestExists(test_name)) {
+      fprintf(stderr, "Failed to get code location for test %s.%s at %s.",
+              case_name, test_name.c_str(),
+              FormatFileLocation(code_location.file.c_str(),
+                                 code_location.line).c_str());
+      fflush(stderr);
+      posix::Abort();
+    }
+    const CodeLocation& test_location = state->GetCodeLocation(test_name);
+
+    typedef typename Tests::Head Head;
+
+    // First, register the first test in 'Test' for each type in 'Types'.
+    TypeParameterizedTest<Fixture, Head, Types>::Register(
+        prefix, test_location, case_name, test_names, 0, type_names);
+
+    // Next, recurses (at compile time) with the tail of the test list.
+    return TypeParameterizedTestSuite<Fixture, typename Tests::Tail,
+                                      Types>::Register(prefix, code_location,
+                                                       state, case_name,
+                                                       SkipComma(test_names),
+                                                       type_names);
+  }
+};
+
+// The base case for the compile time recursion.
+template <GTEST_TEMPLATE_ Fixture, typename Types>
+class TypeParameterizedTestSuite<Fixture, internal::None, Types> {
+ public:
+  static bool Register(const char* /*prefix*/, const CodeLocation&,
+                       const TypedTestSuitePState* /*state*/,
+                       const char* /*case_name*/, const char* /*test_names*/,
+                       const std::vector<std::string>& =
+                           std::vector<std::string>() /*type_names*/) {
+    return true;
+  }
+};
+
+// Returns the current OS stack trace as an std::string.
+//
+// The maximum number of stack frames to be included is specified by
+// the gtest_stack_trace_depth flag.  The skip_count parameter
+// specifies the number of top frames to be skipped, which doesn't
+// count against the number of frames to be included.
+//
+// For example, if Foo() calls Bar(), which in turn calls
+// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in
+// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't.
+GTEST_API_ std::string GetCurrentOsStackTraceExceptTop(
+    UnitTest* unit_test, int skip_count);
+
+// Helpers for suppressing warnings on unreachable code or constant
+// condition.
+
+// Always returns true.
+GTEST_API_ bool AlwaysTrue();
+
+// Always returns false.
+inline bool AlwaysFalse() { return !AlwaysTrue(); }
+
+// Helper for suppressing false warning from Clang on a const char*
+// variable declared in a conditional expression always being NULL in
+// the else branch.
+struct GTEST_API_ ConstCharPtr {
+  ConstCharPtr(const char* str) : value(str) {}
+  operator bool() const { return true; }
+  const char* value;
+};
+
+// Helper for declaring std::string within 'if' statement
+// in pre C++17 build environment.
+struct TrueWithString {
+  TrueWithString() = default;
+  explicit TrueWithString(const char* str) : value(str) {}
+  explicit TrueWithString(const std::string& str) : value(str) {}
+  explicit operator bool() const { return true; }
+  std::string value;
+};
+
+// A simple Linear Congruential Generator for generating random
+// numbers with a uniform distribution.  Unlike rand() and srand(), it
+// doesn't use global state (and therefore can't interfere with user
+// code).  Unlike rand_r(), it's portable.  An LCG isn't very random,
+// but it's good enough for our purposes.
+class GTEST_API_ Random {
+ public:
+  static const uint32_t kMaxRange = 1u << 31;
+
+  explicit Random(uint32_t seed) : state_(seed) {}
+
+  void Reseed(uint32_t seed) { state_ = seed; }
+
+  // Generates a random number from [0, range).  Crashes if 'range' is
+  // 0 or greater than kMaxRange.
+  uint32_t Generate(uint32_t range);
+
+ private:
+  uint32_t state_;
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Random);
+};
+
+// Turns const U&, U&, const U, and U all into U.
+#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \
+  typename std::remove_const<typename std::remove_reference<T>::type>::type
+
+// HasDebugStringAndShortDebugString<T>::value is a compile-time bool constant
+// that's true if and only if T has methods DebugString() and ShortDebugString()
+// that return std::string.
+template <typename T>
+class HasDebugStringAndShortDebugString {
+ private:
+  template <typename C>
+  static auto CheckDebugString(C*) -> typename std::is_same<
+      std::string, decltype(std::declval<const C>().DebugString())>::type;
+  template <typename>
+  static std::false_type CheckDebugString(...);
+
+  template <typename C>
+  static auto CheckShortDebugString(C*) -> typename std::is_same<
+      std::string, decltype(std::declval<const C>().ShortDebugString())>::type;
+  template <typename>
+  static std::false_type CheckShortDebugString(...);
+
+  using HasDebugStringType = decltype(CheckDebugString<T>(nullptr));
+  using HasShortDebugStringType = decltype(CheckShortDebugString<T>(nullptr));
+
+ public:
+  static constexpr bool value =
+      HasDebugStringType::value && HasShortDebugStringType::value;
+};
+
+template <typename T>
+constexpr bool HasDebugStringAndShortDebugString<T>::value;
+
+// When the compiler sees expression IsContainerTest<C>(0), if C is an
+// STL-style container class, the first overload of IsContainerTest
+// will be viable (since both C::iterator* and C::const_iterator* are
+// valid types and NULL can be implicitly converted to them).  It will
+// be picked over the second overload as 'int' is a perfect match for
+// the type of argument 0.  If C::iterator or C::const_iterator is not
+// a valid type, the first overload is not viable, and the second
+// overload will be picked.  Therefore, we can determine whether C is
+// a container class by checking the type of IsContainerTest<C>(0).
+// The value of the expression is insignificant.
+//
+// In C++11 mode we check the existence of a const_iterator and that an
+// iterator is properly implemented for the container.
+//
+// For pre-C++11 that we look for both C::iterator and C::const_iterator.
+// The reason is that C++ injects the name of a class as a member of the
+// class itself (e.g. you can refer to class iterator as either
+// 'iterator' or 'iterator::iterator').  If we look for C::iterator
+// only, for example, we would mistakenly think that a class named
+// iterator is an STL container.
+//
+// Also note that the simpler approach of overloading
+// IsContainerTest(typename C::const_iterator*) and
+// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++.
+typedef int IsContainer;
+template <class C,
+          class Iterator = decltype(::std::declval<const C&>().begin()),
+          class = decltype(::std::declval<const C&>().end()),
+          class = decltype(++::std::declval<Iterator&>()),
+          class = decltype(*::std::declval<Iterator>()),
+          class = typename C::const_iterator>
+IsContainer IsContainerTest(int /* dummy */) {
+  return 0;
+}
+
+typedef char IsNotContainer;
+template <class C>
+IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; }
+
+// Trait to detect whether a type T is a hash table.
+// The heuristic used is that the type contains an inner type `hasher` and does
+// not contain an inner type `reverse_iterator`.
+// If the container is iterable in reverse, then order might actually matter.
+template <typename T>
+struct IsHashTable {
+ private:
+  template <typename U>
+  static char test(typename U::hasher*, typename U::reverse_iterator*);
+  template <typename U>
+  static int test(typename U::hasher*, ...);
+  template <typename U>
+  static char test(...);
+
+ public:
+  static const bool value = sizeof(test<T>(nullptr, nullptr)) == sizeof(int);
+};
+
+template <typename T>
+const bool IsHashTable<T>::value;
+
+template <typename C,
+          bool = sizeof(IsContainerTest<C>(0)) == sizeof(IsContainer)>
+struct IsRecursiveContainerImpl;
+
+template <typename C>
+struct IsRecursiveContainerImpl<C, false> : public std::false_type {};
+
+// Since the IsRecursiveContainerImpl depends on the IsContainerTest we need to
+// obey the same inconsistencies as the IsContainerTest, namely check if
+// something is a container is relying on only const_iterator in C++11 and
+// is relying on both const_iterator and iterator otherwise
+template <typename C>
+struct IsRecursiveContainerImpl<C, true> {
+  using value_type = decltype(*std::declval<typename C::const_iterator>());
+  using type =
+      std::is_same<typename std::remove_const<
+                       typename std::remove_reference<value_type>::type>::type,
+                   C>;
+};
+
+// IsRecursiveContainer<Type> is a unary compile-time predicate that
+// evaluates whether C is a recursive container type. A recursive container
+// type is a container type whose value_type is equal to the container type
+// itself. An example for a recursive container type is
+// boost::filesystem::path, whose iterator has a value_type that is equal to
+// boost::filesystem::path.
+template <typename C>
+struct IsRecursiveContainer : public IsRecursiveContainerImpl<C>::type {};
+
+// Utilities for native arrays.
+
+// ArrayEq() compares two k-dimensional native arrays using the
+// elements' operator==, where k can be any integer >= 0.  When k is
+// 0, ArrayEq() degenerates into comparing a single pair of values.
+
+template <typename T, typename U>
+bool ArrayEq(const T* lhs, size_t size, const U* rhs);
+
+// This generic version is used when k is 0.
+template <typename T, typename U>
+inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; }
+
+// This overload is used when k >= 1.
+template <typename T, typename U, size_t N>
+inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) {
+  return internal::ArrayEq(lhs, N, rhs);
+}
+
+// This helper reduces code bloat.  If we instead put its logic inside
+// the previous ArrayEq() function, arrays with different sizes would
+// lead to different copies of the template code.
+template <typename T, typename U>
+bool ArrayEq(const T* lhs, size_t size, const U* rhs) {
+  for (size_t i = 0; i != size; i++) {
+    if (!internal::ArrayEq(lhs[i], rhs[i]))
+      return false;
+  }
+  return true;
+}
+
+// Finds the first element in the iterator range [begin, end) that
+// equals elem.  Element may be a native array type itself.
+template <typename Iter, typename Element>
+Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) {
+  for (Iter it = begin; it != end; ++it) {
+    if (internal::ArrayEq(*it, elem))
+      return it;
+  }
+  return end;
+}
+
+// CopyArray() copies a k-dimensional native array using the elements'
+// operator=, where k can be any integer >= 0.  When k is 0,
+// CopyArray() degenerates into copying a single value.
+
+template <typename T, typename U>
+void CopyArray(const T* from, size_t size, U* to);
+
+// This generic version is used when k is 0.
+template <typename T, typename U>
+inline void CopyArray(const T& from, U* to) { *to = from; }
+
+// This overload is used when k >= 1.
+template <typename T, typename U, size_t N>
+inline void CopyArray(const T(&from)[N], U(*to)[N]) {
+  internal::CopyArray(from, N, *to);
+}
+
+// This helper reduces code bloat.  If we instead put its logic inside
+// the previous CopyArray() function, arrays with different sizes
+// would lead to different copies of the template code.
+template <typename T, typename U>
+void CopyArray(const T* from, size_t size, U* to) {
+  for (size_t i = 0; i != size; i++) {
+    internal::CopyArray(from[i], to + i);
+  }
+}
+
+// The relation between an NativeArray object (see below) and the
+// native array it represents.
+// We use 2 different structs to allow non-copyable types to be used, as long
+// as RelationToSourceReference() is passed.
+struct RelationToSourceReference {};
+struct RelationToSourceCopy {};
+
+// Adapts a native array to a read-only STL-style container.  Instead
+// of the complete STL container concept, this adaptor only implements
+// members useful for Google Mock's container matchers.  New members
+// should be added as needed.  To simplify the implementation, we only
+// support Element being a raw type (i.e. having no top-level const or
+// reference modifier).  It's the client's responsibility to satisfy
+// this requirement.  Element can be an array type itself (hence
+// multi-dimensional arrays are supported).
+template <typename Element>
+class NativeArray {
+ public:
+  // STL-style container typedefs.
+  typedef Element value_type;
+  typedef Element* iterator;
+  typedef const Element* const_iterator;
+
+  // Constructs from a native array. References the source.
+  NativeArray(const Element* array, size_t count, RelationToSourceReference) {
+    InitRef(array, count);
+  }
+
+  // Constructs from a native array. Copies the source.
+  NativeArray(const Element* array, size_t count, RelationToSourceCopy) {
+    InitCopy(array, count);
+  }
+
+  // Copy constructor.
+  NativeArray(const NativeArray& rhs) {
+    (this->*rhs.clone_)(rhs.array_, rhs.size_);
+  }
+
+  ~NativeArray() {
+    if (clone_ != &NativeArray::InitRef)
+      delete[] array_;
+  }
+
+  // STL-style container methods.
+  size_t size() const { return size_; }
+  const_iterator begin() const { return array_; }
+  const_iterator end() const { return array_ + size_; }
+  bool operator==(const NativeArray& rhs) const {
+    return size() == rhs.size() &&
+        ArrayEq(begin(), size(), rhs.begin());
+  }
+
+ private:
+  static_assert(!std::is_const<Element>::value, "Type must not be const");
+  static_assert(!std::is_reference<Element>::value,
+                "Type must not be a reference");
+
+  // Initializes this object with a copy of the input.
+  void InitCopy(const Element* array, size_t a_size) {
+    Element* const copy = new Element[a_size];
+    CopyArray(array, a_size, copy);
+    array_ = copy;
+    size_ = a_size;
+    clone_ = &NativeArray::InitCopy;
+  }
+
+  // Initializes this object with a reference of the input.
+  void InitRef(const Element* array, size_t a_size) {
+    array_ = array;
+    size_ = a_size;
+    clone_ = &NativeArray::InitRef;
+  }
+
+  const Element* array_;
+  size_t size_;
+  void (NativeArray::*clone_)(const Element*, size_t);
+};
+
+// Backport of std::index_sequence.
+template <size_t... Is>
+struct IndexSequence {
+  using type = IndexSequence;
+};
+
+// Double the IndexSequence, and one if plus_one is true.
+template <bool plus_one, typename T, size_t sizeofT>
+struct DoubleSequence;
+template <size_t... I, size_t sizeofT>
+struct DoubleSequence<true, IndexSequence<I...>, sizeofT> {
+  using type = IndexSequence<I..., (sizeofT + I)..., 2 * sizeofT>;
+};
+template <size_t... I, size_t sizeofT>
+struct DoubleSequence<false, IndexSequence<I...>, sizeofT> {
+  using type = IndexSequence<I..., (sizeofT + I)...>;
+};
+
+// Backport of std::make_index_sequence.
+// It uses O(ln(N)) instantiation depth.
+template <size_t N>
+struct MakeIndexSequenceImpl
+    : DoubleSequence<N % 2 == 1, typename MakeIndexSequenceImpl<N / 2>::type,
+                     N / 2>::type {};
+
+template <>
+struct MakeIndexSequenceImpl<0> : IndexSequence<> {};
+
+template <size_t N>
+using MakeIndexSequence = typename MakeIndexSequenceImpl<N>::type;
+
+template <typename... T>
+using IndexSequenceFor = typename MakeIndexSequence<sizeof...(T)>::type;
+
+template <size_t>
+struct Ignore {
+  Ignore(...);  // NOLINT
+};
+
+template <typename>
+struct ElemFromListImpl;
+template <size_t... I>
+struct ElemFromListImpl<IndexSequence<I...>> {
+  // We make Ignore a template to solve a problem with MSVC.
+  // A non-template Ignore would work fine with `decltype(Ignore(I))...`, but
+  // MSVC doesn't understand how to deal with that pack expansion.
+  // Use `0 * I` to have a single instantiation of Ignore.
+  template <typename R>
+  static R Apply(Ignore<0 * I>..., R (*)(), ...);
+};
+
+template <size_t N, typename... T>
+struct ElemFromList {
+  using type =
+      decltype(ElemFromListImpl<typename MakeIndexSequence<N>::type>::Apply(
+          static_cast<T (*)()>(nullptr)...));
+};
+
+struct FlatTupleConstructTag {};
+
+template <typename... T>
+class FlatTuple;
+
+template <typename Derived, size_t I>
+struct FlatTupleElemBase;
+
+template <typename... T, size_t I>
+struct FlatTupleElemBase<FlatTuple<T...>, I> {
+  using value_type = typename ElemFromList<I, T...>::type;
+  FlatTupleElemBase() = default;
+  template <typename Arg>
+  explicit FlatTupleElemBase(FlatTupleConstructTag, Arg&& t)
+      : value(std::forward<Arg>(t)) {}
+  value_type value;
+};
+
+template <typename Derived, typename Idx>
+struct FlatTupleBase;
+
+template <size_t... Idx, typename... T>
+struct FlatTupleBase<FlatTuple<T...>, IndexSequence<Idx...>>
+    : FlatTupleElemBase<FlatTuple<T...>, Idx>... {
+  using Indices = IndexSequence<Idx...>;
+  FlatTupleBase() = default;
+  template <typename... Args>
+  explicit FlatTupleBase(FlatTupleConstructTag, Args&&... args)
+      : FlatTupleElemBase<FlatTuple<T...>, Idx>(FlatTupleConstructTag{},
+                                                std::forward<Args>(args))... {}
+
+  template <size_t I>
+  const typename ElemFromList<I, T...>::type& Get() const {
+    return FlatTupleElemBase<FlatTuple<T...>, I>::value;
+  }
+
+  template <size_t I>
+  typename ElemFromList<I, T...>::type& Get() {
+    return FlatTupleElemBase<FlatTuple<T...>, I>::value;
+  }
+
+  template <typename F>
+  auto Apply(F&& f) -> decltype(std::forward<F>(f)(this->Get<Idx>()...)) {
+    return std::forward<F>(f)(Get<Idx>()...);
+  }
+
+  template <typename F>
+  auto Apply(F&& f) const -> decltype(std::forward<F>(f)(this->Get<Idx>()...)) {
+    return std::forward<F>(f)(Get<Idx>()...);
+  }
+};
+
+// Analog to std::tuple but with different tradeoffs.
+// This class minimizes the template instantiation depth, thus allowing more
+// elements than std::tuple would. std::tuple has been seen to require an
+// instantiation depth of more than 10x the number of elements in some
+// implementations.
+// FlatTuple and ElemFromList are not recursive and have a fixed depth
+// regardless of T...
+// MakeIndexSequence, on the other hand, it is recursive but with an
+// instantiation depth of O(ln(N)).
+template <typename... T>
+class FlatTuple
+    : private FlatTupleBase<FlatTuple<T...>,
+                            typename MakeIndexSequence<sizeof...(T)>::type> {
+  using Indices = typename FlatTupleBase<
+      FlatTuple<T...>, typename MakeIndexSequence<sizeof...(T)>::type>::Indices;
+
+ public:
+  FlatTuple() = default;
+  template <typename... Args>
+  explicit FlatTuple(FlatTupleConstructTag tag, Args&&... args)
+      : FlatTuple::FlatTupleBase(tag, std::forward<Args>(args)...) {}
+
+  using FlatTuple::FlatTupleBase::Apply;
+  using FlatTuple::FlatTupleBase::Get;
+};
+
+// Utility functions to be called with static_assert to induce deprecation
+// warnings.
+GTEST_INTERNAL_DEPRECATED(
+    "INSTANTIATE_TEST_CASE_P is deprecated, please use "
+    "INSTANTIATE_TEST_SUITE_P")
+constexpr bool InstantiateTestCase_P_IsDeprecated() { return true; }
+
+GTEST_INTERNAL_DEPRECATED(
+    "TYPED_TEST_CASE_P is deprecated, please use "
+    "TYPED_TEST_SUITE_P")
+constexpr bool TypedTestCase_P_IsDeprecated() { return true; }
+
+GTEST_INTERNAL_DEPRECATED(
+    "TYPED_TEST_CASE is deprecated, please use "
+    "TYPED_TEST_SUITE")
+constexpr bool TypedTestCaseIsDeprecated() { return true; }
+
+GTEST_INTERNAL_DEPRECATED(
+    "REGISTER_TYPED_TEST_CASE_P is deprecated, please use "
+    "REGISTER_TYPED_TEST_SUITE_P")
+constexpr bool RegisterTypedTestCase_P_IsDeprecated() { return true; }
+
+GTEST_INTERNAL_DEPRECATED(
+    "INSTANTIATE_TYPED_TEST_CASE_P is deprecated, please use "
+    "INSTANTIATE_TYPED_TEST_SUITE_P")
+constexpr bool InstantiateTypedTestCase_P_IsDeprecated() { return true; }
+
+}  // namespace internal
+}  // namespace testing
+
+namespace std {
+// Some standard library implementations use `struct tuple_size` and some use
+// `class tuple_size`. Clang warns about the mismatch.
+// https://reviews.llvm.org/D55466
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wmismatched-tags"
+#endif
+template <typename... Ts>
+struct tuple_size<testing::internal::FlatTuple<Ts...>>
+    : std::integral_constant<size_t, sizeof...(Ts)> {};
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+}  // namespace std
+
+#define GTEST_MESSAGE_AT_(file, line, message, result_type) \
+  ::testing::internal::AssertHelper(result_type, file, line, message) \
+    = ::testing::Message()
+
+#define GTEST_MESSAGE_(message, result_type) \
+  GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type)
+
+#define GTEST_FATAL_FAILURE_(message) \
+  return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure)
+
+#define GTEST_NONFATAL_FAILURE_(message) \
+  GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure)
+
+#define GTEST_SUCCESS_(message) \
+  GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
+
+#define GTEST_SKIP_(message) \
+  return GTEST_MESSAGE_(message, ::testing::TestPartResult::kSkip)
+
+// Suppress MSVC warning 4072 (unreachable code) for the code following
+// statement if it returns or throws (or doesn't return or throw in some
+// situations).
+// NOTE: The "else" is important to keep this expansion to prevent a top-level
+// "else" from attaching to our "if".
+#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \
+  if (::testing::internal::AlwaysTrue()) {                        \
+    statement;                                                    \
+  } else                     /* NOLINT */                         \
+    static_assert(true, "")  // User must have a semicolon after expansion.
+
+#if GTEST_HAS_EXCEPTIONS
+
+namespace testing {
+namespace internal {
+
+class NeverThrown {
+ public:
+  const char* what() const noexcept {
+    return "this exception should never be thrown";
+  }
+};
+
+}  // namespace internal
+}  // namespace testing
+
+#if GTEST_HAS_RTTI
+
+#define GTEST_EXCEPTION_TYPE_(e) ::testing::internal::GetTypeName(typeid(e))
+
+#else  // GTEST_HAS_RTTI
+
+#define GTEST_EXCEPTION_TYPE_(e) \
+  std::string { "an std::exception-derived error" }
+
+#endif  // GTEST_HAS_RTTI
+
+#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception)   \
+  catch (typename std::conditional<                                            \
+         std::is_same<typename std::remove_cv<typename std::remove_reference<  \
+                          expected_exception>::type>::type,                    \
+                      std::exception>::value,                                  \
+         const ::testing::internal::NeverThrown&, const std::exception&>::type \
+             e) {                                                              \
+    gtest_msg.value = "Expected: " #statement                                  \
+                      " throws an exception of type " #expected_exception      \
+                      ".\n  Actual: it throws ";                               \
+    gtest_msg.value += GTEST_EXCEPTION_TYPE_(e);                               \
+    gtest_msg.value += " with description \"";                                 \
+    gtest_msg.value += e.what();                                               \
+    gtest_msg.value += "\".";                                                  \
+    goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);                \
+  }
+
+#else  // GTEST_HAS_EXCEPTIONS
+
+#define GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception)
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+#define GTEST_TEST_THROW_(statement, expected_exception, fail)              \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                             \
+  if (::testing::internal::TrueWithString gtest_msg{}) {                    \
+    bool gtest_caught_expected = false;                                     \
+    try {                                                                   \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);            \
+    } catch (expected_exception const&) {                                   \
+      gtest_caught_expected = true;                                         \
+    }                                                                       \
+    GTEST_TEST_THROW_CATCH_STD_EXCEPTION_(statement, expected_exception)    \
+    catch (...) {                                                           \
+      gtest_msg.value = "Expected: " #statement                             \
+                        " throws an exception of type " #expected_exception \
+                        ".\n  Actual: it throws a different type.";         \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);           \
+    }                                                                       \
+    if (!gtest_caught_expected) {                                           \
+      gtest_msg.value = "Expected: " #statement                             \
+                        " throws an exception of type " #expected_exception \
+                        ".\n  Actual: it throws nothing.";                  \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__);           \
+    }                                                                       \
+  } else /*NOLINT*/                                                         \
+    GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__)                   \
+        : fail(gtest_msg.value.c_str())
+
+#if GTEST_HAS_EXCEPTIONS
+
+#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_()                \
+  catch (std::exception const& e) {                               \
+    gtest_msg.value = "it throws ";                               \
+    gtest_msg.value += GTEST_EXCEPTION_TYPE_(e);                  \
+    gtest_msg.value += " with description \"";                    \
+    gtest_msg.value += e.what();                                  \
+    gtest_msg.value += "\".";                                     \
+    goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
+  }
+
+#else  // GTEST_HAS_EXCEPTIONS
+
+#define GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_()
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+#define GTEST_TEST_NO_THROW_(statement, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (::testing::internal::TrueWithString gtest_msg{}) { \
+    try { \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+    } \
+    GTEST_TEST_NO_THROW_CATCH_STD_EXCEPTION_() \
+    catch (...) { \
+      gtest_msg.value = "it throws."; \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
+    } \
+  } else \
+    GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \
+      fail(("Expected: " #statement " doesn't throw an exception.\n" \
+            "  Actual: " + gtest_msg.value).c_str())
+
+#define GTEST_TEST_ANY_THROW_(statement, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (::testing::internal::AlwaysTrue()) { \
+    bool gtest_caught_any = false; \
+    try { \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+    } \
+    catch (...) { \
+      gtest_caught_any = true; \
+    } \
+    if (!gtest_caught_any) { \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \
+    } \
+  } else \
+    GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \
+      fail("Expected: " #statement " throws an exception.\n" \
+           "  Actual: it doesn't.")
+
+
+// Implements Boolean test assertions such as EXPECT_TRUE. expression can be
+// either a boolean expression or an AssertionResult. text is a textual
+// representation of expression as it was passed into the EXPECT_TRUE.
+#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (const ::testing::AssertionResult gtest_ar_ = \
+      ::testing::AssertionResult(expression)) \
+    ; \
+  else \
+    fail(::testing::internal::GetBoolAssertionFailureMessage(\
+        gtest_ar_, text, #actual, #expected).c_str())
+
+#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (::testing::internal::AlwaysTrue()) { \
+    ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \
+    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+    if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \
+    } \
+  } else \
+    GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \
+      fail("Expected: " #statement " doesn't generate new fatal " \
+           "failures in the current thread.\n" \
+           "  Actual: it does.")
+
+// Expands to the name of the class that implements the given test.
+#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
+  test_suite_name##_##test_name##_Test
+
+// Helper macro for defining tests.
+#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)      \
+  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                \
+                "test_suite_name must not be empty");                         \
+  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                      \
+                "test_name must not be empty");                               \
+  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                    \
+      : public parent_class {                                                 \
+   public:                                                                    \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;           \
+    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default; \
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name,   \
+                                                           test_name));       \
+    GTEST_DISALLOW_MOVE_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name,   \
+                                                           test_name));       \
+                                                                              \
+   private:                                                                   \
+    void TestBody() override;                                                 \
+    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;     \
+  };                                                                          \
+                                                                              \
+  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,          \
+                                                    test_name)::test_info_ =  \
+      ::testing::internal::MakeAndRegisterTestInfo(                           \
+          #test_suite_name, #test_name, nullptr, nullptr,                     \
+          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id), \
+          ::testing::internal::SuiteApiResolver<                              \
+              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),         \
+          ::testing::internal::SuiteApiResolver<                              \
+              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),      \
+          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(    \
+              test_suite_name, test_name)>);                                  \
+  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines the public API for death tests.  It is
+// #included by gtest.h so a user doesn't need to include this
+// directly.
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+
+// Copyright 2005, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This header file defines internal utilities needed for implementing
+// death tests.  They are subject to change without notice.
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// The Google C++ Testing and Mocking Framework (Google Test)
+//
+// This file implements just enough of the matcher interface to allow
+// EXPECT_DEATH and friends to accept a matcher argument.
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
+
+#include <atomic>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <type_traits>
+
+// Copyright 2007, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Google Test - The Google C++ Testing and Mocking Framework
+//
+// This file implements a universal value printer that can print a
+// value of any type T:
+//
+//   void ::testing::internal::UniversalPrinter<T>::Print(value, ostream_ptr);
+//
+// A user can teach this function how to print a class type T by
+// defining either operator<<() or PrintTo() in the namespace that
+// defines T.  More specifically, the FIRST defined function in the
+// following list will be used (assuming T is defined in namespace
+// foo):
+//
+//   1. foo::PrintTo(const T&, ostream*)
+//   2. operator<<(ostream&, const T&) defined in either foo or the
+//      global namespace.
+//
+// However if T is an STL-style container then it is printed element-wise
+// unless foo::PrintTo(const T&, ostream*) is defined. Note that
+// operator<<() is ignored for container types.
+//
+// If none of the above is defined, it will print the debug string of
+// the value if it is a protocol buffer, or print the raw bytes in the
+// value otherwise.
+//
+// To aid debugging: when T is a reference type, the address of the
+// value is also printed; when T is a (const) char pointer, both the
+// pointer value and the NUL-terminated string it points to are
+// printed.
+//
+// We also provide some convenient wrappers:
+//
+//   // Prints a value to a string.  For a (const or not) char
+//   // pointer, the NUL-terminated string (but not the pointer) is
+//   // printed.
+//   std::string ::testing::PrintToString(const T& value);
+//
+//   // Prints a value tersely: for a reference type, the referenced
+//   // value (but not the address) is printed; for a (const or not) char
+//   // pointer, the NUL-terminated string (but not the pointer) is
+//   // printed.
+//   void ::testing::internal::UniversalTersePrint(const T& value, ostream*);
+//
+//   // Prints value using the type inferred by the compiler.  The difference
+//   // from UniversalTersePrint() is that this function prints both the
+//   // pointer and the NUL-terminated string for a (const or not) char pointer.
+//   void ::testing::internal::UniversalPrint(const T& value, ostream*);
+//
+//   // Prints the fields of a tuple tersely to a string vector, one
+//   // element for each field. Tuple support must be enabled in
+//   // gtest-port.h.
+//   std::vector<string> UniversalTersePrintTupleFieldsToStrings(
+//       const Tuple& value);
+//
+// Known limitation:
+//
+// The print primitives print the elements of an STL-style container
+// using the compiler-inferred type of *iter where iter is a
+// const_iterator of the container.  When const_iterator is an input
+// iterator but not a forward iterator, this inferred type may not
+// match value_type, and the print output may be incorrect.  In
+// practice, this is rarely a problem as for most containers
+// const_iterator is a forward iterator.  We'll fix this if there's an
+// actual need for it.  Note that this fix cannot rely on value_type
+// being defined as many user-defined container types don't have
+// value_type.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+
+#include <functional>
+#include <memory>
+#include <ostream>  // NOLINT
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+
+#if GTEST_HAS_RTTI
+#include <typeindex>
+#include <typeinfo>
+#endif  // GTEST_HAS_RTTI
+
+namespace testing {
+
+// Definitions in the internal* namespaces are subject to change without notice.
+// DO NOT USE THEM IN USER CODE!
+namespace internal {
+
+template <typename T>
+void UniversalPrint(const T& value, ::std::ostream* os);
+
+// Used to print an STL-style container when the user doesn't define
+// a PrintTo() for it.
+struct ContainerPrinter {
+  template <typename T,
+            typename = typename std::enable_if<
+                (sizeof(IsContainerTest<T>(0)) == sizeof(IsContainer)) &&
+                !IsRecursiveContainer<T>::value>::type>
+  static void PrintValue(const T& container, std::ostream* os) {
+    const size_t kMaxCount = 32;  // The maximum number of elements to print.
+    *os << '{';
+    size_t count = 0;
+    for (auto&& elem : container) {
+      if (count > 0) {
+        *os << ',';
+        if (count == kMaxCount) {  // Enough has been printed.
+          *os << " ...";
+          break;
+        }
+      }
+      *os << ' ';
+      // We cannot call PrintTo(elem, os) here as PrintTo() doesn't
+      // handle `elem` being a native array.
+      internal::UniversalPrint(elem, os);
+      ++count;
+    }
+
+    if (count > 0) {
+      *os << ' ';
+    }
+    *os << '}';
+  }
+};
+
+// Used to print a pointer that is neither a char pointer nor a member
+// pointer, when the user doesn't define PrintTo() for it.  (A member
+// variable pointer or member function pointer doesn't really point to
+// a location in the address space.  Their representation is
+// implementation-defined.  Therefore they will be printed as raw
+// bytes.)
+struct FunctionPointerPrinter {
+  template <typename T, typename = typename std::enable_if<
+                            std::is_function<T>::value>::type>
+  static void PrintValue(T* p, ::std::ostream* os) {
+    if (p == nullptr) {
+      *os << "NULL";
+    } else {
+      // T is a function type, so '*os << p' doesn't do what we want
+      // (it just prints p as bool).  We want to print p as a const
+      // void*.
+      *os << reinterpret_cast<const void*>(p);
+    }
+  }
+};
+
+struct PointerPrinter {
+  template <typename T>
+  static void PrintValue(T* p, ::std::ostream* os) {
+    if (p == nullptr) {
+      *os << "NULL";
+    } else {
+      // T is not a function type.  We just call << to print p,
+      // relying on ADL to pick up user-defined << for their pointer
+      // types, if any.
+      *os << p;
+    }
+  }
+};
+
+namespace internal_stream_operator_without_lexical_name_lookup {
+
+// The presence of an operator<< here will terminate lexical scope lookup
+// straight away (even though it cannot be a match because of its argument
+// types). Thus, the two operator<< calls in StreamPrinter will find only ADL
+// candidates.
+struct LookupBlocker {};
+void operator<<(LookupBlocker, LookupBlocker);
+
+struct StreamPrinter {
+  template <typename T,
+            // Don't accept member pointers here. We'd print them via implicit
+            // conversion to bool, which isn't useful.
+            typename = typename std::enable_if<
+                !std::is_member_pointer<T>::value>::type,
+            // Only accept types for which we can find a streaming operator via
+            // ADL (possibly involving implicit conversions).
+            typename = decltype(std::declval<std::ostream&>()
+                                << std::declval<const T&>())>
+  static void PrintValue(const T& value, ::std::ostream* os) {
+    // Call streaming operator found by ADL, possibly with implicit conversions
+    // of the arguments.
+    *os << value;
+  }
+};
+
+}  // namespace internal_stream_operator_without_lexical_name_lookup
+
+struct ProtobufPrinter {
+  // We print a protobuf using its ShortDebugString() when the string
+  // doesn't exceed this many characters; otherwise we print it using
+  // DebugString() for better readability.
+  static const size_t kProtobufOneLinerMaxLength = 50;
+
+  template <typename T,
+            typename = typename std::enable_if<
+                internal::HasDebugStringAndShortDebugString<T>::value>::type>
+  static void PrintValue(const T& value, ::std::ostream* os) {
+    std::string pretty_str = value.ShortDebugString();
+    if (pretty_str.length() > kProtobufOneLinerMaxLength) {
+      pretty_str = "\n" + value.DebugString();
+    }
+    *os << ("<" + pretty_str + ">");
+  }
+};
+
+struct ConvertibleToIntegerPrinter {
+  // Since T has no << operator or PrintTo() but can be implicitly
+  // converted to BiggestInt, we print it as a BiggestInt.
+  //
+  // Most likely T is an enum type (either named or unnamed), in which
+  // case printing it as an integer is the desired behavior.  In case
+  // T is not an enum, printing it as an integer is the best we can do
+  // given that it has no user-defined printer.
+  static void PrintValue(internal::BiggestInt value, ::std::ostream* os) {
+    *os << value;
+  }
+};
+
+struct ConvertibleToStringViewPrinter {
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  static void PrintValue(internal::StringView value, ::std::ostream* os) {
+    internal::UniversalPrint(value, os);
+  }
+#endif
+};
+
+
+// Prints the given number of bytes in the given object to the given
+// ostream.
+GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes,
+                                     size_t count,
+                                     ::std::ostream* os);
+struct RawBytesPrinter {
+  // SFINAE on `sizeof` to make sure we have a complete type.
+  template <typename T, size_t = sizeof(T)>
+  static void PrintValue(const T& value, ::std::ostream* os) {
+    PrintBytesInObjectTo(
+        static_cast<const unsigned char*>(
+            // Load bearing cast to void* to support iOS
+            reinterpret_cast<const void*>(std::addressof(value))),
+        sizeof(value), os);
+  }
+};
+
+struct FallbackPrinter {
+  template <typename T>
+  static void PrintValue(const T&, ::std::ostream* os) {
+    *os << "(incomplete type)";
+  }
+};
+
+// Try every printer in order and return the first one that works.
+template <typename T, typename E, typename Printer, typename... Printers>
+struct FindFirstPrinter : FindFirstPrinter<T, E, Printers...> {};
+
+template <typename T, typename Printer, typename... Printers>
+struct FindFirstPrinter<
+    T, decltype(Printer::PrintValue(std::declval<const T&>(), nullptr)),
+    Printer, Printers...> {
+  using type = Printer;
+};
+
+// Select the best printer in the following order:
+//  - Print containers (they have begin/end/etc).
+//  - Print function pointers.
+//  - Print object pointers.
+//  - Use the stream operator, if available.
+//  - Print protocol buffers.
+//  - Print types convertible to BiggestInt.
+//  - Print types convertible to StringView, if available.
+//  - Fallback to printing the raw bytes of the object.
+template <typename T>
+void PrintWithFallback(const T& value, ::std::ostream* os) {
+  using Printer = typename FindFirstPrinter<
+      T, void, ContainerPrinter, FunctionPointerPrinter, PointerPrinter,
+      internal_stream_operator_without_lexical_name_lookup::StreamPrinter,
+      ProtobufPrinter, ConvertibleToIntegerPrinter,
+      ConvertibleToStringViewPrinter, RawBytesPrinter, FallbackPrinter>::type;
+  Printer::PrintValue(value, os);
+}
+
+// FormatForComparison<ToPrint, OtherOperand>::Format(value) formats a
+// value of type ToPrint that is an operand of a comparison assertion
+// (e.g. ASSERT_EQ).  OtherOperand is the type of the other operand in
+// the comparison, and is used to help determine the best way to
+// format the value.  In particular, when the value is a C string
+// (char pointer) and the other operand is an STL string object, we
+// want to format the C string as a string, since we know it is
+// compared by value with the string object.  If the value is a char
+// pointer but the other operand is not an STL string object, we don't
+// know whether the pointer is supposed to point to a NUL-terminated
+// string, and thus want to print it as a pointer to be safe.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+// The default case.
+template <typename ToPrint, typename OtherOperand>
+class FormatForComparison {
+ public:
+  static ::std::string Format(const ToPrint& value) {
+    return ::testing::PrintToString(value);
+  }
+};
+
+// Array.
+template <typename ToPrint, size_t N, typename OtherOperand>
+class FormatForComparison<ToPrint[N], OtherOperand> {
+ public:
+  static ::std::string Format(const ToPrint* value) {
+    return FormatForComparison<const ToPrint*, OtherOperand>::Format(value);
+  }
+};
+
+// By default, print C string as pointers to be safe, as we don't know
+// whether they actually point to a NUL-terminated string.
+
+#define GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(CharType)                \
+  template <typename OtherOperand>                                      \
+  class FormatForComparison<CharType*, OtherOperand> {                  \
+   public:                                                              \
+    static ::std::string Format(CharType* value) {                      \
+      return ::testing::PrintToString(static_cast<const void*>(value)); \
+    }                                                                   \
+  }
+
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(wchar_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const wchar_t);
+#ifdef __cpp_char8_t
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char8_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char8_t);
+#endif
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char16_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char16_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(char32_t);
+GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_(const char32_t);
+
+#undef GTEST_IMPL_FORMAT_C_STRING_AS_POINTER_
+
+// If a C string is compared with an STL string object, we know it's meant
+// to point to a NUL-terminated string, and thus can print it as a string.
+
+#define GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(CharType, OtherStringType) \
+  template <>                                                           \
+  class FormatForComparison<CharType*, OtherStringType> {               \
+   public:                                                              \
+    static ::std::string Format(CharType* value) {                      \
+      return ::testing::PrintToString(value);                           \
+    }                                                                   \
+  }
+
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char, ::std::string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char, ::std::string);
+#ifdef __cpp_char8_t
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char8_t, ::std::u8string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char8_t, ::std::u8string);
+#endif
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char16_t, ::std::u16string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char16_t, ::std::u16string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(char32_t, ::std::u32string);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const char32_t, ::std::u32string);
+
+#if GTEST_HAS_STD_WSTRING
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(wchar_t, ::std::wstring);
+GTEST_IMPL_FORMAT_C_STRING_AS_STRING_(const wchar_t, ::std::wstring);
+#endif
+
+#undef GTEST_IMPL_FORMAT_C_STRING_AS_STRING_
+
+// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc)
+// operand to be used in a failure message.  The type (but not value)
+// of the other operand may affect the format.  This allows us to
+// print a char* as a raw pointer when it is compared against another
+// char* or void*, and print it as a C string when it is compared
+// against an std::string object, for example.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+template <typename T1, typename T2>
+std::string FormatForComparisonFailureMessage(
+    const T1& value, const T2& /* other_operand */) {
+  return FormatForComparison<T1, T2>::Format(value);
+}
+
+// UniversalPrinter<T>::Print(value, ostream_ptr) prints the given
+// value to the given ostream.  The caller must ensure that
+// 'ostream_ptr' is not NULL, or the behavior is undefined.
+//
+// We define UniversalPrinter as a class template (as opposed to a
+// function template), as we need to partially specialize it for
+// reference types, which cannot be done with function templates.
+template <typename T>
+class UniversalPrinter;
+
+// Prints the given value using the << operator if it has one;
+// otherwise prints the bytes in it.  This is what
+// UniversalPrinter<T>::Print() does when PrintTo() is not specialized
+// or overloaded for type T.
+//
+// A user can override this behavior for a class type Foo by defining
+// an overload of PrintTo() in the namespace where Foo is defined.  We
+// give the user this option as sometimes defining a << operator for
+// Foo is not desirable (e.g. the coding style may prevent doing it,
+// or there is already a << operator but it doesn't do what the user
+// wants).
+template <typename T>
+void PrintTo(const T& value, ::std::ostream* os) {
+  internal::PrintWithFallback(value, os);
+}
+
+// The following list of PrintTo() overloads tells
+// UniversalPrinter<T>::Print() how to print standard types (built-in
+// types, strings, plain arrays, and pointers).
+
+// Overloads for various char types.
+GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os);
+GTEST_API_ void PrintTo(signed char c, ::std::ostream* os);
+inline void PrintTo(char c, ::std::ostream* os) {
+  // When printing a plain char, we always treat it as unsigned.  This
+  // way, the output won't be affected by whether the compiler thinks
+  // char is signed or not.
+  PrintTo(static_cast<unsigned char>(c), os);
+}
+
+// Overloads for other simple built-in types.
+inline void PrintTo(bool x, ::std::ostream* os) {
+  *os << (x ? "true" : "false");
+}
+
+// Overload for wchar_t type.
+// Prints a wchar_t as a symbol if it is printable or as its internal
+// code otherwise and also as its decimal code (except for L'\0').
+// The L'\0' char is printed as "L'\\0'". The decimal code is printed
+// as signed integer when wchar_t is implemented by the compiler
+// as a signed type and is printed as an unsigned integer when wchar_t
+// is implemented as an unsigned type.
+GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os);
+
+GTEST_API_ void PrintTo(char32_t c, ::std::ostream* os);
+inline void PrintTo(char16_t c, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<char32_t>(c), os);
+}
+#ifdef __cpp_char8_t
+inline void PrintTo(char8_t c, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<char32_t>(c), os);
+}
+#endif
+
+// Overloads for C strings.
+GTEST_API_ void PrintTo(const char* s, ::std::ostream* os);
+inline void PrintTo(char* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const char*>(s), os);
+}
+
+// signed/unsigned char is often used for representing binary data, so
+// we print pointers to it as void* to be safe.
+inline void PrintTo(const signed char* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(signed char* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(const unsigned char* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const void*>(s), os);
+}
+inline void PrintTo(unsigned char* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const void*>(s), os);
+}
+#ifdef __cpp_char8_t
+// Overloads for u8 strings.
+void PrintTo(const char8_t* s, ::std::ostream* os);
+inline void PrintTo(char8_t* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const char8_t*>(s), os);
+}
+#endif
+// Overloads for u16 strings.
+void PrintTo(const char16_t* s, ::std::ostream* os);
+inline void PrintTo(char16_t* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const char16_t*>(s), os);
+}
+// Overloads for u32 strings.
+void PrintTo(const char32_t* s, ::std::ostream* os);
+inline void PrintTo(char32_t* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const char32_t*>(s), os);
+}
+
+// MSVC can be configured to define wchar_t as a typedef of unsigned
+// short.  It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native
+// type.  When wchar_t is a typedef, defining an overload for const
+// wchar_t* would cause unsigned short* be printed as a wide string,
+// possibly causing invalid memory accesses.
+#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED)
+// Overloads for wide C strings
+GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os);
+inline void PrintTo(wchar_t* s, ::std::ostream* os) {
+  PrintTo(ImplicitCast_<const wchar_t*>(s), os);
+}
+#endif
+
+// Overload for C arrays.  Multi-dimensional arrays are printed
+// properly.
+
+// Prints the given number of elements in an array, without printing
+// the curly braces.
+template <typename T>
+void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) {
+  UniversalPrint(a[0], os);
+  for (size_t i = 1; i != count; i++) {
+    *os << ", ";
+    UniversalPrint(a[i], os);
+  }
+}
+
+// Overloads for ::std::string.
+GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os);
+inline void PrintTo(const ::std::string& s, ::std::ostream* os) {
+  PrintStringTo(s, os);
+}
+
+// Overloads for ::std::u8string
+#ifdef __cpp_char8_t
+GTEST_API_ void PrintU8StringTo(const ::std::u8string& s, ::std::ostream* os);
+inline void PrintTo(const ::std::u8string& s, ::std::ostream* os) {
+  PrintU8StringTo(s, os);
+}
+#endif
+
+// Overloads for ::std::u16string
+GTEST_API_ void PrintU16StringTo(const ::std::u16string& s, ::std::ostream* os);
+inline void PrintTo(const ::std::u16string& s, ::std::ostream* os) {
+  PrintU16StringTo(s, os);
+}
+
+// Overloads for ::std::u32string
+GTEST_API_ void PrintU32StringTo(const ::std::u32string& s, ::std::ostream* os);
+inline void PrintTo(const ::std::u32string& s, ::std::ostream* os) {
+  PrintU32StringTo(s, os);
+}
+
+// Overloads for ::std::wstring.
+#if GTEST_HAS_STD_WSTRING
+GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os);
+inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) {
+  PrintWideStringTo(s, os);
+}
+#endif  // GTEST_HAS_STD_WSTRING
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+// Overload for internal::StringView.
+inline void PrintTo(internal::StringView sp, ::std::ostream* os) {
+  PrintTo(::std::string(sp), os);
+}
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+inline void PrintTo(std::nullptr_t, ::std::ostream* os) { *os << "(nullptr)"; }
+
+template <typename T>
+void PrintTo(std::reference_wrapper<T> ref, ::std::ostream* os) {
+  UniversalPrinter<T&>::Print(ref.get(), os);
+}
+
+inline const void* VoidifyPointer(const void* p) { return p; }
+inline const void* VoidifyPointer(volatile const void* p) {
+  return const_cast<const void*>(p);
+}
+
+template <typename T, typename Ptr>
+void PrintSmartPointer(const Ptr& ptr, std::ostream* os, char) {
+  if (ptr == nullptr) {
+    *os << "(nullptr)";
+  } else {
+    // We can't print the value. Just print the pointer..
+    *os << "(" << (VoidifyPointer)(ptr.get()) << ")";
+  }
+}
+template <typename T, typename Ptr,
+          typename = typename std::enable_if<!std::is_void<T>::value &&
+                                             !std::is_array<T>::value>::type>
+void PrintSmartPointer(const Ptr& ptr, std::ostream* os, int) {
+  if (ptr == nullptr) {
+    *os << "(nullptr)";
+  } else {
+    *os << "(ptr = " << (VoidifyPointer)(ptr.get()) << ", value = ";
+    UniversalPrinter<T>::Print(*ptr, os);
+    *os << ")";
+  }
+}
+
+template <typename T, typename D>
+void PrintTo(const std::unique_ptr<T, D>& ptr, std::ostream* os) {
+  (PrintSmartPointer<T>)(ptr, os, 0);
+}
+
+template <typename T>
+void PrintTo(const std::shared_ptr<T>& ptr, std::ostream* os) {
+  (PrintSmartPointer<T>)(ptr, os, 0);
+}
+
+// Helper function for printing a tuple.  T must be instantiated with
+// a tuple type.
+template <typename T>
+void PrintTupleTo(const T&, std::integral_constant<size_t, 0>,
+                  ::std::ostream*) {}
+
+template <typename T, size_t I>
+void PrintTupleTo(const T& t, std::integral_constant<size_t, I>,
+                  ::std::ostream* os) {
+  PrintTupleTo(t, std::integral_constant<size_t, I - 1>(), os);
+  GTEST_INTENTIONAL_CONST_COND_PUSH_()
+  if (I > 1) {
+    GTEST_INTENTIONAL_CONST_COND_POP_()
+    *os << ", ";
+  }
+  UniversalPrinter<typename std::tuple_element<I - 1, T>::type>::Print(
+      std::get<I - 1>(t), os);
+}
+
+template <typename... Types>
+void PrintTo(const ::std::tuple<Types...>& t, ::std::ostream* os) {
+  *os << "(";
+  PrintTupleTo(t, std::integral_constant<size_t, sizeof...(Types)>(), os);
+  *os << ")";
+}
+
+// Overload for std::pair.
+template <typename T1, typename T2>
+void PrintTo(const ::std::pair<T1, T2>& value, ::std::ostream* os) {
+  *os << '(';
+  // We cannot use UniversalPrint(value.first, os) here, as T1 may be
+  // a reference type.  The same for printing value.second.
+  UniversalPrinter<T1>::Print(value.first, os);
+  *os << ", ";
+  UniversalPrinter<T2>::Print(value.second, os);
+  *os << ')';
+}
+
+#if GTEST_HAS_RTTI
+inline void PrintTo(const ::std::type_info& value, ::std::ostream* os) {
+  internal::PrintTo<::std::type_info>(value, os);
+  *os << " (\"" << value.name() << "\")";
+}
+
+inline void PrintTo(const ::std::type_index& value, ::std::ostream* os) {
+  internal::PrintTo<::std::type_index>(value, os);
+  *os << " (\"" << value.name() << "\")";
+}
+#endif  // GTEST_HAS_RTTI
+
+// Implements printing a non-reference type T by letting the compiler
+// pick the right overload of PrintTo() for T.
+template <typename T>
+class UniversalPrinter {
+ public:
+  // MSVC warns about adding const to a function type, so we want to
+  // disable the warning.
+  GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180)
+
+  // Note: we deliberately don't call this PrintTo(), as that name
+  // conflicts with ::testing::internal::PrintTo in the body of the
+  // function.
+  static void Print(const T& value, ::std::ostream* os) {
+    // By default, ::testing::internal::PrintTo() is used for printing
+    // the value.
+    //
+    // Thanks to Koenig look-up, if T is a class and has its own
+    // PrintTo() function defined in its namespace, that function will
+    // be visible here.  Since it is more specific than the generic ones
+    // in ::testing::internal, it will be picked by the compiler in the
+    // following statement - exactly what we want.
+    PrintTo(value, os);
+  }
+
+  GTEST_DISABLE_MSC_WARNINGS_POP_()
+};
+
+// Remove any const-qualifiers before passing a type to UniversalPrinter.
+template <typename T>
+class UniversalPrinter<const T> : public UniversalPrinter<T> {};
+
+#if GTEST_INTERNAL_HAS_ANY
+
+// Printer for std::any / absl::any
+
+template <>
+class UniversalPrinter<Any> {
+ public:
+  static void Print(const Any& value, ::std::ostream* os) {
+    if (value.has_value()) {
+      *os << "value of type " << GetTypeName(value);
+    } else {
+      *os << "no value";
+    }
+  }
+
+ private:
+  static std::string GetTypeName(const Any& value) {
+#if GTEST_HAS_RTTI
+    return internal::GetTypeName(value.type());
+#else
+    static_cast<void>(value);  // possibly unused
+    return "<unknown_type>";
+#endif  // GTEST_HAS_RTTI
+  }
+};
+
+#endif  // GTEST_INTERNAL_HAS_ANY
+
+#if GTEST_INTERNAL_HAS_OPTIONAL
+
+// Printer for std::optional / absl::optional
+
+template <typename T>
+class UniversalPrinter<Optional<T>> {
+ public:
+  static void Print(const Optional<T>& value, ::std::ostream* os) {
+    *os << '(';
+    if (!value) {
+      *os << "nullopt";
+    } else {
+      UniversalPrint(*value, os);
+    }
+    *os << ')';
+  }
+};
+
+#endif  // GTEST_INTERNAL_HAS_OPTIONAL
+
+#if GTEST_INTERNAL_HAS_VARIANT
+
+// Printer for std::variant / absl::variant
+
+template <typename... T>
+class UniversalPrinter<Variant<T...>> {
+ public:
+  static void Print(const Variant<T...>& value, ::std::ostream* os) {
+    *os << '(';
+#if GTEST_HAS_ABSL
+    absl::visit(Visitor{os, value.index()}, value);
+#else
+    std::visit(Visitor{os, value.index()}, value);
+#endif  // GTEST_HAS_ABSL
+    *os << ')';
+  }
+
+ private:
+  struct Visitor {
+    template <typename U>
+    void operator()(const U& u) const {
+      *os << "'" << GetTypeName<U>() << "(index = " << index
+          << ")' with value ";
+      UniversalPrint(u, os);
+    }
+    ::std::ostream* os;
+    std::size_t index;
+  };
+};
+
+#endif  // GTEST_INTERNAL_HAS_VARIANT
+
+// UniversalPrintArray(begin, len, os) prints an array of 'len'
+// elements, starting at address 'begin'.
+template <typename T>
+void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) {
+  if (len == 0) {
+    *os << "{}";
+  } else {
+    *os << "{ ";
+    const size_t kThreshold = 18;
+    const size_t kChunkSize = 8;
+    // If the array has more than kThreshold elements, we'll have to
+    // omit some details by printing only the first and the last
+    // kChunkSize elements.
+    if (len <= kThreshold) {
+      PrintRawArrayTo(begin, len, os);
+    } else {
+      PrintRawArrayTo(begin, kChunkSize, os);
+      *os << ", ..., ";
+      PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os);
+    }
+    *os << " }";
+  }
+}
+// This overload prints a (const) char array compactly.
+GTEST_API_ void UniversalPrintArray(
+    const char* begin, size_t len, ::std::ostream* os);
+
+#ifdef __cpp_char8_t
+// This overload prints a (const) char8_t array compactly.
+GTEST_API_ void UniversalPrintArray(const char8_t* begin, size_t len,
+                                    ::std::ostream* os);
+#endif
+
+// This overload prints a (const) char16_t array compactly.
+GTEST_API_ void UniversalPrintArray(const char16_t* begin, size_t len,
+                                    ::std::ostream* os);
+
+// This overload prints a (const) char32_t array compactly.
+GTEST_API_ void UniversalPrintArray(const char32_t* begin, size_t len,
+                                    ::std::ostream* os);
+
+// This overload prints a (const) wchar_t array compactly.
+GTEST_API_ void UniversalPrintArray(
+    const wchar_t* begin, size_t len, ::std::ostream* os);
+
+// Implements printing an array type T[N].
+template <typename T, size_t N>
+class UniversalPrinter<T[N]> {
+ public:
+  // Prints the given array, omitting some elements when there are too
+  // many.
+  static void Print(const T (&a)[N], ::std::ostream* os) {
+    UniversalPrintArray(a, N, os);
+  }
+};
+
+// Implements printing a reference type T&.
+template <typename T>
+class UniversalPrinter<T&> {
+ public:
+  // MSVC warns about adding const to a function type, so we want to
+  // disable the warning.
+  GTEST_DISABLE_MSC_WARNINGS_PUSH_(4180)
+
+  static void Print(const T& value, ::std::ostream* os) {
+    // Prints the address of the value.  We use reinterpret_cast here
+    // as static_cast doesn't compile when T is a function type.
+    *os << "@" << reinterpret_cast<const void*>(&value) << " ";
+
+    // Then prints the value itself.
+    UniversalPrint(value, os);
+  }
+
+  GTEST_DISABLE_MSC_WARNINGS_POP_()
+};
+
+// Prints a value tersely: for a reference type, the referenced value
+// (but not the address) is printed; for a (const) char pointer, the
+// NUL-terminated string (but not the pointer) is printed.
+
+template <typename T>
+class UniversalTersePrinter {
+ public:
+  static void Print(const T& value, ::std::ostream* os) {
+    UniversalPrint(value, os);
+  }
+};
+template <typename T>
+class UniversalTersePrinter<T&> {
+ public:
+  static void Print(const T& value, ::std::ostream* os) {
+    UniversalPrint(value, os);
+  }
+};
+template <typename T, size_t N>
+class UniversalTersePrinter<T[N]> {
+ public:
+  static void Print(const T (&value)[N], ::std::ostream* os) {
+    UniversalPrinter<T[N]>::Print(value, os);
+  }
+};
+template <>
+class UniversalTersePrinter<const char*> {
+ public:
+  static void Print(const char* str, ::std::ostream* os) {
+    if (str == nullptr) {
+      *os << "NULL";
+    } else {
+      UniversalPrint(std::string(str), os);
+    }
+  }
+};
+template <>
+class UniversalTersePrinter<char*> : public UniversalTersePrinter<const char*> {
+};
+
+#ifdef __cpp_char8_t
+template <>
+class UniversalTersePrinter<const char8_t*> {
+ public:
+  static void Print(const char8_t* str, ::std::ostream* os) {
+    if (str == nullptr) {
+      *os << "NULL";
+    } else {
+      UniversalPrint(::std::u8string(str), os);
+    }
+  }
+};
+template <>
+class UniversalTersePrinter<char8_t*>
+    : public UniversalTersePrinter<const char8_t*> {};
+#endif
+
+template <>
+class UniversalTersePrinter<const char16_t*> {
+ public:
+  static void Print(const char16_t* str, ::std::ostream* os) {
+    if (str == nullptr) {
+      *os << "NULL";
+    } else {
+      UniversalPrint(::std::u16string(str), os);
+    }
+  }
+};
+template <>
+class UniversalTersePrinter<char16_t*>
+    : public UniversalTersePrinter<const char16_t*> {};
+
+template <>
+class UniversalTersePrinter<const char32_t*> {
+ public:
+  static void Print(const char32_t* str, ::std::ostream* os) {
+    if (str == nullptr) {
+      *os << "NULL";
+    } else {
+      UniversalPrint(::std::u32string(str), os);
+    }
+  }
+};
+template <>
+class UniversalTersePrinter<char32_t*>
+    : public UniversalTersePrinter<const char32_t*> {};
+
+#if GTEST_HAS_STD_WSTRING
+template <>
+class UniversalTersePrinter<const wchar_t*> {
+ public:
+  static void Print(const wchar_t* str, ::std::ostream* os) {
+    if (str == nullptr) {
+      *os << "NULL";
+    } else {
+      UniversalPrint(::std::wstring(str), os);
+    }
+  }
+};
+#endif
+
+template <>
+class UniversalTersePrinter<wchar_t*> {
+ public:
+  static void Print(wchar_t* str, ::std::ostream* os) {
+    UniversalTersePrinter<const wchar_t*>::Print(str, os);
+  }
+};
+
+template <typename T>
+void UniversalTersePrint(const T& value, ::std::ostream* os) {
+  UniversalTersePrinter<T>::Print(value, os);
+}
+
+// Prints a value using the type inferred by the compiler.  The
+// difference between this and UniversalTersePrint() is that for a
+// (const) char pointer, this prints both the pointer and the
+// NUL-terminated string.
+template <typename T>
+void UniversalPrint(const T& value, ::std::ostream* os) {
+  // A workarond for the bug in VC++ 7.1 that prevents us from instantiating
+  // UniversalPrinter with T directly.
+  typedef T T1;
+  UniversalPrinter<T1>::Print(value, os);
+}
+
+typedef ::std::vector< ::std::string> Strings;
+
+  // Tersely prints the first N fields of a tuple to a string vector,
+  // one element for each field.
+template <typename Tuple>
+void TersePrintPrefixToStrings(const Tuple&, std::integral_constant<size_t, 0>,
+                               Strings*) {}
+template <typename Tuple, size_t I>
+void TersePrintPrefixToStrings(const Tuple& t,
+                               std::integral_constant<size_t, I>,
+                               Strings* strings) {
+  TersePrintPrefixToStrings(t, std::integral_constant<size_t, I - 1>(),
+                            strings);
+  ::std::stringstream ss;
+  UniversalTersePrint(std::get<I - 1>(t), &ss);
+  strings->push_back(ss.str());
+}
+
+// Prints the fields of a tuple tersely to a string vector, one
+// element for each field.  See the comment before
+// UniversalTersePrint() for how we define "tersely".
+template <typename Tuple>
+Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) {
+  Strings result;
+  TersePrintPrefixToStrings(
+      value, std::integral_constant<size_t, std::tuple_size<Tuple>::value>(),
+      &result);
+  return result;
+}
+
+}  // namespace internal
+
+template <typename T>
+::std::string PrintToString(const T& value) {
+  ::std::stringstream ss;
+  internal::UniversalTersePrinter<T>::Print(value, &ss);
+  return ss.str();
+}
+
+}  // namespace testing
+
+// Include any custom printer added by the local installation.
+// We must include this header at the end to make sure it can use the
+// declarations from this file.
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// This file provides an injection point for custom printers in a local
+// installation of gTest.
+// It will be included from gtest-printers.h and the overrides in this file
+// will be visible to everyone.
+//
+// Injection point for custom user configurations. See README for details
+//
+// ** Custom implementation starts here **
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_PRINTERS_H_
+
+// MSVC warning C5046 is new as of VS2017 version 15.8.
+#if defined(_MSC_VER) && _MSC_VER >= 1915
+#define GTEST_MAYBE_5046_ 5046
+#else
+#define GTEST_MAYBE_5046_
+#endif
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(
+    4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by
+                              clients of class B */
+    /* Symbol involving type with internal linkage not defined */)
+
+namespace testing {
+
+// To implement a matcher Foo for type T, define:
+//   1. a class FooMatcherMatcher that implements the matcher interface:
+//     using is_gtest_matcher = void;
+//     bool MatchAndExplain(const T&, std::ostream*);
+//       (MatchResultListener* can also be used instead of std::ostream*)
+//     void DescribeTo(std::ostream*);
+//     void DescribeNegationTo(std::ostream*);
+//
+//   2. a factory function that creates a Matcher<T> object from a
+//      FooMatcherMatcher.
+
+class MatchResultListener {
+ public:
+  // Creates a listener object with the given underlying ostream.  The
+  // listener does not own the ostream, and does not dereference it
+  // in the constructor or destructor.
+  explicit MatchResultListener(::std::ostream* os) : stream_(os) {}
+  virtual ~MatchResultListener() = 0;  // Makes this class abstract.
+
+  // Streams x to the underlying ostream; does nothing if the ostream
+  // is NULL.
+  template <typename T>
+  MatchResultListener& operator<<(const T& x) {
+    if (stream_ != nullptr) *stream_ << x;
+    return *this;
+  }
+
+  // Returns the underlying ostream.
+  ::std::ostream* stream() { return stream_; }
+
+  // Returns true if and only if the listener is interested in an explanation
+  // of the match result.  A matcher's MatchAndExplain() method can use
+  // this information to avoid generating the explanation when no one
+  // intends to hear it.
+  bool IsInterested() const { return stream_ != nullptr; }
+
+ private:
+  ::std::ostream* const stream_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(MatchResultListener);
+};
+
+inline MatchResultListener::~MatchResultListener() {
+}
+
+// An instance of a subclass of this knows how to describe itself as a
+// matcher.
+class MatcherDescriberInterface {
+ public:
+  virtual ~MatcherDescriberInterface() {}
+
+  // Describes this matcher to an ostream.  The function should print
+  // a verb phrase that describes the property a value matching this
+  // matcher should have.  The subject of the verb phrase is the value
+  // being matched.  For example, the DescribeTo() method of the Gt(7)
+  // matcher prints "is greater than 7".
+  virtual void DescribeTo(::std::ostream* os) const = 0;
+
+  // Describes the negation of this matcher to an ostream.  For
+  // example, if the description of this matcher is "is greater than
+  // 7", the negated description could be "is not greater than 7".
+  // You are not required to override this when implementing
+  // MatcherInterface, but it is highly advised so that your matcher
+  // can produce good error messages.
+  virtual void DescribeNegationTo(::std::ostream* os) const {
+    *os << "not (";
+    DescribeTo(os);
+    *os << ")";
+  }
+};
+
+// The implementation of a matcher.
+template <typename T>
+class MatcherInterface : public MatcherDescriberInterface {
+ public:
+  // Returns true if and only if the matcher matches x; also explains the
+  // match result to 'listener' if necessary (see the next paragraph), in
+  // the form of a non-restrictive relative clause ("which ...",
+  // "whose ...", etc) that describes x.  For example, the
+  // MatchAndExplain() method of the Pointee(...) matcher should
+  // generate an explanation like "which points to ...".
+  //
+  // Implementations of MatchAndExplain() should add an explanation of
+  // the match result *if and only if* they can provide additional
+  // information that's not already present (or not obvious) in the
+  // print-out of x and the matcher's description.  Whether the match
+  // succeeds is not a factor in deciding whether an explanation is
+  // needed, as sometimes the caller needs to print a failure message
+  // when the match succeeds (e.g. when the matcher is used inside
+  // Not()).
+  //
+  // For example, a "has at least 10 elements" matcher should explain
+  // what the actual element count is, regardless of the match result,
+  // as it is useful information to the reader; on the other hand, an
+  // "is empty" matcher probably only needs to explain what the actual
+  // size is when the match fails, as it's redundant to say that the
+  // size is 0 when the value is already known to be empty.
+  //
+  // You should override this method when defining a new matcher.
+  //
+  // It's the responsibility of the caller (Google Test) to guarantee
+  // that 'listener' is not NULL.  This helps to simplify a matcher's
+  // implementation when it doesn't care about the performance, as it
+  // can talk to 'listener' without checking its validity first.
+  // However, in order to implement dummy listeners efficiently,
+  // listener->stream() may be NULL.
+  virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0;
+
+  // Inherits these methods from MatcherDescriberInterface:
+  //   virtual void DescribeTo(::std::ostream* os) const = 0;
+  //   virtual void DescribeNegationTo(::std::ostream* os) const;
+};
+
+namespace internal {
+
+struct AnyEq {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a == b; }
+};
+struct AnyNe {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a != b; }
+};
+struct AnyLt {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a < b; }
+};
+struct AnyGt {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a > b; }
+};
+struct AnyLe {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a <= b; }
+};
+struct AnyGe {
+  template <typename A, typename B>
+  bool operator()(const A& a, const B& b) const { return a >= b; }
+};
+
+// A match result listener that ignores the explanation.
+class DummyMatchResultListener : public MatchResultListener {
+ public:
+  DummyMatchResultListener() : MatchResultListener(nullptr) {}
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(DummyMatchResultListener);
+};
+
+// A match result listener that forwards the explanation to a given
+// ostream.  The difference between this and MatchResultListener is
+// that the former is concrete.
+class StreamMatchResultListener : public MatchResultListener {
+ public:
+  explicit StreamMatchResultListener(::std::ostream* os)
+      : MatchResultListener(os) {}
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener);
+};
+
+struct SharedPayloadBase {
+  std::atomic<int> ref{1};
+  void Ref() { ref.fetch_add(1, std::memory_order_relaxed); }
+  bool Unref() { return ref.fetch_sub(1, std::memory_order_acq_rel) == 1; }
+};
+
+template <typename T>
+struct SharedPayload : SharedPayloadBase {
+  explicit SharedPayload(const T& v) : value(v) {}
+  explicit SharedPayload(T&& v) : value(std::move(v)) {}
+
+  static void Destroy(SharedPayloadBase* shared) {
+    delete static_cast<SharedPayload*>(shared);
+  }
+
+  T value;
+};
+
+template <typename T>
+using is_trivially_copy_constructible =
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
+    std::has_trivial_copy_constructor<T>;
+#else
+    std::is_trivially_copy_constructible<T>;
+#endif
+
+// An internal class for implementing Matcher<T>, which will derive
+// from it.  We put functionalities common to all Matcher<T>
+// specializations here to avoid code duplication.
+template <typename T>
+class MatcherBase : private MatcherDescriberInterface {
+ public:
+  // Returns true if and only if the matcher matches x; also explains the
+  // match result to 'listener'.
+  bool MatchAndExplain(const T& x, MatchResultListener* listener) const {
+    GTEST_CHECK_(vtable_ != nullptr);
+    return vtable_->match_and_explain(*this, x, listener);
+  }
+
+  // Returns true if and only if this matcher matches x.
+  bool Matches(const T& x) const {
+    DummyMatchResultListener dummy;
+    return MatchAndExplain(x, &dummy);
+  }
+
+  // Describes this matcher to an ostream.
+  void DescribeTo(::std::ostream* os) const final {
+    GTEST_CHECK_(vtable_ != nullptr);
+    vtable_->describe(*this, os, false);
+  }
+
+  // Describes the negation of this matcher to an ostream.
+  void DescribeNegationTo(::std::ostream* os) const final {
+    GTEST_CHECK_(vtable_ != nullptr);
+    vtable_->describe(*this, os, true);
+  }
+
+  // Explains why x matches, or doesn't match, the matcher.
+  void ExplainMatchResultTo(const T& x, ::std::ostream* os) const {
+    StreamMatchResultListener listener(os);
+    MatchAndExplain(x, &listener);
+  }
+
+  // Returns the describer for this matcher object; retains ownership
+  // of the describer, which is only guaranteed to be alive when
+  // this matcher object is alive.
+  const MatcherDescriberInterface* GetDescriber() const {
+    if (vtable_ == nullptr) return nullptr;
+    return vtable_->get_describer(*this);
+  }
+
+ protected:
+  MatcherBase() : vtable_(nullptr) {}
+
+  // Constructs a matcher from its implementation.
+  template <typename U>
+  explicit MatcherBase(const MatcherInterface<U>* impl) {
+    Init(impl);
+  }
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  MatcherBase(M&& m) {  // NOLINT
+    Init(std::forward<M>(m));
+  }
+
+  MatcherBase(const MatcherBase& other)
+      : vtable_(other.vtable_), buffer_(other.buffer_) {
+    if (IsShared()) buffer_.shared->Ref();
+  }
+
+  MatcherBase& operator=(const MatcherBase& other) {
+    if (this == &other) return *this;
+    Destroy();
+    vtable_ = other.vtable_;
+    buffer_ = other.buffer_;
+    if (IsShared()) buffer_.shared->Ref();
+    return *this;
+  }
+
+  MatcherBase(MatcherBase&& other)
+      : vtable_(other.vtable_), buffer_(other.buffer_) {
+    other.vtable_ = nullptr;
+  }
+
+  MatcherBase& operator=(MatcherBase&& other) {
+    if (this == &other) return *this;
+    Destroy();
+    vtable_ = other.vtable_;
+    buffer_ = other.buffer_;
+    other.vtable_ = nullptr;
+    return *this;
+  }
+
+  ~MatcherBase() override { Destroy(); }
+
+ private:
+  struct VTable {
+    bool (*match_and_explain)(const MatcherBase&, const T&,
+                              MatchResultListener*);
+    void (*describe)(const MatcherBase&, std::ostream*, bool negation);
+    // Returns the captured object if it implements the interface, otherwise
+    // returns the MatcherBase itself.
+    const MatcherDescriberInterface* (*get_describer)(const MatcherBase&);
+    // Called on shared instances when the reference count reaches 0.
+    void (*shared_destroy)(SharedPayloadBase*);
+  };
+
+  bool IsShared() const {
+    return vtable_ != nullptr && vtable_->shared_destroy != nullptr;
+  }
+
+  // If the implementation uses a listener, call that.
+  template <typename P>
+  static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
+                                  MatchResultListener* listener)
+      -> decltype(P::Get(m).MatchAndExplain(value, listener->stream())) {
+    return P::Get(m).MatchAndExplain(value, listener->stream());
+  }
+
+  template <typename P>
+  static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
+                                  MatchResultListener* listener)
+      -> decltype(P::Get(m).MatchAndExplain(value, listener)) {
+    return P::Get(m).MatchAndExplain(value, listener);
+  }
+
+  template <typename P>
+  static void DescribeImpl(const MatcherBase& m, std::ostream* os,
+                           bool negation) {
+    if (negation) {
+      P::Get(m).DescribeNegationTo(os);
+    } else {
+      P::Get(m).DescribeTo(os);
+    }
+  }
+
+  template <typename P>
+  static const MatcherDescriberInterface* GetDescriberImpl(
+      const MatcherBase& m) {
+    // If the impl is a MatcherDescriberInterface, then return it.
+    // Otherwise use MatcherBase itself.
+    // This allows us to implement the GetDescriber() function without support
+    // from the impl, but some users really want to get their impl back when
+    // they call GetDescriber().
+    // We use std::get on a tuple as a workaround of not having `if constexpr`.
+    return std::get<(
+        std::is_convertible<decltype(&P::Get(m)),
+                            const MatcherDescriberInterface*>::value
+            ? 1
+            : 0)>(std::make_tuple(&m, &P::Get(m)));
+  }
+
+  template <typename P>
+  const VTable* GetVTable() {
+    static constexpr VTable kVTable = {&MatchAndExplainImpl<P>,
+                                       &DescribeImpl<P>, &GetDescriberImpl<P>,
+                                       P::shared_destroy};
+    return &kVTable;
+  }
+
+  union Buffer {
+    // Add some types to give Buffer some common alignment/size use cases.
+    void* ptr;
+    double d;
+    int64_t i;
+    // And add one for the out-of-line cases.
+    SharedPayloadBase* shared;
+  };
+
+  void Destroy() {
+    if (IsShared() && buffer_.shared->Unref()) {
+      vtable_->shared_destroy(buffer_.shared);
+    }
+  }
+
+  template <typename M>
+  static constexpr bool IsInlined() {
+    return sizeof(M) <= sizeof(Buffer) && alignof(M) <= alignof(Buffer) &&
+           is_trivially_copy_constructible<M>::value &&
+           std::is_trivially_destructible<M>::value;
+  }
+
+  template <typename M, bool = MatcherBase::IsInlined<M>()>
+  struct ValuePolicy {
+    static const M& Get(const MatcherBase& m) {
+      // When inlined along with Init, need to be explicit to avoid violating
+      // strict aliasing rules.
+      const M *ptr = static_cast<const M*>(
+          static_cast<const void*>(&m.buffer_));
+      return *ptr;
+    }
+    static void Init(MatcherBase& m, M impl) {
+      ::new (static_cast<void*>(&m.buffer_)) M(impl);
+    }
+    static constexpr auto shared_destroy = nullptr;
+  };
+
+  template <typename M>
+  struct ValuePolicy<M, false> {
+    using Shared = SharedPayload<M>;
+    static const M& Get(const MatcherBase& m) {
+      return static_cast<Shared*>(m.buffer_.shared)->value;
+    }
+    template <typename Arg>
+    static void Init(MatcherBase& m, Arg&& arg) {
+      m.buffer_.shared = new Shared(std::forward<Arg>(arg));
+    }
+    static constexpr auto shared_destroy = &Shared::Destroy;
+  };
+
+  template <typename U, bool B>
+  struct ValuePolicy<const MatcherInterface<U>*, B> {
+    using M = const MatcherInterface<U>;
+    using Shared = SharedPayload<std::unique_ptr<M>>;
+    static const M& Get(const MatcherBase& m) {
+      return *static_cast<Shared*>(m.buffer_.shared)->value;
+    }
+    static void Init(MatcherBase& m, M* impl) {
+      m.buffer_.shared = new Shared(std::unique_ptr<M>(impl));
+    }
+
+    static constexpr auto shared_destroy = &Shared::Destroy;
+  };
+
+  template <typename M>
+  void Init(M&& m) {
+    using MM = typename std::decay<M>::type;
+    using Policy = ValuePolicy<MM>;
+    vtable_ = GetVTable<Policy>();
+    Policy::Init(*this, std::forward<M>(m));
+  }
+
+  const VTable* vtable_;
+  Buffer buffer_;
+};
+
+}  // namespace internal
+
+// A Matcher<T> is a copyable and IMMUTABLE (except by assignment)
+// object that can check whether a value of type T matches.  The
+// implementation of Matcher<T> is just a std::shared_ptr to const
+// MatcherInterface<T>.  Don't inherit from Matcher!
+template <typename T>
+class Matcher : public internal::MatcherBase<T> {
+ public:
+  // Constructs a null matcher.  Needed for storing Matcher objects in STL
+  // containers.  A default-constructed matcher is not yet initialized.  You
+  // cannot use it until a valid value has been assigned to it.
+  explicit Matcher() {}  // NOLINT
+
+  // Constructs a matcher from its implementation.
+  explicit Matcher(const MatcherInterface<const T&>* impl)
+      : internal::MatcherBase<T>(impl) {}
+
+  template <typename U>
+  explicit Matcher(
+      const MatcherInterface<U>* impl,
+      typename std::enable_if<!std::is_same<U, const U&>::value>::type* =
+          nullptr)
+      : internal::MatcherBase<T>(impl) {}
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  Matcher(M&& m) : internal::MatcherBase<T>(std::forward<M>(m)) {}  // NOLINT
+
+  // Implicit constructor here allows people to write
+  // EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes
+  Matcher(T value);  // NOLINT
+};
+
+// The following two specializations allow the user to write str
+// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string
+// matcher is expected.
+template <>
+class GTEST_API_ Matcher<const std::string&>
+    : public internal::MatcherBase<const std::string&> {
+ public:
+  Matcher() {}
+
+  explicit Matcher(const MatcherInterface<const std::string&>* impl)
+      : internal::MatcherBase<const std::string&>(impl) {}
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  Matcher(M&& m)  // NOLINT
+      : internal::MatcherBase<const std::string&>(std::forward<M>(m)) {}
+
+  // Allows the user to write str instead of Eq(str) sometimes, where
+  // str is a std::string object.
+  Matcher(const std::string& s);  // NOLINT
+
+  // Allows the user to write "foo" instead of Eq("foo") sometimes.
+  Matcher(const char* s);  // NOLINT
+};
+
+template <>
+class GTEST_API_ Matcher<std::string>
+    : public internal::MatcherBase<std::string> {
+ public:
+  Matcher() {}
+
+  explicit Matcher(const MatcherInterface<const std::string&>* impl)
+      : internal::MatcherBase<std::string>(impl) {}
+  explicit Matcher(const MatcherInterface<std::string>* impl)
+      : internal::MatcherBase<std::string>(impl) {}
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  Matcher(M&& m)  // NOLINT
+      : internal::MatcherBase<std::string>(std::forward<M>(m)) {}
+
+  // Allows the user to write str instead of Eq(str) sometimes, where
+  // str is a string object.
+  Matcher(const std::string& s);  // NOLINT
+
+  // Allows the user to write "foo" instead of Eq("foo") sometimes.
+  Matcher(const char* s);  // NOLINT
+};
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+// The following two specializations allow the user to write str
+// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view
+// matcher is expected.
+template <>
+class GTEST_API_ Matcher<const internal::StringView&>
+    : public internal::MatcherBase<const internal::StringView&> {
+ public:
+  Matcher() {}
+
+  explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
+      : internal::MatcherBase<const internal::StringView&>(impl) {}
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  Matcher(M&& m)  // NOLINT
+      : internal::MatcherBase<const internal::StringView&>(std::forward<M>(m)) {
+  }
+
+  // Allows the user to write str instead of Eq(str) sometimes, where
+  // str is a std::string object.
+  Matcher(const std::string& s);  // NOLINT
+
+  // Allows the user to write "foo" instead of Eq("foo") sometimes.
+  Matcher(const char* s);  // NOLINT
+
+  // Allows the user to pass absl::string_views or std::string_views directly.
+  Matcher(internal::StringView s);  // NOLINT
+};
+
+template <>
+class GTEST_API_ Matcher<internal::StringView>
+    : public internal::MatcherBase<internal::StringView> {
+ public:
+  Matcher() {}
+
+  explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
+      : internal::MatcherBase<internal::StringView>(impl) {}
+  explicit Matcher(const MatcherInterface<internal::StringView>* impl)
+      : internal::MatcherBase<internal::StringView>(impl) {}
+
+  template <typename M, typename = typename std::remove_reference<
+                            M>::type::is_gtest_matcher>
+  Matcher(M&& m)  // NOLINT
+      : internal::MatcherBase<internal::StringView>(std::forward<M>(m)) {}
+
+  // Allows the user to write str instead of Eq(str) sometimes, where
+  // str is a std::string object.
+  Matcher(const std::string& s);  // NOLINT
+
+  // Allows the user to write "foo" instead of Eq("foo") sometimes.
+  Matcher(const char* s);  // NOLINT
+
+  // Allows the user to pass absl::string_views or std::string_views directly.
+  Matcher(internal::StringView s);  // NOLINT
+};
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+// Prints a matcher in a human-readable format.
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const Matcher<T>& matcher) {
+  matcher.DescribeTo(&os);
+  return os;
+}
+
+// The PolymorphicMatcher class template makes it easy to implement a
+// polymorphic matcher (i.e. a matcher that can match values of more
+// than one type, e.g. Eq(n) and NotNull()).
+//
+// To define a polymorphic matcher, a user should provide an Impl
+// class that has a DescribeTo() method and a DescribeNegationTo()
+// method, and define a member function (or member function template)
+//
+//   bool MatchAndExplain(const Value& value,
+//                        MatchResultListener* listener) const;
+//
+// See the definition of NotNull() for a complete example.
+template <class Impl>
+class PolymorphicMatcher {
+ public:
+  explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {}
+
+  // Returns a mutable reference to the underlying matcher
+  // implementation object.
+  Impl& mutable_impl() { return impl_; }
+
+  // Returns an immutable reference to the underlying matcher
+  // implementation object.
+  const Impl& impl() const { return impl_; }
+
+  template <typename T>
+  operator Matcher<T>() const {
+    return Matcher<T>(new MonomorphicImpl<const T&>(impl_));
+  }
+
+ private:
+  template <typename T>
+  class MonomorphicImpl : public MatcherInterface<T> {
+   public:
+    explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {}
+
+    void DescribeTo(::std::ostream* os) const override { impl_.DescribeTo(os); }
+
+    void DescribeNegationTo(::std::ostream* os) const override {
+      impl_.DescribeNegationTo(os);
+    }
+
+    bool MatchAndExplain(T x, MatchResultListener* listener) const override {
+      return impl_.MatchAndExplain(x, listener);
+    }
+
+   private:
+    const Impl impl_;
+  };
+
+  Impl impl_;
+};
+
+// Creates a matcher from its implementation.
+// DEPRECATED: Especially in the generic code, prefer:
+//   Matcher<T>(new MyMatcherImpl<const T&>(...));
+//
+// MakeMatcher may create a Matcher that accepts its argument by value, which
+// leads to unnecessary copies & lack of support for non-copyable types.
+template <typename T>
+inline Matcher<T> MakeMatcher(const MatcherInterface<T>* impl) {
+  return Matcher<T>(impl);
+}
+
+// Creates a polymorphic matcher from its implementation.  This is
+// easier to use than the PolymorphicMatcher<Impl> constructor as it
+// doesn't require you to explicitly write the template argument, e.g.
+//
+//   MakePolymorphicMatcher(foo);
+// vs
+//   PolymorphicMatcher<TypeOfFoo>(foo);
+template <class Impl>
+inline PolymorphicMatcher<Impl> MakePolymorphicMatcher(const Impl& impl) {
+  return PolymorphicMatcher<Impl>(impl);
+}
+
+namespace internal {
+// Implements a matcher that compares a given value with a
+// pre-supplied value using one of the ==, <=, <, etc, operators.  The
+// two values being compared don't have to have the same type.
+//
+// The matcher defined here is polymorphic (for example, Eq(5) can be
+// used to match an int, a short, a double, etc).  Therefore we use
+// a template type conversion operator in the implementation.
+//
+// The following template definition assumes that the Rhs parameter is
+// a "bare" type (i.e. neither 'const T' nor 'T&').
+template <typename D, typename Rhs, typename Op>
+class ComparisonBase {
+ public:
+  explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {}
+
+  using is_gtest_matcher = void;
+
+  template <typename Lhs>
+  bool MatchAndExplain(const Lhs& lhs, std::ostream*) const {
+    return Op()(lhs, Unwrap(rhs_));
+  }
+  void DescribeTo(std::ostream* os) const {
+    *os << D::Desc() << " ";
+    UniversalPrint(Unwrap(rhs_), os);
+  }
+  void DescribeNegationTo(std::ostream* os) const {
+    *os << D::NegatedDesc() << " ";
+    UniversalPrint(Unwrap(rhs_), os);
+  }
+
+ private:
+  template <typename T>
+  static const T& Unwrap(const T& v) {
+    return v;
+  }
+  template <typename T>
+  static const T& Unwrap(std::reference_wrapper<T> v) {
+    return v;
+  }
+
+  Rhs rhs_;
+};
+
+template <typename Rhs>
+class EqMatcher : public ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq> {
+ public:
+  explicit EqMatcher(const Rhs& rhs)
+      : ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq>(rhs) { }
+  static const char* Desc() { return "is equal to"; }
+  static const char* NegatedDesc() { return "isn't equal to"; }
+};
+template <typename Rhs>
+class NeMatcher : public ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe> {
+ public:
+  explicit NeMatcher(const Rhs& rhs)
+      : ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe>(rhs) { }
+  static const char* Desc() { return "isn't equal to"; }
+  static const char* NegatedDesc() { return "is equal to"; }
+};
+template <typename Rhs>
+class LtMatcher : public ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt> {
+ public:
+  explicit LtMatcher(const Rhs& rhs)
+      : ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt>(rhs) { }
+  static const char* Desc() { return "is <"; }
+  static const char* NegatedDesc() { return "isn't <"; }
+};
+template <typename Rhs>
+class GtMatcher : public ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt> {
+ public:
+  explicit GtMatcher(const Rhs& rhs)
+      : ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt>(rhs) { }
+  static const char* Desc() { return "is >"; }
+  static const char* NegatedDesc() { return "isn't >"; }
+};
+template <typename Rhs>
+class LeMatcher : public ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe> {
+ public:
+  explicit LeMatcher(const Rhs& rhs)
+      : ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe>(rhs) { }
+  static const char* Desc() { return "is <="; }
+  static const char* NegatedDesc() { return "isn't <="; }
+};
+template <typename Rhs>
+class GeMatcher : public ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe> {
+ public:
+  explicit GeMatcher(const Rhs& rhs)
+      : ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe>(rhs) { }
+  static const char* Desc() { return "is >="; }
+  static const char* NegatedDesc() { return "isn't >="; }
+};
+
+template <typename T, typename = typename std::enable_if<
+                          std::is_constructible<std::string, T>::value>::type>
+using StringLike = T;
+
+// Implements polymorphic matchers MatchesRegex(regex) and
+// ContainsRegex(regex), which can be used as a Matcher<T> as long as
+// T can be converted to a string.
+class MatchesRegexMatcher {
+ public:
+  MatchesRegexMatcher(const RE* regex, bool full_match)
+      : regex_(regex), full_match_(full_match) {}
+
+#if GTEST_INTERNAL_HAS_STRING_VIEW
+  bool MatchAndExplain(const internal::StringView& s,
+                       MatchResultListener* listener) const {
+    return MatchAndExplain(std::string(s), listener);
+  }
+#endif  // GTEST_INTERNAL_HAS_STRING_VIEW
+
+  // Accepts pointer types, particularly:
+  //   const char*
+  //   char*
+  //   const wchar_t*
+  //   wchar_t*
+  template <typename CharType>
+  bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
+    return s != nullptr && MatchAndExplain(std::string(s), listener);
+  }
+
+  // Matches anything that can convert to std::string.
+  //
+  // This is a template, not just a plain function with const std::string&,
+  // because absl::string_view has some interfering non-explicit constructors.
+  template <class MatcheeStringType>
+  bool MatchAndExplain(const MatcheeStringType& s,
+                       MatchResultListener* /* listener */) const {
+    const std::string& s2(s);
+    return full_match_ ? RE::FullMatch(s2, *regex_)
+                       : RE::PartialMatch(s2, *regex_);
+  }
+
+  void DescribeTo(::std::ostream* os) const {
+    *os << (full_match_ ? "matches" : "contains") << " regular expression ";
+    UniversalPrinter<std::string>::Print(regex_->pattern(), os);
+  }
+
+  void DescribeNegationTo(::std::ostream* os) const {
+    *os << "doesn't " << (full_match_ ? "match" : "contain")
+        << " regular expression ";
+    UniversalPrinter<std::string>::Print(regex_->pattern(), os);
+  }
+
+ private:
+  const std::shared_ptr<const RE> regex_;
+  const bool full_match_;
+};
+}  // namespace internal
+
+// Matches a string that fully matches regular expression 'regex'.
+// The matcher takes ownership of 'regex'.
+inline PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
+    const internal::RE* regex) {
+  return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true));
+}
+template <typename T = std::string>
+PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
+    const internal::StringLike<T>& regex) {
+  return MatchesRegex(new internal::RE(std::string(regex)));
+}
+
+// Matches a string that contains regular expression 'regex'.
+// The matcher takes ownership of 'regex'.
+inline PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
+    const internal::RE* regex) {
+  return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false));
+}
+template <typename T = std::string>
+PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
+    const internal::StringLike<T>& regex) {
+  return ContainsRegex(new internal::RE(std::string(regex)));
+}
+
+// Creates a polymorphic matcher that matches anything equal to x.
+// Note: if the parameter of Eq() were declared as const T&, Eq("foo")
+// wouldn't compile.
+template <typename T>
+inline internal::EqMatcher<T> Eq(T x) { return internal::EqMatcher<T>(x); }
+
+// Constructs a Matcher<T> from a 'value' of type T.  The constructed
+// matcher matches any value that's equal to 'value'.
+template <typename T>
+Matcher<T>::Matcher(T value) { *this = Eq(value); }
+
+// Creates a monomorphic matcher that matches anything with type Lhs
+// and equal to rhs.  A user may need to use this instead of Eq(...)
+// in order to resolve an overloading ambiguity.
+//
+// TypedEq<T>(x) is just a convenient short-hand for Matcher<T>(Eq(x))
+// or Matcher<T>(x), but more readable than the latter.
+//
+// We could define similar monomorphic matchers for other comparison
+// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do
+// it yet as those are used much less than Eq() in practice.  A user
+// can always write Matcher<T>(Lt(5)) to be explicit about the type,
+// for example.
+template <typename Lhs, typename Rhs>
+inline Matcher<Lhs> TypedEq(const Rhs& rhs) { return Eq(rhs); }
+
+// Creates a polymorphic matcher that matches anything >= x.
+template <typename Rhs>
+inline internal::GeMatcher<Rhs> Ge(Rhs x) {
+  return internal::GeMatcher<Rhs>(x);
+}
+
+// Creates a polymorphic matcher that matches anything > x.
+template <typename Rhs>
+inline internal::GtMatcher<Rhs> Gt(Rhs x) {
+  return internal::GtMatcher<Rhs>(x);
+}
+
+// Creates a polymorphic matcher that matches anything <= x.
+template <typename Rhs>
+inline internal::LeMatcher<Rhs> Le(Rhs x) {
+  return internal::LeMatcher<Rhs>(x);
+}
+
+// Creates a polymorphic matcher that matches anything < x.
+template <typename Rhs>
+inline internal::LtMatcher<Rhs> Lt(Rhs x) {
+  return internal::LtMatcher<Rhs>(x);
+}
+
+// Creates a polymorphic matcher that matches anything != x.
+template <typename Rhs>
+inline internal::NeMatcher<Rhs> Ne(Rhs x) {
+  return internal::NeMatcher<Rhs>(x);
+}
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251 5046
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
+
+#include <stdio.h>
+#include <memory>
+
+namespace testing {
+namespace internal {
+
+GTEST_DECLARE_string_(internal_run_death_test);
+
+// Names of the flags (needed for parsing Google Test flags).
+const char kDeathTestStyleFlag[] = "death_test_style";
+const char kDeathTestUseFork[] = "death_test_use_fork";
+const char kInternalRunDeathTestFlag[] = "internal_run_death_test";
+
+#if GTEST_HAS_DEATH_TEST
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+// DeathTest is a class that hides much of the complexity of the
+// GTEST_DEATH_TEST_ macro.  It is abstract; its static Create method
+// returns a concrete class that depends on the prevailing death test
+// style, as defined by the --gtest_death_test_style and/or
+// --gtest_internal_run_death_test flags.
+
+// In describing the results of death tests, these terms are used with
+// the corresponding definitions:
+//
+// exit status:  The integer exit information in the format specified
+//               by wait(2)
+// exit code:    The integer code passed to exit(3), _exit(2), or
+//               returned from main()
+class GTEST_API_ DeathTest {
+ public:
+  // Create returns false if there was an error determining the
+  // appropriate action to take for the current death test; for example,
+  // if the gtest_death_test_style flag is set to an invalid value.
+  // The LastMessage method will return a more detailed message in that
+  // case.  Otherwise, the DeathTest pointer pointed to by the "test"
+  // argument is set.  If the death test should be skipped, the pointer
+  // is set to NULL; otherwise, it is set to the address of a new concrete
+  // DeathTest object that controls the execution of the current test.
+  static bool Create(const char* statement, Matcher<const std::string&> matcher,
+                     const char* file, int line, DeathTest** test);
+  DeathTest();
+  virtual ~DeathTest() { }
+
+  // A helper class that aborts a death test when it's deleted.
+  class ReturnSentinel {
+   public:
+    explicit ReturnSentinel(DeathTest* test) : test_(test) { }
+    ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); }
+   private:
+    DeathTest* const test_;
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel);
+  } GTEST_ATTRIBUTE_UNUSED_;
+
+  // An enumeration of possible roles that may be taken when a death
+  // test is encountered.  EXECUTE means that the death test logic should
+  // be executed immediately.  OVERSEE means that the program should prepare
+  // the appropriate environment for a child process to execute the death
+  // test, then wait for it to complete.
+  enum TestRole { OVERSEE_TEST, EXECUTE_TEST };
+
+  // An enumeration of the three reasons that a test might be aborted.
+  enum AbortReason {
+    TEST_ENCOUNTERED_RETURN_STATEMENT,
+    TEST_THREW_EXCEPTION,
+    TEST_DID_NOT_DIE
+  };
+
+  // Assumes one of the above roles.
+  virtual TestRole AssumeRole() = 0;
+
+  // Waits for the death test to finish and returns its status.
+  virtual int Wait() = 0;
+
+  // Returns true if the death test passed; that is, the test process
+  // exited during the test, its exit status matches a user-supplied
+  // predicate, and its stderr output matches a user-supplied regular
+  // expression.
+  // The user-supplied predicate may be a macro expression rather
+  // than a function pointer or functor, or else Wait and Passed could
+  // be combined.
+  virtual bool Passed(bool exit_status_ok) = 0;
+
+  // Signals that the death test did not die as expected.
+  virtual void Abort(AbortReason reason) = 0;
+
+  // Returns a human-readable outcome message regarding the outcome of
+  // the last death test.
+  static const char* LastMessage();
+
+  static void set_last_death_test_message(const std::string& message);
+
+ private:
+  // A string containing a description of the outcome of the last death test.
+  static std::string last_death_test_message_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest);
+};
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+// Factory interface for death tests.  May be mocked out for testing.
+class DeathTestFactory {
+ public:
+  virtual ~DeathTestFactory() { }
+  virtual bool Create(const char* statement,
+                      Matcher<const std::string&> matcher, const char* file,
+                      int line, DeathTest** test) = 0;
+};
+
+// A concrete DeathTestFactory implementation for normal use.
+class DefaultDeathTestFactory : public DeathTestFactory {
+ public:
+  bool Create(const char* statement, Matcher<const std::string&> matcher,
+              const char* file, int line, DeathTest** test) override;
+};
+
+// Returns true if exit_status describes a process that was terminated
+// by a signal, or exited normally with a nonzero exit code.
+GTEST_API_ bool ExitedUnsuccessfully(int exit_status);
+
+// A string passed to EXPECT_DEATH (etc.) is caught by one of these overloads
+// and interpreted as a regex (rather than an Eq matcher) for legacy
+// compatibility.
+inline Matcher<const ::std::string&> MakeDeathTestMatcher(
+    ::testing::internal::RE regex) {
+  return ContainsRegex(regex.pattern());
+}
+inline Matcher<const ::std::string&> MakeDeathTestMatcher(const char* regex) {
+  return ContainsRegex(regex);
+}
+inline Matcher<const ::std::string&> MakeDeathTestMatcher(
+    const ::std::string& regex) {
+  return ContainsRegex(regex);
+}
+
+// If a Matcher<const ::std::string&> is passed to EXPECT_DEATH (etc.), it's
+// used directly.
+inline Matcher<const ::std::string&> MakeDeathTestMatcher(
+    Matcher<const ::std::string&> matcher) {
+  return matcher;
+}
+
+// Traps C++ exceptions escaping statement and reports them as test
+// failures. Note that trapping SEH exceptions is not implemented here.
+# if GTEST_HAS_EXCEPTIONS
+#  define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
+  try { \
+    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+  } catch (const ::std::exception& gtest_exception) { \
+    fprintf(\
+        stderr, \
+        "\n%s: Caught std::exception-derived exception escaping the " \
+        "death test statement. Exception message: %s\n", \
+        ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \
+        gtest_exception.what()); \
+    fflush(stderr); \
+    death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
+  } catch (...) { \
+    death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \
+  }
+
+# else
+#  define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \
+  GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
+
+# endif
+
+// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*,
+// ASSERT_EXIT*, and EXPECT_EXIT*.
+#define GTEST_DEATH_TEST_(statement, predicate, regex_or_matcher, fail)        \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                                \
+  if (::testing::internal::AlwaysTrue()) {                                     \
+    ::testing::internal::DeathTest* gtest_dt;                                  \
+    if (!::testing::internal::DeathTest::Create(                               \
+            #statement,                                                        \
+            ::testing::internal::MakeDeathTestMatcher(regex_or_matcher),       \
+            __FILE__, __LINE__, &gtest_dt)) {                                  \
+      goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__);                        \
+    }                                                                          \
+    if (gtest_dt != nullptr) {                                                 \
+      std::unique_ptr< ::testing::internal::DeathTest> gtest_dt_ptr(gtest_dt); \
+      switch (gtest_dt->AssumeRole()) {                                        \
+        case ::testing::internal::DeathTest::OVERSEE_TEST:                     \
+          if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) {                \
+            goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__);                  \
+          }                                                                    \
+          break;                                                               \
+        case ::testing::internal::DeathTest::EXECUTE_TEST: {                   \
+          ::testing::internal::DeathTest::ReturnSentinel gtest_sentinel(       \
+              gtest_dt);                                                       \
+          GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt);            \
+          gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE);   \
+          break;                                                               \
+        }                                                                      \
+        default:                                                               \
+          break;                                                               \
+      }                                                                        \
+    }                                                                          \
+  } else                                                                       \
+    GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__)                                \
+        : fail(::testing::internal::DeathTest::LastMessage())
+// The symbol "fail" here expands to something into which a message
+// can be streamed.
+
+// This macro is for implementing ASSERT/EXPECT_DEBUG_DEATH when compiled in
+// NDEBUG mode. In this case we need the statements to be executed and the macro
+// must accept a streamed message even though the message is never printed.
+// The regex object is not evaluated, but it is used to prevent "unused"
+// warnings and to avoid an expression that doesn't compile in debug mode.
+#define GTEST_EXECUTE_STATEMENT_(statement, regex_or_matcher)    \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_                                  \
+  if (::testing::internal::AlwaysTrue()) {                       \
+    GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement);   \
+  } else if (!::testing::internal::AlwaysTrue()) {               \
+    ::testing::internal::MakeDeathTestMatcher(regex_or_matcher); \
+  } else                                                         \
+    ::testing::Message()
+
+// A class representing the parsed contents of the
+// --gtest_internal_run_death_test flag, as it existed when
+// RUN_ALL_TESTS was called.
+class InternalRunDeathTestFlag {
+ public:
+  InternalRunDeathTestFlag(const std::string& a_file,
+                           int a_line,
+                           int an_index,
+                           int a_write_fd)
+      : file_(a_file), line_(a_line), index_(an_index),
+        write_fd_(a_write_fd) {}
+
+  ~InternalRunDeathTestFlag() {
+    if (write_fd_ >= 0)
+      posix::Close(write_fd_);
+  }
+
+  const std::string& file() const { return file_; }
+  int line() const { return line_; }
+  int index() const { return index_; }
+  int write_fd() const { return write_fd_; }
+
+ private:
+  std::string file_;
+  int line_;
+  int index_;
+  int write_fd_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag);
+};
+
+// Returns a newly created InternalRunDeathTestFlag object with fields
+// initialized from the GTEST_FLAG(internal_run_death_test) flag if
+// the flag is specified; otherwise returns NULL.
+InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag();
+
+#endif  // GTEST_HAS_DEATH_TEST
+
+}  // namespace internal
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_
+
+namespace testing {
+
+// This flag controls the style of death tests.  Valid values are "threadsafe",
+// meaning that the death test child process will re-execute the test binary
+// from the start, running only a single death test, or "fast",
+// meaning that the child process will execute the test logic immediately
+// after forking.
+GTEST_DECLARE_string_(death_test_style);
+
+#if GTEST_HAS_DEATH_TEST
+
+namespace internal {
+
+// Returns a Boolean value indicating whether the caller is currently
+// executing in the context of the death test child process.  Tools such as
+// Valgrind heap checkers may need this to modify their behavior in death
+// tests.  IMPORTANT: This is an internal utility.  Using it may break the
+// implementation of death tests.  User code MUST NOT use it.
+GTEST_API_ bool InDeathTestChild();
+
+}  // namespace internal
+
+// The following macros are useful for writing death tests.
+
+// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is
+// executed:
+//
+//   1. It generates a warning if there is more than one active
+//   thread.  This is because it's safe to fork() or clone() only
+//   when there is a single thread.
+//
+//   2. The parent process clone()s a sub-process and runs the death
+//   test in it; the sub-process exits with code 0 at the end of the
+//   death test, if it hasn't exited already.
+//
+//   3. The parent process waits for the sub-process to terminate.
+//
+//   4. The parent process checks the exit code and error message of
+//   the sub-process.
+//
+// Examples:
+//
+//   ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number");
+//   for (int i = 0; i < 5; i++) {
+//     EXPECT_DEATH(server.ProcessRequest(i),
+//                  "Invalid request .* in ProcessRequest()")
+//                  << "Failed to die on request " << i;
+//   }
+//
+//   ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting");
+//
+//   bool KilledBySIGHUP(int exit_code) {
+//     return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP;
+//   }
+//
+//   ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!");
+//
+// On the regular expressions used in death tests:
+//
+//   GOOGLETEST_CM0005 DO NOT DELETE
+//   On POSIX-compliant systems (*nix), we use the <regex.h> library,
+//   which uses the POSIX extended regex syntax.
+//
+//   On other platforms (e.g. Windows or Mac), we only support a simple regex
+//   syntax implemented as part of Google Test.  This limited
+//   implementation should be enough most of the time when writing
+//   death tests; though it lacks many features you can find in PCRE
+//   or POSIX extended regex syntax.  For example, we don't support
+//   union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
+//   repetition count ("x{5,7}"), among others.
+//
+//   Below is the syntax that we do support.  We chose it to be a
+//   subset of both PCRE and POSIX extended regex, so it's easy to
+//   learn wherever you come from.  In the following: 'A' denotes a
+//   literal character, period (.), or a single \\ escape sequence;
+//   'x' and 'y' denote regular expressions; 'm' and 'n' are for
+//   natural numbers.
+//
+//     c     matches any literal character c
+//     \\d   matches any decimal digit
+//     \\D   matches any character that's not a decimal digit
+//     \\f   matches \f
+//     \\n   matches \n
+//     \\r   matches \r
+//     \\s   matches any ASCII whitespace, including \n
+//     \\S   matches any character that's not a whitespace
+//     \\t   matches \t
+//     \\v   matches \v
+//     \\w   matches any letter, _, or decimal digit
+//     \\W   matches any character that \\w doesn't match
+//     \\c   matches any literal character c, which must be a punctuation
+//     .     matches any single character except \n
+//     A?    matches 0 or 1 occurrences of A
+//     A*    matches 0 or many occurrences of A
+//     A+    matches 1 or many occurrences of A
+//     ^     matches the beginning of a string (not that of each line)
+//     $     matches the end of a string (not that of each line)
+//     xy    matches x followed by y
+//
+//   If you accidentally use PCRE or POSIX extended regex features
+//   not implemented by us, you will get a run-time failure.  In that
+//   case, please try to rewrite your regular expression within the
+//   above syntax.
+//
+//   This implementation is *not* meant to be as highly tuned or robust
+//   as a compiled regex library, but should perform well enough for a
+//   death test, which already incurs significant overhead by launching
+//   a child process.
+//
+// Known caveats:
+//
+//   A "threadsafe" style death test obtains the path to the test
+//   program from argv[0] and re-executes it in the sub-process.  For
+//   simplicity, the current implementation doesn't search the PATH
+//   when launching the sub-process.  This means that the user must
+//   invoke the test program via a path that contains at least one
+//   path separator (e.g. path/to/foo_test and
+//   /absolute/path/to/bar_test are fine, but foo_test is not).  This
+//   is rarely a problem as people usually don't put the test binary
+//   directory in PATH.
+//
+
+// Asserts that a given statement causes the program to exit, with an
+// integer exit status that satisfies predicate, and emitting error output
+// that matches regex.
+# define ASSERT_EXIT(statement, predicate, regex) \
+    GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_)
+
+// Like ASSERT_EXIT, but continues on to successive tests in the
+// test suite, if any:
+# define EXPECT_EXIT(statement, predicate, regex) \
+    GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_)
+
+// Asserts that a given statement causes the program to exit, either by
+// explicitly exiting with a nonzero exit code or being killed by a
+// signal, and emitting error output that matches regex.
+# define ASSERT_DEATH(statement, regex) \
+    ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex)
+
+// Like ASSERT_DEATH, but continues on to successive tests in the
+// test suite, if any:
+# define EXPECT_DEATH(statement, regex) \
+    EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex)
+
+// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*:
+
+// Tests that an exit code describes a normal exit with a given exit code.
+class GTEST_API_ ExitedWithCode {
+ public:
+  explicit ExitedWithCode(int exit_code);
+  ExitedWithCode(const ExitedWithCode&) = default;
+  void operator=(const ExitedWithCode& other) = delete;
+  bool operator()(int exit_status) const;
+ private:
+  const int exit_code_;
+};
+
+# if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
+// Tests that an exit code describes an exit due to termination by a
+// given signal.
+// GOOGLETEST_CM0006 DO NOT DELETE
+class GTEST_API_ KilledBySignal {
+ public:
+  explicit KilledBySignal(int signum);
+  bool operator()(int exit_status) const;
+ private:
+  const int signum_;
+};
+# endif  // !GTEST_OS_WINDOWS
+
+// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode.
+// The death testing framework causes this to have interesting semantics,
+// since the sideeffects of the call are only visible in opt mode, and not
+// in debug mode.
+//
+// In practice, this can be used to test functions that utilize the
+// LOG(DFATAL) macro using the following style:
+//
+// int DieInDebugOr12(int* sideeffect) {
+//   if (sideeffect) {
+//     *sideeffect = 12;
+//   }
+//   LOG(DFATAL) << "death";
+//   return 12;
+// }
+//
+// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) {
+//   int sideeffect = 0;
+//   // Only asserts in dbg.
+//   EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death");
+//
+// #ifdef NDEBUG
+//   // opt-mode has sideeffect visible.
+//   EXPECT_EQ(12, sideeffect);
+// #else
+//   // dbg-mode no visible sideeffect.
+//   EXPECT_EQ(0, sideeffect);
+// #endif
+// }
+//
+// This will assert that DieInDebugReturn12InOpt() crashes in debug
+// mode, usually due to a DCHECK or LOG(DFATAL), but returns the
+// appropriate fallback value (12 in this case) in opt mode. If you
+// need to test that a function has appropriate side-effects in opt
+// mode, include assertions against the side-effects.  A general
+// pattern for this is:
+//
+// EXPECT_DEBUG_DEATH({
+//   // Side-effects here will have an effect after this statement in
+//   // opt mode, but none in debug mode.
+//   EXPECT_EQ(12, DieInDebugOr12(&sideeffect));
+// }, "death");
+//
+# ifdef NDEBUG
+
+#  define EXPECT_DEBUG_DEATH(statement, regex) \
+  GTEST_EXECUTE_STATEMENT_(statement, regex)
+
+#  define ASSERT_DEBUG_DEATH(statement, regex) \
+  GTEST_EXECUTE_STATEMENT_(statement, regex)
+
+# else
+
+#  define EXPECT_DEBUG_DEATH(statement, regex) \
+  EXPECT_DEATH(statement, regex)
+
+#  define ASSERT_DEBUG_DEATH(statement, regex) \
+  ASSERT_DEATH(statement, regex)
+
+# endif  // NDEBUG for EXPECT_DEBUG_DEATH
+#endif  // GTEST_HAS_DEATH_TEST
+
+// This macro is used for implementing macros such as
+// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where
+// death tests are not supported. Those macros must compile on such systems
+// if and only if EXPECT_DEATH and ASSERT_DEATH compile with the same parameters
+// on systems that support death tests. This allows one to write such a macro on
+// a system that does not support death tests and be sure that it will compile
+// on a death-test supporting system. It is exposed publicly so that systems
+// that have death-tests with stricter requirements than GTEST_HAS_DEATH_TEST
+// can write their own equivalent of EXPECT_DEATH_IF_SUPPORTED and
+// ASSERT_DEATH_IF_SUPPORTED.
+//
+// Parameters:
+//   statement -  A statement that a macro such as EXPECT_DEATH would test
+//                for program termination. This macro has to make sure this
+//                statement is compiled but not executed, to ensure that
+//                EXPECT_DEATH_IF_SUPPORTED compiles with a certain
+//                parameter if and only if EXPECT_DEATH compiles with it.
+//   regex     -  A regex that a macro such as EXPECT_DEATH would use to test
+//                the output of statement.  This parameter has to be
+//                compiled but not evaluated by this macro, to ensure that
+//                this macro only accepts expressions that a macro such as
+//                EXPECT_DEATH would accept.
+//   terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED
+//                and a return statement for ASSERT_DEATH_IF_SUPPORTED.
+//                This ensures that ASSERT_DEATH_IF_SUPPORTED will not
+//                compile inside functions where ASSERT_DEATH doesn't
+//                compile.
+//
+//  The branch that has an always false condition is used to ensure that
+//  statement and regex are compiled (and thus syntactically correct) but
+//  never executed. The unreachable code macro protects the terminator
+//  statement from generating an 'unreachable code' warning in case
+//  statement unconditionally returns or throws. The Message constructor at
+//  the end allows the syntax of streaming additional messages into the
+//  macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH.
+# define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \
+    GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+    if (::testing::internal::AlwaysTrue()) { \
+      GTEST_LOG_(WARNING) \
+          << "Death tests are not supported on this platform.\n" \
+          << "Statement '" #statement "' cannot be verified."; \
+    } else if (::testing::internal::AlwaysFalse()) { \
+      ::testing::internal::RE::PartialMatch(".*", (regex)); \
+      GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+      terminator; \
+    } else \
+      ::testing::Message()
+
+// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and
+// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if
+// death tests are supported; otherwise they just issue a warning.  This is
+// useful when you are combining death test assertions with normal test
+// assertions in one test.
+#if GTEST_HAS_DEATH_TEST
+# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
+    EXPECT_DEATH(statement, regex)
+# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
+    ASSERT_DEATH(statement, regex)
+#else
+# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
+    GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, )
+# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
+    GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return)
+#endif
+
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Macros and functions for implementing parameterized tests
+// in Google C++ Testing and Mocking Framework (Google Test)
+//
+// GOOGLETEST_CM0001 DO NOT DELETE
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+
+// Value-parameterized tests allow you to test your code with different
+// parameters without writing multiple copies of the same test.
+//
+// Here is how you use value-parameterized tests:
+
+#if 0
+
+// To write value-parameterized tests, first you should define a fixture
+// class. It is usually derived from testing::TestWithParam<T> (see below for
+// another inheritance scheme that's sometimes useful in more complicated
+// class hierarchies), where the type of your parameter values.
+// TestWithParam<T> is itself derived from testing::Test. T can be any
+// copyable type. If it's a raw pointer, you are responsible for managing the
+// lifespan of the pointed values.
+
+class FooTest : public ::testing::TestWithParam<const char*> {
+  // You can implement all the usual class fixture members here.
+};
+
+// Then, use the TEST_P macro to define as many parameterized tests
+// for this fixture as you want. The _P suffix is for "parameterized"
+// or "pattern", whichever you prefer to think.
+
+TEST_P(FooTest, DoesBlah) {
+  // Inside a test, access the test parameter with the GetParam() method
+  // of the TestWithParam<T> class:
+  EXPECT_TRUE(foo.Blah(GetParam()));
+  ...
+}
+
+TEST_P(FooTest, HasBlahBlah) {
+  ...
+}
+
+// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test
+// case with any set of parameters you want. Google Test defines a number
+// of functions for generating test parameters. They return what we call
+// (surprise!) parameter generators. Here is a summary of them, which
+// are all in the testing namespace:
+//
+//
+//  Range(begin, end [, step]) - Yields values {begin, begin+step,
+//                               begin+step+step, ...}. The values do not
+//                               include end. step defaults to 1.
+//  Values(v1, v2, ..., vN)    - Yields values {v1, v2, ..., vN}.
+//  ValuesIn(container)        - Yields values from a C-style array, an STL
+//  ValuesIn(begin,end)          container, or an iterator range [begin, end).
+//  Bool()                     - Yields sequence {false, true}.
+//  Combine(g1, g2, ..., gN)   - Yields all combinations (the Cartesian product
+//                               for the math savvy) of the values generated
+//                               by the N generators.
+//
+// For more details, see comments at the definitions of these functions below
+// in this file.
+//
+// The following statement will instantiate tests from the FooTest test suite
+// each with parameter values "meeny", "miny", and "moe".
+
+INSTANTIATE_TEST_SUITE_P(InstantiationName,
+                         FooTest,
+                         Values("meeny", "miny", "moe"));
+
+// To distinguish different instances of the pattern, (yes, you
+// can instantiate it more than once) the first argument to the
+// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the
+// actual test suite name. Remember to pick unique prefixes for different
+// instantiations. The tests from the instantiation above will have
+// these names:
+//
+//    * InstantiationName/FooTest.DoesBlah/0 for "meeny"
+//    * InstantiationName/FooTest.DoesBlah/1 for "miny"
+//    * InstantiationName/FooTest.DoesBlah/2 for "moe"
+//    * InstantiationName/FooTest.HasBlahBlah/0 for "meeny"
+//    * InstantiationName/FooTest.HasBlahBlah/1 for "miny"
+//    * InstantiationName/FooTest.HasBlahBlah/2 for "moe"
+//
+// You can use these names in --gtest_filter.
+//
+// This statement will instantiate all tests from FooTest again, each
+// with parameter values "cat" and "dog":
+
+const char* pets[] = {"cat", "dog"};
+INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets));
+
+// The tests from the instantiation above will have these names:
+//
+//    * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat"
+//    * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog"
+//    * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat"
+//    * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog"
+//
+// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests
+// in the given test suite, whether their definitions come before or
+// AFTER the INSTANTIATE_TEST_SUITE_P statement.
+//
+// Please also note that generator expressions (including parameters to the
+// generators) are evaluated in InitGoogleTest(), after main() has started.
+// This allows the user on one hand, to adjust generator parameters in order
+// to dynamically determine a set of tests to run and on the other hand,
+// give the user a chance to inspect the generated tests with Google Test
+// reflection API before RUN_ALL_TESTS() is executed.
+//
+// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc
+// for more examples.
+//
+// In the future, we plan to publish the API for defining new parameter
+// generators. But for now this interface remains part of the internal
+// implementation and is subject to change.
+//
+//
+// A parameterized test fixture must be derived from testing::Test and from
+// testing::WithParamInterface<T>, where T is the type of the parameter
+// values. Inheriting from TestWithParam<T> satisfies that requirement because
+// TestWithParam<T> inherits from both Test and WithParamInterface. In more
+// complicated hierarchies, however, it is occasionally useful to inherit
+// separately from Test and WithParamInterface. For example:
+
+class BaseTest : public ::testing::Test {
+  // You can inherit all the usual members for a non-parameterized test
+  // fixture here.
+};
+
+class DerivedTest : public BaseTest, public ::testing::WithParamInterface<int> {
+  // The usual test fixture members go here too.
+};
+
+TEST_F(BaseTest, HasFoo) {
+  // This is an ordinary non-parameterized test.
+}
+
+TEST_P(DerivedTest, DoesBlah) {
+  // GetParam works just the same here as if you inherit from TestWithParam.
+  EXPECT_TRUE(foo.Blah(GetParam()));
+}
+
+#endif  // 0
+
+#include <iterator>
+#include <utility>
+
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Type and function utilities for implementing parameterized tests.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+
+#include <ctype.h>
+
+#include <cassert>
+#include <iterator>
+#include <memory>
+#include <set>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+
+#include <iosfwd>
+#include <vector>
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// A copyable object representing the result of a test part (i.e. an
+// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()).
+//
+// Don't inherit from TestPartResult as its destructor is not virtual.
+class GTEST_API_ TestPartResult {
+ public:
+  // The possible outcomes of a test part (i.e. an assertion or an
+  // explicit SUCCEED(), FAIL(), or ADD_FAILURE()).
+  enum Type {
+    kSuccess,          // Succeeded.
+    kNonFatalFailure,  // Failed but the test can continue.
+    kFatalFailure,     // Failed and the test should be terminated.
+    kSkip              // Skipped.
+  };
+
+  // C'tor.  TestPartResult does NOT have a default constructor.
+  // Always use this constructor (with parameters) to create a
+  // TestPartResult object.
+  TestPartResult(Type a_type, const char* a_file_name, int a_line_number,
+                 const char* a_message)
+      : type_(a_type),
+        file_name_(a_file_name == nullptr ? "" : a_file_name),
+        line_number_(a_line_number),
+        summary_(ExtractSummary(a_message)),
+        message_(a_message) {}
+
+  // Gets the outcome of the test part.
+  Type type() const { return type_; }
+
+  // Gets the name of the source file where the test part took place, or
+  // NULL if it's unknown.
+  const char* file_name() const {
+    return file_name_.empty() ? nullptr : file_name_.c_str();
+  }
+
+  // Gets the line in the source file where the test part took place,
+  // or -1 if it's unknown.
+  int line_number() const { return line_number_; }
+
+  // Gets the summary of the failure message.
+  const char* summary() const { return summary_.c_str(); }
+
+  // Gets the message associated with the test part.
+  const char* message() const { return message_.c_str(); }
+
+  // Returns true if and only if the test part was skipped.
+  bool skipped() const { return type_ == kSkip; }
+
+  // Returns true if and only if the test part passed.
+  bool passed() const { return type_ == kSuccess; }
+
+  // Returns true if and only if the test part non-fatally failed.
+  bool nonfatally_failed() const { return type_ == kNonFatalFailure; }
+
+  // Returns true if and only if the test part fatally failed.
+  bool fatally_failed() const { return type_ == kFatalFailure; }
+
+  // Returns true if and only if the test part failed.
+  bool failed() const { return fatally_failed() || nonfatally_failed(); }
+
+ private:
+  Type type_;
+
+  // Gets the summary of the failure message by omitting the stack
+  // trace in it.
+  static std::string ExtractSummary(const char* message);
+
+  // The name of the source file where the test part took place, or
+  // "" if the source file is unknown.
+  std::string file_name_;
+  // The line in the source file where the test part took place, or -1
+  // if the line number is unknown.
+  int line_number_;
+  std::string summary_;  // The test failure summary.
+  std::string message_;  // The test failure message.
+};
+
+// Prints a TestPartResult object.
+std::ostream& operator<<(std::ostream& os, const TestPartResult& result);
+
+// An array of TestPartResult objects.
+//
+// Don't inherit from TestPartResultArray as its destructor is not
+// virtual.
+class GTEST_API_ TestPartResultArray {
+ public:
+  TestPartResultArray() {}
+
+  // Appends the given TestPartResult to the array.
+  void Append(const TestPartResult& result);
+
+  // Returns the TestPartResult at the given index (0-based).
+  const TestPartResult& GetTestPartResult(int index) const;
+
+  // Returns the number of TestPartResult objects in the array.
+  int size() const;
+
+ private:
+  std::vector<TestPartResult> array_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray);
+};
+
+// This interface knows how to report a test part result.
+class GTEST_API_ TestPartResultReporterInterface {
+ public:
+  virtual ~TestPartResultReporterInterface() {}
+
+  virtual void ReportTestPartResult(const TestPartResult& result) = 0;
+};
+
+namespace internal {
+
+// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a
+// statement generates new fatal failures. To do so it registers itself as the
+// current test part result reporter. Besides checking if fatal failures were
+// reported, it only delegates the reporting to the former result reporter.
+// The original result reporter is restored in the destructor.
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+class GTEST_API_ HasNewFatalFailureHelper
+    : public TestPartResultReporterInterface {
+ public:
+  HasNewFatalFailureHelper();
+  ~HasNewFatalFailureHelper() override;
+  void ReportTestPartResult(const TestPartResult& result) override;
+  bool has_new_fatal_failure() const { return has_new_fatal_failure_; }
+ private:
+  bool has_new_fatal_failure_;
+  TestPartResultReporterInterface* original_reporter_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper);
+};
+
+}  // namespace internal
+
+}  // namespace testing
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
+
+namespace testing {
+// Input to a parameterized test name generator, describing a test parameter.
+// Consists of the parameter value and the integer parameter index.
+template <class ParamType>
+struct TestParamInfo {
+  TestParamInfo(const ParamType& a_param, size_t an_index) :
+    param(a_param),
+    index(an_index) {}
+  ParamType param;
+  size_t index;
+};
+
+// A builtin parameterized test name generator which returns the result of
+// testing::PrintToString.
+struct PrintToStringParamName {
+  template <class ParamType>
+  std::string operator()(const TestParamInfo<ParamType>& info) const {
+    return PrintToString(info.param);
+  }
+};
+
+namespace internal {
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+// Utility Functions
+
+// Outputs a message explaining invalid registration of different
+// fixture class for the same test suite. This may happen when
+// TEST_P macro is used to define two tests with the same name
+// but in different namespaces.
+GTEST_API_ void ReportInvalidTestSuiteType(const char* test_suite_name,
+                                           CodeLocation code_location);
+
+template <typename> class ParamGeneratorInterface;
+template <typename> class ParamGenerator;
+
+// Interface for iterating over elements provided by an implementation
+// of ParamGeneratorInterface<T>.
+template <typename T>
+class ParamIteratorInterface {
+ public:
+  virtual ~ParamIteratorInterface() {}
+  // A pointer to the base generator instance.
+  // Used only for the purposes of iterator comparison
+  // to make sure that two iterators belong to the same generator.
+  virtual const ParamGeneratorInterface<T>* BaseGenerator() const = 0;
+  // Advances iterator to point to the next element
+  // provided by the generator. The caller is responsible
+  // for not calling Advance() on an iterator equal to
+  // BaseGenerator()->End().
+  virtual void Advance() = 0;
+  // Clones the iterator object. Used for implementing copy semantics
+  // of ParamIterator<T>.
+  virtual ParamIteratorInterface* Clone() const = 0;
+  // Dereferences the current iterator and provides (read-only) access
+  // to the pointed value. It is the caller's responsibility not to call
+  // Current() on an iterator equal to BaseGenerator()->End().
+  // Used for implementing ParamGenerator<T>::operator*().
+  virtual const T* Current() const = 0;
+  // Determines whether the given iterator and other point to the same
+  // element in the sequence generated by the generator.
+  // Used for implementing ParamGenerator<T>::operator==().
+  virtual bool Equals(const ParamIteratorInterface& other) const = 0;
+};
+
+// Class iterating over elements provided by an implementation of
+// ParamGeneratorInterface<T>. It wraps ParamIteratorInterface<T>
+// and implements the const forward iterator concept.
+template <typename T>
+class ParamIterator {
+ public:
+  typedef T value_type;
+  typedef const T& reference;
+  typedef ptrdiff_t difference_type;
+
+  // ParamIterator assumes ownership of the impl_ pointer.
+  ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {}
+  ParamIterator& operator=(const ParamIterator& other) {
+    if (this != &other)
+      impl_.reset(other.impl_->Clone());
+    return *this;
+  }
+
+  const T& operator*() const { return *impl_->Current(); }
+  const T* operator->() const { return impl_->Current(); }
+  // Prefix version of operator++.
+  ParamIterator& operator++() {
+    impl_->Advance();
+    return *this;
+  }
+  // Postfix version of operator++.
+  ParamIterator operator++(int /*unused*/) {
+    ParamIteratorInterface<T>* clone = impl_->Clone();
+    impl_->Advance();
+    return ParamIterator(clone);
+  }
+  bool operator==(const ParamIterator& other) const {
+    return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_);
+  }
+  bool operator!=(const ParamIterator& other) const {
+    return !(*this == other);
+  }
+
+ private:
+  friend class ParamGenerator<T>;
+  explicit ParamIterator(ParamIteratorInterface<T>* impl) : impl_(impl) {}
+  std::unique_ptr<ParamIteratorInterface<T> > impl_;
+};
+
+// ParamGeneratorInterface<T> is the binary interface to access generators
+// defined in other translation units.
+template <typename T>
+class ParamGeneratorInterface {
+ public:
+  typedef T ParamType;
+
+  virtual ~ParamGeneratorInterface() {}
+
+  // Generator interface definition
+  virtual ParamIteratorInterface<T>* Begin() const = 0;
+  virtual ParamIteratorInterface<T>* End() const = 0;
+};
+
+// Wraps ParamGeneratorInterface<T> and provides general generator syntax
+// compatible with the STL Container concept.
+// This class implements copy initialization semantics and the contained
+// ParamGeneratorInterface<T> instance is shared among all copies
+// of the original object. This is possible because that instance is immutable.
+template<typename T>
+class ParamGenerator {
+ public:
+  typedef ParamIterator<T> iterator;
+
+  explicit ParamGenerator(ParamGeneratorInterface<T>* impl) : impl_(impl) {}
+  ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {}
+
+  ParamGenerator& operator=(const ParamGenerator& other) {
+    impl_ = other.impl_;
+    return *this;
+  }
+
+  iterator begin() const { return iterator(impl_->Begin()); }
+  iterator end() const { return iterator(impl_->End()); }
+
+ private:
+  std::shared_ptr<const ParamGeneratorInterface<T> > impl_;
+};
+
+// Generates values from a range of two comparable values. Can be used to
+// generate sequences of user-defined types that implement operator+() and
+// operator<().
+// This class is used in the Range() function.
+template <typename T, typename IncrementT>
+class RangeGenerator : public ParamGeneratorInterface<T> {
+ public:
+  RangeGenerator(T begin, T end, IncrementT step)
+      : begin_(begin), end_(end),
+        step_(step), end_index_(CalculateEndIndex(begin, end, step)) {}
+  ~RangeGenerator() override {}
+
+  ParamIteratorInterface<T>* Begin() const override {
+    return new Iterator(this, begin_, 0, step_);
+  }
+  ParamIteratorInterface<T>* End() const override {
+    return new Iterator(this, end_, end_index_, step_);
+  }
+
+ private:
+  class Iterator : public ParamIteratorInterface<T> {
+   public:
+    Iterator(const ParamGeneratorInterface<T>* base, T value, int index,
+             IncrementT step)
+        : base_(base), value_(value), index_(index), step_(step) {}
+    ~Iterator() override {}
+
+    const ParamGeneratorInterface<T>* BaseGenerator() const override {
+      return base_;
+    }
+    void Advance() override {
+      value_ = static_cast<T>(value_ + step_);
+      index_++;
+    }
+    ParamIteratorInterface<T>* Clone() const override {
+      return new Iterator(*this);
+    }
+    const T* Current() const override { return &value_; }
+    bool Equals(const ParamIteratorInterface<T>& other) const override {
+      // Having the same base generator guarantees that the other
+      // iterator is of the same type and we can downcast.
+      GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+          << "The program attempted to compare iterators "
+          << "from different generators." << std::endl;
+      const int other_index =
+          CheckedDowncastToActualType<const Iterator>(&other)->index_;
+      return index_ == other_index;
+    }
+
+   private:
+    Iterator(const Iterator& other)
+        : ParamIteratorInterface<T>(),
+          base_(other.base_), value_(other.value_), index_(other.index_),
+          step_(other.step_) {}
+
+    // No implementation - assignment is unsupported.
+    void operator=(const Iterator& other);
+
+    const ParamGeneratorInterface<T>* const base_;
+    T value_;
+    int index_;
+    const IncrementT step_;
+  };  // class RangeGenerator::Iterator
+
+  static int CalculateEndIndex(const T& begin,
+                               const T& end,
+                               const IncrementT& step) {
+    int end_index = 0;
+    for (T i = begin; i < end; i = static_cast<T>(i + step))
+      end_index++;
+    return end_index;
+  }
+
+  // No implementation - assignment is unsupported.
+  void operator=(const RangeGenerator& other);
+
+  const T begin_;
+  const T end_;
+  const IncrementT step_;
+  // The index for the end() iterator. All the elements in the generated
+  // sequence are indexed (0-based) to aid iterator comparison.
+  const int end_index_;
+};  // class RangeGenerator
+
+
+// Generates values from a pair of STL-style iterators. Used in the
+// ValuesIn() function. The elements are copied from the source range
+// since the source can be located on the stack, and the generator
+// is likely to persist beyond that stack frame.
+template <typename T>
+class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface<T> {
+ public:
+  template <typename ForwardIterator>
+  ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end)
+      : container_(begin, end) {}
+  ~ValuesInIteratorRangeGenerator() override {}
+
+  ParamIteratorInterface<T>* Begin() const override {
+    return new Iterator(this, container_.begin());
+  }
+  ParamIteratorInterface<T>* End() const override {
+    return new Iterator(this, container_.end());
+  }
+
+ private:
+  typedef typename ::std::vector<T> ContainerType;
+
+  class Iterator : public ParamIteratorInterface<T> {
+   public:
+    Iterator(const ParamGeneratorInterface<T>* base,
+             typename ContainerType::const_iterator iterator)
+        : base_(base), iterator_(iterator) {}
+    ~Iterator() override {}
+
+    const ParamGeneratorInterface<T>* BaseGenerator() const override {
+      return base_;
+    }
+    void Advance() override {
+      ++iterator_;
+      value_.reset();
+    }
+    ParamIteratorInterface<T>* Clone() const override {
+      return new Iterator(*this);
+    }
+    // We need to use cached value referenced by iterator_ because *iterator_
+    // can return a temporary object (and of type other then T), so just
+    // having "return &*iterator_;" doesn't work.
+    // value_ is updated here and not in Advance() because Advance()
+    // can advance iterator_ beyond the end of the range, and we cannot
+    // detect that fact. The client code, on the other hand, is
+    // responsible for not calling Current() on an out-of-range iterator.
+    const T* Current() const override {
+      if (value_.get() == nullptr) value_.reset(new T(*iterator_));
+      return value_.get();
+    }
+    bool Equals(const ParamIteratorInterface<T>& other) const override {
+      // Having the same base generator guarantees that the other
+      // iterator is of the same type and we can downcast.
+      GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+          << "The program attempted to compare iterators "
+          << "from different generators." << std::endl;
+      return iterator_ ==
+          CheckedDowncastToActualType<const Iterator>(&other)->iterator_;
+    }
+
+   private:
+    Iterator(const Iterator& other)
+          // The explicit constructor call suppresses a false warning
+          // emitted by gcc when supplied with the -Wextra option.
+        : ParamIteratorInterface<T>(),
+          base_(other.base_),
+          iterator_(other.iterator_) {}
+
+    const ParamGeneratorInterface<T>* const base_;
+    typename ContainerType::const_iterator iterator_;
+    // A cached value of *iterator_. We keep it here to allow access by
+    // pointer in the wrapping iterator's operator->().
+    // value_ needs to be mutable to be accessed in Current().
+    // Use of std::unique_ptr helps manage cached value's lifetime,
+    // which is bound by the lifespan of the iterator itself.
+    mutable std::unique_ptr<const T> value_;
+  };  // class ValuesInIteratorRangeGenerator::Iterator
+
+  // No implementation - assignment is unsupported.
+  void operator=(const ValuesInIteratorRangeGenerator& other);
+
+  const ContainerType container_;
+};  // class ValuesInIteratorRangeGenerator
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Default parameterized test name generator, returns a string containing the
+// integer test parameter index.
+template <class ParamType>
+std::string DefaultParamName(const TestParamInfo<ParamType>& info) {
+  Message name_stream;
+  name_stream << info.index;
+  return name_stream.GetString();
+}
+
+template <typename T = int>
+void TestNotEmpty() {
+  static_assert(sizeof(T) == 0, "Empty arguments are not allowed.");
+}
+template <typename T = int>
+void TestNotEmpty(const T&) {}
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Stores a parameter value and later creates tests parameterized with that
+// value.
+template <class TestClass>
+class ParameterizedTestFactory : public TestFactoryBase {
+ public:
+  typedef typename TestClass::ParamType ParamType;
+  explicit ParameterizedTestFactory(ParamType parameter) :
+      parameter_(parameter) {}
+  Test* CreateTest() override {
+    TestClass::SetParam(&parameter_);
+    return new TestClass();
+  }
+
+ private:
+  const ParamType parameter_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// TestMetaFactoryBase is a base class for meta-factories that create
+// test factories for passing into MakeAndRegisterTestInfo function.
+template <class ParamType>
+class TestMetaFactoryBase {
+ public:
+  virtual ~TestMetaFactoryBase() {}
+
+  virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0;
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// TestMetaFactory creates test factories for passing into
+// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives
+// ownership of test factory pointer, same factory object cannot be passed
+// into that method twice. But ParameterizedTestSuiteInfo is going to call
+// it for each Test/Parameter value combination. Thus it needs meta factory
+// creator class.
+template <class TestSuite>
+class TestMetaFactory
+    : public TestMetaFactoryBase<typename TestSuite::ParamType> {
+ public:
+  using ParamType = typename TestSuite::ParamType;
+
+  TestMetaFactory() {}
+
+  TestFactoryBase* CreateTestFactory(ParamType parameter) override {
+    return new ParameterizedTestFactory<TestSuite>(parameter);
+  }
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestSuiteInfoBase is a generic interface
+// to ParameterizedTestSuiteInfo classes. ParameterizedTestSuiteInfoBase
+// accumulates test information provided by TEST_P macro invocations
+// and generators provided by INSTANTIATE_TEST_SUITE_P macro invocations
+// and uses that information to register all resulting test instances
+// in RegisterTests method. The ParameterizeTestSuiteRegistry class holds
+// a collection of pointers to the ParameterizedTestSuiteInfo objects
+// and calls RegisterTests() on each of them when asked.
+class ParameterizedTestSuiteInfoBase {
+ public:
+  virtual ~ParameterizedTestSuiteInfoBase() {}
+
+  // Base part of test suite name for display purposes.
+  virtual const std::string& GetTestSuiteName() const = 0;
+  // Test suite id to verify identity.
+  virtual TypeId GetTestSuiteTypeId() const = 0;
+  // UnitTest class invokes this method to register tests in this
+  // test suite right before running them in RUN_ALL_TESTS macro.
+  // This method should not be called more than once on any single
+  // instance of a ParameterizedTestSuiteInfoBase derived class.
+  virtual void RegisterTests() = 0;
+
+ protected:
+  ParameterizedTestSuiteInfoBase() {}
+
+ private:
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfoBase);
+};
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Report a the name of a test_suit as safe to ignore
+// as the side effect of construction of this type.
+struct MarkAsIgnored {
+  explicit MarkAsIgnored(const char* test_suite);
+};
+
+GTEST_API_ void InsertSyntheticTestCase(const std::string& name,
+                                        CodeLocation location, bool has_test_p);
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestSuiteInfo accumulates tests obtained from TEST_P
+// macro invocations for a particular test suite and generators
+// obtained from INSTANTIATE_TEST_SUITE_P macro invocations for that
+// test suite. It registers tests with all values generated by all
+// generators when asked.
+template <class TestSuite>
+class ParameterizedTestSuiteInfo : public ParameterizedTestSuiteInfoBase {
+ public:
+  // ParamType and GeneratorCreationFunc are private types but are required
+  // for declarations of public methods AddTestPattern() and
+  // AddTestSuiteInstantiation().
+  using ParamType = typename TestSuite::ParamType;
+  // A function that returns an instance of appropriate generator type.
+  typedef ParamGenerator<ParamType>(GeneratorCreationFunc)();
+  using ParamNameGeneratorFunc = std::string(const TestParamInfo<ParamType>&);
+
+  explicit ParameterizedTestSuiteInfo(const char* name,
+                                      CodeLocation code_location)
+      : test_suite_name_(name), code_location_(code_location) {}
+
+  // Test suite base name for display purposes.
+  const std::string& GetTestSuiteName() const override {
+    return test_suite_name_;
+  }
+  // Test suite id to verify identity.
+  TypeId GetTestSuiteTypeId() const override { return GetTypeId<TestSuite>(); }
+  // TEST_P macro uses AddTestPattern() to record information
+  // about a single test in a LocalTestInfo structure.
+  // test_suite_name is the base name of the test suite (without invocation
+  // prefix). test_base_name is the name of an individual test without
+  // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is
+  // test suite base name and DoBar is test base name.
+  void AddTestPattern(const char* test_suite_name, const char* test_base_name,
+                      TestMetaFactoryBase<ParamType>* meta_factory,
+                      CodeLocation code_location) {
+    tests_.push_back(std::shared_ptr<TestInfo>(new TestInfo(
+        test_suite_name, test_base_name, meta_factory, code_location)));
+  }
+  // INSTANTIATE_TEST_SUITE_P macro uses AddGenerator() to record information
+  // about a generator.
+  int AddTestSuiteInstantiation(const std::string& instantiation_name,
+                                GeneratorCreationFunc* func,
+                                ParamNameGeneratorFunc* name_func,
+                                const char* file, int line) {
+    instantiations_.push_back(
+        InstantiationInfo(instantiation_name, func, name_func, file, line));
+    return 0;  // Return value used only to run this method in namespace scope.
+  }
+  // UnitTest class invokes this method to register tests in this test suite
+  // right before running tests in RUN_ALL_TESTS macro.
+  // This method should not be called more than once on any single
+  // instance of a ParameterizedTestSuiteInfoBase derived class.
+  // UnitTest has a guard to prevent from calling this method more than once.
+  void RegisterTests() override {
+    bool generated_instantiations = false;
+
+    for (typename TestInfoContainer::iterator test_it = tests_.begin();
+         test_it != tests_.end(); ++test_it) {
+      std::shared_ptr<TestInfo> test_info = *test_it;
+      for (typename InstantiationContainer::iterator gen_it =
+               instantiations_.begin(); gen_it != instantiations_.end();
+               ++gen_it) {
+        const std::string& instantiation_name = gen_it->name;
+        ParamGenerator<ParamType> generator((*gen_it->generator)());
+        ParamNameGeneratorFunc* name_func = gen_it->name_func;
+        const char* file = gen_it->file;
+        int line = gen_it->line;
+
+        std::string test_suite_name;
+        if ( !instantiation_name.empty() )
+          test_suite_name = instantiation_name + "/";
+        test_suite_name += test_info->test_suite_base_name;
+
+        size_t i = 0;
+        std::set<std::string> test_param_names;
+        for (typename ParamGenerator<ParamType>::iterator param_it =
+                 generator.begin();
+             param_it != generator.end(); ++param_it, ++i) {
+          generated_instantiations = true;
+
+          Message test_name_stream;
+
+          std::string param_name = name_func(
+              TestParamInfo<ParamType>(*param_it, i));
+
+          GTEST_CHECK_(IsValidParamName(param_name))
+              << "Parameterized test name '" << param_name
+              << "' is invalid, in " << file
+              << " line " << line << std::endl;
+
+          GTEST_CHECK_(test_param_names.count(param_name) == 0)
+              << "Duplicate parameterized test name '" << param_name
+              << "', in " << file << " line " << line << std::endl;
+
+          test_param_names.insert(param_name);
+
+          if (!test_info->test_base_name.empty()) {
+            test_name_stream << test_info->test_base_name << "/";
+          }
+          test_name_stream << param_name;
+          MakeAndRegisterTestInfo(
+              test_suite_name.c_str(), test_name_stream.GetString().c_str(),
+              nullptr,  // No type parameter.
+              PrintToString(*param_it).c_str(), test_info->code_location,
+              GetTestSuiteTypeId(),
+              SuiteApiResolver<TestSuite>::GetSetUpCaseOrSuite(file, line),
+              SuiteApiResolver<TestSuite>::GetTearDownCaseOrSuite(file, line),
+              test_info->test_meta_factory->CreateTestFactory(*param_it));
+        }  // for param_it
+      }  // for gen_it
+    }  // for test_it
+
+    if (!generated_instantiations) {
+      // There are no generaotrs, or they all generate nothing ...
+      InsertSyntheticTestCase(GetTestSuiteName(), code_location_,
+                              !tests_.empty());
+    }
+  }    // RegisterTests
+
+ private:
+  // LocalTestInfo structure keeps information about a single test registered
+  // with TEST_P macro.
+  struct TestInfo {
+    TestInfo(const char* a_test_suite_base_name, const char* a_test_base_name,
+             TestMetaFactoryBase<ParamType>* a_test_meta_factory,
+             CodeLocation a_code_location)
+        : test_suite_base_name(a_test_suite_base_name),
+          test_base_name(a_test_base_name),
+          test_meta_factory(a_test_meta_factory),
+          code_location(a_code_location) {}
+
+    const std::string test_suite_base_name;
+    const std::string test_base_name;
+    const std::unique_ptr<TestMetaFactoryBase<ParamType> > test_meta_factory;
+    const CodeLocation code_location;
+  };
+  using TestInfoContainer = ::std::vector<std::shared_ptr<TestInfo> >;
+  // Records data received from INSTANTIATE_TEST_SUITE_P macros:
+  //  <Instantiation name, Sequence generator creation function,
+  //     Name generator function, Source file, Source line>
+  struct InstantiationInfo {
+      InstantiationInfo(const std::string &name_in,
+                        GeneratorCreationFunc* generator_in,
+                        ParamNameGeneratorFunc* name_func_in,
+                        const char* file_in,
+                        int line_in)
+          : name(name_in),
+            generator(generator_in),
+            name_func(name_func_in),
+            file(file_in),
+            line(line_in) {}
+
+      std::string name;
+      GeneratorCreationFunc* generator;
+      ParamNameGeneratorFunc* name_func;
+      const char* file;
+      int line;
+  };
+  typedef ::std::vector<InstantiationInfo> InstantiationContainer;
+
+  static bool IsValidParamName(const std::string& name) {
+    // Check for empty string
+    if (name.empty())
+      return false;
+
+    // Check for invalid characters
+    for (std::string::size_type index = 0; index < name.size(); ++index) {
+      if (!isalnum(name[index]) && name[index] != '_')
+        return false;
+    }
+
+    return true;
+  }
+
+  const std::string test_suite_name_;
+  CodeLocation code_location_;
+  TestInfoContainer tests_;
+  InstantiationContainer instantiations_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteInfo);
+};  // class ParameterizedTestSuiteInfo
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+template <class TestCase>
+using ParameterizedTestCaseInfo = ParameterizedTestSuiteInfo<TestCase>;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// ParameterizedTestSuiteRegistry contains a map of
+// ParameterizedTestSuiteInfoBase classes accessed by test suite names. TEST_P
+// and INSTANTIATE_TEST_SUITE_P macros use it to locate their corresponding
+// ParameterizedTestSuiteInfo descriptors.
+class ParameterizedTestSuiteRegistry {
+ public:
+  ParameterizedTestSuiteRegistry() {}
+  ~ParameterizedTestSuiteRegistry() {
+    for (auto& test_suite_info : test_suite_infos_) {
+      delete test_suite_info;
+    }
+  }
+
+  // Looks up or creates and returns a structure containing information about
+  // tests and instantiations of a particular test suite.
+  template <class TestSuite>
+  ParameterizedTestSuiteInfo<TestSuite>* GetTestSuitePatternHolder(
+      const char* test_suite_name, CodeLocation code_location) {
+    ParameterizedTestSuiteInfo<TestSuite>* typed_test_info = nullptr;
+    for (auto& test_suite_info : test_suite_infos_) {
+      if (test_suite_info->GetTestSuiteName() == test_suite_name) {
+        if (test_suite_info->GetTestSuiteTypeId() != GetTypeId<TestSuite>()) {
+          // Complain about incorrect usage of Google Test facilities
+          // and terminate the program since we cannot guaranty correct
+          // test suite setup and tear-down in this case.
+          ReportInvalidTestSuiteType(test_suite_name, code_location);
+          posix::Abort();
+        } else {
+          // At this point we are sure that the object we found is of the same
+          // type we are looking for, so we downcast it to that type
+          // without further checks.
+          typed_test_info = CheckedDowncastToActualType<
+              ParameterizedTestSuiteInfo<TestSuite> >(test_suite_info);
+        }
+        break;
+      }
+    }
+    if (typed_test_info == nullptr) {
+      typed_test_info = new ParameterizedTestSuiteInfo<TestSuite>(
+          test_suite_name, code_location);
+      test_suite_infos_.push_back(typed_test_info);
+    }
+    return typed_test_info;
+  }
+  void RegisterTests() {
+    for (auto& test_suite_info : test_suite_infos_) {
+      test_suite_info->RegisterTests();
+    }
+  }
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  template <class TestCase>
+  ParameterizedTestCaseInfo<TestCase>* GetTestCasePatternHolder(
+      const char* test_case_name, CodeLocation code_location) {
+    return GetTestSuitePatternHolder<TestCase>(test_case_name, code_location);
+  }
+
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+ private:
+  using TestSuiteInfoContainer = ::std::vector<ParameterizedTestSuiteInfoBase*>;
+
+  TestSuiteInfoContainer test_suite_infos_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestSuiteRegistry);
+};
+
+// Keep track of what type-parameterized test suite are defined and
+// where as well as which are intatiated. This allows susequently
+// identifying suits that are defined but never used.
+class TypeParameterizedTestSuiteRegistry {
+ public:
+  // Add a suite definition
+  void RegisterTestSuite(const char* test_suite_name,
+                         CodeLocation code_location);
+
+  // Add an instantiation of a suit.
+  void RegisterInstantiation(const char* test_suite_name);
+
+  // For each suit repored as defined but not reported as instantiation,
+  // emit a test that reports that fact (configurably, as an error).
+  void CheckForInstantiations();
+
+ private:
+  struct TypeParameterizedTestSuiteInfo {
+    explicit TypeParameterizedTestSuiteInfo(CodeLocation c)
+        : code_location(c), instantiated(false) {}
+
+    CodeLocation code_location;
+    bool instantiated;
+  };
+
+  std::map<std::string, TypeParameterizedTestSuiteInfo> suites_;
+};
+
+}  // namespace internal
+
+// Forward declarations of ValuesIn(), which is implemented in
+// include/gtest/gtest-param-test.h.
+template <class Container>
+internal::ParamGenerator<typename Container::value_type> ValuesIn(
+    const Container& container);
+
+namespace internal {
+// Used in the Values() function to provide polymorphic capabilities.
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4100)
+#endif
+
+template <typename... Ts>
+class ValueArray {
+ public:
+  explicit ValueArray(Ts... v) : v_(FlatTupleConstructTag{}, std::move(v)...) {}
+
+  template <typename T>
+  operator ParamGenerator<T>() const {  // NOLINT
+    return ValuesIn(MakeVector<T>(MakeIndexSequence<sizeof...(Ts)>()));
+  }
+
+ private:
+  template <typename T, size_t... I>
+  std::vector<T> MakeVector(IndexSequence<I...>) const {
+    return std::vector<T>{static_cast<T>(v_.template Get<I>())...};
+  }
+
+  FlatTuple<Ts...> v_;
+};
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+template <typename... T>
+class CartesianProductGenerator
+    : public ParamGeneratorInterface<::std::tuple<T...>> {
+ public:
+  typedef ::std::tuple<T...> ParamType;
+
+  CartesianProductGenerator(const std::tuple<ParamGenerator<T>...>& g)
+      : generators_(g) {}
+  ~CartesianProductGenerator() override {}
+
+  ParamIteratorInterface<ParamType>* Begin() const override {
+    return new Iterator(this, generators_, false);
+  }
+  ParamIteratorInterface<ParamType>* End() const override {
+    return new Iterator(this, generators_, true);
+  }
+
+ private:
+  template <class I>
+  class IteratorImpl;
+  template <size_t... I>
+  class IteratorImpl<IndexSequence<I...>>
+      : public ParamIteratorInterface<ParamType> {
+   public:
+    IteratorImpl(const ParamGeneratorInterface<ParamType>* base,
+             const std::tuple<ParamGenerator<T>...>& generators, bool is_end)
+        : base_(base),
+          begin_(std::get<I>(generators).begin()...),
+          end_(std::get<I>(generators).end()...),
+          current_(is_end ? end_ : begin_) {
+      ComputeCurrentValue();
+    }
+    ~IteratorImpl() override {}
+
+    const ParamGeneratorInterface<ParamType>* BaseGenerator() const override {
+      return base_;
+    }
+    // Advance should not be called on beyond-of-range iterators
+    // so no component iterators must be beyond end of range, either.
+    void Advance() override {
+      assert(!AtEnd());
+      // Advance the last iterator.
+      ++std::get<sizeof...(T) - 1>(current_);
+      // if that reaches end, propagate that up.
+      AdvanceIfEnd<sizeof...(T) - 1>();
+      ComputeCurrentValue();
+    }
+    ParamIteratorInterface<ParamType>* Clone() const override {
+      return new IteratorImpl(*this);
+    }
+
+    const ParamType* Current() const override { return current_value_.get(); }
+
+    bool Equals(const ParamIteratorInterface<ParamType>& other) const override {
+      // Having the same base generator guarantees that the other
+      // iterator is of the same type and we can downcast.
+      GTEST_CHECK_(BaseGenerator() == other.BaseGenerator())
+          << "The program attempted to compare iterators "
+          << "from different generators." << std::endl;
+      const IteratorImpl* typed_other =
+          CheckedDowncastToActualType<const IteratorImpl>(&other);
+
+      // We must report iterators equal if they both point beyond their
+      // respective ranges. That can happen in a variety of fashions,
+      // so we have to consult AtEnd().
+      if (AtEnd() && typed_other->AtEnd()) return true;
+
+      bool same = true;
+      bool dummy[] = {
+          (same = same && std::get<I>(current_) ==
+                              std::get<I>(typed_other->current_))...};
+      (void)dummy;
+      return same;
+    }
+
+   private:
+    template <size_t ThisI>
+    void AdvanceIfEnd() {
+      if (std::get<ThisI>(current_) != std::get<ThisI>(end_)) return;
+
+      bool last = ThisI == 0;
+      if (last) {
+        // We are done. Nothing else to propagate.
+        return;
+      }
+
+      constexpr size_t NextI = ThisI - (ThisI != 0);
+      std::get<ThisI>(current_) = std::get<ThisI>(begin_);
+      ++std::get<NextI>(current_);
+      AdvanceIfEnd<NextI>();
+    }
+
+    void ComputeCurrentValue() {
+      if (!AtEnd())
+        current_value_ = std::make_shared<ParamType>(*std::get<I>(current_)...);
+    }
+    bool AtEnd() const {
+      bool at_end = false;
+      bool dummy[] = {
+          (at_end = at_end || std::get<I>(current_) == std::get<I>(end_))...};
+      (void)dummy;
+      return at_end;
+    }
+
+    const ParamGeneratorInterface<ParamType>* const base_;
+    std::tuple<typename ParamGenerator<T>::iterator...> begin_;
+    std::tuple<typename ParamGenerator<T>::iterator...> end_;
+    std::tuple<typename ParamGenerator<T>::iterator...> current_;
+    std::shared_ptr<ParamType> current_value_;
+  };
+
+  using Iterator = IteratorImpl<typename MakeIndexSequence<sizeof...(T)>::type>;
+
+  std::tuple<ParamGenerator<T>...> generators_;
+};
+
+template <class... Gen>
+class CartesianProductHolder {
+ public:
+  CartesianProductHolder(const Gen&... g) : generators_(g...) {}
+  template <typename... T>
+  operator ParamGenerator<::std::tuple<T...>>() const {
+    return ParamGenerator<::std::tuple<T...>>(
+        new CartesianProductGenerator<T...>(generators_));
+  }
+
+ private:
+  std::tuple<Gen...> generators_;
+};
+
+}  // namespace internal
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_
+
+namespace testing {
+
+// Functions producing parameter generators.
+//
+// Google Test uses these generators to produce parameters for value-
+// parameterized tests. When a parameterized test suite is instantiated
+// with a particular generator, Google Test creates and runs tests
+// for each element in the sequence produced by the generator.
+//
+// In the following sample, tests from test suite FooTest are instantiated
+// each three times with parameter values 3, 5, and 8:
+//
+// class FooTest : public TestWithParam<int> { ... };
+//
+// TEST_P(FooTest, TestThis) {
+// }
+// TEST_P(FooTest, TestThat) {
+// }
+// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8));
+//
+
+// Range() returns generators providing sequences of values in a range.
+//
+// Synopsis:
+// Range(start, end)
+//   - returns a generator producing a sequence of values {start, start+1,
+//     start+2, ..., }.
+// Range(start, end, step)
+//   - returns a generator producing a sequence of values {start, start+step,
+//     start+step+step, ..., }.
+// Notes:
+//   * The generated sequences never include end. For example, Range(1, 5)
+//     returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2)
+//     returns a generator producing {1, 3, 5, 7}.
+//   * start and end must have the same type. That type may be any integral or
+//     floating-point type or a user defined type satisfying these conditions:
+//     * It must be assignable (have operator=() defined).
+//     * It must have operator+() (operator+(int-compatible type) for
+//       two-operand version).
+//     * It must have operator<() defined.
+//     Elements in the resulting sequences will also have that type.
+//   * Condition start < end must be satisfied in order for resulting sequences
+//     to contain any elements.
+//
+template <typename T, typename IncrementT>
+internal::ParamGenerator<T> Range(T start, T end, IncrementT step) {
+  return internal::ParamGenerator<T>(
+      new internal::RangeGenerator<T, IncrementT>(start, end, step));
+}
+
+template <typename T>
+internal::ParamGenerator<T> Range(T start, T end) {
+  return Range(start, end, 1);
+}
+
+// ValuesIn() function allows generation of tests with parameters coming from
+// a container.
+//
+// Synopsis:
+// ValuesIn(const T (&array)[N])
+//   - returns a generator producing sequences with elements from
+//     a C-style array.
+// ValuesIn(const Container& container)
+//   - returns a generator producing sequences with elements from
+//     an STL-style container.
+// ValuesIn(Iterator begin, Iterator end)
+//   - returns a generator producing sequences with elements from
+//     a range [begin, end) defined by a pair of STL-style iterators. These
+//     iterators can also be plain C pointers.
+//
+// Please note that ValuesIn copies the values from the containers
+// passed in and keeps them to generate tests in RUN_ALL_TESTS().
+//
+// Examples:
+//
+// This instantiates tests from test suite StringTest
+// each with C-string values of "foo", "bar", and "baz":
+//
+// const char* strings[] = {"foo", "bar", "baz"};
+// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings));
+//
+// This instantiates tests from test suite StlStringTest
+// each with STL strings with values "a" and "b":
+//
+// ::std::vector< ::std::string> GetParameterStrings() {
+//   ::std::vector< ::std::string> v;
+//   v.push_back("a");
+//   v.push_back("b");
+//   return v;
+// }
+//
+// INSTANTIATE_TEST_SUITE_P(CharSequence,
+//                          StlStringTest,
+//                          ValuesIn(GetParameterStrings()));
+//
+//
+// This will also instantiate tests from CharTest
+// each with parameter values 'a' and 'b':
+//
+// ::std::list<char> GetParameterChars() {
+//   ::std::list<char> list;
+//   list.push_back('a');
+//   list.push_back('b');
+//   return list;
+// }
+// ::std::list<char> l = GetParameterChars();
+// INSTANTIATE_TEST_SUITE_P(CharSequence2,
+//                          CharTest,
+//                          ValuesIn(l.begin(), l.end()));
+//
+template <typename ForwardIterator>
+internal::ParamGenerator<
+    typename std::iterator_traits<ForwardIterator>::value_type>
+ValuesIn(ForwardIterator begin, ForwardIterator end) {
+  typedef typename std::iterator_traits<ForwardIterator>::value_type ParamType;
+  return internal::ParamGenerator<ParamType>(
+      new internal::ValuesInIteratorRangeGenerator<ParamType>(begin, end));
+}
+
+template <typename T, size_t N>
+internal::ParamGenerator<T> ValuesIn(const T (&array)[N]) {
+  return ValuesIn(array, array + N);
+}
+
+template <class Container>
+internal::ParamGenerator<typename Container::value_type> ValuesIn(
+    const Container& container) {
+  return ValuesIn(container.begin(), container.end());
+}
+
+// Values() allows generating tests from explicitly specified list of
+// parameters.
+//
+// Synopsis:
+// Values(T v1, T v2, ..., T vN)
+//   - returns a generator producing sequences with elements v1, v2, ..., vN.
+//
+// For example, this instantiates tests from test suite BarTest each
+// with values "one", "two", and "three":
+//
+// INSTANTIATE_TEST_SUITE_P(NumSequence,
+//                          BarTest,
+//                          Values("one", "two", "three"));
+//
+// This instantiates tests from test suite BazTest each with values 1, 2, 3.5.
+// The exact type of values will depend on the type of parameter in BazTest.
+//
+// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5));
+//
+//
+template <typename... T>
+internal::ValueArray<T...> Values(T... v) {
+  return internal::ValueArray<T...>(std::move(v)...);
+}
+
+// Bool() allows generating tests with parameters in a set of (false, true).
+//
+// Synopsis:
+// Bool()
+//   - returns a generator producing sequences with elements {false, true}.
+//
+// It is useful when testing code that depends on Boolean flags. Combinations
+// of multiple flags can be tested when several Bool()'s are combined using
+// Combine() function.
+//
+// In the following example all tests in the test suite FlagDependentTest
+// will be instantiated twice with parameters false and true.
+//
+// class FlagDependentTest : public testing::TestWithParam<bool> {
+//   virtual void SetUp() {
+//     external_flag = GetParam();
+//   }
+// }
+// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool());
+//
+inline internal::ParamGenerator<bool> Bool() {
+  return Values(false, true);
+}
+
+// Combine() allows the user to combine two or more sequences to produce
+// values of a Cartesian product of those sequences' elements.
+//
+// Synopsis:
+// Combine(gen1, gen2, ..., genN)
+//   - returns a generator producing sequences with elements coming from
+//     the Cartesian product of elements from the sequences generated by
+//     gen1, gen2, ..., genN. The sequence elements will have a type of
+//     std::tuple<T1, T2, ..., TN> where T1, T2, ..., TN are the types
+//     of elements from sequences produces by gen1, gen2, ..., genN.
+//
+// Example:
+//
+// This will instantiate tests in test suite AnimalTest each one with
+// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
+// tuple("dog", BLACK), and tuple("dog", WHITE):
+//
+// enum Color { BLACK, GRAY, WHITE };
+// class AnimalTest
+//     : public testing::TestWithParam<std::tuple<const char*, Color> > {...};
+//
+// TEST_P(AnimalTest, AnimalLooksNice) {...}
+//
+// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest,
+//                          Combine(Values("cat", "dog"),
+//                                  Values(BLACK, WHITE)));
+//
+// This will instantiate tests in FlagDependentTest with all variations of two
+// Boolean flags:
+//
+// class FlagDependentTest
+//     : public testing::TestWithParam<std::tuple<bool, bool> > {
+//   virtual void SetUp() {
+//     // Assigns external_flag_1 and external_flag_2 values from the tuple.
+//     std::tie(external_flag_1, external_flag_2) = GetParam();
+//   }
+// };
+//
+// TEST_P(FlagDependentTest, TestFeature1) {
+//   // Test your code using external_flag_1 and external_flag_2 here.
+// }
+// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest,
+//                          Combine(Bool(), Bool()));
+//
+template <typename... Generator>
+internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
+  return internal::CartesianProductHolder<Generator...>(g...);
+}
+
+#define TEST_P(test_suite_name, test_name)                                     \
+  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
+      : public test_suite_name {                                               \
+   public:                                                                     \
+    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {}                    \
+    void TestBody() override;                                                  \
+                                                                               \
+   private:                                                                    \
+    static int AddToRegistry() {                                               \
+      ::testing::UnitTest::GetInstance()                                       \
+          ->parameterized_test_registry()                                      \
+          .GetTestSuitePatternHolder<test_suite_name>(                         \
+              GTEST_STRINGIFY_(test_suite_name),                               \
+              ::testing::internal::CodeLocation(__FILE__, __LINE__))           \
+          ->AddTestPattern(                                                    \
+              GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name),  \
+              new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \
+                  test_suite_name, test_name)>(),                              \
+              ::testing::internal::CodeLocation(__FILE__, __LINE__));          \
+      return 0;                                                                \
+    }                                                                          \
+    static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_;               \
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(GTEST_TEST_CLASS_NAME_(test_suite_name,    \
+                                                           test_name));        \
+  };                                                                           \
+  int GTEST_TEST_CLASS_NAME_(test_suite_name,                                  \
+                             test_name)::gtest_registering_dummy_ =            \
+      GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry();     \
+  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
+
+// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify
+// generator and an optional function or functor that generates custom test name
+// suffixes based on the test parameters. Such a function or functor should
+// accept one argument of type testing::TestParamInfo<class ParamType>, and
+// return std::string.
+//
+// testing::PrintToStringParamName is a builtin test suffix generator that
+// returns the value of testing::PrintToString(GetParam()).
+//
+// Note: test names must be non-empty, unique, and may only contain ASCII
+// alphanumeric characters or underscore. Because PrintToString adds quotes
+// to std::string and C strings, it won't work for these types.
+
+#define GTEST_EXPAND_(arg) arg
+#define GTEST_GET_FIRST_(first, ...) first
+#define GTEST_GET_SECOND_(first, second, ...) second
+
+#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...)                \
+  static ::testing::internal::ParamGenerator<test_suite_name::ParamType>      \
+      gtest_##prefix##test_suite_name##_EvalGenerator_() {                    \
+    return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_));        \
+  }                                                                           \
+  static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_(   \
+      const ::testing::TestParamInfo<test_suite_name::ParamType>& info) {     \
+    if (::testing::internal::AlwaysFalse()) {                                 \
+      ::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_(      \
+          __VA_ARGS__,                                                        \
+          ::testing::internal::DefaultParamName<test_suite_name::ParamType>,  \
+          DUMMY_PARAM_)));                                                    \
+      auto t = std::make_tuple(__VA_ARGS__);                                  \
+      static_assert(std::tuple_size<decltype(t)>::value <= 2,                 \
+                    "Too Many Args!");                                        \
+    }                                                                         \
+    return ((GTEST_EXPAND_(GTEST_GET_SECOND_(                                 \
+        __VA_ARGS__,                                                          \
+        ::testing::internal::DefaultParamName<test_suite_name::ParamType>,    \
+        DUMMY_PARAM_))))(info);                                               \
+  }                                                                           \
+  static int gtest_##prefix##test_suite_name##_dummy_                         \
+      GTEST_ATTRIBUTE_UNUSED_ =                                               \
+          ::testing::UnitTest::GetInstance()                                  \
+              ->parameterized_test_registry()                                 \
+              .GetTestSuitePatternHolder<test_suite_name>(                    \
+                  GTEST_STRINGIFY_(test_suite_name),                          \
+                  ::testing::internal::CodeLocation(__FILE__, __LINE__))      \
+              ->AddTestSuiteInstantiation(                                    \
+                  GTEST_STRINGIFY_(prefix),                                   \
+                  &gtest_##prefix##test_suite_name##_EvalGenerator_,          \
+                  &gtest_##prefix##test_suite_name##_EvalGenerateName_,       \
+                  __FILE__, __LINE__)
+
+
+// Allow Marking a Parameterized test class as not needing to be instantiated.
+#define GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(T)                   \
+  namespace gtest_do_not_use_outside_namespace_scope {}                   \
+  static const ::testing::internal::MarkAsIgnored gtest_allow_ignore_##T( \
+      GTEST_STRINGIFY_(T))
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+#define INSTANTIATE_TEST_CASE_P                                            \
+  static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \
+                "");                                                       \
+  INSTANTIATE_TEST_SUITE_P
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
+// Copyright 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+//
+// Google C++ Testing and Mocking Framework definitions useful in production code.
+// GOOGLETEST_CM0003 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
+
+// When you need to test the private or protected members of a class,
+// use the FRIEND_TEST macro to declare your tests as friends of the
+// class.  For example:
+//
+// class MyClass {
+//  private:
+//   void PrivateMethod();
+//   FRIEND_TEST(MyClassTest, PrivateMethodWorks);
+// };
+//
+// class MyClassTest : public testing::Test {
+//   // ...
+// };
+//
+// TEST_F(MyClassTest, PrivateMethodWorks) {
+//   // Can call MyClass::PrivateMethod() here.
+// }
+//
+// Note: The test class must be in the same namespace as the class being tested.
+// For example, putting MyClassTest in an anonymous namespace will not work.
+
+#define FRIEND_TEST(test_case_name, test_name)\
+friend class test_case_name##_##test_name##_Test
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
+// Copyright 2008 Google Inc.
+// All Rights Reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+
+// This header implements typed tests and type-parameterized tests.
+
+// Typed (aka type-driven) tests repeat the same test for types in a
+// list.  You must know which types you want to test with when writing
+// typed tests. Here's how you do it:
+
+#if 0
+
+// First, define a fixture class template.  It should be parameterized
+// by a type.  Remember to derive it from testing::Test.
+template <typename T>
+class FooTest : public testing::Test {
+ public:
+  ...
+  typedef std::list<T> List;
+  static T shared_;
+  T value_;
+};
+
+// Next, associate a list of types with the test suite, which will be
+// repeated for each type in the list.  The typedef is necessary for
+// the macro to parse correctly.
+typedef testing::Types<char, int, unsigned int> MyTypes;
+TYPED_TEST_SUITE(FooTest, MyTypes);
+
+// If the type list contains only one type, you can write that type
+// directly without Types<...>:
+//   TYPED_TEST_SUITE(FooTest, int);
+
+// Then, use TYPED_TEST() instead of TEST_F() to define as many typed
+// tests for this test suite as you want.
+TYPED_TEST(FooTest, DoesBlah) {
+  // Inside a test, refer to the special name TypeParam to get the type
+  // parameter.  Since we are inside a derived class template, C++ requires
+  // us to visit the members of FooTest via 'this'.
+  TypeParam n = this->value_;
+
+  // To visit static members of the fixture, add the TestFixture::
+  // prefix.
+  n += TestFixture::shared_;
+
+  // To refer to typedefs in the fixture, add the "typename
+  // TestFixture::" prefix.
+  typename TestFixture::List values;
+  values.push_back(n);
+  ...
+}
+
+TYPED_TEST(FooTest, HasPropertyA) { ... }
+
+// TYPED_TEST_SUITE takes an optional third argument which allows to specify a
+// class that generates custom test name suffixes based on the type. This should
+// be a class which has a static template function GetName(int index) returning
+// a string for each type. The provided integer index equals the index of the
+// type in the provided type list. In many cases the index can be ignored.
+//
+// For example:
+//   class MyTypeNames {
+//    public:
+//     template <typename T>
+//     static std::string GetName(int) {
+//       if (std::is_same<T, char>()) return "char";
+//       if (std::is_same<T, int>()) return "int";
+//       if (std::is_same<T, unsigned int>()) return "unsignedInt";
+//     }
+//   };
+//   TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames);
+
+#endif  // 0
+
+// Type-parameterized tests are abstract test patterns parameterized
+// by a type.  Compared with typed tests, type-parameterized tests
+// allow you to define the test pattern without knowing what the type
+// parameters are.  The defined pattern can be instantiated with
+// different types any number of times, in any number of translation
+// units.
+//
+// If you are designing an interface or concept, you can define a
+// suite of type-parameterized tests to verify properties that any
+// valid implementation of the interface/concept should have.  Then,
+// each implementation can easily instantiate the test suite to verify
+// that it conforms to the requirements, without having to write
+// similar tests repeatedly.  Here's an example:
+
+#if 0
+
+// First, define a fixture class template.  It should be parameterized
+// by a type.  Remember to derive it from testing::Test.
+template <typename T>
+class FooTest : public testing::Test {
+  ...
+};
+
+// Next, declare that you will define a type-parameterized test suite
+// (the _P suffix is for "parameterized" or "pattern", whichever you
+// prefer):
+TYPED_TEST_SUITE_P(FooTest);
+
+// Then, use TYPED_TEST_P() to define as many type-parameterized tests
+// for this type-parameterized test suite as you want.
+TYPED_TEST_P(FooTest, DoesBlah) {
+  // Inside a test, refer to TypeParam to get the type parameter.
+  TypeParam n = 0;
+  ...
+}
+
+TYPED_TEST_P(FooTest, HasPropertyA) { ... }
+
+// Now the tricky part: you need to register all test patterns before
+// you can instantiate them.  The first argument of the macro is the
+// test suite name; the rest are the names of the tests in this test
+// case.
+REGISTER_TYPED_TEST_SUITE_P(FooTest,
+                            DoesBlah, HasPropertyA);
+
+// Finally, you are free to instantiate the pattern with the types you
+// want.  If you put the above code in a header file, you can #include
+// it in multiple C++ source files and instantiate it multiple times.
+//
+// To distinguish different instances of the pattern, the first
+// argument to the INSTANTIATE_* macro is a prefix that will be added
+// to the actual test suite name.  Remember to pick unique prefixes for
+// different instances.
+typedef testing::Types<char, int, unsigned int> MyTypes;
+INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
+
+// If the type list contains only one type, you can write that type
+// directly without Types<...>:
+//   INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int);
+//
+// Similar to the optional argument of TYPED_TEST_SUITE above,
+// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to
+// generate custom names.
+//   INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames);
+
+#endif  // 0
+
+
+// Implements typed tests.
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the name of the typedef for the type parameters of the
+// given test suite.
+#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_
+
+// Expands to the name of the typedef for the NameGenerator, responsible for
+// creating the suffixes of the name.
+#define GTEST_NAME_GENERATOR_(TestSuiteName) \
+  gtest_type_params_##TestSuiteName##_NameGenerator
+
+#define TYPED_TEST_SUITE(CaseName, Types, ...)                          \
+  typedef ::testing::internal::GenerateTypeList<Types>::type            \
+      GTEST_TYPE_PARAMS_(CaseName);                                     \
+  typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \
+      GTEST_NAME_GENERATOR_(CaseName)
+
+#define TYPED_TEST(CaseName, TestName)                                        \
+  static_assert(sizeof(GTEST_STRINGIFY_(TestName)) > 1,                       \
+                "test-name must not be empty");                               \
+  template <typename gtest_TypeParam_>                                        \
+  class GTEST_TEST_CLASS_NAME_(CaseName, TestName)                            \
+      : public CaseName<gtest_TypeParam_> {                                   \
+   private:                                                                   \
+    typedef CaseName<gtest_TypeParam_> TestFixture;                           \
+    typedef gtest_TypeParam_ TypeParam;                                       \
+    void TestBody() override;                                                 \
+  };                                                                          \
+  static bool gtest_##CaseName##_##TestName##_registered_                     \
+      GTEST_ATTRIBUTE_UNUSED_ = ::testing::internal::TypeParameterizedTest<   \
+          CaseName,                                                           \
+          ::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(CaseName,   \
+                                                                  TestName)>, \
+          GTEST_TYPE_PARAMS_(                                                 \
+              CaseName)>::Register("",                                        \
+                                   ::testing::internal::CodeLocation(         \
+                                       __FILE__, __LINE__),                   \
+                                   GTEST_STRINGIFY_(CaseName),                \
+                                   GTEST_STRINGIFY_(TestName), 0,             \
+                                   ::testing::internal::GenerateNames<        \
+                                       GTEST_NAME_GENERATOR_(CaseName),       \
+                                       GTEST_TYPE_PARAMS_(CaseName)>());      \
+  template <typename gtest_TypeParam_>                                        \
+  void GTEST_TEST_CLASS_NAME_(CaseName,                                       \
+                              TestName)<gtest_TypeParam_>::TestBody()
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+#define TYPED_TEST_CASE                                                \
+  static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \
+  TYPED_TEST_SUITE
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+// Implements type-parameterized tests.
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the namespace name that the type-parameterized tests for
+// the given type-parameterized test suite are defined in.  The exact
+// name of the namespace is subject to change without notice.
+#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+//
+// Expands to the name of the variable used to remember the names of
+// the defined tests in the given test suite.
+#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \
+  gtest_typed_test_suite_p_state_##TestSuiteName##_
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY.
+//
+// Expands to the name of the variable used to remember the names of
+// the registered tests in the given test suite.
+#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \
+  gtest_registered_test_names_##TestSuiteName##_
+
+// The variables defined in the type-parameterized test macros are
+// static as typically these macros are used in a .h file that can be
+// #included in multiple translation units linked together.
+#define TYPED_TEST_SUITE_P(SuiteName)              \
+  static ::testing::internal::TypedTestSuitePState \
+      GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+#define TYPED_TEST_CASE_P                                                 \
+  static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \
+  TYPED_TEST_SUITE_P
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+#define TYPED_TEST_P(SuiteName, TestName)                             \
+  namespace GTEST_SUITE_NAMESPACE_(SuiteName) {                       \
+    template <typename gtest_TypeParam_>                              \
+    class TestName : public SuiteName<gtest_TypeParam_> {             \
+     private:                                                         \
+      typedef SuiteName<gtest_TypeParam_> TestFixture;                \
+      typedef gtest_TypeParam_ TypeParam;                             \
+      void TestBody() override;                                       \
+    };                                                                \
+    static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \
+        GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName(       \
+            __FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName),          \
+            GTEST_STRINGIFY_(TestName));                              \
+  }                                                                   \
+  template <typename gtest_TypeParam_>                                \
+  void GTEST_SUITE_NAMESPACE_(                                        \
+      SuiteName)::TestName<gtest_TypeParam_>::TestBody()
+
+// Note: this won't work correctly if the trailing arguments are macros.
+#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...)                         \
+  namespace GTEST_SUITE_NAMESPACE_(SuiteName) {                             \
+    typedef ::testing::internal::Templates<__VA_ARGS__> gtest_AllTests_;    \
+  }                                                                         \
+  static const char* const GTEST_REGISTERED_TEST_NAMES_(                    \
+      SuiteName) GTEST_ATTRIBUTE_UNUSED_ =                                  \
+      GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \
+          GTEST_STRINGIFY_(SuiteName), __FILE__, __LINE__, #__VA_ARGS__)
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+#define REGISTER_TYPED_TEST_CASE_P                                           \
+  static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \
+                "");                                                         \
+  REGISTER_TYPED_TEST_SUITE_P
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...)       \
+  static_assert(sizeof(GTEST_STRINGIFY_(Prefix)) > 1,                       \
+                "test-suit-prefix must not be empty");                      \
+  static bool gtest_##Prefix##_##SuiteName GTEST_ATTRIBUTE_UNUSED_ =        \
+      ::testing::internal::TypeParameterizedTestSuite<                      \
+          SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_,    \
+          ::testing::internal::GenerateTypeList<Types>::type>::             \
+          Register(GTEST_STRINGIFY_(Prefix),                                \
+                   ::testing::internal::CodeLocation(__FILE__, __LINE__),   \
+                   &GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName),             \
+                   GTEST_STRINGIFY_(SuiteName),                             \
+                   GTEST_REGISTERED_TEST_NAMES_(SuiteName),                 \
+                   ::testing::internal::GenerateNames<                      \
+                       ::testing::internal::NameGeneratorSelector<          \
+                           __VA_ARGS__>::type,                              \
+                       ::testing::internal::GenerateTypeList<Types>::type>())
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+#define INSTANTIATE_TYPED_TEST_CASE_P                                      \
+  static_assert(                                                           \
+      ::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \
+  INSTANTIATE_TYPED_TEST_SUITE_P
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
+
+GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
+/* class A needs to have dll-interface to be used by clients of class B */)
+
+namespace testing {
+
+// Silence C4100 (unreferenced formal parameter) and 4805
+// unsafe mix of type 'const int' and type 'const bool'
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4805)
+# pragma warning(disable:4100)
+#endif
+
+
+// Declares the flags.
+
+// This flag temporary enables the disabled tests.
+GTEST_DECLARE_bool_(also_run_disabled_tests);
+
+// This flag brings the debugger on an assertion failure.
+GTEST_DECLARE_bool_(break_on_failure);
+
+// This flag controls whether Google Test catches all test-thrown exceptions
+// and logs them as failures.
+GTEST_DECLARE_bool_(catch_exceptions);
+
+// This flag enables using colors in terminal output. Available values are
+// "yes" to enable colors, "no" (disable colors), or "auto" (the default)
+// to let Google Test decide.
+GTEST_DECLARE_string_(color);
+
+// This flag controls whether the test runner should continue execution past
+// first failure.
+GTEST_DECLARE_bool_(fail_fast);
+
+// This flag sets up the filter to select by name using a glob pattern
+// the tests to run. If the filter is not given all tests are executed.
+GTEST_DECLARE_string_(filter);
+
+// This flag controls whether Google Test installs a signal handler that dumps
+// debugging information when fatal signals are raised.
+GTEST_DECLARE_bool_(install_failure_signal_handler);
+
+// This flag causes the Google Test to list tests. None of the tests listed
+// are actually run if the flag is provided.
+GTEST_DECLARE_bool_(list_tests);
+
+// This flag controls whether Google Test emits a detailed XML report to a file
+// in addition to its normal textual output.
+GTEST_DECLARE_string_(output);
+
+// This flags control whether Google Test prints only test failures.
+GTEST_DECLARE_bool_(brief);
+
+// This flags control whether Google Test prints the elapsed time for each
+// test.
+GTEST_DECLARE_bool_(print_time);
+
+// This flags control whether Google Test prints UTF8 characters as text.
+GTEST_DECLARE_bool_(print_utf8);
+
+// This flag specifies the random number seed.
+GTEST_DECLARE_int32_(random_seed);
+
+// This flag sets how many times the tests are repeated. The default value
+// is 1. If the value is -1 the tests are repeating forever.
+GTEST_DECLARE_int32_(repeat);
+
+// This flag controls whether Google Test includes Google Test internal
+// stack frames in failure stack traces.
+GTEST_DECLARE_bool_(show_internal_stack_frames);
+
+// When this flag is specified, tests' order is randomized on every iteration.
+GTEST_DECLARE_bool_(shuffle);
+
+// This flag specifies the maximum number of stack frames to be
+// printed in a failure message.
+GTEST_DECLARE_int32_(stack_trace_depth);
+
+// When this flag is specified, a failed assertion will throw an
+// exception if exceptions are enabled, or exit the program with a
+// non-zero code otherwise. For use with an external test framework.
+GTEST_DECLARE_bool_(throw_on_failure);
+
+// When this flag is set with a "host:port" string, on supported
+// platforms test results are streamed to the specified port on
+// the specified host machine.
+GTEST_DECLARE_string_(stream_result_to);
+
+#if GTEST_USE_OWN_FLAGFILE_FLAG_
+GTEST_DECLARE_string_(flagfile);
+#endif  // GTEST_USE_OWN_FLAGFILE_FLAG_
+
+// The upper limit for valid stack trace depths.
+const int kMaxStackTraceDepth = 100;
+
+namespace internal {
+
+class AssertHelper;
+class DefaultGlobalTestPartResultReporter;
+class ExecDeathTest;
+class NoExecDeathTest;
+class FinalSuccessChecker;
+class GTestFlagSaver;
+class StreamingListenerTest;
+class TestResultAccessor;
+class TestEventListenersAccessor;
+class TestEventRepeater;
+class UnitTestRecordPropertyTestHelper;
+class WindowsDeathTest;
+class FuchsiaDeathTest;
+class UnitTestImpl* GetUnitTestImpl();
+void ReportFailureInUnknownLocation(TestPartResult::Type result_type,
+                                    const std::string& message);
+std::set<std::string>* GetIgnoredParameterizedTestSuites();
+
+}  // namespace internal
+
+// The friend relationship of some of these classes is cyclic.
+// If we don't forward declare them the compiler might confuse the classes
+// in friendship clauses with same named classes on the scope.
+class Test;
+class TestSuite;
+
+// Old API is still available but deprecated
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+using TestCase = TestSuite;
+#endif
+class TestInfo;
+class UnitTest;
+
+// A class for indicating whether an assertion was successful.  When
+// the assertion wasn't successful, the AssertionResult object
+// remembers a non-empty message that describes how it failed.
+//
+// To create an instance of this class, use one of the factory functions
+// (AssertionSuccess() and AssertionFailure()).
+//
+// This class is useful for two purposes:
+//   1. Defining predicate functions to be used with Boolean test assertions
+//      EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts
+//   2. Defining predicate-format functions to be
+//      used with predicate assertions (ASSERT_PRED_FORMAT*, etc).
+//
+// For example, if you define IsEven predicate:
+//
+//   testing::AssertionResult IsEven(int n) {
+//     if ((n % 2) == 0)
+//       return testing::AssertionSuccess();
+//     else
+//       return testing::AssertionFailure() << n << " is odd";
+//   }
+//
+// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5)))
+// will print the message
+//
+//   Value of: IsEven(Fib(5))
+//     Actual: false (5 is odd)
+//   Expected: true
+//
+// instead of a more opaque
+//
+//   Value of: IsEven(Fib(5))
+//     Actual: false
+//   Expected: true
+//
+// in case IsEven is a simple Boolean predicate.
+//
+// If you expect your predicate to be reused and want to support informative
+// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up
+// about half as often as positive ones in our tests), supply messages for
+// both success and failure cases:
+//
+//   testing::AssertionResult IsEven(int n) {
+//     if ((n % 2) == 0)
+//       return testing::AssertionSuccess() << n << " is even";
+//     else
+//       return testing::AssertionFailure() << n << " is odd";
+//   }
+//
+// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print
+//
+//   Value of: IsEven(Fib(6))
+//     Actual: true (8 is even)
+//   Expected: false
+//
+// NB: Predicates that support negative Boolean assertions have reduced
+// performance in positive ones so be careful not to use them in tests
+// that have lots (tens of thousands) of positive Boolean assertions.
+//
+// To use this class with EXPECT_PRED_FORMAT assertions such as:
+//
+//   // Verifies that Foo() returns an even number.
+//   EXPECT_PRED_FORMAT1(IsEven, Foo());
+//
+// you need to define:
+//
+//   testing::AssertionResult IsEven(const char* expr, int n) {
+//     if ((n % 2) == 0)
+//       return testing::AssertionSuccess();
+//     else
+//       return testing::AssertionFailure()
+//         << "Expected: " << expr << " is even\n  Actual: it's " << n;
+//   }
+//
+// If Foo() returns 5, you will see the following message:
+//
+//   Expected: Foo() is even
+//     Actual: it's 5
+//
+class GTEST_API_ AssertionResult {
+ public:
+  // Copy constructor.
+  // Used in EXPECT_TRUE/FALSE(assertion_result).
+  AssertionResult(const AssertionResult& other);
+
+// C4800 is a level 3 warning in Visual Studio 2015 and earlier.
+// This warning is not emitted in Visual Studio 2017.
+// This warning is off by default starting in Visual Studio 2019 but can be
+// enabled with command-line options.
+#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920)
+  GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */)
+#endif
+
+  // Used in the EXPECT_TRUE/FALSE(bool_expression).
+  //
+  // T must be contextually convertible to bool.
+  //
+  // The second parameter prevents this overload from being considered if
+  // the argument is implicitly convertible to AssertionResult. In that case
+  // we want AssertionResult's copy constructor to be used.
+  template <typename T>
+  explicit AssertionResult(
+      const T& success,
+      typename std::enable_if<
+          !std::is_convertible<T, AssertionResult>::value>::type*
+      /*enabler*/
+      = nullptr)
+      : success_(success) {}
+
+#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920)
+  GTEST_DISABLE_MSC_WARNINGS_POP_()
+#endif
+
+  // Assignment operator.
+  AssertionResult& operator=(AssertionResult other) {
+    swap(other);
+    return *this;
+  }
+
+  // Returns true if and only if the assertion succeeded.
+  operator bool() const { return success_; }  // NOLINT
+
+  // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
+  AssertionResult operator!() const;
+
+  // Returns the text streamed into this AssertionResult. Test assertions
+  // use it when they fail (i.e., the predicate's outcome doesn't match the
+  // assertion's expectation). When nothing has been streamed into the
+  // object, returns an empty string.
+  const char* message() const {
+    return message_.get() != nullptr ? message_->c_str() : "";
+  }
+  // Deprecated; please use message() instead.
+  const char* failure_message() const { return message(); }
+
+  // Streams a custom failure message into this object.
+  template <typename T> AssertionResult& operator<<(const T& value) {
+    AppendMessage(Message() << value);
+    return *this;
+  }
+
+  // Allows streaming basic output manipulators such as endl or flush into
+  // this object.
+  AssertionResult& operator<<(
+      ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) {
+    AppendMessage(Message() << basic_manipulator);
+    return *this;
+  }
+
+ private:
+  // Appends the contents of message to message_.
+  void AppendMessage(const Message& a_message) {
+    if (message_.get() == nullptr) message_.reset(new ::std::string);
+    message_->append(a_message.GetString().c_str());
+  }
+
+  // Swap the contents of this AssertionResult with other.
+  void swap(AssertionResult& other);
+
+  // Stores result of the assertion predicate.
+  bool success_;
+  // Stores the message describing the condition in case the expectation
+  // construct is not satisfied with the predicate's outcome.
+  // Referenced via a pointer to avoid taking too much stack frame space
+  // with test assertions.
+  std::unique_ptr< ::std::string> message_;
+};
+
+// Makes a successful assertion result.
+GTEST_API_ AssertionResult AssertionSuccess();
+
+// Makes a failed assertion result.
+GTEST_API_ AssertionResult AssertionFailure();
+
+// Makes a failed assertion result with the given failure message.
+// Deprecated; use AssertionFailure() << msg.
+GTEST_API_ AssertionResult AssertionFailure(const Message& msg);
+
+}  // namespace testing
+
+// Includes the auto-generated header that implements a family of generic
+// predicate assertion macros. This include comes late because it relies on
+// APIs declared above.
+// Copyright 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// This file is AUTOMATICALLY GENERATED on 01/02/2019 by command
+// 'gen_gtest_pred_impl.py 5'.  DO NOT EDIT BY HAND!
+//
+// Implements a family of generic predicate assertion macros.
+// GOOGLETEST_CM0001 DO NOT DELETE
+
+#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+
+
+namespace testing {
+
+// This header implements a family of generic predicate assertion
+// macros:
+//
+//   ASSERT_PRED_FORMAT1(pred_format, v1)
+//   ASSERT_PRED_FORMAT2(pred_format, v1, v2)
+//   ...
+//
+// where pred_format is a function or functor that takes n (in the
+// case of ASSERT_PRED_FORMATn) values and their source expression
+// text, and returns a testing::AssertionResult.  See the definition
+// of ASSERT_EQ in gtest.h for an example.
+//
+// If you don't care about formatting, you can use the more
+// restrictive version:
+//
+//   ASSERT_PRED1(pred, v1)
+//   ASSERT_PRED2(pred, v1, v2)
+//   ...
+//
+// where pred is an n-ary function or functor that returns bool,
+// and the values v1, v2, ..., must support the << operator for
+// streaming to std::ostream.
+//
+// We also define the EXPECT_* variations.
+//
+// For now we only support predicates whose arity is at most 5.
+// Please email googletestframework@googlegroups.com if you need
+// support for higher arities.
+
+// GTEST_ASSERT_ is the basic statement to which all of the assertions
+// in this file reduce.  Don't use this in your code.
+
+#define GTEST_ASSERT_(expression, on_failure) \
+  GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+  if (const ::testing::AssertionResult gtest_ar = (expression)) \
+    ; \
+  else \
+    on_failure(gtest_ar.failure_message())
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED1.  Don't use
+// this in your code.
+template <typename Pred,
+          typename T1>
+AssertionResult AssertPred1Helper(const char* pred_text,
+                                  const char* e1,
+                                  Pred pred,
+                                  const T1& v1) {
+  if (pred(v1)) return AssertionSuccess();
+
+  return AssertionFailure()
+         << pred_text << "(" << e1 << ") evaluates to false, where"
+         << "\n"
+         << e1 << " evaluates to " << ::testing::PrintToString(v1);
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\
+  GTEST_ASSERT_(pred_format(#v1, v1), \
+                on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED1.  Don't use
+// this in your code.
+#define GTEST_PRED1_(pred, v1, on_failure)\
+  GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \
+                                             #v1, \
+                                             pred, \
+                                             v1), on_failure)
+
+// Unary predicate assertion macros.
+#define EXPECT_PRED_FORMAT1(pred_format, v1) \
+  GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED1(pred, v1) \
+  GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT1(pred_format, v1) \
+  GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED1(pred, v1) \
+  GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED2.  Don't use
+// this in your code.
+template <typename Pred,
+          typename T1,
+          typename T2>
+AssertionResult AssertPred2Helper(const char* pred_text,
+                                  const char* e1,
+                                  const char* e2,
+                                  Pred pred,
+                                  const T1& v1,
+                                  const T2& v2) {
+  if (pred(v1, v2)) return AssertionSuccess();
+
+  return AssertionFailure()
+         << pred_text << "(" << e1 << ", " << e2
+         << ") evaluates to false, where"
+         << "\n"
+         << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
+         << e2 << " evaluates to " << ::testing::PrintToString(v2);
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\
+  GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), \
+                on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED2.  Don't use
+// this in your code.
+#define GTEST_PRED2_(pred, v1, v2, on_failure)\
+  GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \
+                                             #v1, \
+                                             #v2, \
+                                             pred, \
+                                             v1, \
+                                             v2), on_failure)
+
+// Binary predicate assertion macros.
+#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
+  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED2(pred, v1, v2) \
+  GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
+  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED2(pred, v1, v2) \
+  GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED3.  Don't use
+// this in your code.
+template <typename Pred,
+          typename T1,
+          typename T2,
+          typename T3>
+AssertionResult AssertPred3Helper(const char* pred_text,
+                                  const char* e1,
+                                  const char* e2,
+                                  const char* e3,
+                                  Pred pred,
+                                  const T1& v1,
+                                  const T2& v2,
+                                  const T3& v3) {
+  if (pred(v1, v2, v3)) return AssertionSuccess();
+
+  return AssertionFailure()
+         << pred_text << "(" << e1 << ", " << e2 << ", " << e3
+         << ") evaluates to false, where"
+         << "\n"
+         << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
+         << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
+         << e3 << " evaluates to " << ::testing::PrintToString(v3);
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\
+  GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), \
+                on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED3.  Don't use
+// this in your code.
+#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\
+  GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \
+                                             #v1, \
+                                             #v2, \
+                                             #v3, \
+                                             pred, \
+                                             v1, \
+                                             v2, \
+                                             v3), on_failure)
+
+// Ternary predicate assertion macros.
+#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \
+  GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED3(pred, v1, v2, v3) \
+  GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \
+  GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED3(pred, v1, v2, v3) \
+  GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED4.  Don't use
+// this in your code.
+template <typename Pred,
+          typename T1,
+          typename T2,
+          typename T3,
+          typename T4>
+AssertionResult AssertPred4Helper(const char* pred_text,
+                                  const char* e1,
+                                  const char* e2,
+                                  const char* e3,
+                                  const char* e4,
+                                  Pred pred,
+                                  const T1& v1,
+                                  const T2& v2,
+                                  const T3& v3,
+                                  const T4& v4) {
+  if (pred(v1, v2, v3, v4)) return AssertionSuccess();
+
+  return AssertionFailure()
+         << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
+         << ") evaluates to false, where"
+         << "\n"
+         << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
+         << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
+         << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
+         << e4 << " evaluates to " << ::testing::PrintToString(v4);
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\
+  GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), \
+                on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED4.  Don't use
+// this in your code.
+#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\
+  GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \
+                                             #v1, \
+                                             #v2, \
+                                             #v3, \
+                                             #v4, \
+                                             pred, \
+                                             v1, \
+                                             v2, \
+                                             v3, \
+                                             v4), on_failure)
+
+// 4-ary predicate assertion macros.
+#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
+  GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED4(pred, v1, v2, v3, v4) \
+  GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
+  GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED4(pred, v1, v2, v3, v4) \
+  GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
+
+
+
+// Helper function for implementing {EXPECT|ASSERT}_PRED5.  Don't use
+// this in your code.
+template <typename Pred,
+          typename T1,
+          typename T2,
+          typename T3,
+          typename T4,
+          typename T5>
+AssertionResult AssertPred5Helper(const char* pred_text,
+                                  const char* e1,
+                                  const char* e2,
+                                  const char* e3,
+                                  const char* e4,
+                                  const char* e5,
+                                  Pred pred,
+                                  const T1& v1,
+                                  const T2& v2,
+                                  const T3& v3,
+                                  const T4& v4,
+                                  const T5& v5) {
+  if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess();
+
+  return AssertionFailure()
+         << pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
+         << ", " << e5 << ") evaluates to false, where"
+         << "\n"
+         << e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
+         << e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
+         << e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
+         << e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n"
+         << e5 << " evaluates to " << ::testing::PrintToString(v5);
+}
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5.
+// Don't use this in your code.
+#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\
+  GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \
+                on_failure)
+
+// Internal macro for implementing {EXPECT|ASSERT}_PRED5.  Don't use
+// this in your code.
+#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\
+  GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \
+                                             #v1, \
+                                             #v2, \
+                                             #v3, \
+                                             #v4, \
+                                             #v5, \
+                                             pred, \
+                                             v1, \
+                                             v2, \
+                                             v3, \
+                                             v4, \
+                                             v5), on_failure)
+
+// 5-ary predicate assertion macros.
+#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
+  GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \
+  GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
+  GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
+#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \
+  GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
+
+
+
+}  // namespace testing
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
+
+namespace testing {
+
+// The abstract class that all tests inherit from.
+//
+// In Google Test, a unit test program contains one or many TestSuites, and
+// each TestSuite contains one or many Tests.
+//
+// When you define a test using the TEST macro, you don't need to
+// explicitly derive from Test - the TEST macro automatically does
+// this for you.
+//
+// The only time you derive from Test is when defining a test fixture
+// to be used in a TEST_F.  For example:
+//
+//   class FooTest : public testing::Test {
+//    protected:
+//     void SetUp() override { ... }
+//     void TearDown() override { ... }
+//     ...
+//   };
+//
+//   TEST_F(FooTest, Bar) { ... }
+//   TEST_F(FooTest, Baz) { ... }
+//
+// Test is not copyable.
+class GTEST_API_ Test {
+ public:
+  friend class TestInfo;
+
+  // The d'tor is virtual as we intend to inherit from Test.
+  virtual ~Test();
+
+  // Sets up the stuff shared by all tests in this test suite.
+  //
+  // Google Test will call Foo::SetUpTestSuite() before running the first
+  // test in test suite Foo.  Hence a sub-class can define its own
+  // SetUpTestSuite() method to shadow the one defined in the super
+  // class.
+  static void SetUpTestSuite() {}
+
+  // Tears down the stuff shared by all tests in this test suite.
+  //
+  // Google Test will call Foo::TearDownTestSuite() after running the last
+  // test in test suite Foo.  Hence a sub-class can define its own
+  // TearDownTestSuite() method to shadow the one defined in the super
+  // class.
+  static void TearDownTestSuite() {}
+
+  // Legacy API is deprecated but still available. Use SetUpTestSuite and
+  // TearDownTestSuite instead.
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  static void TearDownTestCase() {}
+  static void SetUpTestCase() {}
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Returns true if and only if the current test has a fatal failure.
+  static bool HasFatalFailure();
+
+  // Returns true if and only if the current test has a non-fatal failure.
+  static bool HasNonfatalFailure();
+
+  // Returns true if and only if the current test was skipped.
+  static bool IsSkipped();
+
+  // Returns true if and only if the current test has a (either fatal or
+  // non-fatal) failure.
+  static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); }
+
+  // Logs a property for the current test, test suite, or for the entire
+  // invocation of the test program when used outside of the context of a
+  // test suite.  Only the last value for a given key is remembered.  These
+  // are public static so they can be called from utility functions that are
+  // not members of the test fixture.  Calls to RecordProperty made during
+  // lifespan of the test (from the moment its constructor starts to the
+  // moment its destructor finishes) will be output in XML as attributes of
+  // the <testcase> element.  Properties recorded from fixture's
+  // SetUpTestSuite or TearDownTestSuite are logged as attributes of the
+  // corresponding <testsuite> element.  Calls to RecordProperty made in the
+  // global context (before or after invocation of RUN_ALL_TESTS and from
+  // SetUp/TearDown method of Environment objects registered with Google
+  // Test) will be output as attributes of the <testsuites> element.
+  static void RecordProperty(const std::string& key, const std::string& value);
+  static void RecordProperty(const std::string& key, int value);
+
+ protected:
+  // Creates a Test object.
+  Test();
+
+  // Sets up the test fixture.
+  virtual void SetUp();
+
+  // Tears down the test fixture.
+  virtual void TearDown();
+
+ private:
+  // Returns true if and only if the current test has the same fixture class
+  // as the first test in the current test suite.
+  static bool HasSameFixtureClass();
+
+  // Runs the test after the test fixture has been set up.
+  //
+  // A sub-class must implement this to define the test logic.
+  //
+  // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM.
+  // Instead, use the TEST or TEST_F macro.
+  virtual void TestBody() = 0;
+
+  // Sets up, executes, and tears down the test.
+  void Run();
+
+  // Deletes self.  We deliberately pick an unusual name for this
+  // internal method to avoid clashing with names used in user TESTs.
+  void DeleteSelf_() { delete this; }
+
+  const std::unique_ptr<GTEST_FLAG_SAVER_> gtest_flag_saver_;
+
+  // Often a user misspells SetUp() as Setup() and spends a long time
+  // wondering why it is never called by Google Test.  The declaration of
+  // the following method is solely for catching such an error at
+  // compile time:
+  //
+  //   - The return type is deliberately chosen to be not void, so it
+  //   will be a conflict if void Setup() is declared in the user's
+  //   test fixture.
+  //
+  //   - This method is private, so it will be another compiler error
+  //   if the method is called from the user's test fixture.
+  //
+  // DO NOT OVERRIDE THIS FUNCTION.
+  //
+  // If you see an error about overriding the following function or
+  // about it being private, you have mis-spelled SetUp() as Setup().
+  struct Setup_should_be_spelled_SetUp {};
+  virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; }
+
+  // We disallow copying Tests.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(Test);
+};
+
+typedef internal::TimeInMillis TimeInMillis;
+
+// A copyable object representing a user specified test property which can be
+// output as a key/value string pair.
+//
+// Don't inherit from TestProperty as its destructor is not virtual.
+class TestProperty {
+ public:
+  // C'tor.  TestProperty does NOT have a default constructor.
+  // Always use this constructor (with parameters) to create a
+  // TestProperty object.
+  TestProperty(const std::string& a_key, const std::string& a_value) :
+    key_(a_key), value_(a_value) {
+  }
+
+  // Gets the user supplied key.
+  const char* key() const {
+    return key_.c_str();
+  }
+
+  // Gets the user supplied value.
+  const char* value() const {
+    return value_.c_str();
+  }
+
+  // Sets a new value, overriding the one supplied in the constructor.
+  void SetValue(const std::string& new_value) {
+    value_ = new_value;
+  }
+
+ private:
+  // The key supplied by the user.
+  std::string key_;
+  // The value supplied by the user.
+  std::string value_;
+};
+
+// The result of a single Test.  This includes a list of
+// TestPartResults, a list of TestProperties, a count of how many
+// death tests there are in the Test, and how much time it took to run
+// the Test.
+//
+// TestResult is not copyable.
+class GTEST_API_ TestResult {
+ public:
+  // Creates an empty TestResult.
+  TestResult();
+
+  // D'tor.  Do not inherit from TestResult.
+  ~TestResult();
+
+  // Gets the number of all test parts.  This is the sum of the number
+  // of successful test parts and the number of failed test parts.
+  int total_part_count() const;
+
+  // Returns the number of the test properties.
+  int test_property_count() const;
+
+  // Returns true if and only if the test passed (i.e. no test part failed).
+  bool Passed() const { return !Skipped() && !Failed(); }
+
+  // Returns true if and only if the test was skipped.
+  bool Skipped() const;
+
+  // Returns true if and only if the test failed.
+  bool Failed() const;
+
+  // Returns true if and only if the test fatally failed.
+  bool HasFatalFailure() const;
+
+  // Returns true if and only if the test has a non-fatal failure.
+  bool HasNonfatalFailure() const;
+
+  // Returns the elapsed time, in milliseconds.
+  TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+  // Gets the time of the test case start, in ms from the start of the
+  // UNIX epoch.
+  TimeInMillis start_timestamp() const { return start_timestamp_; }
+
+  // Returns the i-th test part result among all the results. i can range from 0
+  // to total_part_count() - 1. If i is not in that range, aborts the program.
+  const TestPartResult& GetTestPartResult(int i) const;
+
+  // Returns the i-th test property. i can range from 0 to
+  // test_property_count() - 1. If i is not in that range, aborts the
+  // program.
+  const TestProperty& GetTestProperty(int i) const;
+
+ private:
+  friend class TestInfo;
+  friend class TestSuite;
+  friend class UnitTest;
+  friend class internal::DefaultGlobalTestPartResultReporter;
+  friend class internal::ExecDeathTest;
+  friend class internal::TestResultAccessor;
+  friend class internal::UnitTestImpl;
+  friend class internal::WindowsDeathTest;
+  friend class internal::FuchsiaDeathTest;
+
+  // Gets the vector of TestPartResults.
+  const std::vector<TestPartResult>& test_part_results() const {
+    return test_part_results_;
+  }
+
+  // Gets the vector of TestProperties.
+  const std::vector<TestProperty>& test_properties() const {
+    return test_properties_;
+  }
+
+  // Sets the start time.
+  void set_start_timestamp(TimeInMillis start) { start_timestamp_ = start; }
+
+  // Sets the elapsed time.
+  void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; }
+
+  // Adds a test property to the list. The property is validated and may add
+  // a non-fatal failure if invalid (e.g., if it conflicts with reserved
+  // key names). If a property is already recorded for the same key, the
+  // value will be updated, rather than storing multiple values for the same
+  // key.  xml_element specifies the element for which the property is being
+  // recorded and is used for validation.
+  void RecordProperty(const std::string& xml_element,
+                      const TestProperty& test_property);
+
+  // Adds a failure if the key is a reserved attribute of Google Test
+  // testsuite tags.  Returns true if the property is valid.
+  // FIXME: Validate attribute names are legal and human readable.
+  static bool ValidateTestProperty(const std::string& xml_element,
+                                   const TestProperty& test_property);
+
+  // Adds a test part result to the list.
+  void AddTestPartResult(const TestPartResult& test_part_result);
+
+  // Returns the death test count.
+  int death_test_count() const { return death_test_count_; }
+
+  // Increments the death test count, returning the new count.
+  int increment_death_test_count() { return ++death_test_count_; }
+
+  // Clears the test part results.
+  void ClearTestPartResults();
+
+  // Clears the object.
+  void Clear();
+
+  // Protects mutable state of the property vector and of owned
+  // properties, whose values may be updated.
+  internal::Mutex test_properties_mutex_;
+
+  // The vector of TestPartResults
+  std::vector<TestPartResult> test_part_results_;
+  // The vector of TestProperties
+  std::vector<TestProperty> test_properties_;
+  // Running count of death tests.
+  int death_test_count_;
+  // The start time, in milliseconds since UNIX Epoch.
+  TimeInMillis start_timestamp_;
+  // The elapsed time, in milliseconds.
+  TimeInMillis elapsed_time_;
+
+  // We disallow copying TestResult.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult);
+};  // class TestResult
+
+// A TestInfo object stores the following information about a test:
+//
+//   Test suite name
+//   Test name
+//   Whether the test should be run
+//   A function pointer that creates the test object when invoked
+//   Test result
+//
+// The constructor of TestInfo registers itself with the UnitTest
+// singleton such that the RUN_ALL_TESTS() macro knows which tests to
+// run.
+class GTEST_API_ TestInfo {
+ public:
+  // Destructs a TestInfo object.  This function is not virtual, so
+  // don't inherit from TestInfo.
+  ~TestInfo();
+
+  // Returns the test suite name.
+  const char* test_suite_name() const { return test_suite_name_.c_str(); }
+
+// Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  const char* test_case_name() const { return test_suite_name(); }
+#endif  // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Returns the test name.
+  const char* name() const { return name_.c_str(); }
+
+  // Returns the name of the parameter type, or NULL if this is not a typed
+  // or a type-parameterized test.
+  const char* type_param() const {
+    if (type_param_.get() != nullptr) return type_param_->c_str();
+    return nullptr;
+  }
+
+  // Returns the text representation of the value parameter, or NULL if this
+  // is not a value-parameterized test.
+  const char* value_param() const {
+    if (value_param_.get() != nullptr) return value_param_->c_str();
+    return nullptr;
+  }
+
+  // Returns the file name where this test is defined.
+  const char* file() const { return location_.file.c_str(); }
+
+  // Returns the line where this test is defined.
+  int line() const { return location_.line; }
+
+  // Return true if this test should not be run because it's in another shard.
+  bool is_in_another_shard() const { return is_in_another_shard_; }
+
+  // Returns true if this test should run, that is if the test is not
+  // disabled (or it is disabled but the also_run_disabled_tests flag has
+  // been specified) and its full name matches the user-specified filter.
+  //
+  // Google Test allows the user to filter the tests by their full names.
+  // The full name of a test Bar in test suite Foo is defined as
+  // "Foo.Bar".  Only the tests that match the filter will run.
+  //
+  // A filter is a colon-separated list of glob (not regex) patterns,
+  // optionally followed by a '-' and a colon-separated list of
+  // negative patterns (tests to exclude).  A test is run if it
+  // matches one of the positive patterns and does not match any of
+  // the negative patterns.
+  //
+  // For example, *A*:Foo.* is a filter that matches any string that
+  // contains the character 'A' or starts with "Foo.".
+  bool should_run() const { return should_run_; }
+
+  // Returns true if and only if this test will appear in the XML report.
+  bool is_reportable() const {
+    // The XML report includes tests matching the filter, excluding those
+    // run in other shards.
+    return matches_filter_ && !is_in_another_shard_;
+  }
+
+  // Returns the result of the test.
+  const TestResult* result() const { return &result_; }
+
+ private:
+#if GTEST_HAS_DEATH_TEST
+  friend class internal::DefaultDeathTestFactory;
+#endif  // GTEST_HAS_DEATH_TEST
+  friend class Test;
+  friend class TestSuite;
+  friend class internal::UnitTestImpl;
+  friend class internal::StreamingListenerTest;
+  friend TestInfo* internal::MakeAndRegisterTestInfo(
+      const char* test_suite_name, const char* name, const char* type_param,
+      const char* value_param, internal::CodeLocation code_location,
+      internal::TypeId fixture_class_id, internal::SetUpTestSuiteFunc set_up_tc,
+      internal::TearDownTestSuiteFunc tear_down_tc,
+      internal::TestFactoryBase* factory);
+
+  // Constructs a TestInfo object. The newly constructed instance assumes
+  // ownership of the factory object.
+  TestInfo(const std::string& test_suite_name, const std::string& name,
+           const char* a_type_param,   // NULL if not a type-parameterized test
+           const char* a_value_param,  // NULL if not a value-parameterized test
+           internal::CodeLocation a_code_location,
+           internal::TypeId fixture_class_id,
+           internal::TestFactoryBase* factory);
+
+  // Increments the number of death tests encountered in this test so
+  // far.
+  int increment_death_test_count() {
+    return result_.increment_death_test_count();
+  }
+
+  // Creates the test object, runs it, records its result, and then
+  // deletes it.
+  void Run();
+
+  // Skip and records the test result for this object.
+  void Skip();
+
+  static void ClearTestResult(TestInfo* test_info) {
+    test_info->result_.Clear();
+  }
+
+  // These fields are immutable properties of the test.
+  const std::string test_suite_name_;    // test suite name
+  const std::string name_;               // Test name
+  // Name of the parameter type, or NULL if this is not a typed or a
+  // type-parameterized test.
+  const std::unique_ptr<const ::std::string> type_param_;
+  // Text representation of the value parameter, or NULL if this is not a
+  // value-parameterized test.
+  const std::unique_ptr<const ::std::string> value_param_;
+  internal::CodeLocation location_;
+  const internal::TypeId fixture_class_id_;  // ID of the test fixture class
+  bool should_run_;           // True if and only if this test should run
+  bool is_disabled_;          // True if and only if this test is disabled
+  bool matches_filter_;       // True if this test matches the
+                              // user-specified filter.
+  bool is_in_another_shard_;  // Will be run in another shard.
+  internal::TestFactoryBase* const factory_;  // The factory that creates
+                                              // the test object
+
+  // This field is mutable and needs to be reset before running the
+  // test for the second time.
+  TestResult result_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo);
+};
+
+// A test suite, which consists of a vector of TestInfos.
+//
+// TestSuite is not copyable.
+class GTEST_API_ TestSuite {
+ public:
+  // Creates a TestSuite with the given name.
+  //
+  // TestSuite does NOT have a default constructor.  Always use this
+  // constructor to create a TestSuite object.
+  //
+  // Arguments:
+  //
+  //   name:         name of the test suite
+  //   a_type_param: the name of the test's type parameter, or NULL if
+  //                 this is not a type-parameterized test.
+  //   set_up_tc:    pointer to the function that sets up the test suite
+  //   tear_down_tc: pointer to the function that tears down the test suite
+  TestSuite(const char* name, const char* a_type_param,
+            internal::SetUpTestSuiteFunc set_up_tc,
+            internal::TearDownTestSuiteFunc tear_down_tc);
+
+  // Destructor of TestSuite.
+  virtual ~TestSuite();
+
+  // Gets the name of the TestSuite.
+  const char* name() const { return name_.c_str(); }
+
+  // Returns the name of the parameter type, or NULL if this is not a
+  // type-parameterized test suite.
+  const char* type_param() const {
+    if (type_param_.get() != nullptr) return type_param_->c_str();
+    return nullptr;
+  }
+
+  // Returns true if any test in this test suite should run.
+  bool should_run() const { return should_run_; }
+
+  // Gets the number of successful tests in this test suite.
+  int successful_test_count() const;
+
+  // Gets the number of skipped tests in this test suite.
+  int skipped_test_count() const;
+
+  // Gets the number of failed tests in this test suite.
+  int failed_test_count() const;
+
+  // Gets the number of disabled tests that will be reported in the XML report.
+  int reportable_disabled_test_count() const;
+
+  // Gets the number of disabled tests in this test suite.
+  int disabled_test_count() const;
+
+  // Gets the number of tests to be printed in the XML report.
+  int reportable_test_count() const;
+
+  // Get the number of tests in this test suite that should run.
+  int test_to_run_count() const;
+
+  // Gets the number of all tests in this test suite.
+  int total_test_count() const;
+
+  // Returns true if and only if the test suite passed.
+  bool Passed() const { return !Failed(); }
+
+  // Returns true if and only if the test suite failed.
+  bool Failed() const {
+    return failed_test_count() > 0 || ad_hoc_test_result().Failed();
+  }
+
+  // Returns the elapsed time, in milliseconds.
+  TimeInMillis elapsed_time() const { return elapsed_time_; }
+
+  // Gets the time of the test suite start, in ms from the start of the
+  // UNIX epoch.
+  TimeInMillis start_timestamp() const { return start_timestamp_; }
+
+  // Returns the i-th test among all the tests. i can range from 0 to
+  // total_test_count() - 1. If i is not in that range, returns NULL.
+  const TestInfo* GetTestInfo(int i) const;
+
+  // Returns the TestResult that holds test properties recorded during
+  // execution of SetUpTestSuite and TearDownTestSuite.
+  const TestResult& ad_hoc_test_result() const { return ad_hoc_test_result_; }
+
+ private:
+  friend class Test;
+  friend class internal::UnitTestImpl;
+
+  // Gets the (mutable) vector of TestInfos in this TestSuite.
+  std::vector<TestInfo*>& test_info_list() { return test_info_list_; }
+
+  // Gets the (immutable) vector of TestInfos in this TestSuite.
+  const std::vector<TestInfo*>& test_info_list() const {
+    return test_info_list_;
+  }
+
+  // Returns the i-th test among all the tests. i can range from 0 to
+  // total_test_count() - 1. If i is not in that range, returns NULL.
+  TestInfo* GetMutableTestInfo(int i);
+
+  // Sets the should_run member.
+  void set_should_run(bool should) { should_run_ = should; }
+
+  // Adds a TestInfo to this test suite.  Will delete the TestInfo upon
+  // destruction of the TestSuite object.
+  void AddTestInfo(TestInfo * test_info);
+
+  // Clears the results of all tests in this test suite.
+  void ClearResult();
+
+  // Clears the results of all tests in the given test suite.
+  static void ClearTestSuiteResult(TestSuite* test_suite) {
+    test_suite->ClearResult();
+  }
+
+  // Runs every test in this TestSuite.
+  void Run();
+
+  // Skips the execution of tests under this TestSuite
+  void Skip();
+
+  // Runs SetUpTestSuite() for this TestSuite.  This wrapper is needed
+  // for catching exceptions thrown from SetUpTestSuite().
+  void RunSetUpTestSuite() {
+    if (set_up_tc_ != nullptr) {
+      (*set_up_tc_)();
+    }
+  }
+
+  // Runs TearDownTestSuite() for this TestSuite.  This wrapper is
+  // needed for catching exceptions thrown from TearDownTestSuite().
+  void RunTearDownTestSuite() {
+    if (tear_down_tc_ != nullptr) {
+      (*tear_down_tc_)();
+    }
+  }
+
+  // Returns true if and only if test passed.
+  static bool TestPassed(const TestInfo* test_info) {
+    return test_info->should_run() && test_info->result()->Passed();
+  }
+
+  // Returns true if and only if test skipped.
+  static bool TestSkipped(const TestInfo* test_info) {
+    return test_info->should_run() && test_info->result()->Skipped();
+  }
+
+  // Returns true if and only if test failed.
+  static bool TestFailed(const TestInfo* test_info) {
+    return test_info->should_run() && test_info->result()->Failed();
+  }
+
+  // Returns true if and only if the test is disabled and will be reported in
+  // the XML report.
+  static bool TestReportableDisabled(const TestInfo* test_info) {
+    return test_info->is_reportable() && test_info->is_disabled_;
+  }
+
+  // Returns true if and only if test is disabled.
+  static bool TestDisabled(const TestInfo* test_info) {
+    return test_info->is_disabled_;
+  }
+
+  // Returns true if and only if this test will appear in the XML report.
+  static bool TestReportable(const TestInfo* test_info) {
+    return test_info->is_reportable();
+  }
+
+  // Returns true if the given test should run.
+  static bool ShouldRunTest(const TestInfo* test_info) {
+    return test_info->should_run();
+  }
+
+  // Shuffles the tests in this test suite.
+  void ShuffleTests(internal::Random* random);
+
+  // Restores the test order to before the first shuffle.
+  void UnshuffleTests();
+
+  // Name of the test suite.
+  std::string name_;
+  // Name of the parameter type, or NULL if this is not a typed or a
+  // type-parameterized test.
+  const std::unique_ptr<const ::std::string> type_param_;
+  // The vector of TestInfos in their original order.  It owns the
+  // elements in the vector.
+  std::vector<TestInfo*> test_info_list_;
+  // Provides a level of indirection for the test list to allow easy
+  // shuffling and restoring the test order.  The i-th element in this
+  // vector is the index of the i-th test in the shuffled test list.
+  std::vector<int> test_indices_;
+  // Pointer to the function that sets up the test suite.
+  internal::SetUpTestSuiteFunc set_up_tc_;
+  // Pointer to the function that tears down the test suite.
+  internal::TearDownTestSuiteFunc tear_down_tc_;
+  // True if and only if any test in this test suite should run.
+  bool should_run_;
+  // The start time, in milliseconds since UNIX Epoch.
+  TimeInMillis start_timestamp_;
+  // Elapsed time, in milliseconds.
+  TimeInMillis elapsed_time_;
+  // Holds test properties recorded during execution of SetUpTestSuite and
+  // TearDownTestSuite.
+  TestResult ad_hoc_test_result_;
+
+  // We disallow copying TestSuites.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestSuite);
+};
+
+// An Environment object is capable of setting up and tearing down an
+// environment.  You should subclass this to define your own
+// environment(s).
+//
+// An Environment object does the set-up and tear-down in virtual
+// methods SetUp() and TearDown() instead of the constructor and the
+// destructor, as:
+//
+//   1. You cannot safely throw from a destructor.  This is a problem
+//      as in some cases Google Test is used where exceptions are enabled, and
+//      we may want to implement ASSERT_* using exceptions where they are
+//      available.
+//   2. You cannot use ASSERT_* directly in a constructor or
+//      destructor.
+class Environment {
+ public:
+  // The d'tor is virtual as we need to subclass Environment.
+  virtual ~Environment() {}
+
+  // Override this to define how to set up the environment.
+  virtual void SetUp() {}
+
+  // Override this to define how to tear down the environment.
+  virtual void TearDown() {}
+ private:
+  // If you see an error about overriding the following function or
+  // about it being private, you have mis-spelled SetUp() as Setup().
+  struct Setup_should_be_spelled_SetUp {};
+  virtual Setup_should_be_spelled_SetUp* Setup() { return nullptr; }
+};
+
+#if GTEST_HAS_EXCEPTIONS
+
+// Exception which can be thrown from TestEventListener::OnTestPartResult.
+class GTEST_API_ AssertionException
+    : public internal::GoogleTestFailureException {
+ public:
+  explicit AssertionException(const TestPartResult& result)
+      : GoogleTestFailureException(result) {}
+};
+
+#endif  // GTEST_HAS_EXCEPTIONS
+
+// The interface for tracing execution of tests. The methods are organized in
+// the order the corresponding events are fired.
+class TestEventListener {
+ public:
+  virtual ~TestEventListener() {}
+
+  // Fired before any test activity starts.
+  virtual void OnTestProgramStart(const UnitTest& unit_test) = 0;
+
+  // Fired before each iteration of tests starts.  There may be more than
+  // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration
+  // index, starting from 0.
+  virtual void OnTestIterationStart(const UnitTest& unit_test,
+                                    int iteration) = 0;
+
+  // Fired before environment set-up for each iteration of tests starts.
+  virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0;
+
+  // Fired after environment set-up for each iteration of tests ends.
+  virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0;
+
+  // Fired before the test suite starts.
+  virtual void OnTestSuiteStart(const TestSuite& /*test_suite*/) {}
+
+  //  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  virtual void OnTestCaseStart(const TestCase& /*test_case*/) {}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Fired before the test starts.
+  virtual void OnTestStart(const TestInfo& test_info) = 0;
+
+  // Fired after a failed assertion or a SUCCEED() invocation.
+  // If you want to throw an exception from this function to skip to the next
+  // TEST, it must be AssertionException defined above, or inherited from it.
+  virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0;
+
+  // Fired after the test ends.
+  virtual void OnTestEnd(const TestInfo& test_info) = 0;
+
+  // Fired after the test suite ends.
+  virtual void OnTestSuiteEnd(const TestSuite& /*test_suite*/) {}
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Fired before environment tear-down for each iteration of tests starts.
+  virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0;
+
+  // Fired after environment tear-down for each iteration of tests ends.
+  virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0;
+
+  // Fired after each iteration of tests finishes.
+  virtual void OnTestIterationEnd(const UnitTest& unit_test,
+                                  int iteration) = 0;
+
+  // Fired after all test activities have ended.
+  virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0;
+};
+
+// The convenience class for users who need to override just one or two
+// methods and are not concerned that a possible change to a signature of
+// the methods they override will not be caught during the build.  For
+// comments about each method please see the definition of TestEventListener
+// above.
+class EmptyTestEventListener : public TestEventListener {
+ public:
+  void OnTestProgramStart(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationStart(const UnitTest& /*unit_test*/,
+                            int /*iteration*/) override {}
+  void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) override {}
+  void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) override {}
+  void OnTestSuiteStart(const TestSuite& /*test_suite*/) override {}
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseStart(const TestCase& /*test_case*/) override {}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  void OnTestStart(const TestInfo& /*test_info*/) override {}
+  void OnTestPartResult(const TestPartResult& /*test_part_result*/) override {}
+  void OnTestEnd(const TestInfo& /*test_info*/) override {}
+  void OnTestSuiteEnd(const TestSuite& /*test_suite*/) override {}
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  void OnTestCaseEnd(const TestCase& /*test_case*/) override {}
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) override {}
+  void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) override {}
+  void OnTestIterationEnd(const UnitTest& /*unit_test*/,
+                          int /*iteration*/) override {}
+  void OnTestProgramEnd(const UnitTest& /*unit_test*/) override {}
+};
+
+// TestEventListeners lets users add listeners to track events in Google Test.
+class GTEST_API_ TestEventListeners {
+ public:
+  TestEventListeners();
+  ~TestEventListeners();
+
+  // Appends an event listener to the end of the list. Google Test assumes
+  // the ownership of the listener (i.e. it will delete the listener when
+  // the test program finishes).
+  void Append(TestEventListener* listener);
+
+  // Removes the given event listener from the list and returns it.  It then
+  // becomes the caller's responsibility to delete the listener. Returns
+  // NULL if the listener is not found in the list.
+  TestEventListener* Release(TestEventListener* listener);
+
+  // Returns the standard listener responsible for the default console
+  // output.  Can be removed from the listeners list to shut down default
+  // console output.  Note that removing this object from the listener list
+  // with Release transfers its ownership to the caller and makes this
+  // function return NULL the next time.
+  TestEventListener* default_result_printer() const {
+    return default_result_printer_;
+  }
+
+  // Returns the standard listener responsible for the default XML output
+  // controlled by the --gtest_output=xml flag.  Can be removed from the
+  // listeners list by users who want to shut down the default XML output
+  // controlled by this flag and substitute it with custom one.  Note that
+  // removing this object from the listener list with Release transfers its
+  // ownership to the caller and makes this function return NULL the next
+  // time.
+  TestEventListener* default_xml_generator() const {
+    return default_xml_generator_;
+  }
+
+ private:
+  friend class TestSuite;
+  friend class TestInfo;
+  friend class internal::DefaultGlobalTestPartResultReporter;
+  friend class internal::NoExecDeathTest;
+  friend class internal::TestEventListenersAccessor;
+  friend class internal::UnitTestImpl;
+
+  // Returns repeater that broadcasts the TestEventListener events to all
+  // subscribers.
+  TestEventListener* repeater();
+
+  // Sets the default_result_printer attribute to the provided listener.
+  // The listener is also added to the listener list and previous
+  // default_result_printer is removed from it and deleted. The listener can
+  // also be NULL in which case it will not be added to the list. Does
+  // nothing if the previous and the current listener objects are the same.
+  void SetDefaultResultPrinter(TestEventListener* listener);
+
+  // Sets the default_xml_generator attribute to the provided listener.  The
+  // listener is also added to the listener list and previous
+  // default_xml_generator is removed from it and deleted. The listener can
+  // also be NULL in which case it will not be added to the list. Does
+  // nothing if the previous and the current listener objects are the same.
+  void SetDefaultXmlGenerator(TestEventListener* listener);
+
+  // Controls whether events will be forwarded by the repeater to the
+  // listeners in the list.
+  bool EventForwardingEnabled() const;
+  void SuppressEventForwarding();
+
+  // The actual list of listeners.
+  internal::TestEventRepeater* repeater_;
+  // Listener responsible for the standard result output.
+  TestEventListener* default_result_printer_;
+  // Listener responsible for the creation of the XML output file.
+  TestEventListener* default_xml_generator_;
+
+  // We disallow copying TestEventListeners.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners);
+};
+
+// A UnitTest consists of a vector of TestSuites.
+//
+// This is a singleton class.  The only instance of UnitTest is
+// created when UnitTest::GetInstance() is first called.  This
+// instance is never deleted.
+//
+// UnitTest is not copyable.
+//
+// This class is thread-safe as long as the methods are called
+// according to their specification.
+class GTEST_API_ UnitTest {
+ public:
+  // Gets the singleton UnitTest object.  The first time this method
+  // is called, a UnitTest object is constructed and returned.
+  // Consecutive calls will return the same object.
+  static UnitTest* GetInstance();
+
+  // Runs all tests in this UnitTest object and prints the result.
+  // Returns 0 if successful, or 1 otherwise.
+  //
+  // This method can only be called from the main thread.
+  //
+  // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+  int Run() GTEST_MUST_USE_RESULT_;
+
+  // Returns the working directory when the first TEST() or TEST_F()
+  // was executed.  The UnitTest object owns the string.
+  const char* original_working_dir() const;
+
+  // Returns the TestSuite object for the test that's currently running,
+  // or NULL if no test is running.
+  const TestSuite* current_test_suite() const GTEST_LOCK_EXCLUDED_(mutex_);
+
+// Legacy API is still available but deprecated
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  const TestCase* current_test_case() const GTEST_LOCK_EXCLUDED_(mutex_);
+#endif
+
+  // Returns the TestInfo object for the test that's currently running,
+  // or NULL if no test is running.
+  const TestInfo* current_test_info() const
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  // Returns the random seed used at the start of the current test run.
+  int random_seed() const;
+
+  // Returns the ParameterizedTestSuiteRegistry object used to keep track of
+  // value-parameterized tests and instantiate and register them.
+  //
+  // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+  internal::ParameterizedTestSuiteRegistry& parameterized_test_registry()
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  // Gets the number of successful test suites.
+  int successful_test_suite_count() const;
+
+  // Gets the number of failed test suites.
+  int failed_test_suite_count() const;
+
+  // Gets the number of all test suites.
+  int total_test_suite_count() const;
+
+  // Gets the number of all test suites that contain at least one test
+  // that should run.
+  int test_suite_to_run_count() const;
+
+  //  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  int successful_test_case_count() const;
+  int failed_test_case_count() const;
+  int total_test_case_count() const;
+  int test_case_to_run_count() const;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Gets the number of successful tests.
+  int successful_test_count() const;
+
+  // Gets the number of skipped tests.
+  int skipped_test_count() const;
+
+  // Gets the number of failed tests.
+  int failed_test_count() const;
+
+  // Gets the number of disabled tests that will be reported in the XML report.
+  int reportable_disabled_test_count() const;
+
+  // Gets the number of disabled tests.
+  int disabled_test_count() const;
+
+  // Gets the number of tests to be printed in the XML report.
+  int reportable_test_count() const;
+
+  // Gets the number of all tests.
+  int total_test_count() const;
+
+  // Gets the number of tests that should run.
+  int test_to_run_count() const;
+
+  // Gets the time of the test program start, in ms from the start of the
+  // UNIX epoch.
+  TimeInMillis start_timestamp() const;
+
+  // Gets the elapsed time, in milliseconds.
+  TimeInMillis elapsed_time() const;
+
+  // Returns true if and only if the unit test passed (i.e. all test suites
+  // passed).
+  bool Passed() const;
+
+  // Returns true if and only if the unit test failed (i.e. some test suite
+  // failed or something outside of all tests failed).
+  bool Failed() const;
+
+  // Gets the i-th test suite among all the test suites. i can range from 0 to
+  // total_test_suite_count() - 1. If i is not in that range, returns NULL.
+  const TestSuite* GetTestSuite(int i) const;
+
+//  Legacy API is deprecated but still available
+#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+  const TestCase* GetTestCase(int i) const;
+#endif  //  GTEST_REMOVE_LEGACY_TEST_CASEAPI_
+
+  // Returns the TestResult containing information on test failures and
+  // properties logged outside of individual test suites.
+  const TestResult& ad_hoc_test_result() const;
+
+  // Returns the list of event listeners that can be used to track events
+  // inside Google Test.
+  TestEventListeners& listeners();
+
+ private:
+  // Registers and returns a global test environment.  When a test
+  // program is run, all global test environments will be set-up in
+  // the order they were registered.  After all tests in the program
+  // have finished, all global test environments will be torn-down in
+  // the *reverse* order they were registered.
+  //
+  // The UnitTest object takes ownership of the given environment.
+  //
+  // This method can only be called from the main thread.
+  Environment* AddEnvironment(Environment* env);
+
+  // Adds a TestPartResult to the current TestResult object.  All
+  // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc)
+  // eventually call this to report their results.  The user code
+  // should use the assertion macros instead of calling this directly.
+  void AddTestPartResult(TestPartResult::Type result_type,
+                         const char* file_name,
+                         int line_number,
+                         const std::string& message,
+                         const std::string& os_stack_trace)
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  // Adds a TestProperty to the current TestResult object when invoked from
+  // inside a test, to current TestSuite's ad_hoc_test_result_ when invoked
+  // from SetUpTestSuite or TearDownTestSuite, or to the global property set
+  // when invoked elsewhere.  If the result already contains a property with
+  // the same key, the value will be updated.
+  void RecordProperty(const std::string& key, const std::string& value);
+
+  // Gets the i-th test suite among all the test suites. i can range from 0 to
+  // total_test_suite_count() - 1. If i is not in that range, returns NULL.
+  TestSuite* GetMutableTestSuite(int i);
+
+  // Accessors for the implementation object.
+  internal::UnitTestImpl* impl() { return impl_; }
+  const internal::UnitTestImpl* impl() const { return impl_; }
+
+  // These classes and functions are friends as they need to access private
+  // members of UnitTest.
+  friend class ScopedTrace;
+  friend class Test;
+  friend class internal::AssertHelper;
+  friend class internal::StreamingListenerTest;
+  friend class internal::UnitTestRecordPropertyTestHelper;
+  friend Environment* AddGlobalTestEnvironment(Environment* env);
+  friend std::set<std::string>* internal::GetIgnoredParameterizedTestSuites();
+  friend internal::UnitTestImpl* internal::GetUnitTestImpl();
+  friend void internal::ReportFailureInUnknownLocation(
+      TestPartResult::Type result_type,
+      const std::string& message);
+
+  // Creates an empty UnitTest.
+  UnitTest();
+
+  // D'tor
+  virtual ~UnitTest();
+
+  // Pushes a trace defined by SCOPED_TRACE() on to the per-thread
+  // Google Test trace stack.
+  void PushGTestTrace(const internal::TraceInfo& trace)
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  // Pops a trace from the per-thread Google Test trace stack.
+  void PopGTestTrace()
+      GTEST_LOCK_EXCLUDED_(mutex_);
+
+  // Protects mutable state in *impl_.  This is mutable as some const
+  // methods need to lock it too.
+  mutable internal::Mutex mutex_;
+
+  // Opaque implementation object.  This field is never changed once
+  // the object is constructed.  We don't mark it as const here, as
+  // doing so will cause a warning in the constructor of UnitTest.
+  // Mutable state in *impl_ is protected by mutex_.
+  internal::UnitTestImpl* impl_;
+
+  // We disallow copying UnitTest.
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest);
+};
+
+// A convenient wrapper for adding an environment for the test
+// program.
+//
+// You should call this before RUN_ALL_TESTS() is called, probably in
+// main().  If you use gtest_main, you need to call this before main()
+// starts for it to take effect.  For example, you can define a global
+// variable like this:
+//
+//   testing::Environment* const foo_env =
+//       testing::AddGlobalTestEnvironment(new FooEnvironment);
+//
+// However, we strongly recommend you to write your own main() and
+// call AddGlobalTestEnvironment() there, as relying on initialization
+// of global variables makes the code harder to read and may cause
+// problems when you register multiple environments from different
+// translation units and the environments have dependencies among them
+// (remember that the compiler doesn't guarantee the order in which
+// global variables from different translation units are initialized).
+inline Environment* AddGlobalTestEnvironment(Environment* env) {
+  return UnitTest::GetInstance()->AddEnvironment(env);
+}
+
+// Initializes Google Test.  This must be called before calling
+// RUN_ALL_TESTS().  In particular, it parses a command line for the
+// flags that Google Test recognizes.  Whenever a Google Test flag is
+// seen, it is removed from argv, and *argc is decremented.
+//
+// No value is returned.  Instead, the Google Test flag variables are
+// updated.
+//
+// Calling the function for the second time has no user-visible effect.
+GTEST_API_ void InitGoogleTest(int* argc, char** argv);
+
+// This overloaded version can be used in Windows programs compiled in
+// UNICODE mode.
+GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv);
+
+// This overloaded version can be used on Arduino/embedded platforms where
+// there is no argc/argv.
+GTEST_API_ void InitGoogleTest();
+
+namespace internal {
+
+// Separate the error generating code from the code path to reduce the stack
+// frame size of CmpHelperEQ. This helps reduce the overhead of some sanitizers
+// when calling EXPECT_* in a tight loop.
+template <typename T1, typename T2>
+AssertionResult CmpHelperEQFailure(const char* lhs_expression,
+                                   const char* rhs_expression,
+                                   const T1& lhs, const T2& rhs) {
+  return EqFailure(lhs_expression,
+                   rhs_expression,
+                   FormatForComparisonFailureMessage(lhs, rhs),
+                   FormatForComparisonFailureMessage(rhs, lhs),
+                   false);
+}
+
+// This block of code defines operator==/!=
+// to block lexical scope lookup.
+// It prevents using invalid operator==/!= defined at namespace scope.
+struct faketype {};
+inline bool operator==(faketype, faketype) { return true; }
+inline bool operator!=(faketype, faketype) { return false; }
+
+// The helper function for {ASSERT|EXPECT}_EQ.
+template <typename T1, typename T2>
+AssertionResult CmpHelperEQ(const char* lhs_expression,
+                            const char* rhs_expression,
+                            const T1& lhs,
+                            const T2& rhs) {
+  if (lhs == rhs) {
+    return AssertionSuccess();
+  }
+
+  return CmpHelperEQFailure(lhs_expression, rhs_expression, lhs, rhs);
+}
+
+class EqHelper {
+ public:
+  // This templatized version is for the general case.
+  template <
+      typename T1, typename T2,
+      // Disable this overload for cases where one argument is a pointer
+      // and the other is the null pointer constant.
+      typename std::enable_if<!std::is_integral<T1>::value ||
+                              !std::is_pointer<T2>::value>::type* = nullptr>
+  static AssertionResult Compare(const char* lhs_expression,
+                                 const char* rhs_expression, const T1& lhs,
+                                 const T2& rhs) {
+    return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
+  }
+
+  // With this overloaded version, we allow anonymous enums to be used
+  // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous
+  // enums can be implicitly cast to BiggestInt.
+  //
+  // Even though its body looks the same as the above version, we
+  // cannot merge the two, as it will make anonymous enums unhappy.
+  static AssertionResult Compare(const char* lhs_expression,
+                                 const char* rhs_expression,
+                                 BiggestInt lhs,
+                                 BiggestInt rhs) {
+    return CmpHelperEQ(lhs_expression, rhs_expression, lhs, rhs);
+  }
+
+  template <typename T>
+  static AssertionResult Compare(
+      const char* lhs_expression, const char* rhs_expression,
+      // Handle cases where '0' is used as a null pointer literal.
+      std::nullptr_t /* lhs */, T* rhs) {
+    // We already know that 'lhs' is a null pointer.
+    return CmpHelperEQ(lhs_expression, rhs_expression, static_cast<T*>(nullptr),
+                       rhs);
+  }
+};
+
+// Separate the error generating code from the code path to reduce the stack
+// frame size of CmpHelperOP. This helps reduce the overhead of some sanitizers
+// when calling EXPECT_OP in a tight loop.
+template <typename T1, typename T2>
+AssertionResult CmpHelperOpFailure(const char* expr1, const char* expr2,
+                                   const T1& val1, const T2& val2,
+                                   const char* op) {
+  return AssertionFailure()
+         << "Expected: (" << expr1 << ") " << op << " (" << expr2
+         << "), actual: " << FormatForComparisonFailureMessage(val1, val2)
+         << " vs " << FormatForComparisonFailureMessage(val2, val1);
+}
+
+// A macro for implementing the helper functions needed to implement
+// ASSERT_?? and EXPECT_??.  It is here just to avoid copy-and-paste
+// of similar code.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+#define GTEST_IMPL_CMP_HELPER_(op_name, op)\
+template <typename T1, typename T2>\
+AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \
+                                   const T1& val1, const T2& val2) {\
+  if (val1 op val2) {\
+    return AssertionSuccess();\
+  } else {\
+    return CmpHelperOpFailure(expr1, expr2, val1, val2, #op);\
+  }\
+}
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+
+// Implements the helper function for {ASSERT|EXPECT}_NE
+GTEST_IMPL_CMP_HELPER_(NE, !=)
+// Implements the helper function for {ASSERT|EXPECT}_LE
+GTEST_IMPL_CMP_HELPER_(LE, <=)
+// Implements the helper function for {ASSERT|EXPECT}_LT
+GTEST_IMPL_CMP_HELPER_(LT, <)
+// Implements the helper function for {ASSERT|EXPECT}_GE
+GTEST_IMPL_CMP_HELPER_(GE, >=)
+// Implements the helper function for {ASSERT|EXPECT}_GT
+GTEST_IMPL_CMP_HELPER_(GT, >)
+
+#undef GTEST_IMPL_CMP_HELPER_
+
+// The helper function for {ASSERT|EXPECT}_STREQ.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression,
+                                          const char* s2_expression,
+                                          const char* s1,
+                                          const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRCASEEQ.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* s1_expression,
+                                              const char* s2_expression,
+                                              const char* s1,
+                                              const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRNE.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression,
+                                          const char* s2_expression,
+                                          const char* s1,
+                                          const char* s2);
+
+// The helper function for {ASSERT|EXPECT}_STRCASENE.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression,
+                                              const char* s2_expression,
+                                              const char* s1,
+                                              const char* s2);
+
+
+// Helper function for *_STREQ on wide strings.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTREQ(const char* s1_expression,
+                                          const char* s2_expression,
+                                          const wchar_t* s1,
+                                          const wchar_t* s2);
+
+// Helper function for *_STRNE on wide strings.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression,
+                                          const char* s2_expression,
+                                          const wchar_t* s1,
+                                          const wchar_t* s2);
+
+}  // namespace internal
+
+// IsSubstring() and IsNotSubstring() are intended to be used as the
+// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by
+// themselves.  They check whether needle is a substring of haystack
+// (NULL is considered a substring of itself only), and return an
+// appropriate error message when they fail.
+//
+// The {needle,haystack}_expr arguments are the stringified
+// expressions that generated the two real arguments.
+GTEST_API_ AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const char* needle, const char* haystack);
+GTEST_API_ AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const wchar_t* needle, const wchar_t* haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const char* needle, const char* haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const wchar_t* needle, const wchar_t* haystack);
+GTEST_API_ AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::string& needle, const ::std::string& haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::string& needle, const ::std::string& haystack);
+
+#if GTEST_HAS_STD_WSTRING
+GTEST_API_ AssertionResult IsSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::wstring& needle, const ::std::wstring& haystack);
+GTEST_API_ AssertionResult IsNotSubstring(
+    const char* needle_expr, const char* haystack_expr,
+    const ::std::wstring& needle, const ::std::wstring& haystack);
+#endif  // GTEST_HAS_STD_WSTRING
+
+namespace internal {
+
+// Helper template function for comparing floating-points.
+//
+// Template parameter:
+//
+//   RawType: the raw floating-point type (either float or double)
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+template <typename RawType>
+AssertionResult CmpHelperFloatingPointEQ(const char* lhs_expression,
+                                         const char* rhs_expression,
+                                         RawType lhs_value,
+                                         RawType rhs_value) {
+  const FloatingPoint<RawType> lhs(lhs_value), rhs(rhs_value);
+
+  if (lhs.AlmostEquals(rhs)) {
+    return AssertionSuccess();
+  }
+
+  ::std::stringstream lhs_ss;
+  lhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+         << lhs_value;
+
+  ::std::stringstream rhs_ss;
+  rhs_ss << std::setprecision(std::numeric_limits<RawType>::digits10 + 2)
+         << rhs_value;
+
+  return EqFailure(lhs_expression,
+                   rhs_expression,
+                   StringStreamToString(&lhs_ss),
+                   StringStreamToString(&rhs_ss),
+                   false);
+}
+
+// Helper function for implementing ASSERT_NEAR.
+//
+// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
+GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1,
+                                                const char* expr2,
+                                                const char* abs_error_expr,
+                                                double val1,
+                                                double val2,
+                                                double abs_error);
+
+// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
+// A class that enables one to stream messages to assertion macros
+class GTEST_API_ AssertHelper {
+ public:
+  // Constructor.
+  AssertHelper(TestPartResult::Type type,
+               const char* file,
+               int line,
+               const char* message);
+  ~AssertHelper();
+
+  // Message assignment is a semantic trick to enable assertion
+  // streaming; see the GTEST_MESSAGE_ macro below.
+  void operator=(const Message& message) const;
+
+ private:
+  // We put our data in a struct so that the size of the AssertHelper class can
+  // be as small as possible.  This is important because gcc is incapable of
+  // re-using stack space even for temporary variables, so every EXPECT_EQ
+  // reserves stack space for another AssertHelper.
+  struct AssertHelperData {
+    AssertHelperData(TestPartResult::Type t,
+                     const char* srcfile,
+                     int line_num,
+                     const char* msg)
+        : type(t), file(srcfile), line(line_num), message(msg) { }
+
+    TestPartResult::Type const type;
+    const char* const file;
+    int const line;
+    std::string const message;
+
+   private:
+    GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData);
+  };
+
+  AssertHelperData* const data_;
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper);
+};
+
+}  // namespace internal
+
+// The pure interface class that all value-parameterized tests inherit from.
+// A value-parameterized class must inherit from both ::testing::Test and
+// ::testing::WithParamInterface. In most cases that just means inheriting
+// from ::testing::TestWithParam, but more complicated test hierarchies
+// may need to inherit from Test and WithParamInterface at different levels.
+//
+// This interface has support for accessing the test parameter value via
+// the GetParam() method.
+//
+// Use it with one of the parameter generator defining functions, like Range(),
+// Values(), ValuesIn(), Bool(), and Combine().
+//
+// class FooTest : public ::testing::TestWithParam<int> {
+//  protected:
+//   FooTest() {
+//     // Can use GetParam() here.
+//   }
+//   ~FooTest() override {
+//     // Can use GetParam() here.
+//   }
+//   void SetUp() override {
+//     // Can use GetParam() here.
+//   }
+//   void TearDown override {
+//     // Can use GetParam() here.
+//   }
+// };
+// TEST_P(FooTest, DoesBar) {
+//   // Can use GetParam() method here.
+//   Foo foo;
+//   ASSERT_TRUE(foo.DoesBar(GetParam()));
+// }
+// INSTANTIATE_TEST_SUITE_P(OneToTenRange, FooTest, ::testing::Range(1, 10));
+
+template <typename T>
+class WithParamInterface {
+ public:
+  typedef T ParamType;
+  virtual ~WithParamInterface() {}
+
+  // The current parameter value. Is also available in the test fixture's
+  // constructor.
+  static const ParamType& GetParam() {
+    GTEST_CHECK_(parameter_ != nullptr)
+        << "GetParam() can only be called inside a value-parameterized test "
+        << "-- did you intend to write TEST_P instead of TEST_F?";
+    return *parameter_;
+  }
+
+ private:
+  // Sets parameter value. The caller is responsible for making sure the value
+  // remains alive and unchanged throughout the current test.
+  static void SetParam(const ParamType* parameter) {
+    parameter_ = parameter;
+  }
+
+  // Static value used for accessing parameter during a test lifetime.
+  static const ParamType* parameter_;
+
+  // TestClass must be a subclass of WithParamInterface<T> and Test.
+  template <class TestClass> friend class internal::ParameterizedTestFactory;
+};
+
+template <typename T>
+const T* WithParamInterface<T>::parameter_ = nullptr;
+
+// Most value-parameterized classes can ignore the existence of
+// WithParamInterface, and can just inherit from ::testing::TestWithParam.
+
+template <typename T>
+class TestWithParam : public Test, public WithParamInterface<T> {
+};
+
+// Macros for indicating success/failure in test code.
+
+// Skips test in runtime.
+// Skipping test aborts current function.
+// Skipped tests are neither successful nor failed.
+#define GTEST_SKIP() GTEST_SKIP_("")
+
+// ADD_FAILURE unconditionally adds a failure to the current test.
+// SUCCEED generates a success - it doesn't automatically make the
+// current test successful, as a test is only successful when it has
+// no failure.
+//
+// EXPECT_* verifies that a certain condition is satisfied.  If not,
+// it behaves like ADD_FAILURE.  In particular:
+//
+//   EXPECT_TRUE  verifies that a Boolean condition is true.
+//   EXPECT_FALSE verifies that a Boolean condition is false.
+//
+// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except
+// that they will also abort the current function on failure.  People
+// usually want the fail-fast behavior of FAIL and ASSERT_*, but those
+// writing data-driven tests often find themselves using ADD_FAILURE
+// and EXPECT_* more.
+
+// Generates a nonfatal failure with a generic message.
+#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed")
+
+// Generates a nonfatal failure at the given source file location with
+// a generic message.
+#define ADD_FAILURE_AT(file, line) \
+  GTEST_MESSAGE_AT_(file, line, "Failed", \
+                    ::testing::TestPartResult::kNonFatalFailure)
+
+// Generates a fatal failure with a generic message.
+#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed")
+
+// Like GTEST_FAIL(), but at the given source file location.
+#define GTEST_FAIL_AT(file, line)         \
+  GTEST_MESSAGE_AT_(file, line, "Failed", \
+                    ::testing::TestPartResult::kFatalFailure)
+
+// Define this macro to 1 to omit the definition of FAIL(), which is a
+// generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_FAIL
+# define FAIL() GTEST_FAIL()
+#endif
+
+// Generates a success with a generic message.
+#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded")
+
+// Define this macro to 1 to omit the definition of SUCCEED(), which
+// is a generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_SUCCEED
+# define SUCCEED() GTEST_SUCCEED()
+#endif
+
+// Macros for testing exceptions.
+//
+//    * {ASSERT|EXPECT}_THROW(statement, expected_exception):
+//         Tests that the statement throws the expected exception.
+//    * {ASSERT|EXPECT}_NO_THROW(statement):
+//         Tests that the statement doesn't throw any exception.
+//    * {ASSERT|EXPECT}_ANY_THROW(statement):
+//         Tests that the statement throws an exception.
+
+#define EXPECT_THROW(statement, expected_exception) \
+  GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_NO_THROW(statement) \
+  GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_)
+#define EXPECT_ANY_THROW(statement) \
+  GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_)
+#define ASSERT_THROW(statement, expected_exception) \
+  GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_)
+#define ASSERT_NO_THROW(statement) \
+  GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_)
+#define ASSERT_ANY_THROW(statement) \
+  GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_)
+
+// Boolean assertions. Condition can be either a Boolean expression or an
+// AssertionResult. For more information on how to use AssertionResult with
+// these macros see comments on that class.
+#define GTEST_EXPECT_TRUE(condition) \
+  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
+                      GTEST_NONFATAL_FAILURE_)
+#define GTEST_EXPECT_FALSE(condition) \
+  GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \
+                      GTEST_NONFATAL_FAILURE_)
+#define GTEST_ASSERT_TRUE(condition) \
+  GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \
+                      GTEST_FATAL_FAILURE_)
+#define GTEST_ASSERT_FALSE(condition) \
+  GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \
+                      GTEST_FATAL_FAILURE_)
+
+// Define these macros to 1 to omit the definition of the corresponding
+// EXPECT or ASSERT, which clashes with some users' own code.
+
+#if !GTEST_DONT_DEFINE_EXPECT_TRUE
+#define EXPECT_TRUE(condition) GTEST_EXPECT_TRUE(condition)
+#endif
+
+#if !GTEST_DONT_DEFINE_EXPECT_FALSE
+#define EXPECT_FALSE(condition) GTEST_EXPECT_FALSE(condition)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_TRUE
+#define ASSERT_TRUE(condition) GTEST_ASSERT_TRUE(condition)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_FALSE
+#define ASSERT_FALSE(condition) GTEST_ASSERT_FALSE(condition)
+#endif
+
+// Macros for testing equalities and inequalities.
+//
+//    * {ASSERT|EXPECT}_EQ(v1, v2): Tests that v1 == v2
+//    * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2
+//    * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2
+//    * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2
+//    * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2
+//    * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2
+//
+// When they are not, Google Test prints both the tested expressions and
+// their actual values.  The values must be compatible built-in types,
+// or you will get a compiler error.  By "compatible" we mean that the
+// values can be compared by the respective operator.
+//
+// Note:
+//
+//   1. It is possible to make a user-defined type work with
+//   {ASSERT|EXPECT}_??(), but that requires overloading the
+//   comparison operators and is thus discouraged by the Google C++
+//   Usage Guide.  Therefore, you are advised to use the
+//   {ASSERT|EXPECT}_TRUE() macro to assert that two objects are
+//   equal.
+//
+//   2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on
+//   pointers (in particular, C strings).  Therefore, if you use it
+//   with two C strings, you are testing how their locations in memory
+//   are related, not how their content is related.  To compare two C
+//   strings by content, use {ASSERT|EXPECT}_STR*().
+//
+//   3. {ASSERT|EXPECT}_EQ(v1, v2) is preferred to
+//   {ASSERT|EXPECT}_TRUE(v1 == v2), as the former tells you
+//   what the actual value is when it fails, and similarly for the
+//   other comparisons.
+//
+//   4. Do not depend on the order in which {ASSERT|EXPECT}_??()
+//   evaluate their arguments, which is undefined.
+//
+//   5. These macros evaluate their arguments exactly once.
+//
+// Examples:
+//
+//   EXPECT_NE(Foo(), 5);
+//   EXPECT_EQ(a_pointer, NULL);
+//   ASSERT_LT(i, array_size);
+//   ASSERT_GT(records.size(), 0) << "There is no record left.";
+
+#define EXPECT_EQ(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
+#define EXPECT_NE(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define EXPECT_LE(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
+#define EXPECT_LT(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define EXPECT_GE(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2)
+#define EXPECT_GT(val1, val2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+
+#define GTEST_ASSERT_EQ(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::EqHelper::Compare, val1, val2)
+#define GTEST_ASSERT_NE(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2)
+#define GTEST_ASSERT_LE(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
+#define GTEST_ASSERT_LT(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
+#define GTEST_ASSERT_GE(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2)
+#define GTEST_ASSERT_GT(val1, val2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2)
+
+// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of
+// ASSERT_XY(), which clashes with some users' own code.
+
+#if !GTEST_DONT_DEFINE_ASSERT_EQ
+# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_NE
+# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_LE
+# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_LT
+# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_GE
+# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2)
+#endif
+
+#if !GTEST_DONT_DEFINE_ASSERT_GT
+# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2)
+#endif
+
+// C-string Comparisons.  All tests treat NULL and any non-NULL string
+// as different.  Two NULLs are equal.
+//
+//    * {ASSERT|EXPECT}_STREQ(s1, s2):     Tests that s1 == s2
+//    * {ASSERT|EXPECT}_STRNE(s1, s2):     Tests that s1 != s2
+//    * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case
+//    * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case
+//
+// For wide or narrow string objects, you can use the
+// {ASSERT|EXPECT}_??() macros.
+//
+// Don't depend on the order in which the arguments are evaluated,
+// which is undefined.
+//
+// These macros evaluate their arguments exactly once.
+
+#define EXPECT_STREQ(s1, s2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2)
+#define EXPECT_STRNE(s1, s2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2)
+#define EXPECT_STRCASEEQ(s1, s2) \
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2)
+#define EXPECT_STRCASENE(s1, s2)\
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2)
+
+#define ASSERT_STREQ(s1, s2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, s1, s2)
+#define ASSERT_STRNE(s1, s2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2)
+#define ASSERT_STRCASEEQ(s1, s2) \
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, s1, s2)
+#define ASSERT_STRCASENE(s1, s2)\
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2)
+
+// Macros for comparing floating-point numbers.
+//
+//    * {ASSERT|EXPECT}_FLOAT_EQ(val1, val2):
+//         Tests that two float values are almost equal.
+//    * {ASSERT|EXPECT}_DOUBLE_EQ(val1, val2):
+//         Tests that two double values are almost equal.
+//    * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error):
+//         Tests that v1 and v2 are within the given distance to each other.
+//
+// Google Test uses ULP-based comparison to automatically pick a default
+// error bound that is appropriate for the operands.  See the
+// FloatingPoint template class in gtest-internal.h if you are
+// interested in the implementation details.
+
+#define EXPECT_FLOAT_EQ(val1, val2)\
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \
+                      val1, val2)
+
+#define EXPECT_DOUBLE_EQ(val1, val2)\
+  EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \
+                      val1, val2)
+
+#define ASSERT_FLOAT_EQ(val1, val2)\
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<float>, \
+                      val1, val2)
+
+#define ASSERT_DOUBLE_EQ(val1, val2)\
+  ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ<double>, \
+                      val1, val2)
+
+#define EXPECT_NEAR(val1, val2, abs_error)\
+  EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \
+                      val1, val2, abs_error)
+
+#define ASSERT_NEAR(val1, val2, abs_error)\
+  ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \
+                      val1, val2, abs_error)
+
+// These predicate format functions work on floating-point values, and
+// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g.
+//
+//   EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0);
+
+// Asserts that val1 is less than, or almost equal to, val2.  Fails
+// otherwise.  In particular, it fails if either val1 or val2 is NaN.
+GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2,
+                                   float val1, float val2);
+GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2,
+                                    double val1, double val2);
+
+
+#if GTEST_OS_WINDOWS
+
+// Macros that test for HRESULT failure and success, these are only useful
+// on Windows, and rely on Windows SDK macros and APIs to compile.
+//
+//    * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr)
+//
+// When expr unexpectedly fails or succeeds, Google Test prints the
+// expected result and the actual result with both a human-readable
+// string representation of the error, if available, as well as the
+// hex result code.
+# define EXPECT_HRESULT_SUCCEEDED(expr) \
+    EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))
+
+# define ASSERT_HRESULT_SUCCEEDED(expr) \
+    ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr))
+
+# define EXPECT_HRESULT_FAILED(expr) \
+    EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr))
+
+# define ASSERT_HRESULT_FAILED(expr) \
+    ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr))
+
+#endif  // GTEST_OS_WINDOWS
+
+// Macros that execute statement and check that it doesn't generate new fatal
+// failures in the current thread.
+//
+//   * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement);
+//
+// Examples:
+//
+//   EXPECT_NO_FATAL_FAILURE(Process());
+//   ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed";
+//
+#define ASSERT_NO_FATAL_FAILURE(statement) \
+    GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_)
+#define EXPECT_NO_FATAL_FAILURE(statement) \
+    GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_)
+
+// Causes a trace (including the given source file path and line number,
+// and the given message) to be included in every test failure message generated
+// by code in the scope of the lifetime of an instance of this class. The effect
+// is undone with the destruction of the instance.
+//
+// The message argument can be anything streamable to std::ostream.
+//
+// Example:
+//   testing::ScopedTrace trace("file.cc", 123, "message");
+//
+class GTEST_API_ ScopedTrace {
+ public:
+  // The c'tor pushes the given source file location and message onto
+  // a trace stack maintained by Google Test.
+
+  // Template version. Uses Message() to convert the values into strings.
+  // Slow, but flexible.
+  template <typename T>
+  ScopedTrace(const char* file, int line, const T& message) {
+    PushTrace(file, line, (Message() << message).GetString());
+  }
+
+  // Optimize for some known types.
+  ScopedTrace(const char* file, int line, const char* message) {
+    PushTrace(file, line, message ? message : "(null)");
+  }
+
+  ScopedTrace(const char* file, int line, const std::string& message) {
+    PushTrace(file, line, message);
+  }
+
+  // The d'tor pops the info pushed by the c'tor.
+  //
+  // Note that the d'tor is not virtual in order to be efficient.
+  // Don't inherit from ScopedTrace!
+  ~ScopedTrace();
+
+ private:
+  void PushTrace(const char* file, int line, std::string message);
+
+  GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace);
+} GTEST_ATTRIBUTE_UNUSED_;  // A ScopedTrace object does its job in its
+                            // c'tor and d'tor.  Therefore it doesn't
+                            // need to be used otherwise.
+
+// Causes a trace (including the source file path, the current line
+// number, and the given message) to be included in every test failure
+// message generated by code in the current scope.  The effect is
+// undone when the control leaves the current scope.
+//
+// The message argument can be anything streamable to std::ostream.
+//
+// In the implementation, we include the current line number as part
+// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s
+// to appear in the same block - as long as they are on different
+// lines.
+//
+// Assuming that each thread maintains its own stack of traces.
+// Therefore, a SCOPED_TRACE() would (correctly) only affect the
+// assertions in its own thread.
+#define SCOPED_TRACE(message) \
+  ::testing::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\
+    __FILE__, __LINE__, (message))
+
+// Compile-time assertion for type equality.
+// StaticAssertTypeEq<type1, type2>() compiles if and only if type1 and type2
+// are the same type.  The value it returns is not interesting.
+//
+// Instead of making StaticAssertTypeEq a class template, we make it a
+// function template that invokes a helper class template.  This
+// prevents a user from misusing StaticAssertTypeEq<T1, T2> by
+// defining objects of that type.
+//
+// CAVEAT:
+//
+// When used inside a method of a class template,
+// StaticAssertTypeEq<T1, T2>() is effective ONLY IF the method is
+// instantiated.  For example, given:
+//
+//   template <typename T> class Foo {
+//    public:
+//     void Bar() { testing::StaticAssertTypeEq<int, T>(); }
+//   };
+//
+// the code:
+//
+//   void Test1() { Foo<bool> foo; }
+//
+// will NOT generate a compiler error, as Foo<bool>::Bar() is never
+// actually instantiated.  Instead, you need:
+//
+//   void Test2() { Foo<bool> foo; foo.Bar(); }
+//
+// to cause a compiler error.
+template <typename T1, typename T2>
+constexpr bool StaticAssertTypeEq() noexcept {
+  static_assert(std::is_same<T1, T2>::value, "T1 and T2 are not the same type");
+  return true;
+}
+
+// Defines a test.
+//
+// The first parameter is the name of the test suite, and the second
+// parameter is the name of the test within the test suite.
+//
+// The convention is to end the test suite name with "Test".  For
+// example, a test suite for the Foo class can be named FooTest.
+//
+// Test code should appear between braces after an invocation of
+// this macro.  Example:
+//
+//   TEST(FooTest, InitializesCorrectly) {
+//     Foo foo;
+//     EXPECT_TRUE(foo.StatusIsOK());
+//   }
+
+// Note that we call GetTestTypeId() instead of GetTypeId<
+// ::testing::Test>() here to get the type ID of testing::Test.  This
+// is to work around a suspected linker bug when using Google Test as
+// a framework on Mac OS X.  The bug causes GetTypeId<
+// ::testing::Test>() to return different values depending on whether
+// the call is from the Google Test framework itself or from user test
+// code.  GetTestTypeId() is guaranteed to always return the same
+// value, as it always calls GetTypeId<>() from the Google Test
+// framework.
+#define GTEST_TEST(test_suite_name, test_name)             \
+  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
+              ::testing::internal::GetTestTypeId())
+
+// Define this macro to 1 to omit the definition of TEST(), which
+// is a generic name and clashes with some other libraries.
+#if !GTEST_DONT_DEFINE_TEST
+#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)
+#endif
+
+// Defines a test that uses a test fixture.
+//
+// The first parameter is the name of the test fixture class, which
+// also doubles as the test suite name.  The second parameter is the
+// name of the test within the test suite.
+//
+// A test fixture class must be declared earlier.  The user should put
+// the test code between braces after using this macro.  Example:
+//
+//   class FooTest : public testing::Test {
+//    protected:
+//     void SetUp() override { b_.AddElement(3); }
+//
+//     Foo a_;
+//     Foo b_;
+//   };
+//
+//   TEST_F(FooTest, InitializesCorrectly) {
+//     EXPECT_TRUE(a_.StatusIsOK());
+//   }
+//
+//   TEST_F(FooTest, ReturnsElementCountCorrectly) {
+//     EXPECT_EQ(a_.size(), 0);
+//     EXPECT_EQ(b_.size(), 1);
+//   }
+//
+// GOOGLETEST_CM0011 DO NOT DELETE
+#if !GTEST_DONT_DEFINE_TEST
+#define TEST_F(test_fixture, test_name)\
+  GTEST_TEST_(test_fixture, test_name, test_fixture, \
+              ::testing::internal::GetTypeId<test_fixture>())
+#endif  // !GTEST_DONT_DEFINE_TEST
+
+// Returns a path to temporary directory.
+// Tries to determine an appropriate directory for the platform.
+GTEST_API_ std::string TempDir();
+
+#ifdef _MSC_VER
+#  pragma warning(pop)
+#endif
+
+// Dynamically registers a test with the framework.
+//
+// This is an advanced API only to be used when the `TEST` macros are
+// insufficient. The macros should be preferred when possible, as they avoid
+// most of the complexity of calling this function.
+//
+// The `factory` argument is a factory callable (move-constructible) object or
+// function pointer that creates a new instance of the Test object. It
+// handles ownership to the caller. The signature of the callable is
+// `Fixture*()`, where `Fixture` is the test fixture class for the test. All
+// tests registered with the same `test_suite_name` must return the same
+// fixture type. This is checked at runtime.
+//
+// The framework will infer the fixture class from the factory and will call
+// the `SetUpTestSuite` and `TearDownTestSuite` for it.
+//
+// Must be called before `RUN_ALL_TESTS()` is invoked, otherwise behavior is
+// undefined.
+//
+// Use case example:
+//
+// class MyFixture : public ::testing::Test {
+//  public:
+//   // All of these optional, just like in regular macro usage.
+//   static void SetUpTestSuite() { ... }
+//   static void TearDownTestSuite() { ... }
+//   void SetUp() override { ... }
+//   void TearDown() override { ... }
+// };
+//
+// class MyTest : public MyFixture {
+//  public:
+//   explicit MyTest(int data) : data_(data) {}
+//   void TestBody() override { ... }
+//
+//  private:
+//   int data_;
+// };
+//
+// void RegisterMyTests(const std::vector<int>& values) {
+//   for (int v : values) {
+//     ::testing::RegisterTest(
+//         "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr,
+//         std::to_string(v).c_str(),
+//         __FILE__, __LINE__,
+//         // Important to use the fixture type as the return type here.
+//         [=]() -> MyFixture* { return new MyTest(v); });
+//   }
+// }
+// ...
+// int main(int argc, char** argv) {
+//   std::vector<int> values_to_test = LoadValuesFromConfig();
+//   RegisterMyTests(values_to_test);
+//   ...
+//   return RUN_ALL_TESTS();
+// }
+//
+template <int&... ExplicitParameterBarrier, typename Factory>
+TestInfo* RegisterTest(const char* test_suite_name, const char* test_name,
+                       const char* type_param, const char* value_param,
+                       const char* file, int line, Factory factory) {
+  using TestT = typename std::remove_pointer<decltype(factory())>::type;
+
+  class FactoryImpl : public internal::TestFactoryBase {
+   public:
+    explicit FactoryImpl(Factory f) : factory_(std::move(f)) {}
+    Test* CreateTest() override { return factory_(); }
+
+   private:
+    Factory factory_;
+  };
+
+  return internal::MakeAndRegisterTestInfo(
+      test_suite_name, test_name, type_param, value_param,
+      internal::CodeLocation(file, line), internal::GetTypeId<TestT>(),
+      internal::SuiteApiResolver<TestT>::GetSetUpCaseOrSuite(file, line),
+      internal::SuiteApiResolver<TestT>::GetTearDownCaseOrSuite(file, line),
+      new FactoryImpl{std::move(factory)});
+}
+
+}  // namespace testing
+
+// Use this function in main() to run all tests.  It returns 0 if all
+// tests are successful, or 1 otherwise.
+//
+// RUN_ALL_TESTS() should be invoked after the command line has been
+// parsed by InitGoogleTest().
+//
+// This function was formerly a macro; thus, it is in the global
+// namespace and has an all-caps name.
+int RUN_ALL_TESTS() GTEST_MUST_USE_RESULT_;
+
+inline int RUN_ALL_TESTS() {
+  return ::testing::UnitTest::GetInstance()->Run();
+}
+
+GTEST_DISABLE_MSC_WARNINGS_POP_()  //  4251
+
+#endif  // GOOGLETEST_INCLUDE_GTEST_GTEST_H_
diff --git a/libs/fmt/test/header-only-test.cc b/libs/fmt/test/header-only-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f0c3a7fa9ff3379f24055146526d594eb7aa93e9
--- /dev/null
+++ b/libs/fmt/test/header-only-test.cc
@@ -0,0 +1,7 @@
+// Header-only configuration test
+
+#include "fmt/core.h"
+
+#ifndef FMT_HEADER_ONLY
+#  error "Not in the header-only mode."
+#endif
diff --git a/libs/fmt/test/locale-test.cc b/libs/fmt/test/locale-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0a7da95239a5460d3a63720bcdd16d661f821ea3
--- /dev/null
+++ b/libs/fmt/test/locale-test.cc
@@ -0,0 +1,166 @@
+// Formatting library for C++ - locale tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/locale.h"
+
+#include <complex>
+
+#include "gmock/gmock.h"
+
+using fmt::detail::max_value;
+
+#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
+template <typename Char> struct numpunct : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const override { return '?'; }
+  std::string do_grouping() const override { return "\03"; }
+  Char do_thousands_sep() const override { return '~'; }
+};
+
+template <typename Char> struct no_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const override { return '.'; }
+  std::string do_grouping() const override { return ""; }
+  Char do_thousands_sep() const override { return ','; }
+};
+
+template <typename Char> struct special_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const override { return '.'; }
+  std::string do_grouping() const override { return "\03\02"; }
+  Char do_thousands_sep() const override { return ','; }
+};
+
+template <typename Char> struct small_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const override { return '.'; }
+  std::string do_grouping() const override { return "\01"; }
+  Char do_thousands_sep() const override { return ','; }
+};
+
+TEST(locale_test, double_decimal_point) {
+  auto loc = std::locale(std::locale(), new numpunct<char>());
+  EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
+  EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23));
+}
+
+TEST(locale_test, format) {
+  auto loc = std::locale(std::locale(), new numpunct<char>());
+  EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567));
+  EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
+  EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
+  EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
+  fmt::format_arg_store<fmt::format_context, int> as{1234567};
+  EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
+  auto s = std::string();
+  fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
+  EXPECT_EQ("1~234~567", s);
+
+  auto no_grouping_loc = std::locale(std::locale(), new no_grouping<char>());
+  EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567));
+
+  auto special_grouping_loc =
+      std::locale(std::locale(), new special_grouping<char>());
+  EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678));
+  EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345));
+
+  auto small_grouping_loc =
+      std::locale(std::locale(), new small_grouping<char>());
+  EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
+            fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>()));
+}
+
+TEST(locale_test, format_detault_align) {
+  auto loc = std::locale({}, new special_grouping<char>());
+  EXPECT_EQ("  12,345", fmt::format(loc, "{:8L}", 12345));
+}
+
+TEST(locale_test, format_plus) {
+  auto loc = std::locale({}, new special_grouping<char>());
+  EXPECT_EQ("+100", fmt::format(loc, "{:+L}", 100));
+}
+
+TEST(locale_test, wformat) {
+  auto loc = std::locale(std::locale(), new numpunct<wchar_t>());
+  EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:L}", 1234567));
+  EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:L}", 1234567));
+  fmt::format_arg_store<fmt::wformat_context, int> as{1234567};
+  EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:L}", fmt::wformat_args(as)));
+  EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:L}", 1234567));
+
+  auto no_grouping_loc = std::locale(std::locale(), new no_grouping<wchar_t>());
+  EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567));
+
+  auto special_grouping_loc =
+      std::locale(std::locale(), new special_grouping<wchar_t>());
+  EXPECT_EQ(L"1,23,45,678",
+            fmt::format(special_grouping_loc, L"{:L}", 12345678));
+
+  auto small_grouping_loc =
+      std::locale(std::locale(), new small_grouping<wchar_t>());
+  EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
+            fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
+}
+
+TEST(locale_test, double_formatter) {
+  auto loc = std::locale(std::locale(), new special_grouping<char>());
+  auto f = fmt::formatter<int>();
+  auto parse_ctx = fmt::format_parse_context("L");
+  f.parse(parse_ctx);
+  char buf[10] = {};
+  fmt::basic_format_context<char*, char> format_ctx(
+      buf, {}, fmt::detail::locale_ref(loc));
+  *f.format(12345, format_ctx) = 0;
+  EXPECT_STREQ("12,345", buf);
+}
+
+FMT_BEGIN_NAMESPACE
+template <class charT> struct formatter<std::complex<double>, charT> {
+ private:
+  detail::dynamic_format_specs<char> specs_;
+
+ public:
+  FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
+      basic_format_parse_context<charT>& ctx) {
+    using handler_type =
+        detail::dynamic_specs_handler<basic_format_parse_context<charT>>;
+    detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+                                                detail::type::string_type);
+    auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+    detail::parse_float_type_spec(specs_, ctx.error_handler());
+    return it;
+  }
+
+  template <class FormatContext>
+  typename FormatContext::iterator format(const std::complex<double>& c,
+                                          FormatContext& ctx) {
+    detail::handle_dynamic_spec<detail::precision_checker>(
+        specs_.precision, specs_.precision_ref, ctx);
+    auto specs = std::string();
+    if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision);
+    if (specs_.type) specs += specs_.type;
+    auto real = fmt::format(ctx.locale().template get<std::locale>(),
+                            "{:" + specs + "}", c.real());
+    auto imag = fmt::format(ctx.locale().template get<std::locale>(),
+                            "{:" + specs + "}", c.imag());
+    auto fill_align_width = std::string();
+    if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width);
+    return format_to(
+        ctx.out(), "{:" + fill_align_width + "}",
+        fmt::format(c.real() != 0 ? "({0}+{1}i)" : "{1}i", real, imag));
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(locale_test, complex) {
+  std::string s = fmt::format("{}", std::complex<double>(1, 2));
+  EXPECT_EQ(s, "(1+2i)");
+  EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)");
+  EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "  (1+2i)");
+}
+
+#endif  // FMT_STATIC_THOUSANDS_SEPARATOR
diff --git a/libs/fmt/test/mock-allocator.h b/libs/fmt/test/mock-allocator.h
new file mode 100644
index 0000000000000000000000000000000000000000..6a67e08f4f2804d6788a62e7b37206dedcbd7dfc
--- /dev/null
+++ b/libs/fmt/test/mock-allocator.h
@@ -0,0 +1,64 @@
+// Formatting library for C++ - mock allocator
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_MOCK_ALLOCATOR_H_
+#define FMT_MOCK_ALLOCATOR_H_
+
+#include <assert.h>  // assert
+#include <stddef.h>  // size_t
+
+#include <memory>  // std::allocator_traits
+
+#include "gmock/gmock.h"
+
+template <typename T> class mock_allocator {
+ public:
+  mock_allocator() {}
+  mock_allocator(const mock_allocator&) {}
+  using value_type = T;
+  MOCK_METHOD1_T(allocate, T*(size_t n));
+  MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
+};
+
+template <typename Allocator> class allocator_ref {
+ private:
+  Allocator* alloc_;
+
+  void move(allocator_ref& other) {
+    alloc_ = other.alloc_;
+    other.alloc_ = nullptr;
+  }
+
+ public:
+  using value_type = typename Allocator::value_type;
+
+  explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
+
+  allocator_ref(const allocator_ref& other) : alloc_(other.alloc_) {}
+  allocator_ref(allocator_ref&& other) { move(other); }
+
+  allocator_ref& operator=(allocator_ref&& other) {
+    assert(this != &other);
+    move(other);
+    return *this;
+  }
+
+  allocator_ref& operator=(const allocator_ref& other) {
+    alloc_ = other.alloc_;
+    return *this;
+  }
+
+ public:
+  Allocator* get() const { return alloc_; }
+
+  value_type* allocate(size_t n) {
+    return std::allocator_traits<Allocator>::allocate(*alloc_, n);
+  }
+  void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
+};
+
+#endif  // FMT_MOCK_ALLOCATOR_H_
diff --git a/libs/fmt/test/os-test.cc b/libs/fmt/test/os-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..91abac63e8496a24000d7332e6fe01164f65c8fa
--- /dev/null
+++ b/libs/fmt/test/os-test.cc
@@ -0,0 +1,543 @@
+// Formatting library for C++ - tests of the OS-specific functionality
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/os.h"
+
+#include <cstdlib>  // std::exit
+#include <cstring>
+#include <memory>
+
+#include "gtest-extra.h"
+#include "util.h"
+
+#ifdef fileno
+#  undef fileno
+#endif
+
+using fmt::buffered_file;
+using fmt::error_code;
+
+#ifdef _WIN32
+
+#  include <windows.h>
+
+TEST(util_test, utf16_to_utf8) {
+  auto s = std::string("ёжик");
+  fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
+  EXPECT_EQ(s, u.str());
+  EXPECT_EQ(s.size(), u.size());
+}
+
+TEST(util_test, utf16_to_utf8_empty_string) {
+  std::string s = "";
+  fmt::detail::utf16_to_utf8 u(L"");
+  EXPECT_EQ(s, u.str());
+  EXPECT_EQ(s.size(), u.size());
+}
+
+template <typename Converter, typename Char>
+void check_utf_conversion_error(
+    const char* message,
+    fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
+  fmt::memory_buffer out;
+  fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
+  auto error = fmt::system_error(0, "");
+  try {
+    (Converter)(str);
+  } catch (const fmt::system_error& e) {
+    error = e;
+  }
+  EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
+  EXPECT_EQ(fmt::to_string(out), error.what());
+}
+
+TEST(util_test, utf16_to_utf8_error) {
+  check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
+      "cannot convert string from UTF-16 to UTF-8");
+}
+
+TEST(util_test, utf16_to_utf8_convert) {
+  fmt::detail::utf16_to_utf8 u;
+  EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1)));
+  EXPECT_EQ(ERROR_INVALID_PARAMETER,
+            u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u)));
+}
+
+TEST(os_test, format_windows_error) {
+  LPWSTR message = 0;
+  FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+                     FORMAT_MESSAGE_IGNORE_INSERTS,
+                 0, ERROR_FILE_EXISTS,
+                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                 reinterpret_cast<LPWSTR>(&message), 0, 0);
+  fmt::detail::utf16_to_utf8 utf8_message(message);
+  LocalFree(message);
+  fmt::memory_buffer actual_message;
+  fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
+  EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
+            fmt::to_string(actual_message));
+  actual_message.resize(0);
+  auto max_size = fmt::detail::max_value<size_t>() / 2;
+  fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS,
+                                    fmt::string_view(nullptr, max_size));
+  EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
+            fmt::to_string(actual_message));
+}
+
+TEST(os_test, format_long_windows_error) {
+  LPWSTR message = 0;
+  // this error code is not available on all Windows platforms and
+  // Windows SDKs, so do not fail the test if the error string cannot
+  // be retrieved.
+  const int provisioning_not_allowed =
+      0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/;
+  if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                         FORMAT_MESSAGE_FROM_SYSTEM |
+                         FORMAT_MESSAGE_IGNORE_INSERTS,
+                     0, static_cast<DWORD>(provisioning_not_allowed),
+                     MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                     reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) {
+    return;
+  }
+  fmt::detail::utf16_to_utf8 utf8_message(message);
+  LocalFree(message);
+  fmt::memory_buffer actual_message;
+  fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
+                                    "test");
+  EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
+            fmt::to_string(actual_message));
+}
+
+TEST(os_test, windows_error) {
+  auto error = fmt::system_error(0, "");
+  try {
+    throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error");
+  } catch (const fmt::system_error& e) {
+    error = e;
+  }
+  fmt::memory_buffer message;
+  fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
+  EXPECT_EQ(to_string(message), error.what());
+  EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code());
+}
+
+TEST(os_test, report_windows_error) {
+  fmt::memory_buffer out;
+  fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
+  out.push_back('\n');
+  EXPECT_WRITE(stderr,
+               fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"),
+               fmt::to_string(out));
+}
+
+#endif  // _WIN32
+
+#if FMT_USE_FCNTL
+
+using fmt::file;
+
+bool isclosed(int fd) {
+  char buffer;
+  auto result = std::streamsize();
+  SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1)));
+  return result == -1 && errno == EBADF;
+}
+
+// Opens a file for reading.
+file open_file() {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  write_end.write(file_content, std::strlen(file_content));
+  write_end.close();
+  return read_end;
+}
+
+// Attempts to write a string to a file.
+void write(file& f, fmt::string_view s) {
+  size_t num_chars_left = s.size();
+  const char* ptr = s.data();
+  do {
+    size_t count = f.write(ptr, num_chars_left);
+    ptr += count;
+    // We can't write more than size_t bytes since num_chars_left
+    // has type size_t.
+    num_chars_left -= count;
+  } while (num_chars_left != 0);
+}
+
+TEST(buffered_file_test, default_ctor) {
+  auto f = buffered_file();
+  EXPECT_TRUE(f.get() == nullptr);
+}
+
+TEST(buffered_file_test, move_ctor) {
+  buffered_file bf = open_buffered_file();
+  FILE* fp = bf.get();
+  EXPECT_TRUE(fp != nullptr);
+  buffered_file bf2(std::move(bf));
+  EXPECT_EQ(fp, bf2.get());
+  EXPECT_TRUE(bf.get() == nullptr);
+}
+
+TEST(buffered_file_test, move_assignment) {
+  buffered_file bf = open_buffered_file();
+  FILE* fp = bf.get();
+  EXPECT_TRUE(fp != nullptr);
+  buffered_file bf2;
+  bf2 = std::move(bf);
+  EXPECT_EQ(fp, bf2.get());
+  EXPECT_TRUE(bf.get() == nullptr);
+}
+
+TEST(buffered_file_test, move_assignment_closes_file) {
+  buffered_file bf = open_buffered_file();
+  buffered_file bf2 = open_buffered_file();
+  int old_fd = bf2.fileno();
+  bf2 = std::move(bf);
+  EXPECT_TRUE(isclosed(old_fd));
+}
+
+TEST(buffered_file_test, move_from_temporary_in_ctor) {
+  FILE* fp = nullptr;
+  buffered_file f = open_buffered_file(&fp);
+  EXPECT_EQ(fp, f.get());
+}
+
+TEST(buffered_file_test, move_from_temporary_in_assignment) {
+  FILE* fp = nullptr;
+  auto f = buffered_file();
+  f = open_buffered_file(&fp);
+  EXPECT_EQ(fp, f.get());
+}
+
+TEST(buffered_file_test, move_from_temporary_in_assignment_closes_file) {
+  buffered_file f = open_buffered_file();
+  int old_fd = f.fileno();
+  f = open_buffered_file();
+  EXPECT_TRUE(isclosed(old_fd));
+}
+
+TEST(buffered_file_test, close_file_in_dtor) {
+  int fd = 0;
+  {
+    buffered_file f = open_buffered_file();
+    fd = f.fileno();
+  }
+  EXPECT_TRUE(isclosed(fd));
+}
+
+TEST(buffered_file_test, close_error_in_dtor) {
+  auto f =
+      std::unique_ptr<buffered_file>(new buffered_file(open_buffered_file()));
+  EXPECT_WRITE(
+      stderr,
+      {
+        // The close function must be called inside EXPECT_WRITE,
+        // otherwise the system may recycle closed file descriptor when
+        // redirecting the output in EXPECT_STDERR and the second close
+        // will break output redirection.
+        FMT_POSIX(close(f->fileno()));
+        SUPPRESS_ASSERT(f.reset(nullptr));
+      },
+      format_system_error(EBADF, "cannot close file") + "\n");
+}
+
+TEST(buffered_file_test, close) {
+  buffered_file f = open_buffered_file();
+  int fd = f.fileno();
+  f.close();
+  EXPECT_TRUE(f.get() == nullptr);
+  EXPECT_TRUE(isclosed(fd));
+}
+
+TEST(buffered_file_test, close_error) {
+  buffered_file f = open_buffered_file();
+  FMT_POSIX(close(f.fileno()));
+  EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
+  EXPECT_TRUE(f.get() == nullptr);
+}
+
+TEST(buffered_file_test, fileno) {
+  auto f = open_buffered_file();
+  EXPECT_TRUE(f.fileno() != -1);
+  file copy = file::dup(f.fileno());
+  EXPECT_READ(copy, file_content);
+}
+
+TEST(ostream_test, move) {
+  fmt::ostream out = fmt::output_file("test-file");
+  fmt::ostream moved(std::move(out));
+  moved.print("hello");
+}
+
+TEST(ostream_test, move_while_holding_data) {
+  {
+    fmt::ostream out = fmt::output_file("test-file");
+    out.print("Hello, ");
+    fmt::ostream moved(std::move(out));
+    moved.print("world!\n");
+  }
+  {
+    file in("test-file", file::RDONLY);
+    EXPECT_READ(in, "Hello, world!\n");
+  }
+}
+
+TEST(ostream_test, print) {
+  fmt::ostream out = fmt::output_file("test-file");
+  out.print("The answer is {}.\n",
+            fmt::join(std::initializer_list<int>{42}, ", "));
+  out.close();
+  file in("test-file", file::RDONLY);
+  EXPECT_READ(in, "The answer is 42.\n");
+}
+
+TEST(ostream_test, buffer_boundary) {
+  auto str = std::string(4096, 'x');
+  fmt::ostream out = fmt::output_file("test-file");
+  out.print("{}", str);
+  out.print("{}", str);
+  out.close();
+  file in("test-file", file::RDONLY);
+  EXPECT_READ(in, str + str);
+}
+
+TEST(ostream_test, buffer_size) {
+  fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size = 1);
+  out.print("{}", "foo");
+  out.close();
+  file in("test-file", file::RDONLY);
+  EXPECT_READ(in, "foo");
+}
+
+TEST(ostream_test, truncate) {
+  {
+    fmt::ostream out = fmt::output_file("test-file");
+    out.print("0123456789");
+  }
+  {
+    fmt::ostream out = fmt::output_file("test-file");
+    out.print("foo");
+  }
+  file in("test-file", file::RDONLY);
+  EXPECT_EQ("foo", read(in, 4));
+}
+
+TEST(file_test, default_ctor) {
+  file f;
+  EXPECT_EQ(-1, f.descriptor());
+}
+
+TEST(file_test, open_buffered_file_in_ctor) {
+  FILE* fp = safe_fopen("test-file", "w");
+  std::fputs(file_content, fp);
+  std::fclose(fp);
+  file f("test-file", file::RDONLY);
+  // Check if the file is open by reading one character from it.
+  char buffer;
+  bool isopen = FMT_POSIX(read(f.descriptor(), &buffer, 1)) == 1;
+  ASSERT_TRUE(isopen);
+}
+
+TEST(file_test, open_buffered_file_error) {
+  EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT,
+                      "cannot open file nonexistent");
+}
+
+TEST(file_test, move_ctor) {
+  file f = open_file();
+  int fd = f.descriptor();
+  EXPECT_NE(-1, fd);
+  file f2(std::move(f));
+  EXPECT_EQ(fd, f2.descriptor());
+  EXPECT_EQ(-1, f.descriptor());
+}
+
+TEST(file_test, move_assignment) {
+  file f = open_file();
+  int fd = f.descriptor();
+  EXPECT_NE(-1, fd);
+  file f2;
+  f2 = std::move(f);
+  EXPECT_EQ(fd, f2.descriptor());
+  EXPECT_EQ(-1, f.descriptor());
+}
+
+TEST(file_test, move_assignment_closes_file) {
+  file f = open_file();
+  file f2 = open_file();
+  int old_fd = f2.descriptor();
+  f2 = std::move(f);
+  EXPECT_TRUE(isclosed(old_fd));
+}
+
+file open_buffered_file(int& fd) {
+  file f = open_file();
+  fd = f.descriptor();
+  return f;
+}
+
+TEST(file_test, move_from_temporary_in_ctor) {
+  int fd = 0xdead;
+  file f(open_buffered_file(fd));
+  EXPECT_EQ(fd, f.descriptor());
+}
+
+TEST(file_test, move_from_temporary_in_assignment) {
+  int fd = 0xdead;
+  file f;
+  f = open_buffered_file(fd);
+  EXPECT_EQ(fd, f.descriptor());
+}
+
+TEST(file_test, move_from_temporary_in_assignment_closes_file) {
+  int fd = 0xdead;
+  file f = open_file();
+  int old_fd = f.descriptor();
+  f = open_buffered_file(fd);
+  EXPECT_TRUE(isclosed(old_fd));
+}
+
+TEST(file_test, close_file_in_dtor) {
+  int fd = 0;
+  {
+    file f = open_file();
+    fd = f.descriptor();
+  }
+  EXPECT_TRUE(isclosed(fd));
+}
+
+TEST(file_test, close_error_in_dtor) {
+  std::unique_ptr<file> f(new file(open_file()));
+  EXPECT_WRITE(
+      stderr,
+      {
+        // The close function must be called inside EXPECT_WRITE,
+        // otherwise the system may recycle closed file descriptor when
+        // redirecting the output in EXPECT_STDERR and the second close
+        // will break output redirection.
+        FMT_POSIX(close(f->descriptor()));
+        SUPPRESS_ASSERT(f.reset(nullptr));
+      },
+      format_system_error(EBADF, "cannot close file") + "\n");
+}
+
+TEST(file_test, close) {
+  file f = open_file();
+  int fd = f.descriptor();
+  f.close();
+  EXPECT_EQ(-1, f.descriptor());
+  EXPECT_TRUE(isclosed(fd));
+}
+
+TEST(file_test, close_error) {
+  file f = open_file();
+  FMT_POSIX(close(f.descriptor()));
+  EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
+  EXPECT_EQ(-1, f.descriptor());
+}
+
+TEST(file_test, read) {
+  file f = open_file();
+  EXPECT_READ(f, file_content);
+}
+
+TEST(file_test, read_error) {
+  file f("test-file", file::WRONLY);
+  char buf;
+  // We intentionally read from a file opened in the write-only mode to
+  // cause error.
+  EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file");
+}
+
+TEST(file_test, write) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  write(write_end, "test");
+  write_end.close();
+  EXPECT_READ(read_end, "test");
+}
+
+TEST(file_test, write_error) {
+  file f("test-file", file::RDONLY);
+  // We intentionally write to a file opened in the read-only mode to
+  // cause error.
+  EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
+}
+
+TEST(file_test, dup) {
+  file f = open_file();
+  file copy = file::dup(f.descriptor());
+  EXPECT_NE(f.descriptor(), copy.descriptor());
+  EXPECT_EQ(file_content, read(copy, std::strlen(file_content)));
+}
+
+#  ifndef __COVERITY__
+TEST(file_test, dup_error) {
+  int value = -1;
+  EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF,
+                               "cannot duplicate file descriptor -1");
+}
+#  endif
+
+TEST(file_test, dup2) {
+  file f = open_file();
+  file copy = open_file();
+  f.dup2(copy.descriptor());
+  EXPECT_NE(f.descriptor(), copy.descriptor());
+  EXPECT_READ(copy, file_content);
+}
+
+TEST(file_test, dup2_error) {
+  file f = open_file();
+  EXPECT_SYSTEM_ERROR_NOASSERT(
+      f.dup2(-1), EBADF,
+      fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor()));
+}
+
+TEST(file_test, dup2_noexcept) {
+  file f = open_file();
+  file copy = open_file();
+  error_code ec;
+  f.dup2(copy.descriptor(), ec);
+  EXPECT_EQ(ec.get(), 0);
+  EXPECT_NE(f.descriptor(), copy.descriptor());
+  EXPECT_READ(copy, file_content);
+}
+
+TEST(file_test, dup2_noexcept_error) {
+  file f = open_file();
+  error_code ec;
+  SUPPRESS_ASSERT(f.dup2(-1, ec));
+  EXPECT_EQ(EBADF, ec.get());
+}
+
+TEST(file_test, pipe) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  EXPECT_NE(-1, read_end.descriptor());
+  EXPECT_NE(-1, write_end.descriptor());
+  write(write_end, "test");
+  EXPECT_READ(read_end, "test");
+}
+
+TEST(file_test, fdopen) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  int read_fd = read_end.descriptor();
+  EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
+}
+
+#  ifdef FMT_LOCALE
+TEST(locale_test, strtod) {
+  fmt::locale loc;
+  const char *start = "4.2", *ptr = start;
+  EXPECT_EQ(4.2, loc.strtod(ptr));
+  EXPECT_EQ(start + 3, ptr);
+}
+#  endif
+#endif  // FMT_USE_FCNTL
diff --git a/libs/fmt/test/ostream-test.cc b/libs/fmt/test/ostream-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2bc2b1cb1235e5055130f9ada0a95968970aeb8a
--- /dev/null
+++ b/libs/fmt/test/ostream-test.cc
@@ -0,0 +1,291 @@
+// Formatting library for C++ - std::ostream support tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/format.h"
+
+struct test {};
+
+// Test that there is no issues with specializations when fmt/ostream.h is
+// included after fmt/format.h.
+namespace fmt {
+template <> struct formatter<test> : formatter<int> {
+  auto format(const test&, format_context& ctx) -> decltype(ctx.out()) {
+    return formatter<int>::format(42, ctx);
+  }
+};
+}  // namespace fmt
+
+#include <sstream>
+
+#include "fmt/ostream.h"
+#include "fmt/ranges.h"
+#include "gmock/gmock.h"
+#include "gtest-extra.h"
+#include "util.h"
+
+std::ostream& operator<<(std::ostream& os, const date& d) {
+  os << d.year() << '-' << d.month() << '-' << d.day();
+  return os;
+}
+
+std::wostream& operator<<(std::wostream& os, const date& d) {
+  os << d.year() << L'-' << d.month() << L'-' << d.day();
+  return os;
+}
+
+// Make sure that overloaded comma operators do no harm to is_streamable.
+struct type_with_comma_op {};
+template <typename T> void operator,(type_with_comma_op, const T&);
+template <typename T> type_with_comma_op operator<<(T&, const date&);
+
+enum streamable_enum {};
+std::ostream& operator<<(std::ostream& os, streamable_enum) {
+  return os << "streamable_enum";
+}
+
+std::wostream& operator<<(std::wostream& os, streamable_enum) {
+  return os << L"streamable_enum";
+}
+
+enum unstreamable_enum {};
+
+TEST(ostream_test, enum) {
+  EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
+  EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
+  EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
+  EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
+}
+
+TEST(ostream_test, format) {
+  EXPECT_EQ("a string", fmt::format("{0}", test_string("a string")));
+  EXPECT_EQ("The date is 2012-12-9",
+            fmt::format("The date is {0}", date(2012, 12, 9)));
+  EXPECT_EQ(L"The date is 2012-12-9",
+            fmt::format(L"The date is {0}", date(2012, 12, 9)));
+}
+
+TEST(ostream_test, format_specs) {
+  using fmt::format_error;
+  EXPECT_EQ("def  ", fmt::format("{0:<5}", test_string("def")));
+  EXPECT_EQ("  def", fmt::format("{0:>5}", test_string("def")));
+  EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
+  EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
+  EXPECT_THROW_MSG(fmt::format(+"{0:+}", test_string()), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:-}", test_string()), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0: }", test_string()), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:#}", test_string()), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_THROW_MSG(fmt::format(+"{0:05}", test_string()), format_error,
+                   "format specifier requires numeric argument");
+  EXPECT_EQ("test         ", fmt::format("{0:13}", test_string("test")));
+  EXPECT_EQ("test         ", fmt::format("{0:{1}}", test_string("test"), 13));
+  EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test")));
+  EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2));
+}
+
+struct empty_test {};
+std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; }
+
+TEST(ostream_test, empty_custom_output) {
+  EXPECT_EQ("", fmt::format("{}", empty_test()));
+}
+
+TEST(ostream_test, print) {
+  std::ostringstream os;
+  fmt::print(os, "Don't {}!", "panic");
+  EXPECT_EQ("Don't panic!", os.str());
+  std::wostringstream wos;
+  fmt::print(wos, L"Don't {}!", L"panic");
+  EXPECT_EQ(L"Don't panic!", wos.str());
+}
+
+TEST(ostream_test, write_to_ostream) {
+  std::ostringstream os;
+  fmt::memory_buffer buffer;
+  const char* foo = "foo";
+  buffer.append(foo, foo + std::strlen(foo));
+  fmt::detail::write_buffer(os, buffer);
+  EXPECT_EQ("foo", os.str());
+}
+
+TEST(ostream_test, write_to_ostream_max_size) {
+  auto max_size = fmt::detail::max_value<size_t>();
+  auto max_streamsize = fmt::detail::max_value<std::streamsize>();
+  if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
+
+  struct test_buffer final : fmt::detail::buffer<char> {
+    explicit test_buffer(size_t size)
+        : fmt::detail::buffer<char>(nullptr, size, size) {}
+    void grow(size_t) {}
+  } buffer(max_size);
+
+  struct mock_streambuf : std::streambuf {
+    MOCK_METHOD2(xsputn, std::streamsize(const void* s, std::streamsize n));
+    std::streamsize xsputn(const char* s, std::streamsize n) {
+      const void* v = s;
+      return xsputn(v, n);
+    }
+  } streambuf;
+
+  struct test_ostream : std::ostream {
+    explicit test_ostream(mock_streambuf& output_buffer)
+        : std::ostream(&output_buffer) {}
+  } os(streambuf);
+
+  testing::InSequence sequence;
+  const char* data = nullptr;
+  using ustreamsize = std::make_unsigned<std::streamsize>::type;
+  ustreamsize size = max_size;
+  do {
+    auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize));
+    EXPECT_CALL(streambuf, xsputn(data, static_cast<std::streamsize>(n)))
+        .WillOnce(testing::Return(max_streamsize));
+    data += n;
+    size -= n;
+  } while (size != 0);
+  fmt::detail::write_buffer(os, buffer);
+}
+
+TEST(ostream_test, join) {
+  int v[3] = {1, 2, 3};
+  EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", ")));
+}
+
+TEST(ostream_test, join_fallback_formatter) {
+  auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
+  EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", ")));
+}
+
+#if FMT_USE_CONSTEXPR
+TEST(ostream_test, constexpr_string) {
+  EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
+  EXPECT_EQ("a string", format(FMT_STRING("{0}"), test_string("a string")));
+}
+#endif
+
+namespace fmt_test {
+struct abc {};
+
+template <typename Output> Output& operator<<(Output& out, abc) {
+  return out << "abc";
+}
+}  // namespace fmt_test
+
+template <typename T> struct test_template {};
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, test_template<T>) {
+  return os << 1;
+}
+
+namespace fmt {
+template <typename T> struct formatter<test_template<T>> : formatter<int> {
+  auto format(test_template<T>, format_context& ctx) -> decltype(ctx.out()) {
+    return formatter<int>::format(2, ctx);
+  }
+};
+}  // namespace fmt
+
+TEST(ostream_test, template) {
+  EXPECT_EQ("2", fmt::format("{}", test_template<int>()));
+}
+
+TEST(ostream_test, format_to_n) {
+  char buffer[4];
+  buffer[3] = 'x';
+  auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::abc());
+  EXPECT_EQ(3u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ("abcx", fmt::string_view(buffer, 4));
+  result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::abc());
+  EXPECT_EQ(5u, result.size);
+  EXPECT_EQ(buffer + 3, result.out);
+  EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
+}
+
+template <typename T> struct convertible {
+  T value;
+  explicit convertible(const T& val) : value(val) {}
+  operator T() const { return value; }
+};
+
+TEST(ostream_test, disable_builtin_ostream_operators) {
+  EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
+  EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
+  EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
+}
+
+struct explicitly_convertible_to_string_like {
+  template <typename String,
+            typename = typename std::enable_if<std::is_constructible<
+                String, const char*, size_t>::value>::type>
+  explicit operator String() const {
+    return String("foo", 3u);
+  }
+};
+
+std::ostream& operator<<(std::ostream& os,
+                         explicitly_convertible_to_string_like) {
+  return os << "bar";
+}
+
+TEST(ostream_test, format_explicitly_convertible_to_string_like) {
+  EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
+}
+
+#ifdef FMT_USE_STRING_VIEW
+struct explicitly_convertible_to_std_string_view {
+  explicit operator fmt::detail::std_string_view<char>() const {
+    return {"foo", 3u};
+  }
+};
+
+std::ostream& operator<<(std::ostream& os,
+                         explicitly_convertible_to_std_string_view) {
+  return os << "bar";
+}
+
+TEST(ostream_test, format_explicitly_convertible_to_std_string_view) {
+  EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
+}
+#endif  // FMT_USE_STRING_VIEW
+
+struct streamable_and_convertible_to_bool {
+  operator bool() const { return true; }
+};
+
+std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
+  return os << "foo";
+}
+
+TEST(ostream_test, format_convertible_to_bool) {
+  EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool()));
+}
+
+struct copyfmt_test {};
+
+std::ostream& operator<<(std::ostream& os, copyfmt_test) {
+  std::ios ios(nullptr);
+  ios.copyfmt(os);
+  return os << "foo";
+}
+
+TEST(ostream_test, copyfmt) {
+  EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
+}
+
+TEST(ostream_test, to_string) {
+  EXPECT_EQ("abc", fmt::to_string(fmt_test::abc()));
+}
+
+TEST(ostream_test, range) {
+  auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
+  EXPECT_EQ("[foo, bar]", fmt::format("{}", strs));
+}
diff --git a/libs/fmt/test/posix-mock-test.cc b/libs/fmt/test/posix-mock-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..dd3aaac62a7fbb9fa051a3b96b325ff7e374c359
--- /dev/null
+++ b/libs/fmt/test/posix-mock-test.cc
@@ -0,0 +1,563 @@
+// Tests of the C++ interface to POSIX functions that require mocks
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+// Disable bogus MSVC warnings.
+#ifndef _CRT_SECURE_NO_WARNINGS
+#  define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "posix-mock.h"
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <climits>
+#include <memory>
+
+#include "../src/os.cc"
+
+#ifdef _WIN32
+#  include <io.h>
+#  undef max
+#endif
+
+#include "gmock/gmock.h"
+#include "gtest-extra.h"
+#include "util.h"
+
+using fmt::buffered_file;
+using fmt::error_code;
+
+using testing::_;
+using testing::Return;
+using testing::StrEq;
+
+template <typename Mock> struct scoped_mock : testing::StrictMock<Mock> {
+  scoped_mock() { Mock::instance = this; }
+  ~scoped_mock() { Mock::instance = nullptr; }
+};
+
+namespace {
+int open_count;
+int close_count;
+int dup_count;
+int dup2_count;
+int fdopen_count;
+int read_count;
+int write_count;
+int pipe_count;
+int fopen_count;
+int fclose_count;
+int fileno_count;
+size_t read_nbyte;
+size_t write_nbyte;
+bool sysconf_error;
+
+enum { none, max_size, error } fstat_sim;
+}  // namespace
+
+#define EMULATE_EINTR(func, error_result) \
+  if (func##_count != 0) {                \
+    if (func##_count++ != 3) {            \
+      errno = EINTR;                      \
+      return error_result;                \
+    }                                     \
+  }
+
+#ifndef _MSC_VER
+int test::open(const char* path, int oflag, int mode) {
+  EMULATE_EINTR(open, -1);
+  return ::open(path, oflag, mode);
+}
+#else
+errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
+                      int pmode) {
+  EMULATE_EINTR(open, EINTR);
+  return _sopen_s(pfh, filename, oflag, shflag, pmode);
+}
+#endif
+
+#ifndef _WIN32
+
+long test::sysconf(int name) {
+  long result = ::sysconf(name);
+  if (!sysconf_error) return result;
+  // Simulate an error.
+  errno = EINVAL;
+  return -1;
+}
+
+static off_t max_file_size() { return std::numeric_limits<off_t>::max(); }
+
+int test::fstat(int fd, struct stat* buf) {
+  int result = ::fstat(fd, buf);
+  if (fstat_sim == max_size) buf->st_size = max_file_size();
+  return result;
+}
+
+#else
+
+static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
+
+DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
+  if (fstat_sim == error) {
+    SetLastError(ERROR_ACCESS_DENIED);
+    return INVALID_FILE_SIZE;
+  }
+  if (fstat_sim == max_size) {
+    DWORD max = std::numeric_limits<DWORD>::max();
+    *lpFileSizeHigh = max >> 1;
+    return max;
+  }
+  return ::GetFileSize(hFile, lpFileSizeHigh);
+}
+
+#endif
+
+int test::close(int fildes) {
+  // Close the file first because close shouldn't be retried.
+  int result = ::FMT_POSIX(close(fildes));
+  EMULATE_EINTR(close, -1);
+  return result;
+}
+
+int test::dup(int fildes) {
+  EMULATE_EINTR(dup, -1);
+  return ::FMT_POSIX(dup(fildes));
+}
+
+int test::dup2(int fildes, int fildes2) {
+  EMULATE_EINTR(dup2, -1);
+  return ::FMT_POSIX(dup2(fildes, fildes2));
+}
+
+FILE* test::fdopen(int fildes, const char* mode) {
+  EMULATE_EINTR(fdopen, nullptr);
+  return ::FMT_POSIX(fdopen(fildes, mode));
+}
+
+test::ssize_t test::read(int fildes, void* buf, test::size_t nbyte) {
+  read_nbyte = nbyte;
+  EMULATE_EINTR(read, -1);
+  return ::FMT_POSIX(read(fildes, buf, nbyte));
+}
+
+test::ssize_t test::write(int fildes, const void* buf, test::size_t nbyte) {
+  write_nbyte = nbyte;
+  EMULATE_EINTR(write, -1);
+  return ::FMT_POSIX(write(fildes, buf, nbyte));
+}
+
+#ifndef _WIN32
+int test::pipe(int fildes[2]) {
+  EMULATE_EINTR(pipe, -1);
+  return ::pipe(fildes);
+}
+#else
+int test::pipe(int* pfds, unsigned psize, int textmode) {
+  EMULATE_EINTR(pipe, -1);
+  return _pipe(pfds, psize, textmode);
+}
+#endif
+
+FILE* test::fopen(const char* filename, const char* mode) {
+  EMULATE_EINTR(fopen, nullptr);
+  return ::fopen(filename, mode);
+}
+
+int test::fclose(FILE* stream) {
+  EMULATE_EINTR(fclose, EOF);
+  return ::fclose(stream);
+}
+
+int(test::fileno)(FILE* stream) {
+  EMULATE_EINTR(fileno, -1);
+#ifdef fileno
+  return FMT_POSIX(fileno(stream));
+#else
+  return ::FMT_POSIX(fileno(stream));
+#endif
+}
+
+#ifndef _WIN32
+#  define EXPECT_RETRY(statement, func, message) \
+    func##_count = 1;                            \
+    statement;                                   \
+    EXPECT_EQ(4, func##_count);                  \
+    func##_count = 0;
+#  define EXPECT_EQ_POSIX(expected, actual) EXPECT_EQ(expected, actual)
+#else
+#  define EXPECT_RETRY(statement, func, message)    \
+    func##_count = 1;                               \
+    EXPECT_SYSTEM_ERROR(statement, EINTR, message); \
+    func##_count = 0;
+#  define EXPECT_EQ_POSIX(expected, actual)
+#endif
+
+#if FMT_USE_FCNTL
+void write_file(fmt::cstring_view filename, fmt::string_view content) {
+  fmt::buffered_file f(filename, "w");
+  f.print("{}", content);
+}
+
+using fmt::file;
+
+TEST(os_test, getpagesize) {
+#  ifdef _WIN32
+  SYSTEM_INFO si = {};
+  GetSystemInfo(&si);
+  EXPECT_EQ(si.dwPageSize, fmt::getpagesize());
+#  else
+  EXPECT_EQ(sysconf(_SC_PAGESIZE), fmt::getpagesize());
+  sysconf_error = true;
+  EXPECT_SYSTEM_ERROR(fmt::getpagesize(), EINVAL,
+                      "cannot get memory page size");
+  sysconf_error = false;
+#  endif
+}
+
+TEST(file_test, open_retry) {
+  write_file("temp", "there must be something here");
+  std::unique_ptr<file> f{nullptr};
+  EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
+               "cannot open file temp");
+#  ifndef _WIN32
+  char c = 0;
+  f->read(&c, 1);
+#  endif
+}
+
+TEST(file_test, close_no_retry_in_dtor) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  std::unique_ptr<file> f(new file(std::move(read_end)));
+  int saved_close_count = 0;
+  EXPECT_WRITE(
+      stderr,
+      {
+        close_count = 1;
+        f.reset(nullptr);
+        saved_close_count = close_count;
+        close_count = 0;
+      },
+      format_system_error(EINTR, "cannot close file") + "\n");
+  EXPECT_EQ(2, saved_close_count);
+}
+
+TEST(file_test, close_no_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  close_count = 1;
+  EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file");
+  EXPECT_EQ(2, close_count);
+  close_count = 0;
+}
+
+TEST(file_test, size) {
+  std::string content = "top secret, destroy before reading";
+  write_file("temp", content);
+  file f("temp", file::RDONLY);
+  EXPECT_GE(f.size(), 0);
+  EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
+#  ifdef _WIN32
+  fmt::memory_buffer message;
+  fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED,
+                                    "cannot get file size");
+  fstat_sim = error;
+  EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
+  fstat_sim = none;
+#  else
+  f.close();
+  EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
+#  endif
+}
+
+TEST(file_test, max_size) {
+  write_file("temp", "");
+  file f("temp", file::RDONLY);
+  fstat_sim = max_size;
+  EXPECT_GE(f.size(), 0);
+  EXPECT_EQ(max_file_size(), f.size());
+  fstat_sim = none;
+}
+
+TEST(file_test, read_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  enum { SIZE = 4 };
+  write_end.write("test", SIZE);
+  write_end.close();
+  char buffer[SIZE];
+  size_t count = 0;
+  EXPECT_RETRY(count = read_end.read(buffer, SIZE), read,
+               "cannot read from file");
+  EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
+}
+
+TEST(file_test, write_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  enum { SIZE = 4 };
+  size_t count = 0;
+  EXPECT_RETRY(count = write_end.write("test", SIZE), write,
+               "cannot write to file");
+  write_end.close();
+#  ifndef _WIN32
+  EXPECT_EQ(static_cast<std::streamsize>(SIZE), count);
+  char buffer[SIZE + 1];
+  read_end.read(buffer, SIZE);
+  buffer[SIZE] = '\0';
+  EXPECT_STREQ("test", buffer);
+#  endif
+}
+
+#  ifdef _WIN32
+TEST(file_test, convert_read_count) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  char c;
+  size_t size = UINT_MAX;
+  if (sizeof(unsigned) != sizeof(size_t)) ++size;
+  read_count = 1;
+  read_nbyte = 0;
+  EXPECT_THROW(read_end.read(&c, size), fmt::system_error);
+  read_count = 0;
+  EXPECT_EQ(UINT_MAX, read_nbyte);
+}
+
+TEST(file_test, convert_write_count) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  char c;
+  size_t size = UINT_MAX;
+  if (sizeof(unsigned) != sizeof(size_t)) ++size;
+  write_count = 1;
+  write_nbyte = 0;
+  EXPECT_THROW(write_end.write(&c, size), fmt::system_error);
+  write_count = 0;
+  EXPECT_EQ(UINT_MAX, write_nbyte);
+}
+#  endif
+
+TEST(file_test, dup_no_retry) {
+  int stdout_fd = FMT_POSIX(fileno(stdout));
+  dup_count = 1;
+  EXPECT_SYSTEM_ERROR(
+      file::dup(stdout_fd), EINTR,
+      fmt::format("cannot duplicate file descriptor {}", stdout_fd));
+  dup_count = 0;
+}
+
+TEST(file_test, dup2_retry) {
+  int stdout_fd = FMT_POSIX(fileno(stdout));
+  file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
+  EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
+               fmt::format("cannot duplicate file descriptor {} to {}",
+                           f1.descriptor(), f2.descriptor()));
+}
+
+TEST(file_test, dup2_no_except_retry) {
+  int stdout_fd = FMT_POSIX(fileno(stdout));
+  file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
+  error_code ec;
+  dup2_count = 1;
+  f1.dup2(f2.descriptor(), ec);
+#  ifndef _WIN32
+  EXPECT_EQ(4, dup2_count);
+#  else
+  EXPECT_EQ(EINTR, ec.get());
+#  endif
+  dup2_count = 0;
+}
+
+TEST(file_test, pipe_no_retry) {
+  file read_end, write_end;
+  pipe_count = 1;
+  EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
+                      "cannot create pipe");
+  pipe_count = 0;
+}
+
+TEST(file_test, fdopen_no_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  fdopen_count = 1;
+  EXPECT_SYSTEM_ERROR(read_end.fdopen("r"), EINTR,
+                      "cannot associate stream with file descriptor");
+  fdopen_count = 0;
+}
+
+TEST(buffered_file_test, open_retry) {
+  write_file("temp", "there must be something here");
+  std::unique_ptr<buffered_file> f{nullptr};
+  EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
+               "cannot open file temp");
+#  ifndef _WIN32
+  char c = 0;
+  if (fread(&c, 1, 1, f->get()) < 1)
+    throw fmt::system_error(errno, "fread failed");
+#  endif
+}
+
+TEST(buffered_file_test, close_no_retry_in_dtor) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
+  int saved_fclose_count = 0;
+  EXPECT_WRITE(
+      stderr,
+      {
+        fclose_count = 1;
+        f.reset(nullptr);
+        saved_fclose_count = fclose_count;
+        fclose_count = 0;
+      },
+      format_system_error(EINTR, "cannot close file") + "\n");
+  EXPECT_EQ(2, saved_fclose_count);
+}
+
+TEST(buffered_file_test, close_no_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  buffered_file f = read_end.fdopen("r");
+  fclose_count = 1;
+  EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file");
+  EXPECT_EQ(2, fclose_count);
+  fclose_count = 0;
+}
+
+TEST(buffered_file_test, fileno_no_retry) {
+  file read_end, write_end;
+  file::pipe(read_end, write_end);
+  buffered_file f = read_end.fdopen("r");
+  fileno_count = 1;
+  EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor");
+  EXPECT_EQ(2, fileno_count);
+  fileno_count = 0;
+}
+#endif  // FMT_USE_FCNTL
+
+struct test_mock {
+  static test_mock* instance;
+} * test_mock::instance;
+
+TEST(scoped_mock, scope) {
+  {
+    scoped_mock<test_mock> mock;
+    EXPECT_EQ(&mock, test_mock::instance);
+    test_mock& copy = mock;
+    static_cast<void>(copy);
+  }
+  EXPECT_EQ(nullptr, test_mock::instance);
+}
+
+#ifdef FMT_LOCALE
+
+using locale_type = fmt::locale::type;
+
+struct locale_mock {
+  static locale_mock* instance;
+  MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
+                                      locale_type base));
+  MOCK_METHOD1(freelocale, void(locale_type locale));
+
+  MOCK_METHOD3(strtod_l,
+               double(const char* nptr, char** endptr, locale_type locale));
+} * locale_mock::instance;
+
+#  ifdef _MSC_VER
+#    pragma warning(push)
+#    pragma warning(disable : 4273)
+#    ifdef __clang__
+#      pragma clang diagnostic push
+#      pragma clang diagnostic ignored "-Winconsistent-dllimport"
+#    endif
+
+_locale_t _create_locale(int category, const char* locale) {
+  return locale_mock::instance->newlocale(category, locale, 0);
+}
+
+void _free_locale(_locale_t locale) {
+  locale_mock::instance->freelocale(locale);
+}
+
+double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
+  return locale_mock::instance->strtod_l(nptr, endptr, locale);
+}
+#    ifdef __clang__
+#      pragma clang diagnostic pop
+#    endif
+#    pragma warning(pop)
+#  endif
+
+#  if defined(__THROW) && \
+      ((FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408) || defined(__e2k__))
+#    define FMT_LOCALE_THROW __THROW
+#  else
+#    define FMT_LOCALE_THROW
+#  endif
+
+#  if defined(__APPLE__) || \
+      (defined(__FreeBSD__) && __FreeBSD_version < 1200002)
+typedef int FreeLocaleResult;
+#  else
+typedef void FreeLocaleResult;
+#  endif
+
+FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
+  locale_mock::instance->freelocale(locale);
+  return FreeLocaleResult();
+}
+
+double strtod_l(const char* nptr, char** endptr,
+                locale_type locale) FMT_LOCALE_THROW {
+  return locale_mock::instance->strtod_l(nptr, endptr, locale);
+}
+
+#  undef FMT_LOCALE_THROW
+
+#  ifndef _WIN32
+locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
+  return locale_mock::instance->newlocale(category_mask, locale, base);
+}
+
+TEST(locale_test, locale_mock) {
+  scoped_mock<locale_mock> mock;
+  auto locale = reinterpret_cast<locale_type>(11);
+  EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
+  FMT_SYSTEM(newlocale(222, "foo", locale));
+}
+#  endif
+
+TEST(locale_test, locale) {
+#  ifndef LC_NUMERIC_MASK
+  enum { LC_NUMERIC_MASK = LC_NUMERIC };
+#  endif
+  scoped_mock<locale_mock> mock;
+  auto impl = reinterpret_cast<locale_type>(42);
+  EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
+      .WillOnce(Return(impl));
+  EXPECT_CALL(mock, freelocale(impl));
+  fmt::locale loc;
+  EXPECT_EQ(impl, loc.get());
+}
+
+TEST(locale_test, strtod) {
+  scoped_mock<locale_mock> mock;
+  EXPECT_CALL(mock, newlocale(_, _, _))
+      .WillOnce(Return(reinterpret_cast<locale_type>(42)));
+  EXPECT_CALL(mock, freelocale(_));
+  fmt::locale loc;
+  const char* str = "4.2";
+  char end = 'x';
+  EXPECT_CALL(mock, strtod_l(str, _, loc.get()))
+      .WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
+  EXPECT_EQ(777, loc.strtod(str));
+  EXPECT_EQ(&end, str);
+}
+
+#endif  // FMT_LOCALE
diff --git a/libs/fmt/test/posix-mock.h b/libs/fmt/test/posix-mock.h
new file mode 100644
index 0000000000000000000000000000000000000000..e76af70022403c8d3a8e7a54fef5f9b728785238
--- /dev/null
+++ b/libs/fmt/test/posix-mock.h
@@ -0,0 +1,77 @@
+// Formatting library for C++ - mocks of POSIX functions
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_POSIX_TEST_H
+#define FMT_POSIX_TEST_H
+
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#ifdef __APPLE__
+#  include <xlocale.h>
+#endif
+
+#ifdef _WIN32
+#  include <windows.h>
+#else
+#  include <sys/param.h>  // for FreeBSD version
+#  include <sys/types.h>  // for ssize_t
+#endif
+
+#ifndef _MSC_VER
+struct stat;
+#endif
+
+namespace test {
+
+#ifndef _MSC_VER
+// Size type for read and write.
+typedef size_t size_t;
+typedef ssize_t ssize_t;
+int open(const char* path, int oflag, int mode);
+int fstat(int fd, struct stat* buf);
+#else
+typedef unsigned size_t;
+typedef int ssize_t;
+errno_t sopen_s(int* pfh, const char* filename, int oflag, int shflag,
+                int pmode);
+#endif
+
+#ifndef _WIN32
+long sysconf(int name);
+#else
+DWORD GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh);
+#endif
+
+int close(int fildes);
+
+int dup(int fildes);
+int dup2(int fildes, int fildes2);
+
+FILE* fdopen(int fildes, const char* mode);
+
+ssize_t read(int fildes, void* buf, size_t nbyte);
+ssize_t write(int fildes, const void* buf, size_t nbyte);
+
+#ifndef _WIN32
+int pipe(int fildes[2]);
+#else
+int pipe(int* pfds, unsigned psize, int textmode);
+#endif
+
+FILE* fopen(const char* filename, const char* mode);
+int fclose(FILE* stream);
+int(fileno)(FILE* stream);
+
+#if defined(FMT_LOCALE) && !defined(_WIN32)
+locale_t newlocale(int category_mask, const char* locale, locale_t base);
+#endif
+}  // namespace test
+
+#define FMT_SYSTEM(call) test::call
+
+#endif  // FMT_POSIX_TEST_H
diff --git a/libs/fmt/test/printf-test.cc b/libs/fmt/test/printf-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f8db99aed5e9af7a07826e39bfd9c58b25f52fdc
--- /dev/null
+++ b/libs/fmt/test/printf-test.cc
@@ -0,0 +1,629 @@
+// Formatting library for C++ - printf tests
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "fmt/printf.h"
+
+#include <cctype>
+#include <climits>
+#include <cstring>
+
+#include "fmt/core.h"
+#include "fmt/ostream.h"
+#include "gtest-extra.h"
+#include "util.h"
+
+using fmt::format;
+using fmt::format_error;
+using fmt::detail::max_value;
+
+const unsigned big_num = INT_MAX + 1u;
+
+// Makes format string argument positional.
+static std::string make_positional(fmt::string_view format) {
+  std::string s(format.data(), format.size());
+  s.replace(s.find('%'), 1, "%1$");
+  return s;
+}
+
+static std::wstring make_positional(fmt::wstring_view format) {
+  std::wstring s(format.data(), format.size());
+  s.replace(s.find(L'%'), 1, L"%1$");
+  return s;
+}
+
+// A wrapper around fmt::sprintf to workaround bogus warnings about invalid
+// format strings in MSVC.
+template <typename... Args>
+std::string test_sprintf(fmt::string_view format, const Args&... args) {
+  return fmt::sprintf(format, args...);
+}
+template <typename... Args>
+std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) {
+  return fmt::sprintf(format, args...);
+}
+
+#define EXPECT_PRINTF(expected_output, format, arg)     \
+  EXPECT_EQ(expected_output, test_sprintf(format, arg)) \
+      << "format: " << format;                          \
+  EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
+
+template <typename T> struct value_extractor {
+  T operator()(T value) { return value; }
+
+  template <typename U> FMT_NORETURN T operator()(U) {
+    throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
+  }
+
+#if FMT_USE_INT128
+  // Apple Clang does not define typeid for __int128_t and __uint128_t.
+  FMT_NORETURN T operator()(fmt::detail::int128_t) {
+    throw std::runtime_error("invalid type __int128_t");
+  }
+
+  FMT_NORETURN T operator()(fmt::detail::uint128_t) {
+    throw std::runtime_error("invalid type __uint128_t");
+  }
+#endif
+};
+
+TEST(printf_test, arg_converter) {
+  long long value = max_value<long long>();
+  auto arg = fmt::detail::make_arg<fmt::format_context>(value);
+  fmt::visit_format_arg(
+      fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'),
+      arg);
+  EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg));
+}
+
+TEST(printf_test, no_args) {
+  EXPECT_EQ("test", test_sprintf("test"));
+  EXPECT_EQ(L"test", fmt::sprintf(L"test"));
+}
+
+TEST(printf_test, escape) {
+  EXPECT_EQ("%", test_sprintf("%%"));
+  EXPECT_EQ("before %", test_sprintf("before %%"));
+  EXPECT_EQ("% after", test_sprintf("%% after"));
+  EXPECT_EQ("before % after", test_sprintf("before %% after"));
+  EXPECT_EQ("%s", test_sprintf("%%s"));
+  EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
+  EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
+  EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
+  EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
+  EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
+}
+
+TEST(printf_test, positional_args) {
+  EXPECT_EQ("42", test_sprintf("%1$d", 42));
+  EXPECT_EQ("before 42", test_sprintf("before %1$d", 42));
+  EXPECT_EQ("42 after", test_sprintf("%1$d after", 42));
+  EXPECT_EQ("before 42 after", test_sprintf("before %1$d after", 42));
+  EXPECT_EQ("answer = 42", test_sprintf("%1$s = %2$d", "answer", 42));
+  EXPECT_EQ("42 is the answer", test_sprintf("%2$d is the %1$s", "answer", 42));
+  EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad"));
+}
+
+TEST(printf_test, automatic_arg_indexing) {
+  EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c'));
+}
+
+TEST(printf_test, number_is_too_big_in_arg_index) {
+  EXPECT_THROW_MSG(test_sprintf(format("%{}$", big_num)), format_error,
+                   "number is too big");
+  EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num)), format_error,
+                   "number is too big");
+}
+
+TEST(printf_test, switch_arg_indexing) {
+  EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+  EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
+                   format_error, "number is too big");
+  EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error,
+                   "cannot switch from manual to automatic argument indexing");
+
+  EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error,
+                   "cannot switch from automatic to manual argument indexing");
+  EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", big_num), 1, 2), format_error,
+                   "number is too big");
+  EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error,
+                   "cannot switch from automatic to manual argument indexing");
+
+  // Indexing errors override width errors.
+  EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", big_num), 1, 2),
+                   format_error, "number is too big");
+  EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
+                   format_error, "number is too big");
+}
+
+TEST(printf_test, invalid_arg_index) {
+  EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
+                   "argument not found");
+  EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
+                   "argument not found");
+  EXPECT_THROW_MSG(test_sprintf(format("%{}$d", INT_MAX), 42), format_error,
+                   "argument not found");
+
+  EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found");
+  EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num), 42), format_error,
+                   "number is too big");
+}
+
+TEST(printf_test, default_align_right) {
+  EXPECT_PRINTF("   42", "%5d", 42);
+  EXPECT_PRINTF("  abc", "%5s", "abc");
+}
+
+TEST(printf_test, zero_flag) {
+  EXPECT_PRINTF("00042", "%05d", 42);
+  EXPECT_PRINTF("-0042", "%05d", -42);
+
+  EXPECT_PRINTF("00042", "%05d", 42);
+  EXPECT_PRINTF("-0042", "%05d", -42);
+  EXPECT_PRINTF("-004.2", "%06g", -4.2);
+
+  EXPECT_PRINTF("+00042", "%00+6d", 42);
+
+  EXPECT_PRINTF("   42", "%05.d", 42);
+  EXPECT_PRINTF(" 0042", "%05.4d", 42);
+
+  // '0' flag is ignored for non-numeric types.
+  EXPECT_PRINTF("    x", "%05c", 'x');
+}
+
+TEST(printf_test, plus_flag) {
+  EXPECT_PRINTF("+42", "%+d", 42);
+  EXPECT_PRINTF("-42", "%+d", -42);
+  EXPECT_PRINTF("+0042", "%+05d", 42);
+  EXPECT_PRINTF("+0042", "%0++5d", 42);
+
+  // '+' flag is ignored for non-numeric types.
+  EXPECT_PRINTF("x", "%+c", 'x');
+
+  // '+' flag wins over space flag
+  EXPECT_PRINTF("+42", "%+ d", 42);
+  EXPECT_PRINTF("-42", "%+ d", -42);
+  EXPECT_PRINTF("+42", "% +d", 42);
+  EXPECT_PRINTF("-42", "% +d", -42);
+  EXPECT_PRINTF("+0042", "% +05d", 42);
+  EXPECT_PRINTF("+0042", "%0+ 5d", 42);
+
+  // '+' flag and space flag are both ignored for non-numeric types.
+  EXPECT_PRINTF("x", "%+ c", 'x');
+  EXPECT_PRINTF("x", "% +c", 'x');
+}
+
+TEST(printf_test, minus_flag) {
+  EXPECT_PRINTF("abc  ", "%-5s", "abc");
+  EXPECT_PRINTF("abc  ", "%0--5s", "abc");
+
+  EXPECT_PRINTF("7    ", "%-5d", 7);
+  EXPECT_PRINTF("97   ", "%-5hhi", 'a');
+  EXPECT_PRINTF("a    ", "%-5c", 'a');
+
+  // '0' flag is ignored if '-' flag is given
+  EXPECT_PRINTF("7    ", "%-05d", 7);
+  EXPECT_PRINTF("7    ", "%0-5d", 7);
+  EXPECT_PRINTF("a    ", "%-05c", 'a');
+  EXPECT_PRINTF("a    ", "%0-5c", 'a');
+  EXPECT_PRINTF("97   ", "%-05hhi", 'a');
+  EXPECT_PRINTF("97   ", "%0-5hhi", 'a');
+
+  // '-' and space flag don't interfere
+  EXPECT_PRINTF(" 42", "%- d", 42);
+}
+
+TEST(printf_test, space_flag) {
+  EXPECT_PRINTF(" 42", "% d", 42);
+  EXPECT_PRINTF("-42", "% d", -42);
+  EXPECT_PRINTF(" 0042", "% 05d", 42);
+  EXPECT_PRINTF(" 0042", "%0  5d", 42);
+
+  // ' ' flag is ignored for non-numeric types.
+  EXPECT_PRINTF("x", "% c", 'x');
+}
+
+TEST(printf_test, hash_flag) {
+  EXPECT_PRINTF("042", "%#o", 042);
+  EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042);
+  EXPECT_PRINTF("0", "%#o", 0);
+
+  EXPECT_PRINTF("0x42", "%#x", 0x42);
+  EXPECT_PRINTF("0X42", "%#X", 0x42);
+  EXPECT_PRINTF(fmt::format("0x{:x}", static_cast<unsigned>(-0x42)), "%#x",
+                -0x42);
+  EXPECT_PRINTF("0", "%#x", 0);
+
+  EXPECT_PRINTF("0x0042", "%#06x", 0x42);
+  EXPECT_PRINTF("0x0042", "%0##6x", 0x42);
+
+  EXPECT_PRINTF("-42.000000", "%#f", -42.0);
+  EXPECT_PRINTF("-42.000000", "%#F", -42.0);
+
+  char buffer[256];
+  safe_sprintf(buffer, "%#e", -42.0);
+  EXPECT_PRINTF(buffer, "%#e", -42.0);
+  safe_sprintf(buffer, "%#E", -42.0);
+  EXPECT_PRINTF(buffer, "%#E", -42.0);
+
+  EXPECT_PRINTF("-42.0000", "%#g", -42.0);
+  EXPECT_PRINTF("-42.0000", "%#G", -42.0);
+
+  safe_sprintf(buffer, "%#a", 16.0);
+  EXPECT_PRINTF(buffer, "%#a", 16.0);
+  safe_sprintf(buffer, "%#A", 16.0);
+  EXPECT_PRINTF(buffer, "%#A", 16.0);
+
+  // '#' flag is ignored for non-numeric types.
+  EXPECT_PRINTF("x", "%#c", 'x');
+}
+
+TEST(printf_test, width) {
+  EXPECT_PRINTF("  abc", "%5s", "abc");
+
+  // Width cannot be specified twice.
+  EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
+                   "invalid type specifier");
+
+  EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
+                   "number is too big");
+  EXPECT_THROW_MSG(test_sprintf(format("%1${}d", big_num), 42), format_error,
+                   "number is too big");
+}
+
+TEST(printf_test, dynamic_width) {
+  EXPECT_EQ("   42", test_sprintf("%*d", 5, 42));
+  EXPECT_EQ("42   ", test_sprintf("%*d", -5, 42));
+  EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
+                   "width is not integer");
+  EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found");
+  EXPECT_THROW_MSG(test_sprintf("%*d", big_num, 42), format_error,
+                   "number is too big");
+}
+
+TEST(printf_test, int_precision) {
+  EXPECT_PRINTF("00042", "%.5d", 42);
+  EXPECT_PRINTF("-00042", "%.5d", -42);
+  EXPECT_PRINTF("00042", "%.5x", 0x42);
+  EXPECT_PRINTF("0x00042", "%#.5x", 0x42);
+  EXPECT_PRINTF("00042", "%.5o", 042);
+  EXPECT_PRINTF("00042", "%#.5o", 042);
+
+  EXPECT_PRINTF("  00042", "%7.5d", 42);
+  EXPECT_PRINTF("  00042", "%7.5x", 0x42);
+  EXPECT_PRINTF("   0x00042", "%#10.5x", 0x42);
+  EXPECT_PRINTF("  00042", "%7.5o", 042);
+  EXPECT_PRINTF("     00042", "%#10.5o", 042);
+
+  EXPECT_PRINTF("00042  ", "%-7.5d", 42);
+  EXPECT_PRINTF("00042  ", "%-7.5x", 0x42);
+  EXPECT_PRINTF("0x00042   ", "%-#10.5x", 0x42);
+  EXPECT_PRINTF("00042  ", "%-7.5o", 042);
+  EXPECT_PRINTF("00042     ", "%-#10.5o", 042);
+}
+
+TEST(printf_test, float_precision) {
+  char buffer[256];
+  safe_sprintf(buffer, "%.3e", 1234.5678);
+  EXPECT_PRINTF(buffer, "%.3e", 1234.5678);
+  EXPECT_PRINTF("1234.568", "%.3f", 1234.5678);
+  EXPECT_PRINTF("1.23e+03", "%.3g", 1234.5678);
+  safe_sprintf(buffer, "%.3a", 1234.5678);
+  EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
+}
+
+TEST(printf_test, string_precision) {
+  char test[] = {'H', 'e', 'l', 'l', 'o'};
+  EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
+}
+
+TEST(printf_test, ignore_precision_for_non_numeric_arg) {
+  EXPECT_PRINTF("abc", "%.5s", "abc");
+}
+
+TEST(printf_test, dynamic_precision) {
+  EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42));
+  EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
+  EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
+                   "precision is not integer");
+  EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
+  EXPECT_THROW_MSG(test_sprintf("%.*d", big_num, 42), format_error,
+                   "number is too big");
+  if (sizeof(long long) != sizeof(int)) {
+    long long prec = static_cast<long long>(INT_MIN) - 1;
+    EXPECT_THROW_MSG(test_sprintf("%.*d", prec, 42), format_error,
+                     "number is too big");
+  }
+}
+
+template <typename T> struct make_signed { typedef T type; };
+
+#define SPECIALIZE_MAKE_SIGNED(T, S) \
+  template <> struct make_signed<T> { typedef S type; }
+
+SPECIALIZE_MAKE_SIGNED(char, signed char);
+SPECIALIZE_MAKE_SIGNED(unsigned char, signed char);
+SPECIALIZE_MAKE_SIGNED(unsigned short, short);
+SPECIALIZE_MAKE_SIGNED(unsigned, int);
+SPECIALIZE_MAKE_SIGNED(unsigned long, long);
+SPECIALIZE_MAKE_SIGNED(unsigned long long, long long);
+
+// Test length format specifier ``length_spec``.
+template <typename T, typename U>
+void test_length(const char* length_spec, U value) {
+  long long signed_value = 0;
+  unsigned long long unsigned_value = 0;
+  // Apply integer promotion to the argument.
+  unsigned long long max = max_value<U>();
+  using fmt::detail::const_check;
+  if (const_check(max <= static_cast<unsigned>(max_value<int>()))) {
+    signed_value = static_cast<int>(value);
+    unsigned_value = static_cast<unsigned long long>(value);
+  } else if (const_check(max <= max_value<unsigned>())) {
+    signed_value = static_cast<unsigned>(value);
+    unsigned_value = static_cast<unsigned long long>(value);
+  }
+  if (sizeof(U) <= sizeof(int) && sizeof(int) < sizeof(T)) {
+    signed_value = static_cast<long long>(value);
+    unsigned_value = static_cast<unsigned long long>(
+        static_cast<typename std::make_unsigned<unsigned>::type>(value));
+  } else {
+    signed_value = static_cast<typename make_signed<T>::type>(value);
+    unsigned_value = static_cast<typename std::make_unsigned<T>::type>(value);
+  }
+  std::ostringstream os;
+  os << signed_value;
+  EXPECT_PRINTF(os.str(), fmt::format("%{}d", length_spec), value);
+  EXPECT_PRINTF(os.str(), fmt::format("%{}i", length_spec), value);
+  os.str("");
+  os << unsigned_value;
+  EXPECT_PRINTF(os.str(), fmt::format("%{}u", length_spec), value);
+  os.str("");
+  os << std::oct << unsigned_value;
+  EXPECT_PRINTF(os.str(), fmt::format("%{}o", length_spec), value);
+  os.str("");
+  os << std::hex << unsigned_value;
+  EXPECT_PRINTF(os.str(), fmt::format("%{}x", length_spec), value);
+  os.str("");
+  os << std::hex << std::uppercase << unsigned_value;
+  EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value);
+}
+
+template <typename T> void test_length(const char* length_spec) {
+  T min = std::numeric_limits<T>::min(), max = max_value<T>();
+  test_length<T>(length_spec, 42);
+  test_length<T>(length_spec, -42);
+  test_length<T>(length_spec, min);
+  test_length<T>(length_spec, max);
+  long long long_long_min = std::numeric_limits<long long>::min();
+  if (static_cast<long long>(min) > long_long_min)
+    test_length<T>(length_spec, static_cast<long long>(min) - 1);
+  unsigned long long long_long_max = max_value<long long>();
+  if (static_cast<unsigned long long>(max) < long_long_max)
+    test_length<T>(length_spec, static_cast<long long>(max) + 1);
+  test_length<T>(length_spec, std::numeric_limits<short>::min());
+  test_length<T>(length_spec, max_value<unsigned short>());
+  test_length<T>(length_spec, std::numeric_limits<int>::min());
+  test_length<T>(length_spec, max_value<int>());
+  test_length<T>(length_spec, std::numeric_limits<unsigned>::min());
+  test_length<T>(length_spec, max_value<unsigned>());
+  test_length<T>(length_spec, std::numeric_limits<long long>::min());
+  test_length<T>(length_spec, max_value<long long>());
+  test_length<T>(length_spec, std::numeric_limits<unsigned long long>::min());
+  test_length<T>(length_spec, max_value<unsigned long long>());
+}
+
+TEST(printf_test, length) {
+  test_length<char>("hh");
+  test_length<signed char>("hh");
+  test_length<unsigned char>("hh");
+  test_length<short>("h");
+  test_length<unsigned short>("h");
+  test_length<long>("l");
+  test_length<unsigned long>("l");
+  test_length<long long>("ll");
+  test_length<unsigned long long>("ll");
+  test_length<intmax_t>("j");
+  test_length<size_t>("z");
+  test_length<std::ptrdiff_t>("t");
+  long double max = max_value<long double>();
+  EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
+  EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
+}
+
+TEST(printf_test, bool) {
+  EXPECT_PRINTF("1", "%d", true);
+  EXPECT_PRINTF("true", "%s", true);
+}
+
+TEST(printf_test, int) {
+  EXPECT_PRINTF("-42", "%d", -42);
+  EXPECT_PRINTF("-42", "%i", -42);
+  unsigned u = 0 - 42u;
+  EXPECT_PRINTF(fmt::format("{}", u), "%u", -42);
+  EXPECT_PRINTF(fmt::format("{:o}", u), "%o", -42);
+  EXPECT_PRINTF(fmt::format("{:x}", u), "%x", -42);
+  EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
+}
+
+TEST(printf_test, long_long) {
+  // fmt::printf allows passing long long arguments to %d without length
+  // specifiers.
+  long long max = max_value<long long>();
+  EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
+}
+
+TEST(printf_test, float) {
+  EXPECT_PRINTF("392.650000", "%f", 392.65);
+  EXPECT_PRINTF("392.65", "%.2f", 392.65);
+  EXPECT_PRINTF("392.6", "%.1f", 392.65);
+  EXPECT_PRINTF("393", "%.f", 392.65);
+  EXPECT_PRINTF("392.650000", "%F", 392.65);
+  char buffer[256];
+  safe_sprintf(buffer, "%e", 392.65);
+  EXPECT_PRINTF(buffer, "%e", 392.65);
+  safe_sprintf(buffer, "%E", 392.65);
+  EXPECT_PRINTF(buffer, "%E", 392.65);
+  EXPECT_PRINTF("392.65", "%g", 392.65);
+  EXPECT_PRINTF("392.65", "%G", 392.65);
+  EXPECT_PRINTF("392", "%g", 392.0);
+  EXPECT_PRINTF("392", "%G", 392.0);
+  EXPECT_PRINTF("4.56e-07", "%g", 0.000000456);
+  safe_sprintf(buffer, "%a", -392.65);
+  EXPECT_EQ(buffer, format("{:a}", -392.65));
+  safe_sprintf(buffer, "%A", -392.65);
+  EXPECT_EQ(buffer, format("{:A}", -392.65));
+}
+
+TEST(printf_test, inf) {
+  double inf = std::numeric_limits<double>::infinity();
+  for (const char* type = "fega"; *type; ++type) {
+    EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf);
+    char upper = static_cast<char>(std::toupper(*type));
+    EXPECT_PRINTF("INF", fmt::format("%{}", upper), inf);
+  }
+}
+
+TEST(printf_test, char) {
+  EXPECT_PRINTF("x", "%c", 'x');
+  int max = max_value<int>();
+  EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
+  // EXPECT_PRINTF("x", "%lc", L'x');
+  EXPECT_PRINTF(L"x", L"%c", L'x');
+  EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
+}
+
+TEST(printf_test, string) {
+  EXPECT_PRINTF("abc", "%s", "abc");
+  const char* null_str = nullptr;
+  EXPECT_PRINTF("(null)", "%s", null_str);
+  EXPECT_PRINTF("    (null)", "%10s", null_str);
+  EXPECT_PRINTF(L"abc", L"%s", L"abc");
+  const wchar_t* null_wstr = nullptr;
+  EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
+  EXPECT_PRINTF(L"    (null)", L"%10s", null_wstr);
+}
+
+TEST(printf_test, uchar_string) {
+  unsigned char str[] = "test";
+  unsigned char* pstr = str;
+  EXPECT_EQ("test", fmt::sprintf("%s", pstr));
+}
+
+TEST(printf_test, pointer) {
+  int n;
+  void* p = &n;
+  EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
+  p = nullptr;
+  EXPECT_PRINTF("(nil)", "%p", p);
+  EXPECT_PRINTF("     (nil)", "%10p", p);
+  const char* s = "test";
+  EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
+  const char* null_str = nullptr;
+  EXPECT_PRINTF("(nil)", "%p", null_str);
+
+  p = &n;
+  EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
+  p = nullptr;
+  EXPECT_PRINTF(L"(nil)", L"%p", p);
+  EXPECT_PRINTF(L"     (nil)", L"%10p", p);
+  const wchar_t* w = L"test";
+  EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
+  const wchar_t* null_wstr = nullptr;
+  EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
+}
+
+enum test_enum { answer = 42 };
+
+TEST(printf_test, enum) {
+  EXPECT_PRINTF("42", "%d", answer);
+  volatile test_enum volatile_enum = answer;
+  EXPECT_PRINTF("42", "%d", volatile_enum);
+}
+
+#if FMT_USE_FCNTL
+TEST(printf_test, examples) {
+  const char* weekday = "Thursday";
+  const char* month = "August";
+  int day = 21;
+  EXPECT_WRITE(stdout, fmt::printf("%1$s, %3$d %2$s", weekday, month, day),
+               "Thursday, 21 August");
+}
+
+TEST(printf_test, printf_error) {
+  fmt::file read_end, write_end;
+  fmt::file::pipe(read_end, write_end);
+  int result = fmt::fprintf(read_end.fdopen("r").get(), "test");
+  EXPECT_LT(result, 0);
+}
+#endif
+
+TEST(printf_test, wide_string) {
+  EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
+}
+
+TEST(printf_test, printf_custom) {
+  EXPECT_EQ("abc", test_sprintf("%s", test_string("abc")));
+}
+
+TEST(printf_test, ostream) {
+  std::ostringstream os;
+  int ret = fmt::fprintf(os, "Don't %s!", "panic");
+  EXPECT_EQ("Don't panic!", os.str());
+  EXPECT_EQ(12, ret);
+}
+
+TEST(printf_test, vprintf) {
+  fmt::format_arg_store<fmt::printf_context, int> as{42};
+  fmt::basic_format_args<fmt::printf_context> args(as);
+  EXPECT_EQ(fmt::vsprintf("%d", args), "42");
+  EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
+  EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
+  EXPECT_WRITE(stdout, fmt::vfprintf(std::cout, "%d", args), "42");
+}
+
+template <typename... Args>
+void check_format_string_regression(fmt::string_view s, const Args&... args) {
+  fmt::sprintf(s, args...);
+}
+
+TEST(printf_test, check_format_string_regression) {
+  check_format_string_regression("%c%s", 'x', "");
+}
+
+TEST(printf_test, fixed_large_exponent) {
+  EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
+}
+
+TEST(printf_test, vsprintf_make_args_example) {
+  fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
+                                                                  "something"};
+  fmt::basic_format_args<fmt::printf_context> args(as);
+  EXPECT_EQ("[42] something happened", fmt::vsprintf("[%d] %s happened", args));
+  auto as2 = fmt::make_printf_args(42, "something");
+  fmt::basic_format_args<fmt::printf_context> args2(as2);
+  EXPECT_EQ("[42] something happened",
+            fmt::vsprintf("[%d] %s happened", args2));
+  EXPECT_EQ("[42] something happened",
+            fmt::vsprintf("[%d] %s happened",
+                          {fmt::make_printf_args(42, "something")}));
+}
+
+TEST(printf_test, vsprintf_make_wargs_example) {
+  fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
+      42, L"something"};
+  fmt::basic_format_args<fmt::wprintf_context> args(as);
+  EXPECT_EQ(L"[42] something happened",
+            fmt::vsprintf(L"[%d] %s happened", args));
+  auto as2 = fmt::make_wprintf_args(42, L"something");
+  fmt::basic_format_args<fmt::wprintf_context> args2(as2);
+  EXPECT_EQ(L"[42] something happened",
+            fmt::vsprintf(L"[%d] %s happened", args2));
+  EXPECT_EQ(L"[42] something happened",
+            fmt::vsprintf(L"[%d] %s happened",
+                          {fmt::make_wprintf_args(42, L"something")}));
+}
diff --git a/libs/fmt/test/ranges-test.cc b/libs/fmt/test/ranges-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..64f65e463465b5c93f6a9d397fb3aa894b7262f3
--- /dev/null
+++ b/libs/fmt/test/ranges-test.cc
@@ -0,0 +1,269 @@
+// Formatting library for C++ - the core API
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+//
+// Copyright (c) 2018 - present, Remotion (Igor Schulz)
+// All Rights Reserved
+// {fmt} support for ranges, containers and types tuple interface.
+
+#include "fmt/ranges.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601
+#  define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
+#endif
+
+#if !FMT_MSC_VER || FMT_MSC_VER > 1910
+#  define FMT_RANGES_TEST_ENABLE_JOIN
+#  define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
+#endif
+
+#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
+TEST(ranges_test, format_array) {
+  int arr[] = {1, 2, 3, 5, 7, 11};
+  EXPECT_EQ(fmt::format("{}", arr), "[1, 2, 3, 5, 7, 11]");
+}
+
+TEST(ranges_test, format_2d_array) {
+  int arr[][2] = {{1, 2}, {3, 5}, {7, 11}};
+  EXPECT_EQ(fmt::format("{}", arr), "[[1, 2], [3, 5], [7, 11]]");
+}
+
+TEST(ranges_test, format_array_of_literals) {
+  const char* arr[] = {"1234", "abcd"};
+  EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
+}
+#endif  // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
+
+TEST(ranges_test, format_vector) {
+  auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
+  EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
+}
+
+TEST(ranges_test, format_vector2) {
+  auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
+  EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
+}
+
+TEST(ranges_test, format_map) {
+  auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
+  EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]");
+}
+
+TEST(ranges_test, format_pair) {
+  auto p = std::pair<int, float>(42, 1.5f);
+  EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
+}
+
+TEST(ranges_test, format_tuple) {
+  auto t =
+      std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
+  EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
+  EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
+}
+
+#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
+struct tuple_like {
+  int i;
+  std::string str;
+
+  template <size_t N> fmt::enable_if_t<N == 0, int> get() const noexcept {
+    return i;
+  }
+  template <size_t N>
+  fmt::enable_if_t<N == 1, fmt::string_view> get() const noexcept {
+    return str;
+  }
+};
+
+template <size_t N>
+auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
+  return t.get<N>();
+}
+
+namespace std {
+template <>
+struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
+
+template <size_t N> struct tuple_element<N, tuple_like> {
+  using type = decltype(std::declval<tuple_like>().get<N>());
+};
+}  // namespace std
+
+TEST(ranges_test, format_struct) {
+  auto t = tuple_like{42, "foo"};
+  EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
+}
+#endif  // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
+
+TEST(ranges_test, format_to) {
+  char buf[10];
+  auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
+  *end = '\0';
+  EXPECT_STREQ(buf, "[1, 2, 3]");
+}
+
+struct path_like {
+  const path_like* begin() const;
+  const path_like* end() const;
+
+  operator std::string() const;
+};
+
+TEST(ranges_test, path_like) {
+  EXPECT_FALSE((fmt::is_range<path_like, char>::value));
+}
+
+#ifdef FMT_USE_STRING_VIEW
+struct string_like {
+  const char* begin();
+  const char* end();
+  explicit operator fmt::string_view() const { return "foo"; }
+  explicit operator std::string_view() const { return "foo"; }
+};
+
+TEST(ranges_test, format_string_like) {
+  EXPECT_EQ(fmt::format("{}", string_like()), "foo");
+}
+#endif  // FMT_USE_STRING_VIEW
+
+// A range that provides non-const only begin()/end() to test fmt::join handles
+// that.
+//
+// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
+// information during iteration so they only provide non-const begin()/end().
+template <typename T> class non_const_only_range {
+ private:
+  std::vector<T> vec;
+
+ public:
+  using const_iterator = typename ::std::vector<T>::const_iterator;
+
+  template <typename... Args>
+  explicit non_const_only_range(Args&&... args)
+      : vec(std::forward<Args>(args)...) {}
+
+  const_iterator begin() { return vec.begin(); }
+  const_iterator end() { return vec.end(); }
+};
+
+template <typename T> class noncopyable_range {
+ private:
+  std::vector<T> vec;
+
+ public:
+  using const_iterator = typename ::std::vector<T>::const_iterator;
+
+  template <typename... Args>
+  explicit noncopyable_range(Args&&... args)
+      : vec(std::forward<Args>(args)...) {}
+
+  noncopyable_range(noncopyable_range const&) = delete;
+  noncopyable_range(noncopyable_range&) = delete;
+
+  const_iterator begin() const { return vec.begin(); }
+  const_iterator end() const { return vec.end(); }
+};
+
+TEST(ranges_test, range) {
+  noncopyable_range<int> w(3u, 0);
+  EXPECT_EQ(fmt::format("{}", w), "[0, 0, 0]");
+  EXPECT_EQ(fmt::format("{}", noncopyable_range<int>(3u, 0)), "[0, 0, 0]");
+
+  non_const_only_range<int> x(3u, 0);
+  EXPECT_EQ(fmt::format("{}", x), "[0, 0, 0]");
+  EXPECT_EQ(fmt::format("{}", non_const_only_range<int>(3u, 0)), "[0, 0, 0]");
+
+  auto y = std::vector<int>(3u, 0);
+  EXPECT_EQ(fmt::format("{}", y), "[0, 0, 0]");
+  EXPECT_EQ(fmt::format("{}", std::vector<int>(3u, 0)), "[0, 0, 0]");
+
+  const auto z = std::vector<int>(3u, 0);
+  EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]");
+}
+
+#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
+struct unformattable {};
+
+TEST(ranges_test, unformattable_range) {
+  EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
+                                   fmt::format_context>::value));
+}
+#endif
+
+#ifdef FMT_RANGES_TEST_ENABLE_JOIN
+TEST(ranges_test, join_tuple) {
+  // Value tuple args.
+  auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
+  EXPECT_EQ(fmt::format("({})", fmt::join(t1, ", ")), "(a, 1, 2)");
+
+  // Testing lvalue tuple args.
+  int x = 4;
+  auto t2 = std::tuple<char, int&>('b', x);
+  EXPECT_EQ(fmt::format("{}", fmt::join(t2, " + ")), "b + 4");
+
+  // Empty tuple.
+  auto t3 = std::tuple<>();
+  EXPECT_EQ(fmt::format("{}", fmt::join(t3, "|")), "");
+
+  // Single element tuple.
+  auto t4 = std::tuple<float>(4.0f);
+  EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
+}
+
+TEST(ranges_test, wide_string_join_tuple) {
+  auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f);
+  EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
+}
+
+TEST(ranges_test, join_initializer_list) {
+  EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3");
+  EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")),
+            "fmt rocks !");
+}
+
+struct zstring_sentinel {};
+
+bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
+bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
+
+struct zstring {
+  const char* p;
+  const char* begin() const { return p; }
+  zstring_sentinel end() const { return {}; }
+};
+
+TEST(ranges_test, join_sentinel) {
+  auto hello = zstring{"hello"};
+  EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']");
+  EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
+}
+
+TEST(ranges_test, join_range) {
+  noncopyable_range<int> w(3u, 0);
+  EXPECT_EQ(fmt::format("{}", fmt::join(w, ",")), "0,0,0");
+  EXPECT_EQ(fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")),
+            "0,0,0");
+
+  non_const_only_range<int> x(3u, 0);
+  EXPECT_EQ(fmt::format("{}", fmt::join(x, ",")), "0,0,0");
+  EXPECT_EQ(fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")),
+            "0,0,0");
+
+  auto y = std::vector<int>(3u, 0);
+  EXPECT_EQ(fmt::format("{}", fmt::join(y, ",")), "0,0,0");
+  EXPECT_EQ(fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")),
+            "0,0,0");
+
+  const auto z = std::vector<int>(3u, 0);
+  EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
+}
+#endif  // FMT_RANGES_TEST_ENABLE_JOIN
diff --git a/libs/fmt/test/scan-test.cc b/libs/fmt/test/scan-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7e327ea3f5fdcc25829f0bdca81971b08e04c60b
--- /dev/null
+++ b/libs/fmt/test/scan-test.cc
@@ -0,0 +1,116 @@
+// Formatting library for C++ - scanning API test
+//
+// Copyright (c) 2019 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "scan.h"
+
+#include <time.h>
+
+#include <climits>
+
+#include "gmock/gmock.h"
+#include "gtest-extra.h"
+
+TEST(scan_test, read_text) {
+  auto s = fmt::string_view("foo");
+  auto end = fmt::scan(s, "foo");
+  EXPECT_EQ(end, s.end());
+  EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input");
+}
+
+TEST(scan_test, read_int) {
+  auto n = int();
+  fmt::scan("42", "{}", n);
+  EXPECT_EQ(n, 42);
+  fmt::scan("-42", "{}", n);
+  EXPECT_EQ(n, -42);
+}
+
+TEST(scan_test, read_longlong) {
+  long long n = 0;
+  fmt::scan("42", "{}", n);
+  EXPECT_EQ(n, 42);
+  fmt::scan("-42", "{}", n);
+  EXPECT_EQ(n, -42);
+}
+
+TEST(scan_test, read_uint) {
+  auto n = unsigned();
+  fmt::scan("42", "{}", n);
+  EXPECT_EQ(n, 42);
+  EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
+                   "invalid input");
+}
+
+TEST(scan_test, read_ulonglong) {
+  unsigned long long n = 0;
+  fmt::scan("42", "{}", n);
+  EXPECT_EQ(n, 42);
+  EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
+                   "invalid input");
+}
+
+TEST(scan_test, read_string) {
+  auto s = std::string();
+  fmt::scan("foo", "{}", s);
+  EXPECT_EQ(s, "foo");
+}
+
+TEST(scan_test, read_string_view) {
+  auto s = fmt::string_view();
+  fmt::scan("foo", "{}", s);
+  EXPECT_EQ(s, "foo");
+}
+
+#ifndef _WIN32
+namespace fmt {
+template <> struct scanner<tm> {
+  std::string format;
+
+  scan_parse_context::iterator parse(scan_parse_context& ctx) {
+    auto it = ctx.begin();
+    if (it != ctx.end() && *it == ':') ++it;
+    auto end = it;
+    while (end != ctx.end() && *end != '}') ++end;
+    format.reserve(detail::to_unsigned(end - it + 1));
+    format.append(it, end);
+    format.push_back('\0');
+    return end;
+  }
+
+  template <class ScanContext>
+  typename ScanContext::iterator scan(tm& t, ScanContext& ctx) {
+    auto result = strptime(ctx.begin(), format.c_str(), &t);
+    if (!result) throw format_error("failed to parse time");
+    return result;
+  }
+};
+}  // namespace fmt
+
+TEST(scan_test, read_custom) {
+  auto input = "Date: 1985-10-25";
+  auto t = tm();
+  fmt::scan(input, "Date: {0:%Y-%m-%d}", t);
+  EXPECT_EQ(t.tm_year, 85);
+  EXPECT_EQ(t.tm_mon, 9);
+  EXPECT_EQ(t.tm_mday, 25);
+}
+#endif
+
+TEST(scan_test, invalid_format) {
+  EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error,
+                   "argument index out of range");
+  EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error,
+                   "invalid format string");
+}
+
+TEST(scan_test, example) {
+  auto key = std::string();
+  auto value = int();
+  fmt::scan("answer = 42", "{} = {}", key, value);
+  EXPECT_EQ(key, "answer");
+  EXPECT_EQ(value, 42);
+}
diff --git a/libs/fmt/test/scan.h b/libs/fmt/test/scan.h
new file mode 100644
index 0000000000000000000000000000000000000000..de82067a499577b6ff97c792b882ba2fde8ec3b1
--- /dev/null
+++ b/libs/fmt/test/scan.h
@@ -0,0 +1,238 @@
+// Formatting library for C++ - scanning API proof of concept
+//
+// Copyright (c) 2019 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include <array>
+#include <cassert>
+#include <climits>
+
+#include "fmt/format.h"
+
+FMT_BEGIN_NAMESPACE
+template <typename T, typename Char = char> struct scanner {
+  // A deleted default constructor indicates a disabled scanner.
+  scanner() = delete;
+};
+
+class scan_parse_context {
+ private:
+  string_view format_;
+
+ public:
+  using iterator = string_view::iterator;
+
+  explicit FMT_CONSTEXPR scan_parse_context(string_view format)
+      : format_(format) {}
+
+  FMT_CONSTEXPR iterator begin() const { return format_.begin(); }
+  FMT_CONSTEXPR iterator end() const { return format_.end(); }
+
+  void advance_to(iterator it) {
+    format_.remove_prefix(detail::to_unsigned(it - begin()));
+  }
+};
+
+struct scan_context {
+ private:
+  string_view input_;
+
+ public:
+  using iterator = const char*;
+
+  explicit scan_context(string_view input) : input_(input) {}
+
+  iterator begin() const { return input_.data(); }
+  iterator end() const { return begin() + input_.size(); }
+
+  void advance_to(iterator it) {
+    input_.remove_prefix(detail::to_unsigned(it - begin()));
+  }
+};
+
+namespace detail {
+enum class scan_type {
+  none_type,
+  int_type,
+  uint_type,
+  long_long_type,
+  ulong_long_type,
+  string_type,
+  string_view_type,
+  custom_type
+};
+
+struct custom_scan_arg {
+  void* value;
+  void (*scan)(void* arg, scan_parse_context& parse_ctx, scan_context& ctx);
+};
+
+class scan_arg {
+ public:
+  scan_type type;
+  union {
+    int* int_value;
+    unsigned* uint_value;
+    long long* long_long_value;
+    unsigned long long* ulong_long_value;
+    std::string* string;
+    fmt::string_view* string_view;
+    custom_scan_arg custom;
+    // TODO: more types
+  };
+
+  scan_arg() : type(scan_type::none_type) {}
+  scan_arg(int& value) : type(scan_type::int_type), int_value(&value) {}
+  scan_arg(unsigned& value) : type(scan_type::uint_type), uint_value(&value) {}
+  scan_arg(long long& value)
+      : type(scan_type::long_long_type), long_long_value(&value) {}
+  scan_arg(unsigned long long& value)
+      : type(scan_type::ulong_long_type), ulong_long_value(&value) {}
+  scan_arg(std::string& value) : type(scan_type::string_type), string(&value) {}
+  scan_arg(fmt::string_view& value)
+      : type(scan_type::string_view_type), string_view(&value) {}
+  template <typename T> scan_arg(T& value) : type(scan_type::custom_type) {
+    custom.value = &value;
+    custom.scan = scan_custom_arg<T>;
+  }
+
+ private:
+  template <typename T>
+  static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx,
+                              scan_context& ctx) {
+    scanner<T> s;
+    parse_ctx.advance_to(s.parse(parse_ctx));
+    ctx.advance_to(s.scan(*static_cast<T*>(arg), ctx));
+  }
+};
+}  // namespace detail
+
+struct scan_args {
+  int size;
+  const detail::scan_arg* data;
+
+  template <size_t N>
+  scan_args(const std::array<detail::scan_arg, N>& store)
+      : size(N), data(store.data()) {
+    static_assert(N < INT_MAX, "too many arguments");
+  }
+};
+
+namespace detail {
+
+struct scan_handler : error_handler {
+ private:
+  scan_parse_context parse_ctx_;
+  scan_context scan_ctx_;
+  scan_args args_;
+  int next_arg_id_;
+  scan_arg arg_;
+
+  template <typename T = unsigned> T read_uint() {
+    T value = 0;
+    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
+    while (it != end) {
+      char c = *it++;
+      if (c < '0' || c > '9') on_error("invalid input");
+      // TODO: check overflow
+      value = value * 10 + static_cast<unsigned>(c - '0');
+    }
+    scan_ctx_.advance_to(it);
+    return value;
+  }
+
+  template <typename T = int> T read_int() {
+    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
+    bool negative = it != end && *it == '-';
+    if (negative) ++it;
+    scan_ctx_.advance_to(it);
+    const auto value = read_uint<typename std::make_unsigned<T>::type>();
+    if (negative) return -static_cast<T>(value);
+    return static_cast<T>(value);
+  }
+
+ public:
+  scan_handler(string_view format, string_view input, scan_args args)
+      : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {}
+
+  const char* pos() const { return scan_ctx_.begin(); }
+
+  void on_text(const char* begin, const char* end) {
+    auto size = to_unsigned(end - begin);
+    auto it = scan_ctx_.begin();
+    if (it + size > scan_ctx_.end() ||
+        !std::equal(begin, end, make_checked(it, size))) {
+      on_error("invalid input");
+    }
+    scan_ctx_.advance_to(it + size);
+  }
+
+  int on_arg_id() { return on_arg_id(next_arg_id_++); }
+  int on_arg_id(int id) {
+    if (id >= args_.size) on_error("argument index out of range");
+    arg_ = args_.data[id];
+    return id;
+  }
+  int on_arg_id(string_view) { return on_error("invalid format"), 0; }
+
+  void on_replacement_field(int, const char*) {
+    auto it = scan_ctx_.begin(), end = scan_ctx_.end();
+    switch (arg_.type) {
+    case scan_type::int_type:
+      *arg_.int_value = read_int();
+      break;
+    case scan_type::uint_type:
+      *arg_.uint_value = read_uint();
+      break;
+    case scan_type::long_long_type:
+      *arg_.long_long_value = read_int<long long>();
+      break;
+    case scan_type::ulong_long_type:
+      *arg_.ulong_long_value = read_uint<unsigned long long>();
+      break;
+    case scan_type::string_type:
+      while (it != end && *it != ' ') arg_.string->push_back(*it++);
+      scan_ctx_.advance_to(it);
+      break;
+    case scan_type::string_view_type: {
+      auto s = it;
+      while (it != end && *it != ' ') ++it;
+      *arg_.string_view = fmt::string_view(s, to_unsigned(it - s));
+      scan_ctx_.advance_to(it);
+      break;
+    }
+    case scan_type::none_type:
+    case scan_type::custom_type:
+      assert(false);
+    }
+  }
+
+  const char* on_format_specs(int, const char* begin, const char*) {
+    if (arg_.type != scan_type::custom_type) return begin;
+    parse_ctx_.advance_to(begin);
+    arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_);
+    return parse_ctx_.begin();
+  }
+};
+}  // namespace detail
+
+template <typename... Args>
+std::array<detail::scan_arg, sizeof...(Args)> make_scan_args(Args&... args) {
+  return {{args...}};
+}
+
+string_view::iterator vscan(string_view input, string_view format_str,
+                            scan_args args) {
+  detail::scan_handler h(format_str, input, args);
+  detail::parse_format_string<false>(format_str, h);
+  return input.begin() + (h.pos() - &*input.begin());
+}
+
+template <typename... Args>
+string_view::iterator scan(string_view input, string_view format_str,
+                           Args&... args) {
+  return vscan(input, format_str, make_scan_args(args...));
+}
+FMT_END_NAMESPACE
diff --git a/libs/fmt/test/std-format-test.cc b/libs/fmt/test/std-format-test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c67a2a0309230478d2f6246c103507d01f95cace
--- /dev/null
+++ b/libs/fmt/test/std-format-test.cc
@@ -0,0 +1,161 @@
+#include <format>
+
+#include "gtest/gtest.h"
+
+TEST(std_format_test, escaping) {
+  using namespace std;
+  string s = format("{0}-{{", 8);  // s == "8-{"
+  EXPECT_EQ(s, "8-{");
+}
+
+TEST(std_format_test, indexing) {
+  using namespace std;
+  string s0 = format("{} to {}", "a", "b");    // OK: automatic indexing
+  string s1 = format("{1} to {0}", "a", "b");  // OK: manual indexing
+  EXPECT_EQ(s0, "a to b");
+  EXPECT_EQ(s1, "b to a");
+  // Error: mixing automatic and manual indexing
+  EXPECT_THROW(string s2 = format("{0} to {}", "a", "b"), std::format_error);
+  // Error: mixing automatic and manual indexing
+  EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error);
+}
+
+TEST(std_format_test, alignment) {
+  using namespace std;
+  char c = 120;
+  string s0 = format("{:6}", 42);     // s0 == "    42"
+  string s1 = format("{:6}", 'x');    // s1 == "x     "
+  string s2 = format("{:*<6}", 'x');  // s2 == "x*****"
+  string s3 = format("{:*>6}", 'x');  // s3 == "*****x"
+  string s4 = format("{:*^6}", 'x');  // s4 == "**x***"
+  // Error: '=' with charT and no integer presentation type
+  EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error);
+  string s6 = format("{:6d}", c);    // s6 == "   120"
+  string s7 = format("{:6}", true);  // s9 == "true  "
+  EXPECT_EQ(s0, "    42");
+  EXPECT_EQ(s1, "x     ");
+  EXPECT_EQ(s2, "x*****");
+  EXPECT_EQ(s3, "*****x");
+  EXPECT_EQ(s4, "**x***");
+  EXPECT_EQ(s6, "   120");
+  EXPECT_EQ(s7, "true  ");
+}
+
+TEST(std_format_test, float) {
+  using namespace std;
+  double inf = numeric_limits<double>::infinity();
+  double nan = numeric_limits<double>::quiet_NaN();
+  string s0 = format("{0:} {0:+} {0:-} {0: }", 1);   // s0 == "1 +1 1  1"
+  string s1 = format("{0:} {0:+} {0:-} {0: }", -1);  // s1 == "-1 -1 -1 -1"
+  string s2 =
+      format("{0:} {0:+} {0:-} {0: }", inf);  // s2 == "inf +inf inf  inf"
+  string s3 =
+      format("{0:} {0:+} {0:-} {0: }", nan);  // s3 == "nan +nan nan  nan"
+  EXPECT_EQ(s0, "1 +1 1  1");
+  EXPECT_EQ(s1, "-1 -1 -1 -1");
+  EXPECT_EQ(s2, "inf +inf inf  inf");
+  EXPECT_EQ(s3, "nan +nan nan  nan");
+}
+
+TEST(std_format_test, int) {
+  using namespace std;
+  string s0 = format("{}", 42);                       // s0 == "42"
+  string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42);  // s1 == "101010 42 52 2a"
+  string s2 = format("{0:#x} {0:#X}", 42);            // s2 == "0x2a 0X2A"
+  string s3 = format("{:L}", 1234);  // s3 == "1234" (depends on the locale)
+  EXPECT_EQ(s0, "42");
+  EXPECT_EQ(s1, "101010 42 52 2a");
+  EXPECT_EQ(s2, "0x2a 0X2A");
+  EXPECT_EQ(s3, "1234");
+}
+
+#include <format>
+
+enum color { red, green, blue };
+
+const char* color_names[] = {"red", "green", "blue"};
+
+template <> struct std::formatter<color> : std::formatter<const char*> {
+  auto format(color c, format_context& ctx) {
+    return formatter<const char*>::format(color_names[c], ctx);
+  }
+};
+
+struct err {};
+
+TEST(std_format_test, formatter) {
+  std::string s0 = std::format("{}", 42);  // OK: library-provided formatter
+  // std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled
+  // formatter
+  std::string s2 = std::format("{}", red);  // OK: user-provided formatter
+  // std::string s3 = std::format("{}", err{});  // Ill-formed: disabled
+  // formatter
+  EXPECT_EQ(s0, "42");
+  EXPECT_EQ(s2, "red");
+}
+
+struct S {
+  int value;
+};
+
+template <> struct std::formatter<S> {
+  size_t width_arg_id = 0;
+
+  // Parses a width argument id in the format { <digit> }.
+  constexpr auto parse(format_parse_context& ctx) {
+    constexpr auto is_ascii_digit = [](const char c) {
+      return c >= '0' && c <= '9';
+    };
+
+    auto iter = ctx.begin();
+    // auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; };
+    auto get_char = [&]() { return iter != ctx.end() ? *iter : '\0'; };
+    if (get_char() != '{') return iter;
+    ++iter;
+    char c = get_char();
+    if (!is_ascii_digit(c) || (++iter, get_char()) != '}')
+      throw format_error("invalid format");
+    width_arg_id = fmt::detail::to_unsigned(c - '0');
+    ctx.check_arg_id(width_arg_id);
+    return ++iter;
+  }
+
+  // Formats S with width given by the argument width_arg_id.
+  auto format(S s, format_context& ctx) {
+    int width = visit_format_arg(
+        [](auto value) -> int {
+          using type = decltype(value);
+          if constexpr (!is_integral_v<type> || is_same_v<type, bool>)
+            throw format_error("width is not integral");
+          // else if (value < 0 || value > numeric_limits<int>::max())
+          else if (fmt::detail::is_negative(value) ||
+                   value > numeric_limits<int>::max())
+            throw format_error("invalid width");
+          else
+            return static_cast<int>(value);
+        },
+        ctx.arg(width_arg_id));
+    return format_to(ctx.out(), "{0:{1}}", s.value, width);
+  }
+};
+
+TEST(std_format_test, parsing) {
+  std::string s = std::format("{0:{1}}", S{42}, 10);  // s == "        42"
+  EXPECT_EQ(s, "        42");
+}
+
+#if FMT_USE_INT128
+template <> struct std::formatter<__int128_t> : std::formatter<long long> {
+  auto format(__int128_t n, format_context& ctx) {
+    // Format as a long long since we only want to check if it is possible to
+    // specialize formatter for __int128_t.
+    return formatter<long long>::format(static_cast<long long>(n), ctx);
+  }
+};
+
+TEST(std_format_test, int128) {
+  __int128_t n = 42;
+  auto s = std::format("{}", n);
+  EXPECT_EQ(s, "42");
+}
+#endif  // FMT_USE_INT128
diff --git a/libs/fmt/test/test-assert.h b/libs/fmt/test/test-assert.h
new file mode 100644
index 0000000000000000000000000000000000000000..cc7920dfe398f63e8aa60b05bf00ef020bc7f871
--- /dev/null
+++ b/libs/fmt/test/test-assert.h
@@ -0,0 +1,40 @@
+// Formatting library for C++ - test version of FMT_ASSERT
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#ifndef FMT_TEST_ASSERT_H_
+#define FMT_TEST_ASSERT_H_
+
+#include <stdexcept>
+
+void throw_assertion_failure(const char* message);
+#define FMT_ASSERT(condition, message) \
+  if (!(condition)) throw_assertion_failure(message);
+
+#include "gtest-extra.h"
+#include "gtest/gtest.h"
+
+class assertion_failure : public std::logic_error {
+ public:
+  explicit assertion_failure(const char* message) : std::logic_error(message) {}
+
+ private:
+  virtual void avoid_weak_vtable();
+};
+
+void assertion_failure::avoid_weak_vtable() {}
+
+// We use a separate function (rather than throw directly from FMT_ASSERT) to
+// avoid GCC's -Wterminate warning when FMT_ASSERT is used in a destructor.
+inline void throw_assertion_failure(const char* message) {
+  throw assertion_failure(message);
+}
+
+// Expects an assertion failure.
+#define EXPECT_ASSERT(stmt, message) \
+  FMT_TEST_THROW_(stmt, assertion_failure, message, GTEST_NONFATAL_FAILURE_)
+
+#endif  // FMT_TEST_ASSERT_H_
diff --git a/libs/fmt/test/test-main.cc b/libs/fmt/test/test-main.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b69cc5002c358b4d759ec8c9446cf7485deda009
--- /dev/null
+++ b/libs/fmt/test/test-main.cc
@@ -0,0 +1,43 @@
+// Formatting library for C++ - test main function.
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include <cstdlib>
+
+#include "gtest/gtest.h"
+
+#ifdef _WIN32
+#  include <windows.h>
+#endif
+
+#ifdef _MSC_VER
+#  include <crtdbg.h>
+#else
+#  define _CrtSetReportFile(a, b)
+#  define _CrtSetReportMode(a, b)
+#endif
+
+int main(int argc, char** argv) {
+#ifdef _WIN32
+  // Don't display any error dialogs. This also suppresses message boxes
+  // on assertion failures in MinGW where _set_error_mode/CrtSetReportMode
+  // doesn't help.
+  SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX |
+               SEM_NOOPENFILEERRORBOX);
+#endif
+  // Disable message boxes on assertion failures.
+  _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+  _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
+  _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+  _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+  try {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+  } catch (...) {
+    // Catch all exceptions to make Coverity happy.
+  }
+  return EXIT_FAILURE;
+}
diff --git a/libs/fmt/test/util.cc b/libs/fmt/test/util.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6bdbe6137ceac447ea576d4bb6c5a6d99aba3b79
--- /dev/null
+++ b/libs/fmt/test/util.cc
@@ -0,0 +1,40 @@
+// Formatting library for C++ - test utilities
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include "util.h"
+
+#include <cstring>
+
+std::string get_system_error(int error_code) {
+#if defined(__MINGW32__) || !defined(_WIN32)
+  return strerror(error_code);
+#else
+  enum { buffer_size = 200 };
+  char buffer[buffer_size];
+  if (strerror_s(buffer, buffer_size, error_code))
+    throw std::exception("strerror_s failed");
+  return buffer;
+#endif
+}
+
+const char* const file_content = "Don't panic!";
+
+fmt::buffered_file open_buffered_file(FILE** fp) {
+#if FMT_USE_FCNTL
+  fmt::file read_end, write_end;
+  fmt::file::pipe(read_end, write_end);
+  write_end.write(file_content, std::strlen(file_content));
+  write_end.close();
+  fmt::buffered_file f = read_end.fdopen("r");
+  if (fp) *fp = f.get();
+#else
+  fmt::buffered_file f("test-file", "w");
+  fputs(file_content, f.get());
+  if (fp) *fp = f.get();
+#endif
+  return f;
+}
diff --git a/libs/fmt/test/util.h b/libs/fmt/test/util.h
new file mode 100644
index 0000000000000000000000000000000000000000..92871cbd107e018a841001a47e01366fde2fa7e0
--- /dev/null
+++ b/libs/fmt/test/util.h
@@ -0,0 +1,79 @@
+// Formatting library for C++ - test utilities
+//
+// Copyright (c) 2012 - present, Victor Zverovich
+// All rights reserved.
+//
+// For the license information refer to format.h.
+
+#include <cstdarg>
+#include <cstdio>
+#include <string>
+
+#include "fmt/os.h"
+
+#ifdef _MSC_VER
+#  define FMT_VSNPRINTF vsprintf_s
+#else
+#  define FMT_VSNPRINTF vsnprintf
+#endif
+
+template <size_t SIZE>
+void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) {
+  std::va_list args;
+  va_start(args, format);
+  FMT_VSNPRINTF(buffer, SIZE, format, args);
+  va_end(args);
+}
+
+std::string get_system_error(int error_code);
+
+extern const char* const file_content;
+
+// Opens a buffered file for reading.
+fmt::buffered_file open_buffered_file(FILE** fp = nullptr);
+
+inline FILE* safe_fopen(const char* filename, const char* mode) {
+#if defined(_WIN32) && !defined(__MINGW32__)
+  // Fix MSVC warning about "unsafe" fopen.
+  FILE* f = 0;
+  errno = fopen_s(&f, filename, mode);
+  return f;
+#else
+  return std::fopen(filename, mode);
+#endif
+}
+
+template <typename Char> class basic_test_string {
+ private:
+  std::basic_string<Char> value_;
+
+  static const Char empty[];
+
+ public:
+  explicit basic_test_string(const Char* value = empty) : value_(value) {}
+
+  const std::basic_string<Char>& value() const { return value_; }
+};
+
+template <typename Char> const Char basic_test_string<Char>::empty[] = {0};
+
+typedef basic_test_string<char> test_string;
+typedef basic_test_string<wchar_t> test_wstring;
+
+template <typename Char>
+std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os,
+                                     const basic_test_string<Char>& s) {
+  os << s.value();
+  return os;
+}
+
+class date {
+  int year_, month_, day_;
+
+ public:
+  date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
+
+  int year() const { return year_; }
+  int month() const { return month_; }
+  int day() const { return day_; }
+};
diff --git a/libs/mavlink_skyward_lib b/libs/mavlink_skyward_lib
index 00a22f01347688d08464eafcbb86998a0690a8f8..d7c4c0179f0cde99b3346181b51c6cd1a95747a8 160000
--- a/libs/mavlink_skyward_lib
+++ b/libs/mavlink_skyward_lib
@@ -1 +1 @@
-Subproject commit 00a22f01347688d08464eafcbb86998a0690a8f8
+Subproject commit d7c4c0179f0cde99b3346181b51c6cd1a95747a8
diff --git a/libs/miosix-kernel b/libs/miosix-kernel
index a34d3827097db99973d5119c5a0c817c2d7b306c..a87c0b76db9d11e099e8ebf040b405b24ff52e63 160000
--- a/libs/miosix-kernel
+++ b/libs/miosix-kernel
@@ -1 +1 @@
-Subproject commit a34d3827097db99973d5119c5a0c817c2d7b306c
+Subproject commit a87c0b76db9d11e099e8ebf040b405b24ff52e63
diff --git a/libs/mxgui b/libs/mxgui
new file mode 160000
index 0000000000000000000000000000000000000000..cab17f6185da95502d20dd31b17fef67e06ca6ef
--- /dev/null
+++ b/libs/mxgui
@@ -0,0 +1 @@
+Subproject commit cab17f6185da95502d20dd31b17fef67e06ca6ef
diff --git a/sbs b/sbs
index d159ba3a51bb38c2280f2f0853a1aa75da16d187..2e3243ea1fd30d96522a5ff5087ecee367c2846e 100755
--- a/sbs
+++ b/sbs
@@ -17,7 +17,7 @@
 #
 # 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
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS 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
@@ -48,9 +48,8 @@ except ImportError:
 # Global variables
 #
 srcfiles={}
+extlibs={}
 mainfiles={}
-tests={}
-entrypoints={}
 colors=False
 
 # Map for the project configuration, filled in when parsing sbs.conf
@@ -154,27 +153,31 @@ def parseConf(path):
                 projconf[i] = conf.get('SBS_PROJECT', i)
         elif stype == 'srcfiles':
             srcfiles[i] = [x.strip() for x in conf.get(i, 'Files').split("\n")]
-        elif stype == 'board':
-            entrypoints[i] = {   'id': conf.get(i, 'BoardId'),
-                            'bin': conf.get(i, 'BinName'),
-                            'defines': conf.get(i, 'Defines'),
-                            'files': [entrypoint_mask % conf.get(i, 'Main').strip()] +
-                             [x.strip() for x in conf.get(i, 'Include').split(' ')]
-                        }
-        elif stype == 'test':
-            tests[i] = {   'id': conf.get(i, 'BoardId'),
+        elif stype == 'library':
+            extlibs[i] = {'subdir': conf.get(i, 'Subdir'),
+                          'libs': [x.strip() for x in conf.get(i, 'Libs').split()],
+                          'defines':  conf.get(i, 'Defines').strip(),
+                          'includes': [x.strip() for x in conf.get(i, 'Includes').split()]}
+        elif stype == 'board' or stype == 'test':
+            if stype == 'test':
+                mask = test_mask
+                add_def = " -I" + projconf['TESTS_PATH']
+            else:
+                mask = entrypoint_mask
+                add_def = ""
+            
+            mainfiles[i] = {'type': stype,
+                            'id': conf.get(i, 'BoardId').strip(),
                             'bin': conf.get(i, 'BinName'),
-                            'defines': conf.get(i, 'Defines') + " -I" + projconf['TESTS_PATH'],
-                            'files': [test_mask % conf.get(i, 'Main').strip()] +
+                            'defines': conf.get(i, 'Defines') + add_def,
+                            'libs': [x.strip() for x in conf.get(i, 'Libs', fallback="").split()],
+                            'files': [mask % conf.get(i, 'Main').strip()] +
                              [x.strip() for x in conf.get(i, 'Include').split(' ')]
                         }
         else:
             print('[SBS] Syntax error in %s: type %s not implemented' % (path, stype))
             exit(1)
 
-    # Join tests and entrypoints in mainfiles
-    mainfiles.update(entrypoints)
-    mainfiles.update(tests)
 
     # Exit if nothing was found
     if (len(mainfiles) == 0 ):
@@ -184,6 +187,7 @@ def parseConf(path):
     # Import also srcfiles from boardcore if executing from another folders
     if projconf['SBS_BASE'] != '.':
         importSrcFiles(projconf['SBS_BASE'] + "/sbs.conf")
+        importLibs(projconf['SBS_BASE'] + "/sbs.conf")
 
     # Substitute includes
     for i in mainfiles:
@@ -194,6 +198,13 @@ def parseConf(path):
             else:
                 files += [j]
         mainfiles[i]['files'] = files
+    
+    # Substitute libs
+    for i in mainfiles:
+        lib_dicts = {}
+        for j in mainfiles[i]['libs']:
+            lib_dicts[j] = extlibs[j]
+        mainfiles[i]['libs'] = lib_dicts
 
 #
 # Create a Makefile for a specific board starting from Makefile.template
@@ -208,7 +219,11 @@ def build_makefile(template, board, bname):
              "MAP_FILE": "export MAIN_MAP_FILE := bin/%s.map\n" % board['bin'],
              "SOURCE_FILES": "%s\n" % (" ".join(board['files'])),
              "CUSTOM_DEFINES": board['defines'],
-             "BIN_NAME": board['bin']
+             "BIN_NAME": board['bin'],
+             "LIB_SUBDIR": " ".join([x['subdir'] for _,x in board['libs'].items()]),
+             "LIB_LIBS": " ".join([" ".join(x['libs']) for _,x in board['libs'].items()]),
+             "LIB_INCLUDES": " ".join([" ".join([ "-I" + s for s in x['includes']]) for _,x in board['libs'].items()]),
+             "LIB_DEFINES": " ".join(x['defines'] for _,x in board['libs'].items())
     }
 
     rmap["PROJECT_INCLUDES"] = "-I" + projconf["SRC_PATH"].strip() + " "
@@ -255,7 +270,25 @@ def importSrcFiles(path):
             else:
                 print('[SBS] Syntax error in %s: a srcfiles group named %s is already defined. Terminating' % (path, i))
                 exit(1)
+#
+# Import libraries groups from another .conf file.
+# Libraries are added only if there's not a group already with the same name.
+#
+def importLibs(path):
+    conf = cp.RawConfigParser()
+    conf.read(path)
 
+    for i in conf.sections():
+        stype = conf.get(i, 'Type')
+        if stype == 'library':
+            if i not in extlibs:
+                extlibs[i] =  {'subdir': '' if conf.get(i, 'Subdir') == '' else projconf['SBS_BASE'] + "/" +  conf.get(i, 'Subdir'),
+                            'libs': [projconf['SBS_BASE'] + "/" + x.strip() for x in conf.get(i, 'Libs').split()],
+                            'defines':  conf.get(i, 'Defines').strip(),
+                            'includes': [projconf['SBS_BASE'] + "/" + x.strip() for x in conf.get(i, 'Includes').split()]}
+            else:
+                print('[SBS] Syntax error in %s: a library group named %s is already defined. Terminating' % (path, i))
+                exit(1)
 
 ##-------------------------------------------------------------
 ##                       MAIN
@@ -297,14 +330,17 @@ if options.lint == True:
 # List
 if options.list == True:
     print('[SBS] List of available entrypoints:')
-    for entrypoint in entrypoints:
-        print(entrypoint)
+    for main in mainfiles:
+        if(mainfiles[main]['type'] == 'board'):
+            print(main)
     print('\n[SBS] List of available tests:')
-    for test in tests:
-        print(test)
+    for main in mainfiles:
+        if(mainfiles[main]['type'] == 'test'):
+            print(main)
     exit(0)
 
 # Set how many main files to build:
+files = list(mainfiles.keys()).copy()
 if options.board != None:       # just one
     try:
         good = mainfiles[options.board]
@@ -313,9 +349,13 @@ if options.board != None:       # just one
         print('[SBS] No entrypoint or test named %s. Terminating.' % (options.board))
         sys.exit(2)
 elif options.all_entry == True:  # all entrypoints
-    mainfiles = entrypoints
+     for main in files:
+        if(mainfiles[main]['type'] == 'test'):
+            del mainfiles[main]
 elif options.all_tests == True:  # all tests
-    mainfiles = tests
+    for main in files:
+        if(mainfiles[main]['type'] == 'board'):
+            del mainfiles[main]
 
 
 # Set action
diff --git a/sbs.conf b/sbs.conf
index 7d3af427a1afe1322773e14d5bfc34159341539d..cfaf4af5ed85e8b0ca9d53c8b400f91693663463 100644
--- a/sbs.conf
+++ b/sbs.conf
@@ -15,7 +15,7 @@
 #
 # 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
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS 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
@@ -81,39 +81,90 @@ PROJECT_INCLUDES:
 PROJECT_SUBDIRS:
 PROJECT_LIBS:
 
+#----------------------------------------------#
+#          List of External Libraries          #
+#----------------------------------------------#
+# [name]    -> can be included using 'name'
+#     Type: library
+#     Subdir: Subdirectory containing the library makefile
+#     Libs: Static libraries to link against (whitespace-separated)
+#     Includes: List of additional include directories (whitespace-separated)
+[libmxgui]
+Type:       library
+Subdir:     libs/mxgui
+Libs:       libs/mxgui/libmxgui.a
+Defines:    
+Includes:   libs/mxgui
+
+[eigen]
+Type:       library
+Subdir:     
+Libs:       
+Defines:    -DEIGEN_MAX_ALIGN_BYTES=0
+Includes:   libs/eigen
+
+[fmt]
+Type:       library
+Subdir:     libs/fmt/
+Libs:       libs/fmt/libfmt.a
+Defines:    -DFMT_STATIC_THOUSANDS_SEPARATOR -D_GLIBCXX_USE_WCHAR_T
+Includes:   libs/fmt/include
+
+[fmt-header-only]
+Type:       library
+Subdir:     
+Libs:       
+Defines:    
+Includes:   libs/fmt/include
+
 #-----------------------------------#
 #          List of Sources          #
 #-----------------------------------#
 # [name]    -> can be included using '%name'
 #     Type: srcfiles
-#     Files: a '/n'-separated list of f
-
-[canbus]
-Type:       srcfiles
-Files:      src/shared/drivers/canbus/CanManager.cpp
-            src/shared/drivers/canbus/CanBus.cpp
-            src/shared/drivers/canbus/CanInterrupt.cpp
-
-[piksi]
-Type:       srcfiles
-Files:      src/shared/drivers/piksi/piksi.cpp
+#     Files: a '/n'-separated list of source files
 
 [shared]
 Type:       srcfiles
 Files:      src/shared/scheduler/TaskScheduler.cpp
-            src/shared/drivers/spi/SensorSpi.cpp
             src/shared/diagnostic/CpuMeter.cpp
             src/shared/events/EventBroker.cpp
             src/shared/math/Stats.cpp
-            src/shared/math/Matrix.cpp
             src/shared/drivers/interrupt/external_interrupts.cpp
             src/shared/utils/aero/AeroUtils.cpp
             src/shared/Debug.cpp
+            src/shared/TimestampTimer.cpp
+            src/shared/diagnostic/PrintLogger.cpp
+            src/shared/logger/Logger.cpp
+			libs/tscpp/buffer.cpp
+            libs/tscpp/stream.cpp
+
+[ubloxgps]
+Type:       srcfiles
+Files:      src/shared/drivers/gps/ublox/UbloxGPS.cpp
+
+[canbus]
+Type:       srcfiles
+Files:      src/shared/drivers/canbus/CanManager.cpp
+            src/shared/drivers/canbus/CanBus.cpp
+            src/shared/drivers/canbus/CanInterrupt.cpp
+
+[calibration]
+Type:       srcfiles
+Files:      src/shared/sensors/calibration/SensorDataExtra.cpp
+
+[piksi]
+Type:       srcfiles
+Files:      src/shared/drivers/old_examples/piksi/piksi.cpp
 
 [pwm]
 Type:       srcfiles
 Files:      src/shared/drivers/pwm/pwm.cpp
 
+[spi]
+Type:       srcfiles
+Files:      src/shared/drivers/spi/SPITransaction.cpp
+
 [i2c]
 Type:       srcfiles
 Files:      src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
@@ -131,11 +182,15 @@ Type:       srcfiles
 Files:      src/shared/boards/AnakinBoard.cpp
             src/shared/drivers/Leds.cpp
 
-[logger]
+[sensormanager]
 Type:       srcfiles
-Files:      src/shared/logger/Logger.cpp
-			libs/tscpp/buffer.cpp
-            libs/tscpp/stream.cpp
+Files:      src/shared/sensors/SensorManager.cpp
+            src/shared/sensors/SensorSampler.cpp
+
+[math]
+Type:       srcfiles
+Files:      src/shared/math/SkyQuaternion.cpp
+            src/shared/math/Matrix.cpp
 
 [gamma868]
 Type:       srcfiles
@@ -143,7 +198,8 @@ Files:      src/shared/drivers/gamma868/Gamma868.cpp
 
 [xbee]
 Type:       srcfiles
-Files:      src/shared/drivers/Xbee/Xbee.cpp
+Files:      src/shared/drivers/Xbee/APIFrameParser.cpp
+            src/shared/drivers/Xbee/Xbee.cpp
 
 [events]
 Type:       srcfiles
@@ -153,18 +209,57 @@ Files:      src/shared/events/EventBroker.cpp
 Type:       srcfiles
 Files:      src/shared/drivers/servo/servo.cpp
 
+[hbridge]
+Type:       srcfiles
+Files:      src/shared/drivers/hbridge/HBridge.cpp
+
 [test-utils]
 Type:       srcfiles
 Files:      src/shared/utils/testutils/TestHelper.cpp
 
-[tests]
+[tests-boardcore]
 Type:       srcfiles
-Files:      src/tests/catch/test-eventbroker.cpp
-            src/tests/catch/test-circularbuffer.cpp
-            src/tests/catch/test-aero.cpp
+Files:      src/tests/catch/test-aero.cpp
             src/tests/catch/test-buttonhandler.cpp
+            src/tests/catch/test-circularbuffer.cpp
+            src/tests/catch/test-eventbroker.cpp
+            src/tests/catch/test-sensormanager-catch.cpp
+            src/tests/catch/test-hardwaretimer.cpp
+            src/tests/catch/test-kalman.cpp
+            #src/tests/catch/test-kalman-eigen.cpp
+            src/tests/catch/test-matrix.cpp
             src/tests/catch/test-packetqueue.cpp
+            src/tests/catch/spidriver/test-spidriver
+            src/tests/catch/xbee/test-xbee-parser.cpp
+            src/tests/catch/xbee/test-xbee-driver.cpp
+
+[internal-adc]
+Type:       srcfiles
+Files:      src/shared/drivers/adc/InternalADC/InternalADC.cpp
 
+[ads1118]
+Type:       srcfiles
+Files:      src/shared/drivers/adc/ADS1118/ADS1118.cpp
+
+[mpu9250]
+Type:       srcfiles
+Files:      src/shared/sensors/MPU9250/MPU9250.cpp
+
+[bme280]
+Type:       srcfiles
+Files:      src/shared/sensors/BME280/BME280.cpp
+
+[bmx160]
+Type:       srcfiles
+Files:      src/shared/sensors/BMX160/BMX160.cpp
+
+[bmx160withcorrection]
+Type:       srcfiles
+Files:      src/shared/sensors/BMX160/BMX160WithCorrection.cpp
+
+[ms5803]
+Type:       srcfiles
+Files:      src/shared/sensors/MS5803/MS5803.cpp
 
 #-------------------------------#
 #          Entrypoints          #
@@ -180,8 +275,9 @@ Files:      src/tests/catch/test-eventbroker.cpp
 Type:       board
 BoardId:    stm32f429zi_stm32f4discovery
 BinName:    dsgamma-config
-Include:    %gamma868
+Include:    %shared %gamma868
 Defines:
+Libs:       fmt
 Main:       config-dsgamma
 
 [kernel-testsuite]
@@ -192,6 +288,32 @@ Include:
 Defines:
 Main:       kernel-testsuite
 
+[mxgui-helloworld]
+Type:       board
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    mxgui-helloworld
+Include:    
+Defines:    -DDEBUG
+Libs:       libmxgui
+Main:       examples/mxgui-helloworld
+
+[sdcard-benchmark]
+Type:       board
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    sdcard-benchmark
+Include:    %shared
+Defines:
+Libs:       fmt
+Main:       sdcard-benchmark
+
+[bmx160-calibration-entry]
+Type:       board
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    bmx160-calibration-entry
+Libs:       eigen fmt
+Include:    %shared %spi %bmx160 %bmx160withcorrection %calibration
+Defines:    
+Main:       bmx160-calibration-entry
 
 #--------------------------#
 #          Tests           #
@@ -207,59 +329,110 @@ Main:       kernel-testsuite
 
 [tests-catch]
 Type:       test
-BoardId:    stm32f429zi_skyward_homeone
+BoardId:    stm32f429zi_stm32f4discovery
 BinName:    tests-catch
-Include:    %tests %shared %test-utils
-Defines:
+Include:    %tests-boardcore %shared %test-utils %spi %sensormanager %xbee
+Defines:    -DUSE_MOCK_PERIPHERALS
+Libs:       fmt eigen
 Main:       catch/catch-tests-entry
 
+# [test-kalman]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-kalman
+# Include:    %shared
+# Defines:    -DDEBUG -DSTANDALONE_CATCH1_TEST
+# Libs:       fmt
+# Main:       catch/test-kalman
+
+# [test-kalman-eigen]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-kalman-eigen
+# Include:    %shared 
+# Defines:    -DDEBUG -DSTANDALONE_CATCH1_TEST
+# Libs:       eigen fmt
+# Main:       catch/test-kalman-eigen
+
 [test-taskscheduler]
 Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
+BoardId:    stm32f429zi_skyward_death_stack_x
 BinName:    test-taskscheduler
 Include:    %shared
 Defines:
+Libs:       fmt
 Main:       test-taskscheduler
 
 [test-serial]
 Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
+BoardId:    stm32f429zi_skyward_death_stack_x
 BinName:    test-serial
-Include:
-Defines:
+Include:    %shared
+Defines:    
+Libs:       fmt
 Main:       test-serial
 
+[test-trace-logger]
+Type:       test
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-trace-logger
+Include:    %shared
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       test-trace-logger
+
 [test-hsm]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
 BinName:    test-hsm
 Include:    %shared
 Defines:   -DDEBUG
+Libs:       fmt
 Main:       test-hsm
 
 [test-interrupt]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
 BinName:    test-interrupt
-Include:
+Include:    %shared
 Defines:    -DDEBUG
+Libs:       fmt
 Main:       test-interrupt
 
+[test-timestamp-timer]
+Type:       test
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-timestamp-timer
+Include:    %shared
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       test-timestamp-timer
+
 [test-logger]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
 BinName:    test-logger
-Include:    %shared %logger
+Include:    %shared
 Defines:
+Libs:       fmt
 Main:       logger/test-logger
 
-[test-kalman-benchmark]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-kalman-benchmark
-Include:
-Defines:
-Main:       kalman/test-kalman-benchmark
+# [test-kalman-benchmark]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-kalman-benchmark
+# Include:
+# Defines:    
+# Main:       kalman/test-kalman-benchmark
+
+# [test-kalman-eigen-benchmark]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-kalman-eigen-benchmark
+# Include:    %shared 
+# Defines:    
+# Libs:       eigen fmt
+# Main:       kalman/test-kalman-eigen-benchmark
 
 [test-pinobserver]
 Type:       test
@@ -267,48 +440,72 @@ BoardId:    stm32f429zi_stm32f4discovery
 BinName:    test-pinobserver
 Include:    %shared
 Defines:
+Libs:       fmt
 Main:       test-pinobserver
 
-## Drivers
+# [test-sensormanager-catch]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack_x
+# BinName:    test-sensormanager-catch
+# Include:    %shared %sensormanager
+# Defines:    -DSTANDALONE_CATCH1_TEST
+# Libs:       fmt
+# Main:       catch/test-sensormanager-catch
 
-[test-dsgamma]
+[test-sensormanager]
 Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-dsgamma
-Include:    %gamma868
-Defines:
-Main:       drivers/test-dsgamma
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-sensormanager
+Include:    %shared %sensormanager
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       test-sensormanager
 
-[test-imu-adis]
+[test-calibration]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-imu-adis
-Include:    %shared
-Defines:
-Main:       drivers/test-imu-adis
+BoardId:    stm32f407vg_stm32f4discovery
+BinName:    test-calibration
+Include:    %shared %spi %calibration
+Defines:    -DDEBUG
+Libs:       eigen fmt
+Main:       calibration/test-calibration
+
+# [test-calibration-benchmark]
+# Type:       test
+# BoardId:    stm32f407vg_stm32f4discovery
+# BinName:    test-calibration-benchmark
+# Include:    %shared %spi %calibration
+# Defines:    -DDEBUG
+# Libs:       eigen fmt
+# Main:       calibration/test-calibration-benchmark
+
+# [test-calibration-stats]
+# Type:       test
+# BoardId:    stm32f407vg_stm32f4discovery
+# BinName:    test-calibration-stats
+# Include:    %shared %spi %calibration
+# Defines:    -DDEBUG
+# Libs:       eigen fmt
+# Main:       calibration/test-calibration-stats
 
-[test-mpu9250]
-Type:       test
-BoardId:    stm32f429zi_skyward_homeone
-BinName:    test-mpu9250
-Include:    %shared
-Defines:
-Main:       drivers/test-mpu9250
+## Drivers
 
-[test-timer]
+[test-dsgamma]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-timer
-Include:    %shared
-Defines:    -DDEBUG
-Main:       drivers/test-timer
+BinName:    test-dsgamma
+Include:    %shared %gamma868
+Defines:
+Libs:       fmt
+Main:       drivers/test-dsgamma
 
 [test-canbus]
 Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
+BoardId:    stm32f429zi_skyward_death_stack_x
 BinName:    test-canbus
 Include:    %shared %canbus
 Defines:    -DDEBUG
+Libs:       fmt
 Main:       drivers/test-canbus
 
 [test-pwm]
@@ -317,31 +514,17 @@ BoardId:    stm32f429zi_stm32f4discovery
 BinName:    test-pwm
 Include:    %shared %pwm
 Defines:    -DDEBUG
+Libs:       fmt
 Main:       drivers/test-pwm
 
-[test-piksi]
+[test-lis3mdl]
 Type:       test
-BoardId:    stm32f429zi_skyward_homeone
-BinName:    test-piksi
-Include:    %shared %piksi
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-lis3mdl
+Include:    %shared %spi
 Defines:    -DDEBUG
-Main:       drivers/test-piksi
-
-[test-IMU]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-IMU
-Include:    %shared
-Defines:
-Main:       drivers/test-IMU
-
-#[test-mavlink-multi]
-#Type:       test
-#BoardId:    stm32f429zi_skyward_homeone
-#BinName:    test-mavlink-multi
-#Include:    %shared %gamma868
-#Defines:   -DDEBUG
-#Main:       drivers/test-mavlink-multi
+Libs:       fmt
+Main:       drivers/test-lis3mdl
 
 [test-mavlink]
 Type:       test
@@ -349,124 +532,353 @@ BoardId:    stm32f429zi_skyward_homeone
 BinName:    test-mavlink
 Include:    %shared %gamma868
 Defines:   -DDEBUG
+Libs:       fmt
 Main:       drivers/test-mavlink
 
-[xbee-bitrate]
+# [xbee-bitrate]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    xbee-bitrate
+# Include:    %shared %xbee %spi
+# Defines:   -DDEBUG -DSDRAM_ISSI
+# Main:       misc/xbee-bitrate
+
+# [xbee-send-rcv]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    xbee-send-rcv
+# Include:    %shared %xbee %spi
+# Defines:    -DDEBUG
+# Main:       misc/xbee-send-rcv
+
+# [xbee-time-to-send]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    xbee-time-to-send
+# Include:    %shared %xbee %spi
+# Defines:    -DDEBUG
+# Main:       misc/xbee-time-to-send
+
+# [test-circularbuffer]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-circularbuffer
+# Include:    %shared  %test-utils
+# Defines:    -DSTANDALONE_CATCH1_TEST
+# Libs:       fmt
+# Main:       catch/test-circularbuffer
+
+# [test-eventbroker]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-eventbroker
+# Include:    %shared %test-utils
+# Defines:    -DSTANDALONE_CATCH1_TEST
+# Libs:       fmt
+# Main:       catch/test-eventbroker
+
+[test-servo]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    xbee-bitrate
-Include:    %shared %xbee
-Defines:   -DDEBUG -DSDRAM_ISSI
-Main:       misc/xbee-bitrate
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-servo
+Include:    %shared %servo %pwm
+Defines:   -DDEBUG
+Libs:       fmt
+Main:       drivers/test-servo
 
-[xbee-send-rcv]
+[test-hbridge]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    xbee-send-rcv
-Include:    %shared %xbee
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-hbridge
+Include:    %shared %hbridge %pwm
+Defines:   -DDEBUG
+Libs:       fmt
+Main:       drivers/test-hbridge
+
+[test-ms5803]
+Type:       test
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-ms5803
+Include:    %shared %spi %sensormanager %ms5803
 Defines:    -DDEBUG
-Main:       misc/xbee-send-rcv
+Libs:       fmt
+Main:       drivers/test-ms5803
 
-[xbee-time-to-send]
+[test-ublox]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    xbee-time-to-send
-Include:    %shared %xbee
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-ublox
+Include:    %ubloxgps %shared
 Defines:    -DDEBUG
-Main:       misc/xbee-time-to-send
+Libs:       fmt
+Main:       drivers/test-ublox
+
+[test-l3gd20]
+Type:       test
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-l3gd20
+Include:    %shared %spi
+Defines:
+Libs:       fmt
+Main:       drivers/test-l3gd20
+
+[test-l3gd20-fifo]
+Type:       test
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-l3gd20-fifo
+Include:    %shared %spi
+Defines:
+Libs:       fmt
+Main:       drivers/test-l3gd20-fifo
 
-[test-ad7994]
+[test-rls]
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-ad7994
+BinName:    test-rls
 Include:    %shared
 Defines:
-Main:       drivers/test-ad7994
+Libs:       fmt
+Main:       test-rls
 
-[test-matrix]
+[test-lis3dsh]
+Type:       test
+BoardId:    stm32f407vg_stm32f4discovery
+BinName:    test-lis3dsh
+Include:    %shared %spi
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-lis3dsh
+
+[test-bmx160]
+Type:       test
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-bmx160
+Include:    %shared %spi %bmx160
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-bmx160
+
+[test-bmx160-with-correction]
+Type:       test
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-bmx160-with-correction
+Libs:       eigen fmt
+Include:    %shared %spi %bmx160 %bmx160withcorrection %calibration
+Defines:    -DDEBUG
+Main:       drivers/test-bmx160-with-correction
+
+[test-ads1118]
+Type:       test
+BoardId:    stm32f407vg_stm32f4discovery
+BinName:    test-ads1118
+Include:    %shared %spi %ads1118
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-ads1118
+
+[test-xbee-snd]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-matrix
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-matrix
+BinName:    test-xbee-snd
+Include:    %shared %spi %xbee
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/xbee/test-xbee-snd
 
-[test-kalman]
+[test-xbee-rcv]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-kalman
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-kalman
+BinName:    test-xbee-rcv
+Include:    %shared %spi %xbee
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/xbee/test-xbee-rcv
 
-#[test-eventbroker]
-#Type:       test
-#BoardId:    stm32f429zi_stm32f4discovery
-#BinName:    test-eventbroker
-#Include:    %shared %events
-#Defines:    -DSTANDALONE_CATCH1_TEST
-#Main:       catch/test-eventbroker
+[test-xbee-bidir]
+Type:       test
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-xbee-bidir
+Include:    %shared %spi %xbee
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/xbee/test-xbee-bidir
 
-[test-circularbuffer]
+[test-xbee-gui]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-circularbuffer
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-circularbuffer
+BinName:    test-xbee-gui
+Include:    %shared %spi %xbee src/tests/drivers/xbee/gui/res/respect.cpp
+Defines:    -DDEBUG 
+Libs:       libmxgui fmt
+Main:       drivers/xbee/test-xbee-gui
 
-[test-tempSensor]
+[test-bme280]
 Type:       test
-BoardId:    stm32f429zi_skyward_homeone
-BinName:    test-tempSensor
-Include:    %shared
+BoardId:    stm32f407vg_stm32f4discovery
+BinName:    test-bme280
+Include:    %shared %spi %bme280
 Defines:    -DDEBUG
-Main:       drivers/test-tempSensor
+Libs:       fmt
+Main:       drivers/test-bme280
 
-[test-spi2]
+[test-internal-adc]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-spi2
-Include:    %shared
-Defines:   -DDEBUG -DSDRAM_ISSI
-Main:       drivers/test-spi2
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-internal-adc
+Include:    %shared %internal-adc
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-internal-adc
 
-[test-i2c-mpu9255]
+[test-internal-adc-dma]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-i2c-mpu9255
-Include:    %shared
-Defines:   -DDEBUG -DSDRAM_ISSI
-Main:       drivers/test-i2c-mpu9255
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-internal-adc-dma
+Include:    %shared %internal-adc
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-internal-adc-dma
 
-[test-ad7994-bare]
+[test-analog-pressure-sensors]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-ad7994-bare
-Include:    %shared
-Defines:   -DDEBUG -DSDRAM_ISSI
-Main:       drivers/test-ad7994-bare
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-analog-pressure-sensors
+Include:    %shared %spi %ads1118
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/analog/test-analog-pressure-sensors
 
-[test-servo]
+[test-battery-voltage]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-servo
-Include:    %shared %servo %pwm
-Defines:   -DDEBUG
-Main:       drivers/test-servo
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-battery-voltage
+Include:    %shared %internal-adc
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/analog/test-battery-voltage
 
-[calibrate-mpu9250]
+[test-current-sensor]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    calibrate-mpu9250
-Include:    %shared
-Defines:
-Main:       drivers/calibrate-mpu9250
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-current-sensor
+Include:    %shared %internal-adc
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/analog/test-current-sensor
 
-[test-ms5803]
+[test-spi]
 Type:       test
-BoardId:    stm32f429zi_skyward_death_stack
-BinName:    test-ms5803
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-spi
+Include:    %shared %spi
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-spi
+
+[test-hwtimer-chain]
+Type:       test
+BoardId:    stm32f407vg_stm32f4discovery
+BinName:    test-hwtimer-chain
 Include:    %shared
-Defines:
-Main:       drivers/test-ms5803
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-hwtimer-chain
+
+[test-mpu9250]
+Type:       test
+BoardId:    stm32f429zi_skyward_death_stack_x
+BinName:    test-mpu9250
+Include:    %shared %spi %mpu9250
+Defines:    -DDEBUG
+Libs:       fmt
+Main:       drivers/test-mpu9250
+
+## Older drivers (not up-to-date)
+
+# [test-piksi]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_homeone
+# BinName:    test-piksi
+# Include:    %shared %piksi
+# Defines:    -DDEBUG
+# Main:       drivers/test-piksi
+
+# [test-imu-adis]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-imu-adis
+# Include:    %shared %sensormanager
+# Defines:
+# Main:       drivers/old_examples/test-imu-adis
+
+# [test-lsm]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-lsm
+# Include:    %shared %sensormanager
+# Defines:
+# Main:       drivers/old_examples/test-lsm
+
+# [test-IMU]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-IMU
+# Include:    %shared
+# Defines:
+# Main:       drivers/old_examples/test-IMU
+
+# [test-mavlink-multi]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_homeone
+# BinName:    test-mavlink-multi
+# Include:    %shared %gamma868
+# Defines:   -DDEBUG
+# Main:       drivers/test-mavlink-multi
+
+# [test-ad7994]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-ad7994
+# Include:    %shared
+# Defines:
+# Main:       drivers/old_examples/test-ad7994
+
+# [test-tempSensor]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_homeone
+# BinName:    test-tempSensor
+# Include:    %shared
+# Defines:    -DDEBUG
+# Main:       drivers/old_examples/test-tempSensor
+
+# [test-spi2]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-spi2
+# Include:    %shared
+# Defines:   -DDEBUG -DSDRAM_ISSI
+# Main:       drivers/old_examples/test-spi2
+
+# [test-i2c-mpu9255]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-i2c-mpu9255
+# Include:    %shared
+# Defines:   -DDEBUG -DSDRAM_ISSI
+# Main:       drivers/test-i2c-mpu9255
+
+# [test-ad7994-bare]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    test-ad7994-bare
+# Include:    %shared
+# Defines:   -DDEBUG -DSDRAM_ISSI
+# Main:       drivers/test-ad7994-bare
+
+# [calibrate-mpu9250]
+# Type:       test
+# BoardId:    stm32f429zi_skyward_death_stack
+# BinName:    calibrate-mpu9250
+# Include:    %shared %sensormanager
+# Defines:
+# Main:       drivers/calibrate-mpu9250
diff --git a/scripts/anakin.sh b/scripts/anakin.sh
deleted file mode 100755
index 52d32b6107f171f011925e0da0f9f215ff9caf98..0000000000000000000000000000000000000000
--- a/scripts/anakin.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-if [ $# -ne 1 ]; then
-    echo "Usage: $0 <filename.bin>"
-    exit -1
-fi;
-
-stm32flash -b 500000 -w $1 -v /dev/ttyUSB0
diff --git a/scripts/anakin_client_curses/.gitignore b/scripts/anakin_client_curses/.gitignore
deleted file mode 100644
index b051c6c57fa8b6cc588a3ebad860005ec6fcf7cc..0000000000000000000000000000000000000000
--- a/scripts/anakin_client_curses/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-client
diff --git a/scripts/anakin_client_curses/Makefile b/scripts/anakin_client_curses/Makefile
deleted file mode 100644
index f97ef085b53c809ca0363276bbae66fbf22e6ded..0000000000000000000000000000000000000000
--- a/scripts/anakin_client_curses/Makefile
+++ /dev/null
@@ -1,2 +0,0 @@
-all:
-	gcc -o client client.c -lcurses
diff --git a/scripts/anakin_client_curses/client.c b/scripts/anakin_client_curses/client.c
deleted file mode 100644
index 5c45227bddf8b8aa5ced0341a9cb56e0ce624d63..0000000000000000000000000000000000000000
--- a/scripts/anakin_client_curses/client.c
+++ /dev/null
@@ -1,319 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <ncurses.h>
-#include <signal.h>
-#include <stdint.h>
-#include <termios.h>
-#include <unistd.h>
-
-volatile int stop = 0;
-uint8_t buf[8192] = {0};
-uint16_t rptr = 0, wptr = 0;
-
-#pragma pack(1)
-struct vec3_t {
-    float x;
-    float y;
-    float z; 
-};
-#pragma pack()
-
-enum DataType
-{
-    DATA_VEC3  = 0,
-    DATA_QUAT  = 1,
-    DATA_FLOAT = 2,
-    DATA_INT   = 3,
-    DATA_STRING= 4,
-    DATA_LIMITED_INT = 5,
-    DATA_UINT32 = 6
-};
-
-const char* sensor_names[] = 
-{
-    "ACCEL_MPU9250",
-    "ACCEL_INEMO",
-    "ACCEL_MAX21105",
-
-    "GYRO_MPU9250",
-    "GYRO_INEMO",
-    "GYRO_FXAS21002",
-    "GYRO_MAX21105",
-
-    "COMPASS_MPU9250",
-    "COMPASS_INEMO",
-
-    "TEMP_MPU9250",
-    "TEMP_INEMO",
-    "TEMP_LPS331AP",
-    "TEMP_MAX21105",
-    "TEMP_MS580",
-
-    "PRESS_LPS331AP",
-    "PRESS_MS580",
-
-    "UNUSED_16",
-    "DMA_TX_FIFOSZ",
-    "DMA_RX_FIFOSZ",
-    "DMA_FIFO_ERR",
-    "CPU%",
-    "LOG_QUEUE_SZ"
-};
-
-uint8_t sensor_type[] = 
-{
-    DATA_VEC3,
-    DATA_VEC3,
-    DATA_VEC3,
-
-    DATA_VEC3,
-    DATA_VEC3,
-    DATA_VEC3,
-    DATA_VEC3,
-
-    DATA_VEC3,
-    DATA_VEC3,
-
-    DATA_FLOAT,
-    DATA_FLOAT,
-    DATA_FLOAT,
-    DATA_FLOAT,
-    DATA_FLOAT,
-
-    DATA_FLOAT,
-    DATA_FLOAT,
-
-    DATA_INT,
-    DATA_LIMITED_INT,
-    DATA_LIMITED_INT,
-    DATA_UINT32,
-    DATA_UINT32,
-    DATA_LIMITED_INT
-};
-
-#define NUM_SENSORS ((int)((sizeof(sensor_names) / sizeof(sensor_names[0]))))
-
-
-void on_signal(int s)
-{
-    if(!stop)
-    {
-        signal(s, on_signal);
-        stop = 1; 
-    }
-}
-
-int logline = 0;
-void log_string(const char* data, int len)
-{
-    if(len <= 0)
-        return;
-
-    move(NUM_SENSORS + 2 + logline, 10);
-    attron(COLOR_PAIR(1));
-    printw(data);
-    if(len < 30)
-        for(int i=len;i<30;i++)
-            printw(" ");
-    refresh();
-    logline = (logline+1)%20;
-}
-
-void draw_sensor(int id, int type, const void* data)
-{
-    char tmp[256] = {0};
-
-    if(id >= (int)NUM_SENSORS)
-    {
-        sprintf(tmp, "Got packet for invalid sensor %d\n", id);
-        log_string(tmp, strlen(tmp)); 
-        return;
-    }
-
-    if(type < 0 || type > 6 || type == 4)
-    {
-        sprintf(tmp, "Got packet for invalid type %d\n", type);
-        log_string(tmp, strlen(tmp)); 
-        return; 
-    }
-
-    move(1+id, 10);
-    sprintf(tmp, "%20s : ", sensor_names[id]);
-
-    uint8_t color = (type != 5) ? (type + 1) : 2;
-    attron(COLOR_PAIR(color));
-    printw(tmp);
-    attroff(COLOR_PAIR(color));
-
-    int all_null = 1;
-    for(int i=0;i<NUM_SENSORS && all_null == 1;i++)
-        if(((const char *)data)[i] != 0)
-            all_null = 0;
-    if((data == NULL || all_null == 1) && type != 5)
-    {
-        attron(COLOR_PAIR(5));
-        attron(A_BOLD);
-        printw("[NULL]                                  ");
-        attroff(A_BOLD);
-        attroff(COLOR_PAIR(5));
-        return;
-    }
-
-    const uint8_t* i            = (const uint8_t*) data;
-    const struct vec3_t* v  = (const struct vec3_t*) data;
-    const float* f          = (const float*) data;
-    const uint8_t* ui       = (const uint8_t*) data;
-    const uint32_t* ui32       = (const uint32_t*) data;
-
-    switch(type)
-    {
-        case DATA_FLOAT: 
-            sprintf(tmp, "%7.4f                      ", *f);
-            printw(tmp);
-            break;
-        case DATA_INT: 
-            sprintf(tmp, "%d                         ", *i);
-            printw(tmp);
-            break;
-        case DATA_UINT32: 
-            sprintf(tmp, "%d                         ", *ui32);
-            printw(tmp);
-            break;
-        case DATA_VEC3:
-            sprintf(tmp, "(%7.4f, %7.4f, %7.4f)", v->x, v->y, v->z);
-            printw(tmp);
-            break;
-        case DATA_LIMITED_INT:
-        {
-            uint32_t vv = (*ui * 27) / 256;
-            for(uint32_t i=0;i<27;i++)
-            {
-                if(i < vv)
-                    printw("#");
-                else
-                    printw(".");
-            }
-            break;
-        }
-        default:
-            printw("[NOT IMPLEMENTED]");
-            break;
-    }
-}
-
-int sfd;
-int init_serial(const char *device)
-{
-    sfd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
-    if(sfd < 0)
-    {
-        printf("Cannot open ttyUSB0\n");
-        return 1;
-    }
-
-    struct termios tty;
-    memset(&tty, 0, sizeof(tty));
-    if(tcgetattr(sfd, &tty) != 0)
-    {
-        printf("Cannot get attributes\n");
-        return 1;
-    }
-    cfmakeraw(&tty);
-    cfsetospeed(&tty, B115200);
-    cfsetispeed(&tty, B115200);
-    tcsetattr(sfd, TCSANOW, &tty);
-
-    return 0;
-}
-
-void parse(int len)
-{
-    if(len == 0)
-        return;
-
-    uint8_t type = buf[0];
-    if(type == DATA_STRING)
-        log_string((const char *)&buf[1], len-1);
-    else 
-    {
-        uint8_t id = buf[1];
-        draw_sensor(id, type, &buf[2]);
-    }
-}
-
-int fromHex(char l)
-{
-    if(l >= '0' && l <= '9')
-        return l - '0';
-    if(l >= 'a' && l <= 'f')
-        return 10 + l - 'a';
-    if(l >= 'A' && l <= 'F')
-        return 10 + l - 'A';
-    return 0;
-}
-
-int read_and_draw()
-{
-    memset(buf, 0, sizeof(buf));
-    int bpos = 0;
-    char b;
-    char tmp = 0;
-    do {
-        read(sfd, &b, 1);
-        if(b == '\n' || b == '\r')
-            break;
-        else {
-            if(tmp == 0)
-                tmp = b;
-            else {
-                buf[bpos++] = (fromHex(tmp) << 4) | fromHex(b);
-                tmp = 0;
-            }
-        }
-    } while(1);
-    parse(bpos);
-    return 0;
-}
-
-int main(int argc, char *argv[])
-{
-    if(argc < 2)
-    {
-        printf("Usage: %s <PORT>\n", argv[0]);
-        return -1;
-    }
-    if(init_serial(argv[1]))
-        return -1;
-
-    signal(SIGINT, on_signal);
-    signal(SIGTERM, on_signal);
-
-    initscr();
-    start_color();
-    init_pair(1, COLOR_BLUE, COLOR_BLACK);
-    init_pair(2, COLOR_GREEN, COLOR_BLACK);
-    init_pair(3, COLOR_CYAN, COLOR_BLACK);
-    init_pair(4, COLOR_WHITE, COLOR_BLACK);
-    init_pair(5, COLOR_RED, COLOR_BLACK);
-    init_pair(7, COLOR_MAGENTA, COLOR_BLACK);
-    erase();
-    refresh();
-
-    log_string("Ready...", 8);
-    while(!stop)
-    {
-        if(read_and_draw() == 1)
-            stop = 1;
-        refresh();
-    }
-
-    endwin();
-
-    signal(SIGINT, 0);
-    signal(SIGTERM, 0);
-
-    return 0;
-}
diff --git a/scripts/copyrightchecker.py b/scripts/copyrightchecker.py
new file mode 100755
index 0000000000000000000000000000000000000000..6c39c3f208259009c21dfd14cdbc0532819fd007
--- /dev/null
+++ b/scripts/copyrightchecker.py
@@ -0,0 +1,140 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2021 Skyward Experimental Rocketry
+# Authors: Alberto Nidasio
+#
+# 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
+# AUTHORS OR COPYRIGHT HOLDERS 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.
+
+import re
+from argparse import ArgumentParser
+from os import walk
+from os.path import join
+from pprint import pprint
+
+# Copyright template for C++ code files and headers
+CPP_TEMPLATE = r'\/\* Copyright \(c\) 20\d\d(?:-20\d\d)? Skyward Experimental Rocketry\n' + \
+r' \* (Authors?): (.+)\n' + \
+r' \*\n' + \
+r' \* Permission is hereby granted, free of charge, to any person obtaining a copy\n' + \
+r' \* of this software and associated documentation files \(the "Software"\), to deal\n' + \
+r' \* in the Software without restriction, including without limitation the rights\n' + \
+r' \* to use, copy, modify, merge, publish, distribute, sublicense, and\/or sell\n' + \
+r' \* copies of the Software, and to permit persons to whom the Software is\n' + \
+r' \* furnished to do so, subject to the following conditions:\n' + \
+r' \*\n' + \
+r' \* The above copyright notice and this permission notice shall be included in\n' + \
+r' \* all copies or substantial portions of the Software.\n' + \
+r' \*\n' + \
+r' \* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n' + \
+r' \* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n' + \
+r' \* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n' + \
+r' \* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n' + \
+r' \* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n' + \
+r' \* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n' + \
+r' \* THE SOFTWARE.\n' + \
+r' \*\/'
+AUTHOR_DELIMITER = ','
+
+def config_cmd_parser():
+    parser = ArgumentParser(
+    description='The Copyright Checker script verifies that all files in the specified directory contains the correct copyright notice.\nSupport cpp code and header files.')
+    parser.add_argument('directory', nargs='?',
+                        help='Directory where to search files')
+    return parser
+
+def print_banner():
+    # Font: Ivrit
+    print("+------------------------------------------------+")
+    print("|    ___                       _       _     _   |")
+    print("|  / ___|___  _ __  _   _ _ __(_) __ _| |__ | |_ |")
+    print("| | |   / _ \\| '_ \\| | | | '__| |/ _` | '_ \\| __||")
+    print("| | |__| (_) | |_) | |_| | |  | | (_| | | | | |_ |")
+    print("|  \\____\\___/| .__/ \\__, |_|  |_|\\__, |_| |_|\\__||")
+    print("|        ____|_|    |___/     _  |___/           |")
+    print("|       / ___| |__   ___  ___| | _____ _ __      |")
+    print("|      | |   | '_ \\ / _ \\/ __| |/ / _ \\ '__|     |")
+    print("|      | |___| | | |  __/ (__|   <  __/ |        |")
+    print("|       \\____|_| |_|\\___|\\___|_|\\_\\___|_|        |")
+    print("+------------------------------------------------+")
+
+# -------------------------------------------------------------
+# MAIN
+# -------------------------------------------------------------
+
+
+parser = config_cmd_parser()
+args = parser.parse_args()
+totalCheckdFilesCounter = 0
+filesWithErrorsCounter = 0
+
+# Statistics
+authors = {}
+averageAuthorsPerFile = 0
+
+if(not args.directory):
+    print('[chopyrightchecker] No directory specified')
+    print('')
+    parser.print_help()
+    exit(-1)
+
+# Walk throgh the directory and check each file
+for dirpath, dirnames, filenames in walk(args.directory):
+    for filename in [f for f in filenames if f.endswith(('.cpp', '.h'))]:
+        totalCheckdFilesCounter += 1
+
+        # Prepare the complete filepath
+        currentFilepath = join(dirpath, filename)
+
+        # Check the current file
+        with open(currentFilepath) as file:
+            match = re.search(CPP_TEMPLATE, file.read())
+            if not match:
+                filesWithErrorsCounter += 1
+
+                # The file's copyright notice does not match the template!
+                print('[chopyrightchecker] Wrong copyright notice in file {0}'.format(currentFilepath))
+            else:
+                fileAuthors = [a.strip() for a in match.group(2).split(AUTHOR_DELIMITER)]
+
+                # Check the number of authors against 'Author' or `Authors`
+                if len(fileAuthors) == 1 and match.group(1)[-1] == 's':
+                    print('[chopyrightchecker] \'Authors\' should to be changed to \'Author\' in {0}'.format(currentFilepath))
+                if len(fileAuthors) > 1 and match.group(1)[-1] != 's':
+                    print('[chopyrightchecker] \'Author\' should to be changed to \'Authors\' in {0}'.format(currentFilepath))
+
+                # Save statistics on authors
+                for author in fileAuthors:
+                    if author in authors:
+                        authors[author] += 1
+                    else:
+                        authors[author] = 1
+                averageAuthorsPerFile += len(fileAuthors)
+averageAuthorsPerFile /= totalCheckdFilesCounter     
+
+
+print('[chopyrightchecker] Checked {} files'.format(totalCheckdFilesCounter))        
+if filesWithErrorsCounter == 0:
+    print('[chopyrightchecker] All the files have the correct copyright notice')
+else:
+    print('[chopyrightchecker] {} files do not match with the copyright template'.format(filesWithErrorsCounter))
+
+print('[chopyrightchecker] {:.2} authors per file'.format(averageAuthorsPerFile))
+print('[chopyrightchecker] Number of mentrions per author:')
+for author in authors:
+    print('[chopyrightchecker]   {:3} - {}'.format(authors[author], author))
\ No newline at end of file
diff --git a/scripts/gen_fault_headers.py b/scripts/gen_fault_headers.py
deleted file mode 100755
index 0a5badbdaac96f3de9d501d912fbe39c1f3f4ef2..0000000000000000000000000000000000000000
--- a/scripts/gen_fault_headers.py
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (c) 2017 Skyward Experimental Rocketry
-# Authors: Alain Carlucci
-# 
-# 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
-# AUTHORS OR COPYRIGHT HOLDERS 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.
-
-
-import csv,string,hashlib,datetime,sys
-
-if len(sys.argv) != 3:
-    print("Usage: %s <CSV File> <Output header file>" % (sys.argv[0]))
-    exit(-1)
-
-headerLine = None
-
-newLines = []
-
-lineCtr = 0
-curCatId = 0
-categories={}
-enums={}
-
-import hashlib
-
-def fileHash(filename):
-    h = hashlib.sha1()
-    with open(filename, 'rb', buffering=0) as f:
-        for b in iter(lambda : f.read(128*1024), b''):
-            h.update(b)
-    return h.hexdigest()
-
-def genEnum(name, s, inc):
-    a = ('''enum class %s
-{
-''') % (name,);
-    cnt = 0
-    for i in s:
-        if inc:
-            a += "    " + i + " = " + str(cnt) + ",\n"
-        else:
-            a += "    " + i + " = " + str(s[i]) + ",\n"
-        cnt += 1
-    a += "};\n"
-    a += "const std::size_t %s_SIZE = %d;\n" %(name, cnt)
-    return a
-
-def genHeaderFile(fileName,csvFile):
-    print("[*] Generating header file (%s)" %(fileName,))
-    content = ('''/* Copyright (c) 2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
- * 
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-/*
- ******************************************************************************
- *                  THIS FILE IS AUTOGENERATED. DO NOT EDIT.                  *
- ******************************************************************************
- */
-
-// CSV File:         %s
-// SHA1 of CSV File: %s
-// Autogen date:     %s
-
-#include <cstdint>
-
-#ifndef SKYWARD_FAULT_CTRL_LIST_H
-#define SKYWARD_FAULT_CTRL_LIST_H
-
-''') % (csvFile, fileHash(csvFile), datetime.datetime.now())
-    content += genEnum("Fault", enums, True)
-    content += "\nnamespace FaultCounterData\n{\n\n"
-    content += genEnum("FaultCategory", categories, False)
-    content += '''
-
-// Usage: categoryID = FaultCounter::FaultToCategory[faultID];
-const uint32_t FaultToCategory[] = 
-{
-    '''
-    colCnt = 0
-    for i in enums:
-        content += "%d, " % (categories[enums[i]], )
-        colCnt += 1
-        if colCnt == 20:
-            content += "\n    "
-            colCnt = 0
-    content += '''
-}; /* CategoryMapping */
-
-} /* FaultCounterData */
-
-#endif /* SKYWARD_FAULT_CTRL_LIST_H */
-
-'''
-    try:
-        with open(fileName, "w") as f:
-            f.write(content)
-        print("[+] Written %d lines in %s" % (len(content.split("\n")), fileName))
-    except:
-        print("[!] Cannot open %s for writing.", fileName)
-        exit(-1)
-
-
-def checkForGoodName(n):
-    if len(n) == 0:
-        return False
-
-    if n[0] not in string.ascii_uppercase:
-        return False
-
-    for x in n:
-        if x not in string.ascii_uppercase + string.digits + '_':
-            return False
-
-    if n[-1] not in string.ascii_uppercase + string.digits:
-        return False
-
-    return True
-
-def genEnumName(cat,name):
-    ctr = 2
-    
-    genBName = "F_" + cat + "_" + name
-    genName = genBName
-
-    while genName in enums:
-        genName = genBName + "_" + str(ctr) 
-        ctr += 1
-
-    return genName
-
-# return False: csv file is ok
-# return True: csv file must be updated
-def handleLine(r):
-    global lineCtr,categories,newLines,enums,curCatId
-
-    if len(r) != 4:
-        print("[!] Parsing error at line %d. I'm expecting 4 fields" % (lineCtr,))
-        exit(-1)
-
-    curCat = r[0].strip().upper()
-    curName = r[1].strip().upper()
-    curDesc = r[2].strip()
-    curEnum = r[3].strip().upper()
-
-    if checkForGoodName(curCat) == False:
-        print("[!] Invalid category at line %d." % (lineCtr,))
-        exit(-1)
-
-    if checkForGoodName(curName) == False:
-        print("[!] Invalid name at line %d." % (lineCtr,))
-        exit(-1)
-
-    if len(curEnum) > 0 and (checkForGoodName(curEnum) == False or curEnum in enums):
-        print("[!] Invalid generated name at line %d." % (lineCtr,))
-        exit(-1)
-
-    retVal = False
-    if len(curEnum) == 0:
-        curEnum = genEnumName(curCat, curName)
-        retVal = True
-
-    if curCat not in categories:
-        print("[+] Added category '%s'" % (curCat,))
-        categories[curCat] = curCatId
-        curCatId += 1
-
-    print("[+] Added %s in category %s" %(curEnum,curCat))
-    enums[curEnum] = curCat
-
-    newLines.append([curCat, curName, curDesc, curEnum])
-    return retVal
-
-mustUpdate = False
-print("[*] Opening %s..." % (sys.argv[1],))
-try:
-    with open(sys.argv[1], "r") as csvfile:
-        r = csv.reader(csvfile, delimiter=',', quotechar='"')
-        for row in r:
-            lineCtr += 1
-            if headerLine is None:
-                headerLine = row
-                continue
-            if handleLine(row):
-                mustUpdate = True
-except Exception as e:
-    print("[!] Cannot open %s for reading: %s!" %(sys.argv[1],str(e)))
-    exit(-1)
-
-if not mustUpdate:
-    print("[+] No need to update the csv file.")
-else:
-    print("[*] Updating %s..." % (sys.argv[1],))
-
-    try:
-        with open(sys.argv[1], "w") as csvfile:
-            r = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
-            r.writerow(headerLine)
-            for row in newLines:
-                r.writerow(row)
-            print("[+] Successfully updated csv file")
-    except:
-        print("[!] Cannot open %s for writing!" %(sys.argv[1],))
-        exit(-1)
-
-genHeaderFile(sys.argv[2], sys.argv[1])
diff --git a/scripts/generators/README.md b/scripts/generators/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dfa4189d688093436b7c79293db6ad612aa7513a
--- /dev/null
+++ b/scripts/generators/README.md
@@ -0,0 +1,38 @@
+# Generators scripts
+
+The scripts contained in this directory are used to generate cpp code from scxml files, they accept a directory or a list of files.
+
+## Eventgen
+
+The Eventgen program generates the files `Events.h`, `Topics.h` and `EventString.cpp` gathering information from all scxml files specified.
+
+```shell
+usage: eventgen.py [-h] [-q] [-f [F [F ...]]] [directory]
+
+positional arguments:
+  directory             Directory where to search files
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -q, --quiet           Output only essential messages
+  -f [F [F ...]], --files [F [F ...]]
+```
+
+## FSMGen
+
+The FSMGen program generates cpp files (`FSMController.h`, `FSMController.cpp`, `FSMData.h` and `test-FMS.cpp`) for each scxml file specified.
+
+```shell
+usage: fsmgen.py [-h] [-q] [-a AUTHORS] [-n MAIN_NAMESPACE] [-f [F [F ...]]] [directory]
+
+positional arguments:
+  directory             Directory where to search files
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -q, --quiet           Output only essential messages
+  -a AUTHORS, --authors AUTHORS
+  -n MAIN_NAMESPACE, --main_namespace MAIN_NAMESPACE
+                        DeathStackBoard as default
+  -f [F [F ...]], --files [F [F ...]]
+```
diff --git a/scripts/generators/eventgen.py b/scripts/generators/eventgen.py
new file mode 100755
index 0000000000000000000000000000000000000000..dedf167d77171b073d56623f605f9139855b4010
--- /dev/null
+++ b/scripts/generators/eventgen.py
@@ -0,0 +1,215 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2018-2021 Skyward Experimental Rocketry
+# Authors: Luca Erbetta, Alvise de' Faveri Tron, Alberto Nidasio
+#
+# 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
+# AUTHORS OR COPYRIGHT HOLDERS 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.
+
+import sys
+import datetime
+from os import walk
+from os.path import join, dirname
+from argparse import ArgumentParser
+from genutils import parse_scxml, read_file, write_file
+
+current_directory = dirname(sys.argv[0])
+TEMPLATE_FOLDER = 'templates'
+OUTPUT_FOLDER = 'generated'
+
+# Templates file names
+EVENTS_H_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/Events.h.template'
+TOPICS_H_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/Topics.h.template'
+EVENT_STRINGS_CPP_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/EventStrings.cpp.template'
+
+
+def config_cmd_parser():
+    parser = ArgumentParser(
+        description='The Eventgen program generates the files Events.h, Topics.hand EventString.cpp gathering information from all scxml files specified.')
+    parser.add_argument('-q', '--quiet', dest='quiet',
+                        action='store_true', help='Output only essential messages')
+    parser.add_argument('-f', '--files', dest='files', metavar='F', nargs='*')
+    parser.add_argument('directory', nargs='?',
+                        help='Directory where to search files')
+    return parser
+
+
+def print_banner():
+    # Font: Ivrit
+    print("+-----------------------------------------------------+")
+    print("|  ____  _                                _           |")
+    print("| / ___|| | ___   ___      ____ _ _ __ __| |          |")
+    print("| \\___ \\| |/ / | | \\ \\ /\\ / / _` | '__/ _` |          |")
+    print("|  ___) |   <| |_| |\\ V  V / (_| | | | (_| |          |")
+    print("| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_|  \\__,_|          |")
+    print("|        _____|___/            _                      |")
+    print("|       | ____|_   _____ _ __ | |_ __ _  ___ _ __     |")
+    print("|       |  _| \\ \\ / / _ \\ '_ \\| __/ _` |/ _ \\ '_ \\    |")
+    print("|       | |___ \\ V /  __/ | | | || (_| |  __/ | | |   |")
+    print("|       |_____| \\_/ \\___|_| |_|\\__\\__, |\\___|_| |_|   |")
+    print("|                                 |___/               |")
+    print("+-----------------------------------------------------+")
+
+
+def extract_events(fsmData):
+    # Get all possible events
+    events = []
+    for state in fsmData[3]:
+        for event in state[3]:
+            events.append(event[0])
+
+    # Remove doubles
+    events = list(dict.fromkeys(events))
+
+    return events
+
+
+def generate_events_h(events, date):
+    template = read_file(EVENTS_H_TEMPLATE_FILE)
+
+    if not events or len(events) == 0:
+        return
+
+    # Prepare the event list
+    enum_event_list = list(events)
+    enum_event_list[0] += ' = EV_FIRST_SIGNAL'
+    enum_event_list = '\n'.join(
+        [' '*4 + event + ',' for event in enum_event_list])
+    vector_event_list = '\n'.join([' '*4 + event + ',' for event in events])
+
+    # Insert information into template file
+    content = template.format(
+        year=date.year, date=date, enum_event_list=enum_event_list,
+        vector_event_list=vector_event_list
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/events', 'Events.h', content)
+
+
+def generate_topics_h(topics, date):
+    template = read_file(TOPICS_H_TEMPLATE_FILE)
+
+    if not topics or len(topics) == 0:
+        return
+
+    # Prepare the topic list
+    topic_list = '\n'.join([' '*4 + topic + ',' for topic in topics])
+
+    # Insert information into template file
+    content = template.format(year=date.year, date=date, topic_list=topic_list)
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/events', 'Topics.h', content)
+
+
+def generate_event_string_cpp(events, topics, date):
+    template = read_file(EVENT_STRINGS_CPP_TEMPLATE_FILE)
+
+    if not events or len(events) == 0 or not topics or len(topics) == 0:
+        return
+
+    # Prepare the event and topic lists
+    event_list = '\n'.join(
+        [' '*8 + '{' + event + ', "' + event + '"},' for event in events])
+    topic_list = '\n'.join(
+        [' '*8 + '{' + topic + ', "' + topic + '"},' for topic in topics])
+
+    # Insert information into template file
+    content = template.format(
+        year=date.year, date=date,
+        event_list=event_list, topic_list=topic_list
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/events', 'EventStrings.cpp', content)
+
+
+# -------------------------------------------------------------
+# MAIN
+# -------------------------------------------------------------
+
+
+parser = config_cmd_parser()
+args = parser.parse_args()
+files = []
+
+if(not args.files and not args.directory):
+    print('[eventgen] No scxml files specified nor a directory')
+    print('')
+    parser.print_help()
+    exit(-1)
+
+# Fetch files to parse
+if args.files:
+    files.extend(args.files)
+if args.directory:
+    for dirpath, dirnames, filenames in walk(args.directory):
+        for filename in [f for f in filenames if f.endswith(".scxml")]:
+            if not 'template' in filename:
+                files.append(join(dirpath, filename))
+
+if(not args.quiet):
+    print_banner()
+    print('')
+    print('[eventgen] Welcome to the eventgen script!')
+    print('')
+
+date = datetime.datetime.now()
+events = []
+topics = []
+
+for file in files:
+    print('[eventgen] Now parsing ' + file)
+
+    fsmData = parse_scxml(file)
+
+    # Extract events
+    events.extend(extract_events(fsmData))
+
+    # Save topic
+    topics.extend(['TOPIC_' + topic for topic in fsmData[5]])
+
+# Remove duplicates
+events = list(dict.fromkeys(events))
+topics = list(dict.fromkeys(topics))
+
+# Sort
+events.sort()
+topics.sort()
+
+# Generate Events.h
+file_name = generate_events_h(events, date)
+if file_name:
+    print('[eventgen] Generated ' + file_name)
+
+# Generate Topics.h
+file_name = generate_topics_h(topics, date)
+if file_name:
+    print('[eventgen] Generated ' + file_name)
+
+# Generate EventString.cpp
+file_name = generate_event_string_cpp(events, topics, date)
+if file_name:
+    print('[eventgen] Generated ' + file_name)
diff --git a/scripts/generators/fsmgen.py b/scripts/generators/fsmgen.py
new file mode 100755
index 0000000000000000000000000000000000000000..874a15a32a561595b0d6a1a01ed8a3c6e45af7b1
--- /dev/null
+++ b/scripts/generators/fsmgen.py
@@ -0,0 +1,388 @@
+#!/usr/bin/python3
+
+# Copyright (c) 2021 Skyward Experimental Rocketry
+# Authors: Alberto Nidasio
+#
+# 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
+# AUTHORS OR COPYRIGHT HOLDERS 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.
+
+import os
+import sys
+import datetime
+from os import walk
+from os.path import join, dirname
+from argparse import ArgumentParser
+from genutils import parse_scxml, read_file, write_file
+
+current_directory = dirname(sys.argv[0])
+TEMPLATE_FOLDER = 'templates'
+OUTPUT_FOLDER = 'generated'
+DEFAULT_NAMESPACE = 'DeathStackBoard'
+
+# Templates file names
+FSM_CONTROLLER_H_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/FSMController.h.template'
+FSM_CONTROLLER_CPP_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/FSMController.cpp.template'
+FSM_DATA_H_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/FSMData.h.template'
+STATE_FUNCTION_DEFINITION_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/state_function_definition.template'
+FSM_TEST_CPP_TEMPLATE_FILE = current_directory + \
+    ('/' if current_directory != '' else '') + \
+    TEMPLATE_FOLDER + '/test.cpp.template'
+
+# Templates strings
+STATE_FUNCTION_DECLARATION_TEMPLATE = 'void state_{state_name}(const Event& ev);'
+STATE_FUNCTION_CASE_TEMPLATE = ' '*8 + 'case {event}:\n' + \
+    ' '*8 + '{{\n' + \
+    '{actions}\n' + \
+    ' '*12 + 'break;\n' + \
+    ' '*8 + '}}'
+UTILITY_FUNCTION_DECLARATION_TEMPLATE = 'void {function_name}();'
+TOPICS_SUBSCIPTION_TEMPLATE = ' '*4 + 'sEventBroker->subscribe(this, {topic});'
+UTILITY_FUNCTION_DEFINITION_TEMPLATE = 'void {state_machine_name}Controller::{function_name}()\n{{\n    // ...\n}}'
+STATE_FUNCTION_CASE_TRANSITION_TO_TARGET_TEMPLATE = 'transition(&{state_machine_name}Controller::state_{state_name});'
+TEST_CASE_METHOD_TEMPLATE = 'TEST_CASE_METHOD({state_machine_name}ControllerFixture, "Testing transitions from {state}")\n' + \
+    '{{\n' + \
+    ' '*4 + 'controller->transition(&{state_machine_name}Controller::state_{start_state});\n\n' + \
+    '{test_case_sections}' + \
+    '\n}}'
+TEST_CASE_SECTION_TEMPLATE = ' '*4 + 'SECTION("{event} -> {target_state_upper}")\n' + \
+    ' '*4 + '{{\n' + \
+    ' '*8 + 'REQUIRE(testFSMTransition(*controller, Event{{{event}}}, &{state_machine_name}Controller::state_{target_state}));\n' + \
+    ' '*4 + '}}'
+README_TEMPLATE = '```mermaid\nstateDiagram-v2\n{mermaid}\n```'
+
+
+def config_cmd_parser():
+    parser = ArgumentParser(
+        description='The FSMGen program generates cpp files (FSMController.h, FSMController.cpp, FSMData.h and test-FMS.cpp) for each scxml file specified.')
+    parser.add_argument('-q', '--quiet', help='Output only essential messages', dest='quiet',
+                        action='store_true')
+    parser.add_argument('-a', '--authors', dest='authors')
+    parser.add_argument('-n', '--main_namespace',
+                        help=DEFAULT_NAMESPACE + ' as default', dest='main_namespace')
+    parser.add_argument('-f', '--files', dest='files', metavar='F', nargs='*')
+    parser.add_argument('directory', nargs='?',
+                        help='Directory where to search files')
+    return parser
+
+
+def print_banner():
+    # Font: Ivrit
+    print("+----------------------------------------------------------------------------+")
+    print("|  ____  _                                _                                  |")
+    print("| / ___|| | ___   ___      ____ _ _ __ __| |                                 |")
+    print("| \\___ \\| |/ / | | \\ \\ /\\ / / _` | '__/ _` |                                 |")
+    print(
+        "|  ___) |   <| |_| |\\ V  V / (_| | | | (_| |                                 |")
+    print("| |____/|_|\\_\\\\__, | \\_/\\_/ \\__,_|_|  \\__,_|                                 |")
+    print("|        _____|___/  __  __    ____                           _              |")
+    print("|       |  ___/ ___||  \\/  |  / ___| ___ _ __   ___ _ __ __ _| |_ ___  _ __  |")
+    print("|       | |_  \___ \\| |\\/| | | |  _ / _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\| '__| |")
+    print("|       |  _|  ___) | |  | | | |_| |  __/ | | |  __/ | | (_| | || (_) | |    |")
+    print("|       |_|   |____/|_|  |_|  \\____|\\___|_| |_|\\___|_|  \\__,_|\\__\___/|_|    |")
+    print("|                                                                            |")
+    print("+----------------------------------------------------------------------------+")
+
+
+def generate_fsm_controller_h(fsmData, year, authors, main_namespace):
+    state_machine_name = fsmData[0]
+
+    # Open template file
+    template = read_file(FSM_CONTROLLER_H_TEMPLATE_FILE)
+
+    # Prepare the state functions declarations
+    states_funcions_declarations = [
+        STATE_FUNCTION_DECLARATION_TEMPLATE.format(state_name=state[0])
+        for state in fsmData[3]]
+    states_funcions_declarations = '\n'.join(
+        [' '*4 + declaration for declaration in states_funcions_declarations])
+
+    # Preapare utility functions declarations
+    utility_functions_declarations = [
+        UTILITY_FUNCTION_DECLARATION_TEMPLATE.format(
+            function_name=function.replace('()', ''))
+        for function in fsmData[4]]
+    utility_functions_declarations = '\n'.join(
+        [' '*4 + declaration for declaration in utility_functions_declarations]
+    )
+
+    # Insert information into template file
+    content = template.format(
+        year=year, authors=authors, main_namespace=main_namespace,
+        state_machine_name=state_machine_name,
+        states_funcions_declarations=states_funcions_declarations,
+        start_state=fsmData[2] + '_' + fsmData[1].upper(),
+        utility_functions_declarations=utility_functions_declarations
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/' + state_machine_name,
+                      state_machine_name + 'Controller.h', content)
+
+
+def generate_fsm_controller_cpp(fsmData, year, authors, main_namespace):
+    state_machine_name = fsmData[0]
+
+    # Open template files
+    template = read_file(FSM_CONTROLLER_CPP_TEMPLATE_FILE)
+    functon_definition_template = read_file(
+        STATE_FUNCTION_DEFINITION_TEMPLATE_FILE)
+
+    # Prepare the state functions definitions
+    states_functions_definitions = []
+    for state in fsmData[3]:
+        event_cases = []
+
+        # Prepare onentry and onexit actions
+        onentry_actions = '\n'.join(
+            [' '*12 + action + ';' for action in state[1]])
+        onexit_actions = '\n'.join(
+            [' '*12 + action + ';' for action in state[2]])
+        if onentry_actions == '':
+            onentry_actions = ' '*12 + '// ...'
+        if onexit_actions == '':
+            onexit_actions = ' '*12 + '// ...'
+
+        # Prepare the cases
+        for event in state[3]:
+            actions = [action + ';' for action in event[2]]
+            if event[1] and event[1] != state[0]:
+                actions.append(STATE_FUNCTION_CASE_TRANSITION_TO_TARGET_TEMPLATE.format(
+                    state_machine_name=state_machine_name, state_name=event[1]))
+            actions = '\n'.join([' '*12 + action for action in actions])
+            if actions == '':
+                actions = ' '*12 + '// ...'
+            event_cases.append(STATE_FUNCTION_CASE_TEMPLATE.format(
+                event=event[0], actions=actions
+            ))
+        event_cases = '\n'.join(event_cases)
+
+        states_functions_definitions.append(
+            functon_definition_template.format(
+                state_machine_name=state_machine_name,
+                state_name=state[0].lower(),
+                current_state=state[0].upper(),
+                onentry_actions=onentry_actions, onexit_actions=onexit_actions,
+                event_cases=event_cases
+            )
+        )
+    states_functions_definitions = '\n\n'.join(states_functions_definitions)
+
+    # Preapare utility functions declarations
+    utility_functions_definiton = []
+    for function in fsmData[4]:
+        utility_functions_definiton.append(
+            UTILITY_FUNCTION_DEFINITION_TEMPLATE.format(
+                state_machine_name=state_machine_name,
+                function_name=function.replace('()', '')
+            )
+        )
+    utility_functions_definiton = '\n\n'.join(utility_functions_definiton)
+
+    # Prepare topics subscriptions
+    topics_subscriptions = []
+    for topic in fsmData[5]:
+        topics_subscriptions.append(TOPICS_SUBSCIPTION_TEMPLATE.format(
+            topic='TOPIC_' + topic
+        ))
+    topics_subscriptions = '\n'.join(topics_subscriptions)
+
+    # Insert information into template file
+    content = template.format(
+        year=year, authors=authors, state_machine_name=state_machine_name,
+        initial_state=fsmData[1], topics_subscriptions=topics_subscriptions,
+        topic='TOPIC_' + fsmData[2], main_namespace=main_namespace,
+        states_functions_definitions=states_functions_definitions,
+        utility_functions_definiton=utility_functions_definiton
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/' + state_machine_name,
+                      state_machine_name + 'Controller.cpp', content)
+
+
+def generate_fsm_data_h(fsmData, year, authors, main_namespace):
+    state_machine_name = fsmData[0]
+
+    # Open template file
+    template = read_file(FSM_DATA_H_TEMPLATE_FILE)
+
+    # Prepare the states list
+    states = [state[0].upper() for state in fsmData[3]]
+    states[0] += ' = 0'
+    states = '\n'.join([' '*4 + state + ',' for state in states])
+
+    # Insert information into template file
+    content = template.format(
+        year=year, authors=authors, main_namespace=main_namespace,
+        state_machine_name=state_machine_name, states=states
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/' + state_machine_name,
+                      state_machine_name + 'Data.h', content)
+
+
+def generate_fsm_test_cpp(fsmData, year, authors):
+    state_machine_name = fsmData[0]
+
+    # Open template file
+    template = read_file(FSM_TEST_CPP_TEMPLATE_FILE)
+
+    # Prepare the test cases list
+    test_cases = []
+    for state in fsmData[3]:
+        # Prepare test case sections
+        test_case_sections = []
+        for event in state[3]:
+            test_case_sections.append(TEST_CASE_SECTION_TEMPLATE.format(
+                target_state_upper=event[1].upper(), target_state=event[1],
+                state_upper=state[0].upper(), event=event[0],
+                state_machine_name=state_machine_name
+            ))
+        test_case_sections = '\n\n'.join(test_case_sections)
+
+        test_cases.append(TEST_CASE_METHOD_TEMPLATE.format(
+            state_machine_name=state_machine_name, state=state[0],
+            start_state=state[0],
+            test_case_sections=test_case_sections
+        ))
+    test_cases = '\n\n'.join(test_cases)
+
+    # Insert information into template file
+    content = template.format(
+        year=year, authors=authors, state_machine_name=state_machine_name,
+        test_cases=test_cases
+    )
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/' + 'tests', 'test-' + fsmData[0].lower() + '.cpp', content)
+
+
+def generate_readme_with_mermaid(fsmData):
+    state_machine_name = fsmData[0]
+    lines = []
+
+    # Initial state
+    lines.append('[*] --> ' + fsmData[1])
+
+    # Most commented code is because gitlab does not support some features of mermaid
+
+    # Generate descriptions and transitons
+    for state in fsmData[3]:
+        # Name of the state
+        # lines.append(state[0] + ' : ' + state[0])
+
+        # Description with onentry, onexit and other actions
+        # for action in state[1]:
+        #     lines.append(state[0] + ' : onentry - ' + action)
+        # for action in state[2]:
+        #     lines.append(state[0] + ' : onexit - ' + action)
+        for transiton in state[3]:
+            # if transiton[1] == state[0]:
+            #     lines.append(state[0] + ' : ' + transiton[0] + (
+            #         ' - ' + ''.join(transiton[2])
+            #         if len(transiton[2]) > 0 else ''))
+            # Transitions
+            # else:
+            if not transiton[1] == state[0]:
+                lines.append(state[0] + ' --> ' +
+                             transiton[1] + ' : ' + transiton[0])
+                #  + ('\\n' + ''.join(transiton[2]) if len(transiton[2]) > 0 else '')
+
+    mermaid = '\n'.join([' '*4 + line for line in lines])
+    mermaid = README_TEMPLATE.format(mermaid=mermaid)
+
+    # Save the file
+    return write_file(OUTPUT_FOLDER + '/' + state_machine_name, 'readme.md', mermaid)
+
+
+# -------------------------------------------------------------
+# MAIN
+# -------------------------------------------------------------
+
+
+parser = config_cmd_parser()
+args = parser.parse_args()
+files = []
+
+if(not args.files and not args.directory):
+    print('[eventgen] No scxml files specified nor a directory')
+    print('')
+    parser.print_help()
+    exit(-1)
+
+# Fetch files to parse
+if args.files:
+    files.extend(args.files)
+if args.directory:
+    for dirpath, dirnames, filenames in walk(args.directory):
+        for filename in [f for f in filenames if f.endswith(".scxml")]:
+            if not 'template' in filename:
+                files.append(join(dirpath, filename))
+
+if(not args.quiet):
+    print_banner()
+    print('')
+    print('[fsmgen] Welcome to the FSMgen script!')
+    print('')
+
+if(not args.authors):
+    print('[fsmgen] WARNING missing authors parameter')
+    args.authors = 'Someone'
+
+if(not args.main_namespace):
+    print('[fsmgen] WARNING missing namespace parameter')
+    args.main_namespace = DEFAULT_NAMESPACE
+
+date = datetime.datetime.now()
+
+for file in files:
+    print('[fsmgen] Now parsing ' + file)
+
+    fsmData = parse_scxml(file)
+
+    # Generate FSMController.h
+    file_name = generate_fsm_controller_h(
+        fsmData, date.year, args.authors, args.main_namespace)
+    print('[fsmgen] Generated ' + file_name)
+
+    # Generate FSMController.cpp
+    file_name = generate_fsm_controller_cpp(
+        fsmData, date.year, args.authors, args.main_namespace)
+    print('[fsmgen] Generated ' + file_name)
+
+    # Generate FSMData.h
+    file_name = generate_fsm_data_h(
+        fsmData, date.year, args.authors, args.main_namespace)
+    print('[fsmgen] Generated ' + file_name)
+
+    # Generate test-FMS.cpp
+    file_name = generate_fsm_test_cpp(fsmData, date.year, args.authors)
+    print('[fsmgen] Generated ' + file_name)
+
+    # Generate readme.md
+    file_name = generate_readme_with_mermaid(fsmData)
+    print('[fsmgen] Generated ' + file_name)
diff --git a/scripts/generators/genutils.py b/scripts/generators/genutils.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a2d0fee71df89f9edd44309f71c07c1a3188e04
--- /dev/null
+++ b/scripts/generators/genutils.py
@@ -0,0 +1,142 @@
+# Copyright (c) 2021 Skyward Experimental Rocketry
+# Authors: Alberto Nidasio
+#
+# 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
+# AUTHORS OR COPYRIGHT HOLDERS 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.
+
+from re import sub
+from os import makedirs
+from os.path import join, basename
+from xml.etree.ElementTree import parse
+
+
+def camel_case_to_underscores(string):
+    return sub(r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))', r'_\1', string)
+
+
+def parse_scxml(file):
+    name = basename(file).split('.')[0]
+
+    # Parse the scxml file
+    tree = parse(str(file))
+    root = tree.getroot()
+    initial_state = root.attrib['initial']
+
+    # Extract states and actions
+    topics = []
+    states_list = []
+    actions_list = []
+    for state in root:
+        state_id = camel_case_to_underscores(state.attrib['id']).lower()
+
+        # Extract on entry and on exit actions
+        onentry_actions = []
+        onexit_actions = []
+        for onentry in state.iter('{http://www.w3.org/2005/07/scxml}onentry'):
+            if 'postD' in onentry.text or 'removeD' in onentry.text:
+                onentry_actions.append(onentry.text)
+            else:
+                onentry_actions.append(
+                    camel_case_to_underscores(onentry.text).lower())
+        for onexit in state.iter('{http://www.w3.org/2005/07/scxml}onexit'):
+            if 'postD' in onexit.text or 'removeD' in onexit.text:
+                onexit_actions.append(onexit.text)
+            else:
+                onexit_actions.append(
+                    camel_case_to_underscores(onexit.text).lower())
+
+        # Extract transitions and topics
+        transitions_list = []
+        for transition in state.iter('{http://www.w3.org/2005/07/scxml}transition'):
+            try:
+                if '.' in transition.attrib['event']:
+                    (topic, event) = camel_case_to_underscores(
+                        transition.attrib['event']).split('.')
+                    topics.append(topic)
+                else:
+                    event = camel_case_to_underscores(
+                        transition.attrib['event'])
+            except:
+                print(
+                    '[genutils] Transition is missing event parameter in file ' + file)
+                exit(-1)
+            try:
+                target = camel_case_to_underscores(
+                    transition.attrib['target']).lower()
+            except:
+                target = None
+
+            # Divide each line of the transition text into a single action
+            try:
+                transition_actions = transition.text.splitlines()
+                for i, line in enumerate(transition_actions):
+                    transition_actions[i] = line.lstrip().rstrip()
+                transition_actions = list(
+                    filter(lambda a: a != '', transition_actions))
+                transition_actions = [
+                    camel_case_to_underscores(action).lower()
+                    if transition.text and not 'postD' in transition.text
+                    and not 'removeD' in transition.text else action
+                    for action in transition_actions]
+            except:
+                transition_actions = []
+
+            transitions_list.append((event, target, transition_actions))
+            actions_list.extend(transition_actions)
+
+        states_list.append(
+            (state_id, onentry_actions, onexit_actions, transitions_list))
+
+        # Remove from the actions list all the duplicates, postD and removeD
+        actions_list.extend(onentry_actions)
+        actions_list.extend(onexit_actions)
+        actions_list = list(dict.fromkeys(actions_list))
+        actions_list = list(
+            filter(lambda a: not 'postD' in a and not 'removeD' in a, actions_list))
+        actions_list.sort()
+
+    state_machine_topic = max(set(topics), key=topics.count)
+    topics = list(dict.fromkeys(topics))
+
+    return (name, initial_state, state_machine_topic, states_list, actions_list, topics)
+
+
+def read_file(file_name):
+    try:
+        with open(file_name, 'r') as r_file:
+            return r_file.read()
+    except:
+        print('Could not open template file "' + file_name + '"')
+        exit(-1)
+
+
+def write_file(file_path, file_name, content):
+    file_path = file_path
+
+    try:
+        # Ensures the path exists
+        makedirs(file_path, exist_ok=True)
+
+        with open(join(file_path, file_name), 'w') as w_file:
+            w_file.write(content)
+
+        return file_name
+    except:
+        print('Could not create and/or write file "' +
+              file_path + '/' + file_name + '"')
+        exit(-1)
diff --git a/scripts/homeone/event_header_generator/EventFunctions.cpp.template b/scripts/generators/templates/EventStrings.cpp.template
similarity index 80%
rename from scripts/homeone/event_header_generator/EventFunctions.cpp.template
rename to scripts/generators/templates/EventStrings.cpp.template
index 18f7690704c127e2bd5eb516097a08868e505565..81fc1be2a016fc4f2e471915eec57a24c7a8f80b 100644
--- a/scripts/homeone/event_header_generator/EventFunctions.cpp.template
+++ b/scripts/generators/templates/EventStrings.cpp.template
@@ -1,5 +1,5 @@
-/* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018-2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Alvise de' Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -26,34 +26,28 @@
  ******************************************************************************
  */
 
-// Generated from:  {sheet_link}
-// Autogen date:    {date}
+// Autogen date: {date}
 
+#include <map>
+using std::map;
 
 #include "Events.h"
 #include "Topics.h"
 
-#include <map>
-
-namespace HomeoneBoard
-{{
-
 string getEventString(uint8_t event)
 {{
-    static const map<uint8_t, string> event_string_map {{
-{event_map_data}
+    static const map<uint8_t, string> event_string_map{{
+{event_list}
     }};
-    auto   it  = event_string_map.find(event);
+    auto it = event_string_map.find(event);
     return it == event_string_map.end() ? "EV_UNKNOWN" : it->second;
 }}
 
 string getTopicString(uint8_t topic)
 {{
 	static const map<uint8_t, string> topic_string_map{{
-{topic_map_data}
+{topic_list}
 	}};
 	auto it = topic_string_map.find(topic);
-	return it == topic_string_map.end() ? "TOPIC_UNKNOWN" : it->second; 
-}}
-
+	return it == topic_string_map.end() ? "TOPIC_UNKNOWN" : it->second;
 }}
\ No newline at end of file
diff --git a/scripts/homeone/event_header_generator/Events.h.template b/scripts/generators/templates/Events.h.template
similarity index 70%
rename from scripts/homeone/event_header_generator/Events.h.template
rename to scripts/generators/templates/Events.h.template
index 2c4937a1fdd509e3307baf666120b77fdd067fec..7273a2c74fafc83ba7cc103300c71a5f0639b3d4 100644
--- a/scripts/homeone/event_header_generator/Events.h.template
+++ b/scripts/generators/templates/Events.h.template
@@ -1,5 +1,5 @@
-/* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018-{year} Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Alvise de' Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -26,42 +26,37 @@
  ******************************************************************************
  */
 
-// Generated from:  {sheet_link}
-// Autogen date:    {date}
+// Autogen date: {date}
 
-#ifndef SRC_SHARED_BOARDS_HOMEONE_EVENTS_H
-#define SRC_SHARED_BOARDS_HOMEONE_EVENTS_H
+#pragma once
 
 #include <cstdint>
 #include <string>
+#include <vector>
 
+#include "Topics.h"
 #include "events/Event.h"
 #include "events/EventBroker.h"
-#include "EventClasses.h"
-#include "Topics.h"
 
 using std::string;
-using std::map;
 
-namespace HomeoneBoard
-{{
 /**
- * Definition of all events in the Homeone Board software
- * Refer to section 5.1.1 of the Software Design Document.
+ * Definition of all events in the Board software.
+ * Refer to the Software Design Document.
  */
 enum Events : uint8_t
 {{
-{enum_data}
+{enum_event_list}
+}};
+
+const std::vector<uint8_t> EVENT_LIST{{
+{vector_event_list}
 }};
 
 /**
  * @brief Returns the name of the provided event
- * 
- * @param event 
- * @return string 
+ *
+ * @param event
+ * @return string
  */
-string getEventString(uint8_t event);
-
-}}
-
-#endif /* SRC_SHARED_BOARDS_HOMEONE_EVENTS_H */
+string getEventString(uint8_t event);
\ No newline at end of file
diff --git a/scripts/generators/templates/FSMController.cpp.template b/scripts/generators/templates/FSMController.cpp.template
new file mode 100644
index 0000000000000000000000000000000000000000..4107e696a81e098e64998a6eb4814efe6c59a04d
--- /dev/null
+++ b/scripts/generators/templates/FSMController.cpp.template
@@ -0,0 +1,66 @@
+/* Copyright (c) {year} Skyward Experimental Rocketry
+ * Authors: {authors}
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "{state_machine_name}Controller.h"
+
+#include <miosix.h>
+
+#include "configs/{state_machine_name}Config.h"
+#include "events/EventBroker.h"
+#include "events/Events.h"
+
+namespace {main_namespace}
+{{
+
+using namespace {state_machine_name}Config;
+
+{state_machine_name}Controller::{state_machine_name}Controller() : FSM(&{state_machine_name}Controller::state_{initial_state})
+{{
+    memset(&status, 0, sizeof({state_machine_name}ControllerStatus));
+{topics_subscriptions}
+}}
+
+{state_machine_name}Controller::~{state_machine_name}Controller()
+{{
+    sEventBroker->unsubscribe(this);
+}}
+
+{states_functions_definitions}
+
+{utility_functions_definiton}
+
+/* --- LOGGER --- */
+
+void {state_machine_name}Controller::logStatus({state_machine_name}ControllerState state)
+{{
+    status.state = state;
+    logStatus();
+}}
+
+void {state_machine_name}Controller::logStatus()
+{{
+    status.timestamp = miosix::getTick();
+    logger.log(status);
+    StackLogger::getInstance()->updateStack(THID);
+}}
+
+}}  // namespace {main_namespace}
\ No newline at end of file
diff --git a/scripts/generators/templates/FSMController.h.template b/scripts/generators/templates/FSMController.h.template
new file mode 100644
index 0000000000000000000000000000000000000000..99b4dd6b5dbf34d27d3977439b5c5b636d5121a0
--- /dev/null
+++ b/scripts/generators/templates/FSMController.h.template
@@ -0,0 +1,55 @@
+/* Copyright (c) {year} Skyward Experimental Rocketry
+ * Authors: {authors}
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "{state_machine_name}Data.h"
+#include "events/FSM.h"
+#include "LoggerService/LoggerService.h"
+
+namespace {main_namespace}
+{{
+
+/**
+ * @brief {state_machine_name} state machine
+ */
+class {state_machine_name}Controller : public FSM<{state_machine_name}Controller>
+{{
+public:
+    {state_machine_name}Controller();
+    ~{state_machine_name}Controller();
+
+{states_funcions_declarations}
+
+private:
+    {state_machine_name}ControllerStatus status;
+    LoggerService& logger = *(LoggerService::getInstance());
+
+{utility_functions_declarations}
+
+    /* --- LOGGER --- */
+
+    void logStatus({state_machine_name}ControllerState state);
+    void logStatus();
+}};
+
+}}  // namespace {main_namespace}
\ No newline at end of file
diff --git a/scripts/generators/templates/FSMData.h.template b/scripts/generators/templates/FSMData.h.template
new file mode 100644
index 0000000000000000000000000000000000000000..b2bb851febc395f5f2b633241b83a55c4c5f3167
--- /dev/null
+++ b/scripts/generators/templates/FSMData.h.template
@@ -0,0 +1,60 @@
+/* Copyright (c) {year} Skyward Experimental Rocketry
+ * Authors: {authors}
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <iostream>
+#include <string>
+
+namespace {main_namespace}
+{{
+
+/**
+ * Enum defining the possibile FSM states.
+ */
+enum {state_machine_name}ControllerState : uint8_t
+{{
+{states}
+}};
+
+/**
+ * Structure defining the overall controller status.
+ */
+struct {state_machine_name}ControllerStatus
+{{
+    long long timestamp;
+    {state_machine_name}ControllerState state;
+
+    static std::string header()
+    {{
+        return "timestamp,state\n";
+    }}
+
+    void print(std::ostream& os) const
+    {{
+        os << timestamp << "," << (int)state << "\n";
+    }}
+}};
+
+}}  // namespace {main_namespace}
diff --git a/scripts/homeone/event_header_generator/Topics.h.template b/scripts/generators/templates/Topics.h.template
similarity index 75%
rename from scripts/homeone/event_header_generator/Topics.h.template
rename to scripts/generators/templates/Topics.h.template
index 43e72f7302673cf61bbb3a6605681feb08cf7236..97c6448c83a044411e79dec4ba8f52a460384300 100644
--- a/scripts/homeone/event_header_generator/Topics.h.template
+++ b/scripts/generators/templates/Topics.h.template
@@ -1,5 +1,5 @@
-/* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018-{year} Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Alvise de' Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -26,26 +26,26 @@
  ******************************************************************************
  */
 
-// Generated from:  {sheet_link}
-// Autogen date:    {date}
+// Autogen date: {date}
 
-#ifndef SRC_SHARED_BOARDS_HOMEONE_TOPICS_H
-#define SRC_SHARED_BOARDS_HOMEONE_TOPICS_H
+#pragma once
 
-#include <stdint.h>
+#include <cstdint>
 #include <string>
+#include <vector>
 
-using std::map;
 using std::string;
 
-namespace HomeoneBoard
-{{
 /**
  * Definition of various event topics to use in the EventBroker
  */
 enum Topics : uint8_t
 {{
-{enum_data}
+{topic_list}
+}};
+
+const std::vector<uint8_t> TOPIC_LIST{{
+{topic_list}
 }};
 
 /**
@@ -54,8 +54,4 @@ enum Topics : uint8_t
  * @param event
  * @return string
  */
-string getTopicString(uint8_t topic);
-
-}}  // namespace HomeoneBoard
-
-#endif /* SRC_SHARED_BOARDS_HOMEONE_TOPICS_H_ */
+string getTopicString(uint8_t topic);
\ No newline at end of file
diff --git a/scripts/generators/templates/state_function_definition.template b/scripts/generators/templates/state_function_definition.template
new file mode 100644
index 0000000000000000000000000000000000000000..80522fb70a5ffa66eaf361b3332521f1bf90b363
--- /dev/null
+++ b/scripts/generators/templates/state_function_definition.template
@@ -0,0 +1,27 @@
+void {state_machine_name}Controller::state_{state_name}(const Event& ev)
+{{
+    switch (ev.sig)
+    {{
+        case EV_ENTRY:
+        {{
+{onentry_actions}
+
+            logStatus({current_state});
+
+            TRACE("[{state_machine_name}] entering state {state_name}\n");
+            break;
+        }}
+        case EV_EXIT:
+        {{
+{onexit_actions}
+
+            TRACE("[{state_machine_name}] exiting state {state_name}\n");
+            break;
+        }}
+{event_cases}
+        default:
+        {{
+            break;
+        }}  
+    }}
+}}
\ No newline at end of file
diff --git a/scripts/generators/templates/test.cpp.template b/scripts/generators/templates/test.cpp.template
new file mode 100644
index 0000000000000000000000000000000000000000..4fc96e72bcd7c57462c6899f04c0681916330a0a
--- /dev/null
+++ b/scripts/generators/templates/test.cpp.template
@@ -0,0 +1,66 @@
+/* Copyright (c) {year} Skyward Experimental Rocketry
+ * Authors: {authors}
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "catch/catch-tests-entry.cpp"
+#endif
+
+// We need access to the handleEvent(...) function in state machines in order to
+// test them synchronously
+#define protected public
+
+#include <miosix.h>
+
+#include <utils/testutils/catch.hpp>
+
+#include "{state_machine_name}Controller/{state_machine_name}Controller.h"
+#include "events/Events.h"
+#include "utils/testutils/TestHelper.h"
+
+using miosix::Thread;
+using namespace DeathStackBoard;
+
+class {state_machine_name}ControllerFixture
+{{
+public:
+    // This is called at the beginning of each test / section
+    {state_machine_name}ControllerFixture()
+    {{
+        controller = new {state_machine_name}Controller();
+        sEventBroker->start();
+        controller->start();
+    }}
+
+    // This is called at the end of each test / section
+    ~{state_machine_name}ControllerFixture()
+    {{
+        controller->stop();
+        sEventBroker->unsubscribe(controller);
+        sEventBroker->clearDelayedEvents();
+        delete controller;
+    }}
+
+protected:
+    {state_machine_name}Controller* controller;
+}};
+
+{test_cases}
\ No newline at end of file
diff --git a/scripts/homeone/event_header_generator/README.md b/scripts/homeone/event_header_generator/README.md
deleted file mode 100644
index f87e46c67ebcec406b02c58e10dbe91b00a0c8d3..0000000000000000000000000000000000000000
--- a/scripts/homeone/event_header_generator/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Events Generator Script
-
-This script generates the Events.h and Topics.h heander file from a GoogleSheets document on Google Drive.
-
-To execute the script:
-
-```
-pip install --upgrade google-api-python-client
-pip install oauth2client
-python scripts/homeone/event_header_generator/event_generator.py
-```
-
-The first time a browser window should open asking for your credentials (use the Skyward ones).
-If it doesn't and only gives you an error, try executing it by clicking on the icon.
\ No newline at end of file
diff --git a/scripts/homeone/event_header_generator/credentials.json b/scripts/homeone/event_header_generator/credentials.json
deleted file mode 100644
index dae318afc5661116b82fe75f80f4316e538e670a..0000000000000000000000000000000000000000
--- a/scripts/homeone/event_header_generator/credentials.json
+++ /dev/null
@@ -1 +0,0 @@
-{"installed":{"client_id":"1025168905991-tv31etsgm3lecodc5c798shqciekad40.apps.googleusercontent.com","project_id":"homeone-event-ge-1540834105298","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://www.googleapis.com/oauth2/v3/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"Yhaf67HuHR4DyXZKNXWu2lre","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}
\ No newline at end of file
diff --git a/scripts/homeone/event_header_generator/event_generator.py b/scripts/homeone/event_header_generator/event_generator.py
deleted file mode 100755
index e895e6fc1bf6ee8615103c51250e5e85ae4a241a..0000000000000000000000000000000000000000
--- a/scripts/homeone/event_header_generator/event_generator.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/python3
-
-# Copyright (c) 2018 Skyward Experimental Rocketry
-# Authors: Luca Erbetta
-#
-# 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
-# AUTHORS OR COPYRIGHT HOLDERS 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.
-#
-
-from googleapiclient.discovery import build
-from httplib2 import Http
-from oauth2client import file, client, tools
-
-import re
-import datetime
-import sys
-from os.path import join
-import os
-
-OUTPUT_FOLDER = "generated"
-SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
-
-service = None
-
-SPREADSHEET_ID = '12TecOmDd7Uot-MvXkCbhDJRU48-XO6s5ChKDlr4AOvI'
-EVENTS_RANGE_NAME = 'EventList!A2:A'
-TOPICS_RANGE_NAME = 'Topics!B3:B'
-
-
-def auth():
-    try:
-        store = file.Storage('store.json')
-        creds = store.get()
-        if not creds or creds.invalid:
-            flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
-            creds = tools.run_flow(flow, store)
-
-        global service
-        service = build('sheets', 'v4', http=creds.authorize(Http()))
-        return True
-    except:
-        print("Authentication error:", sys.exc_info()[0])
-        return False
-
-
-def load_events():
-    result = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID,
-                                                 range=EVENTS_RANGE_NAME).execute()
-    lines = result.get('values', [])
-
-    # Event names start with EV_ and contains only uppercase letters or underscores
-    re_event = re.compile(r'(?P<ev>^EV_([A-Z_]+)+)')
-
-    # Only return lines with valid events, remove additional data
-    events = []
-    for l in lines:
-        m = re_event.match(l[0])
-        if m is not None:
-            events.append(m.group('ev'))
-        else:
-            print("Skipped line containing invalid event: {}".format(l))
-
-    return events
-
-
-def load_topics():
-    result = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID,
-                                                 range=TOPICS_RANGE_NAME).execute()
-    lines = result.get('values', [])
-    re_topics = re.compile(r'(?P<topic>^TOPIC_([A-Z_]+)+)')
-
-    # Only return lines with valid topics, remove additional data
-    topics = []
-    for l in lines:
-        m = re_topics.match(l[0])
-        if m is not None:
-            topics.append(m.group('topic'))
-        else:
-            print("Skipped line containing invalid topics: {}".format(l))
-
-    return topics
-
-
-def has_duplicates(lst):
-    if len(lst) != len(set(lst)):
-        return True
-    return False
-
-
-
-print("Homeone on-board software event header generator v0.2")
-print("Google sheets API auth in progress...")
-
-if auth():
-    print("Auth successfull.")
-else:
-    exit()
-
-#directory = os.path.dirname(OUTPUT_FOLDER)
-if not os.path.exists(OUTPUT_FOLDER):
-    os.mkdir(OUTPUT_FOLDER)
-
-print("Reading from: https://docs.google.com/spreadsheets/d/12TecOmDd7Uot-MvXkCbhDJRU48-XO6s5ChKDlr4AOvI")
-
-events = load_events()
-topics = load_topics()
-
-# Check duplicates
-if has_duplicates(events):
-    print("Duplicate events found! Terminating.")
-    exit()
-
-if has_duplicates(topics):
-    print("Duplicate topics found! Terminating.")
-    exit()
-
-print("{} events loaded.".format(len(events)))
-print("{} topics loaded.".format(len(topics)))
-
-enum_str = ""
-event_map_str = ""
-
-date = datetime.datetime.now()
-link = "https://docs.google.com/spreadsheets/d/{id}".format(id=SPREADSHEET_ID)
-
-
-# Events.h generation
-
-print("Generating Events.h...")
-
-for e in events:
-    endl = ",\n" if e != events[-1] else ""
-    enum_str += "    " + e + \
-        (" = EV_FIRST_SIGNAL" if e == events[0] else "") + endl
-    event_map_str += "        {{ {event}, {string} }}{endl}".format(
-        event=e, string='"' + e + '"', endl=endl)
-
-with open('Events.h.template', 'r') as template_file:
-    template = template_file.read()
-
-template = template.format(sheet_link=link, date=date,
-                           enum_data=enum_str)
-
-with open(join(OUTPUT_FOLDER, 'Events.h'), 'w') as header_file:
-    header_file.write(template)
-
-print("Events.h successfully generated.")
-# Topics.h generation
-
-print("Generating Topics.h...")
-enum_str = ""
-topic_map_str = ""
-
-for t in topics:
-    endl = ",\n" if t != topics[-1] else ""
-    enum_str += "    " + t + endl
-    topic_map_str += "        {{ {topics}, {string} }}{endl}".format(
-        topics=t, string='"' + t + '"', endl=endl)
-
-with open('Topics.h.template', 'r') as template_file:
-    template = template_file.read()
-
-template = template.format(sheet_link=link, date=date,
-                           enum_data=enum_str)
-
-with open(join(OUTPUT_FOLDER, 'Topics.h'), 'w') as header_file:
-    header_file.write(template)
-
-with open('EventFunctions.cpp.template', 'r') as cpp_template_file:
-    cpp = cpp_template_file.read()
-
-cpp = cpp.format(sheet_link=link, date=date,
-                 event_map_data=event_map_str, topic_map_data=topic_map_str)
-
-with open(join(OUTPUT_FOLDER, 'EventFunctions.cpp'), 'w') as cpp_file:
-    cpp_file.write(cpp)
-
-print("Topics.h successfully generated.")
-
-print()
-print("All files successfully generated. Please move Events.h, Topics.h, EventFunction.cpp into the Homeone board folder.")
-print(".... Done.")
diff --git a/scripts/linter.sh b/scripts/linter.sh
index 70dd0adb8d12a393cc67d72cc2051353b27c2b22..ae9e1e73a401e5debf1f3377e8676e5fc95f29a4 100755
--- a/scripts/linter.sh
+++ b/scripts/linter.sh
@@ -1,18 +1,83 @@
 #!/bin/bash
 
+#
+# Usage: grepcheck <FOLDER_TO_CHECK> <STRING_TO_FIND> <EGREP_ARGS>
+#
+function grepcheck {
+
+    FILES=$(grep -rl "$2" $1 | egrep "$3")
+
+    for f in $FILES
+    do
+        # Print name of the file in red
+        echo -ne '\033\x5b\x33\x33\x6d'
+
+        echo $f
+        echo -ne '\033\x5b\x30\x6d'
+
+        if [[ $verbose -eq 1 ]]; then
+            grep --color=always -r "$2" $f -C3
+            echo ''
+        fi
+    done
+
+    echo ''
+
+    # If VERBOSE is defined then also print the context in the file
+}
+
+# Horrifying way of checking if -v or --verbose is enabled
+verbose=0
+
+case "$1" in
+-v|--verbose)
+    verbose=1
+    shift ;;
+esac
+
+case "$2" in
+-v|--verbose)
+    verbose=1
+esac
+
+# Execute checks
+echo '--------------------------- CPPCHECK'
 echo '[Linter] Executing cppcheck'
-cppcheck  --template=gcc -q --std=c++11 --enable=all \
+echo ''
+cppcheck  -U "RCC_APB1ENR_CAN2EN" --template=gcc -q --std=c++11 --enable=all \
             --suppress=unusedFunction --suppress=missingInclude --suppress=noExplicitConstructor \
-            "$@" 2>&1 | awk '
+            "$1" 2>&1 | awk '
                 function color(c,s) {
                         printf("\033[%dm%s\033[0m\n",30+c,s)
                 }
-                /warning/ {color(1,$0);next}
+                /error/ {color(1,$0);next}
+                /warning/ {color(3,$0);next}
                 /style/ {color(2,$0);next}
-                /performance/ {color(3,$0);next}
+                /performance/ {color(6,$0);next}
                 /information/ {color(4,$0);next}
                 /portability/ {color(5,$0);next}
                 {print}
                 '
+echo ''
+
+echo '--------------------------- USING NAMESPACE'
+echo '[Linter] Checking for using namespace in .h(pp) files'
+
+grepcheck $1 'using namespace' '.h(pp)?$'
+
+echo '--------------------------- GREP PRINTF'
+echo '[Linter] Checking for printf instead of TRACE'
+
+grepcheck $1 "printf" '.*'
+
+echo '--------------------------- GREP ASSERT'
+echo '[Linter] Checking if there are asserts in the code'
+
+grepcheck $1 "assert" '.*'
+
+echo '--------------------------- GREP THROW'
+echo '[Linter] Checking if there are exceptions in the code'
+
+grepcheck $1 "throw" '~*catch.hpp'
 
 #exit 0
diff --git a/scripts/homeone/mavlink_client/Makefile b/scripts/mavlink_client/Makefile
similarity index 100%
rename from scripts/homeone/mavlink_client/Makefile
rename to scripts/mavlink_client/Makefile
diff --git a/scripts/mavlink_client/README.md b/scripts/mavlink_client/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e8cd75de7fbd87bf075a2506218e1de6d4be6a3c
--- /dev/null
+++ b/scripts/mavlink_client/README.md
@@ -0,0 +1,14 @@
+# Mavlink Client
+
+These scripts can be used to transmit and receive mavlink data from a PC, either
+connected via serial port to a transceiver or directly connected to one of the boards.
+
+Use `make` to compile the 3 `.c` files:
+
+* continuous_tx: sends a message of raw bytes with a given length at a certain
+frequency. This was used to test the LoRa modules.
+* mavlink_demo_tx: sends a message whenever it receives a char on stdin
+* mavlink_demo_rx: receives and prints packets, eventually sending back an ACK.
+
+`plot.py`: reads value on stdin and plots them. Can be used piped with the output
+of mavlink_demo_rx.
\ No newline at end of file
diff --git a/scripts/homeone/mavlink_client/continuous_tx.c b/scripts/mavlink_client/continuous_tx.c
similarity index 97%
rename from scripts/homeone/mavlink_client/continuous_tx.c
rename to scripts/mavlink_client/continuous_tx.c
index bf97e2f0b1ba01dfd5bca65087a44d4ca2aafe17..e477fb533d055d9d18f6a324c80158156428fe93 100644
--- a/scripts/homeone/mavlink_client/continuous_tx.c
+++ b/scripts/mavlink_client/continuous_tx.c
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/scripts/homeone/mavlink_client/mavlink_demo_rx.c b/scripts/mavlink_client/mavlink_demo_rx.c
similarity index 98%
rename from scripts/homeone/mavlink_client/mavlink_demo_rx.c
rename to scripts/mavlink_client/mavlink_demo_rx.c
index e33145dfb2ba3eb00163ba5c0329326d3e5bdfc5..95c49aa44d440abf09822dd0889435438775b51b 100644
--- a/scripts/homeone/mavlink_client/mavlink_demo_rx.c
+++ b/scripts/mavlink_client/mavlink_demo_rx.c
@@ -1,6 +1,6 @@
 
 /**************************************************
-Ssimple demo that receives mavlink
+Simple demo that receives mavlink
 messages on the serial port and prints them on the screen.
 
 **************************************************/
diff --git a/scripts/homeone/mavlink_client/mavlink_demo_tx.c b/scripts/mavlink_client/mavlink_demo_tx.c
similarity index 100%
rename from scripts/homeone/mavlink_client/mavlink_demo_tx.c
rename to scripts/mavlink_client/mavlink_demo_tx.c
diff --git a/scripts/homeone/mavlink_client/plot.py b/scripts/mavlink_client/plot.py
similarity index 100%
rename from scripts/homeone/mavlink_client/plot.py
rename to scripts/mavlink_client/plot.py
diff --git a/scripts/homeone/mavlink_client/rs232.c b/scripts/mavlink_client/rs232.c
similarity index 100%
rename from scripts/homeone/mavlink_client/rs232.c
rename to scripts/mavlink_client/rs232.c
diff --git a/scripts/homeone/mavlink_client/rs232.h b/scripts/mavlink_client/rs232.h
similarity index 100%
rename from scripts/homeone/mavlink_client/rs232.h
rename to scripts/mavlink_client/rs232.h
diff --git a/scripts/openocd-flash.sh b/scripts/openocd/openocd-flash.sh
similarity index 100%
rename from scripts/openocd-flash.sh
rename to scripts/openocd/openocd-flash.sh
diff --git a/scripts/start_openocd.sh b/scripts/openocd/start_openocd.sh
similarity index 100%
rename from scripts/start_openocd.sh
rename to scripts/openocd/start_openocd.sh
diff --git a/scripts/scxml2plant/scxml2plant.sh b/scripts/scxml2plant/scxml2plant.sh
new file mode 100755
index 0000000000000000000000000000000000000000..dbd1f04907a38ead88b5e27d8ae037cf95b88bac
--- /dev/null
+++ b/scripts/scxml2plant/scxml2plant.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+# Input checks
+if [ "$#" -ne 1 ]; then
+   echo "Usage: scxml2plant <FOLDER_TO_SEARCH>"
+   exit 1
+fi
+
+# Find this folder when calling the script from another folder
+PATH_TO_XALAN=$(dirname $0)
+
+# Select all scxml files
+for file in $(find $1 -name "*.scxml")
+do
+    # generate plantuml
+    INPUT_XML=$file
+    OUTPUT_NAME=$(dirname $file)/$(basename -s ".scxml" $file).plantuml
+    echo "Input file: " $INPUT_XML " Output basename: " $OUTPUT_NAME
+
+    java -cp $PATH_TO_XALAN/xalan/xalan.jar \
+    -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeParserConfiguration org.apache.xalan.xslt.Process \
+    -IN $INPUT_XML -XSL $PATH_TO_XALAN/xslt/scxml2plantuml.xslt -OUT $OUTPUT_NAME
+
+    # generate README
+    README_FILE=$(dirname $file)/README.md
+
+    touch $README_FILE
+    echo "\`\`\`plantuml" > $README_FILE
+    cat $OUTPUT_NAME >> $README_FILE
+    echo "\`\`\`" >> $README_FILE
+    rm $OUTPUT_NAME
+done
diff --git a/scripts/scxml2plant/xalan/LICENSE.txt b/scripts/scxml2plant/xalan/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..47ad566a4592992bb8c6cfe6bda017487dda8cf5
--- /dev/null
+++ b/scripts/scxml2plant/xalan/LICENSE.txt
@@ -0,0 +1,684 @@
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+                                 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
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+The license above applies to this Apache Xalan release of:
+  Xalan-Java 2 - XSLT Processor
+  Xalan-Java 2 - Serializer
+  
+The license above also applies to the jar files
+xalan.jar and xsltc.jar - Xalan-Java 2 - XSLT Processor from 
+Source: http://xalan.apache.org/
+  
+The license above also applies to the jar file
+serializer.jar - Xalan-Java 2 - Serializer
+Source:  http://xalan.apache.org/
+Used by: Xalan-Java 2 and Xerces-Java 2
+
+The license above also applies to the jar file
+xercesImpl.jar - Xerces-Java 2 XML Parser.
+Source:	  http://xerces.apache.org/
+Used by:  Xalan-Java 2
+
+The license above also applies to the jar file
+xml-apis.jar - Xerces-Java 2 XML Parser.
+Source:   http://xerces.apache.org/
+Used by:  Xalan-Java 2 and release copy of Xerces-Java 2
+
+
+
+
+
+
+
+
+The following license applies to the included files:
+  tools/ant.jar
+  tools/antRun
+  tools/antRun.bat
+Source:	 http://ant.apache.org/
+Used By: Xalan's build process: java/build.xml and test/build.xml
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+/*
+ * ============================================================================
+ *                   The Apache Software License, Version 1.1
+ * ============================================================================
+ * 
+ *    Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modifica-
+ * tion, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of  source code must  retain the above copyright  notice,
+ *    this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any, must
+ *    include  the following  acknowledgment:  "This product includes  software
+ *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
+ *    Alternately, this  acknowledgment may  appear in the software itself,  if
+ *    and wherever such third-party acknowledgments normally appear.
+ * 
+ * 4. The names "Ant" and  "Apache Software Foundation"  must not be used to
+ *    endorse  or promote  products derived  from this  software without  prior
+ *    written permission. For written permission, please contact
+ *    apache@apache.org.
+ * 
+ * 5. Products  derived from this software may not  be called "Apache", nor may
+ *    "Apache" appear  in their name,  without prior written permission  of the
+ *    Apache Software Foundation.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
+ * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
+ * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
+ * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
+ * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * This software  consists of voluntary contributions made  by many individuals
+ * on behalf of the  Apache Software Foundation.  For more  information  on the 
+ * Apache Software Foundation, please see <http://www.apache.org/>.
+ *
+ */
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license, Apache Software License, Version 1.1,
+applies to the included BCEL.jar from Apache Jakarta
+(Byte Code Engineering Library).
+Source:  http://jakarta.apache.org/bcel
+Used By: XSLTC component of xml-xalan/java
+
+The following license, Apache Software License, Version 1.1, 
+also applies to the included regexp.jar,
+jakarta-regexp-1.2.jar from Apache Jakarta.
+Source:  http://jakarta.apache.org/regexp
+Used By: BCEL.jar which is used by XSLTC component of xml-xalan/java
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+/*
+ *
+ * Copyright (c) 2001 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" and 
+ *    "Apache BCEL" must not be used to endorse or promote products 
+ *    derived from this software without prior written permission. For 
+ *    written permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    "Apache BCEL", nor may "Apache" appear in their name, without 
+ *    prior written permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the DOM documentation
+for the org.w3c.dom.* packages:
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+W3C® DOCUMENT LICENSE
+http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231
+Public documents on the W3C site are provided by the copyright holders 
+under the following license. By using and/or copying this document, 
+or the W3C document from which this statement is linked, you (the licensee) 
+agree that you have read, understood, and will comply with the following 
+terms and conditions:
+
+Permission to copy, and distribute the contents of this document, or the 
+W3C document from which this statement is linked, in any medium for any 
+purpose and without fee or royalty is hereby granted, provided that you include 
+the following on ALL copies of the document, or portions thereof, that you use:
+
+1. A link or URL to the original W3C document. 
+2. The pre-existing copyright notice of the original author, or if it 
+   doesn't exist, a notice (hypertext is preferred, but a textual representation
+    is permitted) of the form: "Copyright © [$date-of-document] World Wide Web
+    Consortium, (Massachusetts Institute of Technology, European Research
+    Consortium for Informatics and Mathematics, Keio University). All Rights 
+    Reserved. http://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231" 
+3. If it exists, the STATUS of the W3C document. 
+
+When space permits, inclusion of the full text of this NOTICE should be provided. 
+We request that authorship attribution be provided in any software, documents, 
+or other items or products that you create pursuant to the implementation of the
+contents of this document, or any portion thereof.
+
+No right to create modifications or derivatives of W3C documents is granted pursuant
+to this license. However, if additional requirements (documented in the Copyright FAQ)
+are satisfied, the right to create modifications or derivatives is sometimes granted
+by the W3C to individuals complying with those requirements.
+
+THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS
+OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE;
+THAT THE CONTENTS OF THE DOCUMENT ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE
+IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY PATENTS,
+COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE DOCUMENT OR THE PERFORMANCE
+OR IMPLEMENTATION OF THE CONTENTS THEREOF.
+
+The name and trademarks of copyright holders may NOT be used in advertising
+or publicity pertaining to this document or its contents without specific,
+written prior permission. Title to copyright in this document will at all
+times remain with copyright holders.
+
+
+----------------------------------------------------------------------------
+
+This formulation of W3C's notice and license became active on December 31 2002.
+This version removes the copyright ownership notice such that this license
+can be used with materials other than those owned by the W3C, moves information
+on style sheets, DTDs, and schemas to the Copyright FAQ, reflects that ERCIM
+is now a host of the W3C, includes references to this specific dated version
+of the license, and removes the ambiguous grant of "use". See the older
+formulation for the policy prior to this date. Please see our Copyright FAQ for
+common questions about using materials from our site, such as the translating
+or annotating specifications. Other questions about this notice can be directed
+to site-policy@w3.org.
+
+
+Joseph Reagle <mailto:site-policy@w3.org 
+Last revised by Reagle $Date: 2005-07-19 12:33:09 -0400 (Tue, 19 Jul 2005) $
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+
+The following license applies to the DOM software,
+for the org.w3c.dom.* packages in jar file xml-apis.jar:
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+W3C® SOFTWARE NOTICE AND LICENSE
+http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
+This work (and included software, documentation such as READMEs,
+or other related items) is being provided by the copyright holders
+under the following license. By obtaining, using and/or copying this
+work, you (the licensee) agree that you have read, understood, and will
+comply with the following terms and conditions.
+
+Permission to copy, modify, and distribute this software and its
+documentation, with or without modification, for any purpose and
+without fee or royalty is hereby granted, provided that you include
+the following on ALL copies of the software and documentation or
+portions thereof, including modifications:
+
+1. The full text of this NOTICE in a location viewable to users of the
+   redistributed or derivative work. 
+2. Any pre-existing intellectual property disclaimers, notices,
+   or terms and conditions. If none exist, the W3C Software Short Notice
+   should be included (hypertext is preferred, text is permitted) within
+   the body of any redistributed or derivative code. 
+3. Notice of any changes or modifications to the files, including the
+   date changes were made. (We recommend you provide URIs to the location
+   from which the code is derived.) 
+   
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS
+MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT
+NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
+PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE
+ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
+
+The name and trademarks of copyright holders may NOT be used in advertising
+or publicity pertaining to the software without specific, written prior
+permission. Title to copyright in this software and any associated documentation
+will at all times remain with copyright holders.
+
+
+____________________________________
+
+This formulation of W3C's notice and license became active on December 31 2002.
+This version removes the copyright ownership notice such that this license can
+be used with materials other than those owned by the W3C, reflects that ERCIM
+is now a host of the W3C, includes references to this specific dated version
+of the license, and removes the ambiguous grant of "use". Otherwise, this
+version is the same as the previous version and is written so as to preserve
+the Free Software Foundation's assessment of GPL compatibility and OSI's
+certification under the Open Source Definition. Please see our Copyright FAQ
+for common questions about using materials from our site, including specific
+terms and conditions for packages like libwww, Amaya, and Jigsaw. Other
+questions about this notice can be directed to site-policy@w3.org.
+ 
+
+Joseph Reagle <mailto:site-policy@w3.org 
+Last revised by Reagle $Date: 2005-07-19 12:33:09 -0400 (Tue, 19 Jul 2005) $
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the SAX software,
+for the org.xml.sax.* packages in jar file xml-apis.jar:
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+This module, both source code and documentation, is in the Public Domain,
+and comes with NO WARRANTY. See http://www.saxproject.org for further information. 
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the jar file 
+java_cup.jar - LALR Parser Generator for Java(TM).
+Source:  http://www.cs.princeton.edu/~appel/modern/java/CUP
+Used By: XSLTC component of xml-xalan/java
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+CUP Parser Generator Copyright Notice, License, and Disclaimer
+
+Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian 
+
+Permission to use, copy, modify, and distribute this software and its 
+documentation for any purpose and without fee is hereby granted, provided 
+that the above copyright notice appear in all copies and that both
+the copyright notice and this permission notice and warranty disclaimer 
+appear in supporting documentation, and that the names of the authors 
+or their employers not be used in advertising or publicity pertaining 
+to distribution of the software without specific, written prior permission. 
+
+The authors and their employers disclaim all warranties with regard to 
+this software, including all implied warranties of merchantability 
+and fitness. In no event shall the authors or their employers be liable 
+for any special, indirect or consequential damages or any damages 
+whatsoever resulting from loss of use, data or profits, whether in an action 
+of contract, negligence or other tortious action, arising out of or 
+in connection with the use or performance of this software. 
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the jar file runtime.jar - Component
+of JavaCup: LALR Parser Generator for Java(TM).
+Source:  http://www.cs.princeton.edu/~appel/modern/java/CUP
+Used By: XSLTC component of xml-xalan/java
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+CUP Parser Generator Copyright Notice, License, and Disclaimer
+(runtime.jar component)
+
+Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian 
+
+Permission to use, copy, modify, and distribute this software and its 
+documentation for any purpose and without fee is hereby granted, provided 
+that the above copyright notice appear in all copies and that both
+the copyright notice and this permission notice and warranty disclaimer 
+appear in supporting documentation, and that the names of the authors 
+or their employers not be used in advertising or publicity pertaining 
+to distribution of the software without specific, written prior permission. 
+
+The authors and their employers disclaim all warranties with regard to 
+this software, including all implied warranties of merchantability 
+and fitness. In no event shall the authors or their employers be liable 
+for any special, indirect or consequential damages or any damages 
+whatsoever resulting from loss of use, data or profits, whether in an action 
+of contract, negligence or other tortious action, arising out of or 
+in connection with the use or performance of this software. 
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the JLEX jar file 
+JLex.jar - A Lexical Analyzer Generator for Java(TM).
+Source:  http://www.cs.princeton.edu/~appel/modern/java/JLex
+Used By: XSLTC component of xml-xalan/java
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+JLEX COPYRIGHT NOTICE, LICENSE AND DISCLAIMER.
+
+Copyright 1996-2000 by Elliot Joel Berk and C. Scott Ananian 
+
+Permission to use, copy, modify, and distribute this software and its 
+documentation for any purpose and without fee is hereby granted, 
+provided that the above copyright notice appear in all copies and 
+that both the copyright notice and this permission notice and 
+warranty disclaimer appear in supporting documentation, and that the 
+name of the authors or their employers not be used in advertising or 
+publicity pertaining to distribution of the software without specific, 
+written prior permission. 
+
+The authors and their employers disclaim all warranties with regard 
+to this software, including all implied warranties of merchantability and 
+fitness. In no event shall the authors or their employers be liable for any
+special, indirect or consequential damages or any damages whatsoever resulting 
+from loss of use, data or profits, whether in an action of contract, 
+negligence or other tortious action, arising out of or in connection
+with the use or performance of this software. 
+
+Java is a trademark of Sun Microsystems, Inc. References to the Java 
+programming language in relation to JLex are not meant to imply that 
+Sun endorses this product. 
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+
+
+
+
+
+
+The following license applies to the jar file 
+stylebook-1.0-b3_xalan-2.jar - Tool for generating Xalan documentation. 
+Integrated with Xalan-Java 2 and Xerces 2.
+Source:  http://svn.apache.org/viewvc/xml/stylebook/
+Used by: Xalan-Java 2, Xalan-C++
+
+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 1999 The Apache Software Foundation.  All rights 
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:  
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Xalan", "Xerces", and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written 
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
diff --git a/scripts/scxml2plant/xalan/NOTICE.txt b/scripts/scxml2plant/xalan/NOTICE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..132f8bccd53579fb004f2b5d095588d3ff9e832a
--- /dev/null
+++ b/scripts/scxml2plant/xalan/NOTICE.txt
@@ -0,0 +1,80 @@
+   =========================================================================
+   ==  NOTICE file corresponding to section 4(d) of the Apache License,   ==
+   ==  Version 2.0, in this case for the Apache Xalan Java distribution.  ==
+   =========================================================================
+
+   Apache Xalan (Xalan XSLT processor)
+   Copyright 1999-2014 The Apache Software Foundation
+
+   Apache Xalan (Xalan serializer)
+   Copyright 1999-2012 The Apache Software Foundation
+
+   This product includes software developed at
+   The Apache Software Foundation (http://www.apache.org/).
+
+   =========================================================================
+   Portions of this software was originally based on the following:
+     - software copyright (c) 1999-2002, Lotus Development Corporation.,
+       http://www.lotus.com.
+     - software copyright (c) 2001-2002, Sun Microsystems.,
+       http://www.sun.com.
+     - software copyright (c) 2003, IBM Corporation., 
+       http://www.ibm.com.
+       
+   =========================================================================
+   The binary distribution package (ie. jars, samples and documentation) of
+   this product includes software developed by the following:
+       
+     - The Apache Software Foundation 
+         - Xerces Java - see LICENSE.txt 
+         - JAXP 1.3 APIs - see LICENSE.txt
+         - Bytecode Engineering Library - see LICENSE.txt
+         - Regular Expression - see LICENSE.txt
+       
+     - Scott Hudson, Frank Flannery, C. Scott Ananian 
+         - CUP Parser Generator runtime (javacup\runtime) - see LICENSE.txt 
+ 
+   ========================================================================= 
+   The source distribution package (ie. all source and tools required to build
+   Xalan Java) of this product includes software developed by the following:
+       
+     - The Apache Software Foundation
+         - Xerces Java - see LICENSE.txt 
+         - JAXP 1.3 APIs - see LICENSE.txt
+         - Bytecode Engineering Library - see LICENSE.txt
+         - Regular Expression - see LICENSE.txt
+         - Ant - see LICENSE.txt
+         - Stylebook doc tool - see LICENSE.txt    
+       
+     - Elliot Joel Berk and C. Scott Ananian 
+         - Lexical Analyzer Generator (JLex) - see LICENSE.txt
+
+   =========================================================================       
+   Apache Xerces Java
+   Copyright 1999-2006 The Apache Software Foundation
+
+   This product includes software developed at
+   The Apache Software Foundation (http://www.apache.org/).
+
+   Portions of Apache Xerces Java in xercesImpl.jar and xml-apis.jar
+   were originally based on the following:
+     - software copyright (c) 1999, IBM Corporation., http://www.ibm.com.
+     - software copyright (c) 1999, Sun Microsystems., http://www.sun.com.
+     - voluntary contributions made by Paul Eng on behalf of the 
+       Apache Software Foundation that were originally developed at iClick, Inc.,
+       software copyright (c) 1999.    
+
+   =========================================================================   
+   Apache xml-commons xml-apis (redistribution of xml-apis.jar)
+
+   Apache XML Commons
+   Copyright 2001-2003,2006 The Apache Software Foundation.
+
+   This product includes software developed at
+   The Apache Software Foundation (http://www.apache.org/).
+
+   Portions of this software were originally based on the following:
+     - software copyright (c) 1999, IBM Corporation., http://www.ibm.com.
+     - software copyright (c) 1999, Sun Microsystems., http://www.sun.com.
+     - software copyright (c) 2000 World Wide Web Consortium, http://www.w3.org
+
diff --git a/scripts/scxml2plant/xalan/readme.html b/scripts/scxml2plant/xalan/readme.html
new file mode 100644
index 0000000000000000000000000000000000000000..538f4286b0308b84b3c4dcfbeb6937ab2d3d56a4
--- /dev/null
+++ b/scripts/scxml2plant/xalan/readme.html
@@ -0,0 +1,39 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+-->
+
+<html>
+<head>
+<title>
+</title>
+</head>
+<body>
+<meta content="0; URL=docs/whatsnew.html" http-equiv="Refresh">
+      Redirecting to <a href="docs/whatsnew.html">What's New in Xalan_Java 2</a>
+</body>
+</html>
+
+
+
+
+            
+            
+
+
+
+
+            
+   
diff --git a/scripts/scxml2plant/xalan/serializer.jar b/scripts/scxml2plant/xalan/serializer.jar
new file mode 100644
index 0000000000000000000000000000000000000000..175126880ff58fab0922f3e0cd25ecff765989ba
Binary files /dev/null and b/scripts/scxml2plant/xalan/serializer.jar differ
diff --git a/scripts/scxml2plant/xalan/xalan.jar b/scripts/scxml2plant/xalan/xalan.jar
new file mode 100644
index 0000000000000000000000000000000000000000..5c6c6f947071ef42c8d984f13ace5884e2bc7f03
Binary files /dev/null and b/scripts/scxml2plant/xalan/xalan.jar differ
diff --git a/scripts/scxml2plant/xalan/xercesImpl.jar b/scripts/scxml2plant/xalan/xercesImpl.jar
new file mode 100644
index 0000000000000000000000000000000000000000..0aaa990f3ecadf60d28b5395dc87bbe49da0cdd7
Binary files /dev/null and b/scripts/scxml2plant/xalan/xercesImpl.jar differ
diff --git a/scripts/scxml2plant/xalan/xml-apis.jar b/scripts/scxml2plant/xalan/xml-apis.jar
new file mode 100644
index 0000000000000000000000000000000000000000..46733464fc746776c331ecc51061f3a05e662fd1
Binary files /dev/null and b/scripts/scxml2plant/xalan/xml-apis.jar differ
diff --git a/scripts/scxml2plant/xalan/xsltc.jar b/scripts/scxml2plant/xalan/xsltc.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e11dbeca0aa04a138ea1a5fb29ed331ed02191f6
Binary files /dev/null and b/scripts/scxml2plant/xalan/xsltc.jar differ
diff --git a/scripts/scxml2plant/xslt/scxml2plantuml.xslt b/scripts/scxml2plant/xslt/scxml2plantuml.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..8468b2c3f684ab52e06678ff41d747407b5751a3
--- /dev/null
+++ b/scripts/scxml2plant/xslt/scxml2plantuml.xslt
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:sc="http://www.w3.org/2005/07/scxml">
+
+  <xsl:output method="text" encoding="utf-8"/>
+  <xsl:strip-space elements="*" />
+  <xsl:template match="text()|@*"/>
+  <xsl:template match="text()|@*" mode="target"/>
+  <xsl:template match="text()|@*" mode="id"/>
+
+  <xsl:template match="/sc:scxml">
+    <xsl:text>@startuml&#xa;</xsl:text>
+    <xsl:apply-templates select="@initial"/>
+    <xsl:apply-templates select="*"/>
+    <xsl:text>@enduml&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="@initial">
+    <xsl:text>[*] --> </xsl:text>
+    <xsl:call-template name="gettargetname">
+      <xsl:with-param name="id" select="."/>
+    </xsl:call-template>
+    <xsl:text>&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:initial">
+    <xsl:apply-templates select="*"/>
+  </xsl:template>
+
+  <xsl:template match="sc:final[@id]">
+    <xsl:value-of select="concat(@id, ' : &lt;&lt;final>>&#xa;')"/>
+  </xsl:template>
+  <xsl:template match="sc:final">
+    <xsl:text>unnamed : &lt;&lt;final>>&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:state">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+
+    <xsl:apply-templates select="@initial"/>
+    <xsl:apply-templates select="*"/>
+    
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:history">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+    
+    <xsl:apply-templates select="*"/>
+
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:value-of select="concat(' : &lt;&lt;', @type, ' history>>&#xa;')"/>
+
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:parallel">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+
+    <xsl:for-each select="sc:state | sc:parallel | sc:history">
+      <xsl:apply-templates select="."/>
+      <xsl:if test="position() != last()">
+        <xsl:text>||&#xa;</xsl:text>
+      </xsl:if>
+    </xsl:for-each>
+
+    <xsl:apply-templates select="sc:transition"/>
+
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+
+  <!--get id of the state - special case for initial state-->
+  <xsl:template match="sc:state[@id] | sc:history[@id] | sc:parallel[@id] | sc:final[@id]" mode="id">
+    <xsl:value-of select="@id"/>
+  </xsl:template>
+  <xsl:template match="sc:state | sc:history | sc:parallel" mode="id">
+    <xsl:text>unnamed</xsl:text>
+  </xsl:template>
+  <xsl:template match="sc:initial" mode="id">
+    <xsl:text>[*]</xsl:text>
+  </xsl:template>
+
+  <xsl:template name="gettargetname">
+    <xsl:param name="id"/>
+    <xsl:apply-templates select="//*[@id = $id][1]" mode="id"/>
+  </xsl:template>
+
+  <!--target can be specified explicitely as some other state-->
+  <xsl:template match="sc:transition[@target]" mode="target">
+    <xsl:call-template name="gettargetname">
+      <xsl:with-param name="id" select="@target"/>
+    </xsl:call-template>
+  </xsl:template>
+  <!--or self-transiton when @target not specified-->
+  <xsl:template match="sc:transition" mode="target">
+    <xsl:apply-templates select=".." mode="id"/>
+  </xsl:template>
+
+  <!--if 'cond' attribute exists it shall be sourrounded with brackets-->
+  <xsl:template match="sc:transition/@cond">
+    <xsl:value-of select="concat('[', ., ']')"/>
+  </xsl:template>
+
+  <xsl:template match="sc:transition">
+    <xsl:apply-templates select=".." mode="id"/>
+    <xsl:text> --> </xsl:text>
+    <xsl:apply-templates select="." mode="target"/>
+    <xsl:if test="@event or @cond">
+      <xsl:text> : </xsl:text>
+    </xsl:if>
+    <xsl:value-of select="@event"/>
+    <xsl:apply-templates select="@cond"/>
+    <xsl:text>&#xa;</xsl:text>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt b/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..f9050a05ddb1f253be2cea26a355830028cbe939
--- /dev/null
+++ b/scripts/scxml2plant/xslt/scxml2plantuml_finaldot.xslt
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:sc="http://www.w3.org/2005/07/scxml">
+
+  <xsl:output method="text" encoding="utf-8"/>
+  <xsl:strip-space elements="*" />
+  <xsl:template match="text()|@*"/>
+  <xsl:template match="text()|@*" mode="target"/>
+  <xsl:template match="text()|@*" mode="id"/>
+
+  <xsl:template match="/sc:scxml">
+    <xsl:text>@startuml&#xa;</xsl:text>
+    <xsl:apply-templates select="@initial"/>
+    <xsl:apply-templates select="*"/>
+    <xsl:text>@enduml&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="@initial">
+    <xsl:text>[*] --> </xsl:text>
+    <xsl:call-template name="gettargetname">
+      <xsl:with-param name="id" select="."/>
+    </xsl:call-template>
+    <xsl:text>&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:initial">
+    <xsl:apply-templates select="*"/>
+  </xsl:template>
+
+  <xsl:template match="sc:state">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+
+    <xsl:apply-templates select="@initial"/>
+    <xsl:apply-templates select="*"/>
+    
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:history">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+    
+    <xsl:apply-templates select="*"/>
+
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:value-of select="concat(' : &lt;&lt;', @type, ' history>>&#xa;')"/>
+
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+  <xsl:template match="sc:parallel">
+    <xsl:text>state </xsl:text>
+    <xsl:apply-templates select="." mode="id"/>
+    <xsl:text> {&#xa;</xsl:text>
+
+    <xsl:for-each select="sc:state | sc:parallel | sc:history">
+      <xsl:apply-templates select="."/>
+      <xsl:if test="position() != last()">
+        <xsl:text>||&#xa;</xsl:text>
+      </xsl:if>
+    </xsl:for-each>
+
+    <xsl:apply-templates select="sc:transition"/>
+
+    <xsl:text>}&#xa;</xsl:text>
+  </xsl:template>
+
+
+  <!--get id of the state - special case for initial state-->
+  <xsl:template match="sc:state[@id] | sc:history[@id] | sc:parallel[@id]" mode="id">
+    <xsl:value-of select="@id"/>
+  </xsl:template>
+  <xsl:template match="sc:state | sc:history | sc:parallel" mode="id">
+    <xsl:text>unnamed</xsl:text>
+  </xsl:template>
+  <xsl:template match="sc:initial | sc:final" mode="id">
+    <xsl:text>[*]</xsl:text>
+  </xsl:template>
+
+  <xsl:template name="gettargetname">
+    <xsl:param name="id"/>
+    <xsl:apply-templates select="//*[@id = $id][1]" mode="id"/>
+  </xsl:template>
+
+  <!--target can be specified explicitely as some other state-->
+  <xsl:template match="sc:transition[@target]" mode="target">
+    <xsl:call-template name="gettargetname">
+      <xsl:with-param name="id" select="@target"/>
+    </xsl:call-template>
+  </xsl:template>
+  <!--or self-transiton when @target not specified-->
+  <xsl:template match="sc:transition" mode="target">
+    <xsl:apply-templates select=".." mode="id"/>
+  </xsl:template>
+
+  <!--if 'cond' attribute exists it shall be sourrounded with brackets-->
+  <xsl:template match="sc:transition/@cond">
+    <xsl:value-of select="concat('[', ., ']')"/>
+  </xsl:template>
+
+  <xsl:template match="sc:transition">
+    <xsl:apply-templates select=".." mode="id"/>
+    <xsl:text> --> </xsl:text>
+    <xsl:apply-templates select="." mode="target"/>
+    <xsl:if test="@event or @cond">
+      <xsl:text> : </xsl:text>
+    </xsl:if>
+    <xsl:value-of select="@event"/>
+    <xsl:apply-templates select="@cond"/>
+    <xsl:text>&#xa;</xsl:text>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/src/entrypoints/anakin/anakin-accel.cpp b/src/entrypoints/anakin/anakin-accel.cpp
index 01bc91ec214a122d97d17fc079d7455cee7aa3bd..9d22fa1e89177b093c2aa30dad997e8e5e53e191 100644
--- a/src/entrypoints/anakin/anakin-accel.cpp
+++ b/src/entrypoints/anakin/anakin-accel.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla
+ * Authors: Alain Carlucci, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/anakin-demo-board.cpp b/src/entrypoints/anakin/anakin-demo-board.cpp
index 2129e7dccd9e84a4ab0a897afa5fc731fff7456e..a51bc42a4f8e26ba524224d0399ae31054a08e44 100644
--- a/src/entrypoints/anakin/anakin-demo-board.cpp
+++ b/src/entrypoints/anakin/anakin-demo-board.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/anakin-test-canbus.cpp b/src/entrypoints/anakin/anakin-test-canbus.cpp
index 37c67c435d82ffde7102eab25fd2ca2c65d06aa2..63f6884c626a04a731e6305e66f2d2835eb7ff9b 100644
--- a/src/entrypoints/anakin/anakin-test-canbus.cpp
+++ b/src/entrypoints/anakin/anakin-test-canbus.cpp
@@ -1,7 +1,5 @@
-/* Canbus Test ROM
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/anakin-test-dma.cpp b/src/entrypoints/anakin/anakin-test-dma.cpp
index b4d8e5272156c0cf871b7d3810a0de21d42fa425..03815abc7f272b3fea0c471fe82b00508d6650a4 100644
--- a/src/entrypoints/anakin/anakin-test-dma.cpp
+++ b/src/entrypoints/anakin/anakin-test-dma.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/anakin-test-suite.cpp b/src/entrypoints/anakin/anakin-test-suite.cpp
index 5de88b6e37b95f61518121d84e22edcf790c0a5e..3499646a6d6eb3706432ce5ddd517343fa0dcd60 100644
--- a/src/entrypoints/anakin/anakin-test-suite.cpp
+++ b/src/entrypoints/anakin/anakin-test-suite.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla
+ * Authors: Alain Carlucci, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/canbus-test.cpp b/src/entrypoints/anakin/canbus-test.cpp
index 3e8bfc25ad963545b02ee32db66d307546eac31f..a52cde1fa5b67700250305cd1e1ac9669da609ba 100644
--- a/src/entrypoints/anakin/canbus-test.cpp
+++ b/src/entrypoints/anakin/canbus-test.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla
+ * Authors: Alain Carlucci, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/dma-lowlevel-test.cpp b/src/entrypoints/anakin/dma-lowlevel-test.cpp
index caf412e38b260189b6b4b6aaf57a6d8de2601536..7e50325fc32eb15d588d1c11bd32e4c3cfb65ea3 100644
--- a/src/entrypoints/anakin/dma-lowlevel-test.cpp
+++ b/src/entrypoints/anakin/dma-lowlevel-test.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/test-busfault.cpp b/src/entrypoints/anakin/test-busfault.cpp
index ee8ce6062380819afaf6c9e7820ab4482d3056ea..db743d9c73afd3b1234f576965bb78167a3b7af3 100644
--- a/src/entrypoints/anakin/test-busfault.cpp
+++ b/src/entrypoints/anakin/test-busfault.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/test-scheduler.cpp b/src/entrypoints/anakin/test-scheduler.cpp
index b072c2f650af919d4f9ebebd529bfcdfd6a4e488..24e828dee7b83fd608d55786c143502ae7128ad3 100644
--- a/src/entrypoints/anakin/test-scheduler.cpp
+++ b/src/entrypoints/anakin/test-scheduler.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Federico Terraneo
+ * Authors: Matteo Piazzolla, Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/test_ethernet.cpp b/src/entrypoints/anakin/test_ethernet.cpp
index d8d464e1373be3994afbd3d200066ac529f3856e..7b57358d03c086907859076082a52301298bfeb1 100644
--- a/src/entrypoints/anakin/test_ethernet.cpp
+++ b/src/entrypoints/anakin/test_ethernet.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/anakin/test_i2c.cpp b/src/entrypoints/anakin/test_i2c.cpp
index 434de5dc3e0b163e09ea7c42cb6c2ef1674bf33a..dc9dcdccdee9073a61746f2a8c3879c488856efc 100644
--- a/src/entrypoints/anakin/test_i2c.cpp
+++ b/src/entrypoints/anakin/test_i2c.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/bmx160-calibration-entry.cpp b/src/entrypoints/bmx160-calibration-entry.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e0378a4a7694cd31b1b8eba8d82c93cc848bddd5
--- /dev/null
+++ b/src/entrypoints/bmx160-calibration-entry.cpp
@@ -0,0 +1,461 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <drivers/HardwareTimer.h>
+#include <drivers/interrupt/external_interrupts.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/BMX160/BMX160WithCorrection.h>
+#include <sensors/calibration/SoftIronCalibration.h>
+
+#include <fstream>
+#include <iostream>
+
+constexpr const char* CORRECTION_PARAMETER_FILE = "/sd/bmx160_params.csv";
+constexpr const char* MAG_CALIBRATION_DATA_FILE =
+    "/sd/bmx160_mag_calibration_data.csv";
+constexpr int ACC_CALIBRATION_SAMPLES        = 200;
+constexpr int ACC_CALIBRATION_SLEEP_TIME     = 25;  // [us]
+constexpr int ACC_CALIBRATION_N_ORIENTATIONS = 6;
+
+constexpr int MAG_CALIBRATION_SAMPLES  = 500;
+constexpr int MAG_CALIBRATION_DURAITON = 60;  // [s]
+constexpr int MAG_CALIBRATION_SLEEP_TIME =
+    MAG_CALIBRATION_DURAITON * 1000 / MAG_CALIBRATION_SAMPLES;  // [us]
+
+BMX160* bmx160;
+
+/**
+ * Orientations for accelerometer calibration
+ *
+ * The BMX160 reference frame view facing the death stack x is:
+ *          z   x
+ *          ^   ^
+ *          |  /
+ *          | /
+ *   y <----/
+ *
+ * Each AxisOrthoOrientation values indicates how to change x and y
+ */
+AxisOrthoOrientation orientations[ACC_CALIBRATION_N_ORIENTATIONS]{
+    {Direction::POSITIVE_X, Direction::POSITIVE_Y},  // Z up
+    {Direction::POSITIVE_Y, Direction::POSITIVE_Z},  // X up
+    {Direction::POSITIVE_Z, Direction::POSITIVE_X},  // Y up
+    {Direction::POSITIVE_Y, Direction::POSITIVE_X},  // Z down
+    {Direction::POSITIVE_Z, Direction::POSITIVE_Y},  // X down
+    {Direction::POSITIVE_X, Direction::POSITIVE_Z},  // Y down
+};
+
+constexpr const char* testHumanFriendlyDirection[]{
+    "Z up", "X up", "Y up", "Z down", "X down", "Y down",
+};
+
+// BMX160 Watermark interrupt
+void __attribute__((used)) EXTI5_IRQHandlerImpl()
+{
+    if (bmx160)
+    {
+        bmx160->IRQupdateTimestamp(TimestampTimer::getTimestamp());
+    }
+}
+
+int menu();
+bool askToContinue();
+void waitForInput();
+
+BMX160CorrectionParameters calibrateAccelerometer(
+    BMX160CorrectionParameters correctionParameters);
+BMX160CorrectionParameters calibrateMagnetometer(
+    BMX160CorrectionParameters correctionParameters);
+BMX160CorrectionParameters changeMinGyroCorrectionSamples(
+    BMX160CorrectionParameters correctionParameters);
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+
+    // Enable interrupt from BMX pin
+    enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE);
+
+    // Greet the user
+    printf("\nWelcome to the calibration procedure!\n");
+
+    // Read the current correciton parameters
+    BMX160CorrectionParameters correctionParameters;
+    correctionParameters =
+        BMX160WithCorrection::readCorrectionParametersFromFile(
+            CORRECTION_PARAMETER_FILE);
+
+    // Make the user choose what to do
+    switch (menu())
+    {
+        case 1:
+            correctionParameters = calibrateAccelerometer(correctionParameters);
+            break;
+        case 2:
+            correctionParameters = calibrateMagnetometer(correctionParameters);
+            break;
+        case 3:
+            correctionParameters =
+                changeMinGyroCorrectionSamples(correctionParameters);
+            break;
+
+        default:
+            break;
+    }
+
+    // Save the correction parameters in the file
+    {
+        std::ofstream correctionParametersFile(CORRECTION_PARAMETER_FILE);
+        correctionParametersFile << BMX160CorrectionParameters::header()
+                                 << std::endl;
+        correctionParameters.print(correctionParametersFile);
+    }
+
+    return 0;
+}
+
+int menu()
+{
+    int choice;
+
+    printf("\nWhat do you want to do?\n");
+    printf("1. Calibrate accelerometer\n");
+    printf("2. Calibrate magnetometer\n");
+    printf("3. Set minimum gyroscope samples for calibration\n");
+    printf("\n>> ");
+    scanf("%d", &choice);
+
+    return choice;
+}
+
+bool askToContinue()
+{
+    char choice;
+
+    printf("Press [c] to continue, otherwise stop...");
+    scanf("%s", &choice);
+
+    return choice == 'c';
+}
+
+void waitForInput()
+{
+    printf("Press [c] to continue...");
+    while (getchar() != 'c')
+        ;
+}
+
+/**
+ * The accelerometer calibration consists in finding the bias and coefficient
+ * against target 9.8m/s^2 on the three axis positioning the sensor in all the 6
+ * positions (+x, -x, +y, -y, +z, -z).
+ */
+BMX160CorrectionParameters calibrateAccelerometer(
+    BMX160CorrectionParameters correctionParameters)
+{
+    Matrix<float, 3, 2> m;
+    auto* calibrationModel =
+        new SixParameterCalibration<AccelerometerData,
+                                    ACC_CALIBRATION_SAMPLES *
+                                        ACC_CALIBRATION_N_ORIENTATIONS>;
+
+    SPIBus bus(SPI1);
+
+    BMX160Config bmx_config;
+    bmx_config.fifo_mode      = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifo_watermark = 100;
+    bmx_config.fifo_int       = BMX160Config::FifoInterruptPin::PIN_INT1;
+
+    bmx_config.temp_divider = 0;
+
+    bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16;
+    bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000;
+
+    bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50;
+
+    bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config);
+
+    TRACE("Initializing BMX160...\n");
+
+    if (!bmx160->init())
+    {
+        TRACE("Init failed! (code: %d)\n", bmx160->getLastError());
+        return correctionParameters;
+    }
+
+    TRACE("Performing self-test...\n");
+
+    if (!bmx160->selfTest())
+    {
+        TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError());
+        return correctionParameters;
+    }
+
+    TRACE("Initialization and self-test completed!\n");
+
+    Vector3f avgAccel, vec;
+    int fifoAccSampleCount;
+    uint64_t accelTimestamp = 0;
+
+    // Set reference vector, 1g on third axis
+    calibrationModel->setReferenceVector({0, 0, 9.8});
+
+    // Show the user the current correction values
+    printf("\nCurrent bias vector\n");
+    printf("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n",
+           correctionParameters.accelParams(0, 1),
+           correctionParameters.accelParams(1, 1),
+           correctionParameters.accelParams(2, 1));
+    printf("Matrix to be multiplied to the input vector\n");
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n",
+           correctionParameters.accelParams(0, 0), 0.f, 0.f);
+    printf("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f,
+           correctionParameters.accelParams(1, 0), 0.f);
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f,
+           correctionParameters.accelParams(2, 0));
+
+    if (!askToContinue())
+    {
+        return correctionParameters;
+    }
+
+    printf(
+        "Please note that the BMX axis, viewed facing the death stack x, "
+        "are as follows:\n");
+    printf("         z   x\n");
+    printf("         ^   ^\n");
+    printf("         |  /\n");
+    printf("         | /\n");
+    printf("  y <----/\n");
+
+    for (unsigned i = 0; i < ACC_CALIBRATION_N_ORIENTATIONS; i++)
+    {
+        printf(
+            "Step n.%d/%d, please rotate the death stack x so that the "
+            "sensor "
+            "is %s\n",
+            i + 1, ACC_CALIBRATION_N_ORIENTATIONS,
+            testHumanFriendlyDirection[i]);
+
+        waitForInput();
+
+        printf("Reding data and feeding the model...\n");
+
+        // Sample the sensor and feed the data to the calibration model
+        int count = 0;
+        while (count < ACC_CALIBRATION_SAMPLES)
+        {
+            bmx160->sample();
+            avgAccel = {0, 0, 0};
+
+            // Read all the data contained in the fifo
+            uint8_t size       = bmx160->getLastFifoSize();
+            fifoAccSampleCount = 0;
+            for (int i = 0; i < size && count < ACC_CALIBRATION_SAMPLES; i++)
+            {
+                BMX160Data fifoElement = bmx160->getFifoElement(i);
+
+                // Read acceleration data
+                if (fifoElement.accel_timestamp > accelTimestamp)
+                {
+                    static_cast<AccelerometerData>(fifoElement) >> vec;
+                    avgAccel += vec;
+
+                    accelTimestamp = fifoElement.accel_timestamp;
+                    fifoAccSampleCount++;
+                }
+            }
+            avgAccel /= fifoAccSampleCount;
+
+            AccelerometerData data;
+            data << avgAccel;
+
+            miosix::Thread::sleep(ACC_CALIBRATION_SLEEP_TIME);
+
+            calibrationModel->feed(data, orientations[i]);
+            count++;
+        }
+
+        printf("Orientation calibration completed\n");
+    }
+
+    printf("Computing the result....");
+    SixParameterCorrector<AccelerometerData> corrector =
+        calibrationModel->computeResult();
+
+    corrector >> m;
+    corrector >> correctionParameters.accelParams;
+
+    printf("Bias vector\n");
+    printf("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", m(0, 1), m(1, 1),
+           m(2, 1));
+    printf("Matrix to be multiplied to the input vector\n");
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(0, 0), 0.f, 0.f);
+    printf("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, m(1, 0), 0.f);
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f, m(2, 0));
+
+    return correctionParameters;
+}
+
+BMX160CorrectionParameters calibrateMagnetometer(
+    BMX160CorrectionParameters correctionParameters)
+{
+    Matrix<float, 3, 2> m;
+    auto* calibrationModel = new SoftIronCalibration<MAG_CALIBRATION_SAMPLES>;
+    Vector3f avgMag{0, 0, 0}, vec;
+
+    SPIBus bus(SPI1);
+
+    BMX160Config bmx_config;
+    bmx_config.fifo_mode = BMX160Config::FifoMode::DISABLED;
+
+    bmx_config.temp_divider = 0;
+
+    bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16;
+    bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000;
+
+    bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50;
+
+    bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config);
+
+    TRACE("Initializing BMX160...\n");
+
+    if (!bmx160->init())
+    {
+        TRACE("Init failed! (code: %d)\n", bmx160->getLastError());
+        return correctionParameters;
+    }
+
+    TRACE("Performing self-test...\n");
+
+    if (!bmx160->selfTest())
+    {
+        TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError());
+        return correctionParameters;
+    }
+
+    TRACE("Initialization and self-test completed!\n");
+
+    // Show the user the current correction values
+    printf("Current bias vector\n");
+    printf("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n",
+           correctionParameters.magnetoParams(0, 1),
+           correctionParameters.magnetoParams(1, 1),
+           correctionParameters.magnetoParams(2, 1));
+    printf("Matrix to be multiplied to the input vector\n");
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n",
+           correctionParameters.magnetoParams(0, 0), 0.f, 0.f);
+    printf("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f,
+           correctionParameters.magnetoParams(1, 0), 0.f);
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f,
+           correctionParameters.magnetoParams(2, 0));
+
+    printf("Now I will calibrate the magnetometer\n");
+    printf(
+        "Please, after starting the calibration, rotate the gyroscope in "
+        "the "
+        "most different directions.\n");
+    printf("The calibration will run for %d seconds\n",
+           MAG_CALIBRATION_DURAITON);
+
+    if (!askToContinue())
+    {
+        return correctionParameters;
+    }
+
+    // Sample the sensor and feed the data to the calibration model
+    int count = 0;
+    while (count < MAG_CALIBRATION_SAMPLES)
+    {
+        bmx160->sample();
+
+        BMX160Data data = bmx160->getLastSample();
+        calibrationModel->feed(data);
+        count++;
+
+        miosix::Thread::sleep(MAG_CALIBRATION_SLEEP_TIME);
+    }
+
+    printf("Computing the result....");
+    auto corrector = calibrationModel->computeResult();
+
+    // Save the calibration data in the sd card
+    // Save the correction parameters in the file
+    {
+        std::ofstream magnetometerCalibrationDataFile(
+            MAG_CALIBRATION_DATA_FILE);
+
+        auto samples        = calibrationModel->getSamples();
+        int numberOfSamples = samples.rows();
+
+        for (int i = 0; i < numberOfSamples; i++)
+        {
+            auto row = samples.row(i);
+            for (int j = 0; j < row.cols(); j++)
+            {
+                magnetometerCalibrationDataFile << samples.row(i)(j) << ",";
+            }
+            magnetometerCalibrationDataFile << "\n";
+        }
+    }
+
+    corrector >> m;
+    corrector >> correctionParameters.magnetoParams;
+
+    printf("b: the bias vector\n");
+    printf("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", m(0, 1), m(1, 1),
+           m(2, 1));
+    printf("M: the matrix to be multiplied to the input vector\n");
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(0, 0), 0.f, 0.f);
+    printf("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, m(1, 0), 0.f);
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f, m(2, 0));
+
+    return correctionParameters;
+}
+
+BMX160CorrectionParameters changeMinGyroCorrectionSamples(
+    BMX160CorrectionParameters correctionParameters)
+{
+    // Show the user the current parameter
+    printf(
+        "The current minimun number of gyroscope samples for calibration "
+        "is "
+        "%d\n",
+        correctionParameters.minGyroSamplesForCalibration);
+
+    if (askToContinue())
+    {
+        printf("Insert the new value: ");
+        scanf("%d", &correctionParameters.minGyroSamplesForCalibration);
+    }
+
+    return correctionParameters;
+}
\ No newline at end of file
diff --git a/src/entrypoints/config-dsgamma.cpp b/src/entrypoints/config-dsgamma.cpp
index 1df4eb831c7ca65eb596a22f62e168104f759787..389a91ad12187b2cdad0334254c267503787da0b 100644
--- a/src/entrypoints/config-dsgamma.cpp
+++ b/src/entrypoints/config-dsgamma.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -22,6 +22,7 @@
 
 #include <stdio.h>
 #include <string.h>
+
 #include "miosix.h"
 
 using namespace std;
@@ -61,10 +62,10 @@ int main()
         newConf.dest_addr[0]  = 126;
         newConf.dest_addr[1]  = 126;
         newConf.dest_addr[2]  = 126;
-        newConf.lora_sf     = SF6;   // SF6
-        newConf.lora_power      = dbm20;  //+20dbm
+        newConf.lora_sf       = SF6;     // SF6
+        newConf.lora_power    = dbm20;   //+20dbm
         newConf.handshake     = false;   // No handshake
-        newConf.baudrate      = B_9600;   // 9600 baud
+        newConf.baudrate      = B_9600;  // 9600 baud
 
         // printf("LOCAL ADDRESS (3 bytes, 0-127 each):\n");
         // scanf("%d %d %d", &newConf.local_addr[0], &newConf.local_addr[1],
@@ -82,10 +83,12 @@ int main()
         // scanf("%d", &newConf.baudrate);
 
         bool configStatus = gamma.configure(newConf);
-        if(configStatus == true){
+        if (configStatus == true)
+        {
             printf("New configuration set!\n");
         }
-        else{
+        else
+        {
             printf("Failed to set new configuration!\n");
         }
         Thread::sleep(500);
@@ -94,7 +97,6 @@ int main()
     return 0;
 }
 
-
 /*
  * Waits for the discovery user button to be pressed (blocking).
  */
diff --git a/src/entrypoints/examples/example-fsm.cpp b/src/entrypoints/examples/example-fsm.cpp
index 43b4e48695299f83592c1539b9fdd3884e51823f..8e1ee5399247bb2808e77f18e0ebcae49c576387 100644
--- a/src/entrypoints/examples/example-fsm.cpp
+++ b/src/entrypoints/examples/example-fsm.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/entrypoints/examples/example-fsm.h b/src/entrypoints/examples/example-fsm.h
index d9faaf3d887ffb1e03bf79c649a97e7ae0b41a4c..a30ec5433614af04eaf4764e8a5ddf20dc51fead 100644
--- a/src/entrypoints/examples/example-fsm.h
+++ b/src/entrypoints/examples/example-fsm.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-timer.cpp b/src/entrypoints/examples/mxgui-helloworld.cpp
similarity index 73%
rename from src/tests/drivers/test-timer.cpp
rename to src/entrypoints/examples/mxgui-helloworld.cpp
index 5ef955e78440eba187000bb61ad56d60a4b22de8..0595df2aa0b8dcc1bd735bc470d56f101990e88a 100644
--- a/src/tests/drivers/test-timer.cpp
+++ b/src/entrypoints/examples/mxgui-helloworld.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -21,24 +20,24 @@
  * THE SOFTWARE.
  */
 
-#include <miosix.h>
 #include <cstdio>
-#include "drivers/HardwareTimer.h"
+#include <miosix.h>
 
-using miosix::Thread;
+#include <mxgui/entry.h>
+#include <mxgui/display.h>
+
+using namespace mxgui;
+using namespace miosix;
 
 int main()
 {
-    HardwareTimer<uint32_t, 2>& timer2 = HardwareTimer<uint32_t, 2>::instance();
-
-    timer2.start();
-
-    while (true)
     {
-        uint32_t tick = timer2.tick();
-        printf("%lu\t\t(%.3f)\n", tick, timer2.toMilliSeconds(tick));
-
-        usleep(100000);
+		DrawingContext dc(DisplayManager::instance().getDisplay());
+		dc.write(Point(0,0),"Hello world");
+	}
+    for(;;)
+    {
+        printf("Hello world! Look at the screen!\n");
+        Thread::sleep(1000);
     }
-    return 0;
 }
\ No newline at end of file
diff --git a/src/entrypoints/kernel-testsuite.cpp b/src/entrypoints/kernel-testsuite.cpp
index ce71b9421b1a6a3e247a195cfecdd4f3926f9846..f6751fe45d81d3679d37a338a9fc2826ea424145 100644
--- a/src/entrypoints/kernel-testsuite.cpp
+++ b/src/entrypoints/kernel-testsuite.cpp
@@ -1,34 +1,29 @@
-/***************************************************************************
- *   Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
-
-/************************************************************************
-* Part of the Miosix Embedded OS. This is the testsuite used during Miosix
-* development to check for regressions.
-*************************************************************************/
+/* Copyright (c) 2008-2013 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+/* Part of the Miosix Embedded OS. This is the testsuite used during Miosix
+ * development to check for regressions.
+*/
 
 #include <cstdio>
 #include <cstdlib>
@@ -190,6 +185,7 @@ int main()
         }
         //For testing mpu
         unsigned int *m;
+        (void)m; // Disable unused warning
         switch(c)
         {
             case 't':
@@ -347,6 +343,7 @@ int main()
             case 's':
                 iprintf("Shutting down\n");
                 shutdown();
+                break;
             default:
                 iprintf("Unrecognized option\n");
         }
@@ -609,6 +606,7 @@ static volatile bool t1_v1;
 
 static void t1_p1(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -701,6 +699,8 @@ static Thread *t2_p_v1;
 
 static void t2_p1(void *argv)
 {
+    (void)argv;
+
     //This is to fix a race condition: the immediately after the thread
     //creation a yield occurs, t2_p_v1 is not yet assigned so the check fails
     Thread::sleep(5);
@@ -718,6 +718,7 @@ static void t2_p1(void *argv)
 
 static void t2_p2(void *argv)
 {
+    (void)argv;
     while(Thread::testTerminate()==false) t2_v1=true;
 }
 
@@ -782,6 +783,7 @@ also tests creation of multiple instances of the same thread
 
 static void t3_p1(void *argv)
 {
+    (void)argv;
     const int SLEEP_TIME=100;
     for(;;)
     {
@@ -799,6 +801,7 @@ static volatile bool t3_deleted;//Set when an instance of t3_p2 is deleted
 
 static void t3_p2(void *argv)
 {
+    (void)argv;
     const int SLEEP_TIME=15;
     for(;;)
     {
@@ -893,6 +896,7 @@ static volatile bool t4_v1;
 
 static void t4_p1(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -1035,6 +1039,7 @@ static volatile bool t5_v2;//False=testing Thread::wait() else Thread::IRQwait()
 
 static void t5_p1(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -1153,6 +1158,7 @@ static FastMutex t6_m1a;
 
 static void t6_p1(void *argv)
 {
+    (void)argv;
     t6_m1.lock();
     seq.add('1');
     Thread::sleep(100);
@@ -1165,6 +1171,7 @@ static void t6_p1(void *argv)
 
 static void t6_p2(void *argv)
 {
+    (void)argv;
     t6_m1.lock();
     seq.add('2');
     Thread::sleep(100);
@@ -1176,6 +1183,7 @@ static void t6_p2(void *argv)
 
 static void t6_p3(void *argv)
 {
+    (void)argv;
     t6_m1.lock();
     seq.add('3');
     Thread::sleep(100);
@@ -1187,6 +1195,7 @@ static void t6_p3(void *argv)
 
 static void t6_p4(void *argv)
 {
+    (void)argv;
     t6_m1.lock();
     for(;;)
     {
@@ -1205,6 +1214,7 @@ static void t6_p4(void *argv)
 
 static void t6_p1a(void *argv)
 {
+    (void)argv;
     t6_m1a.lock();
     seq.add('1');
     Thread::sleep(100);
@@ -1213,6 +1223,7 @@ static void t6_p1a(void *argv)
 
 static void t6_p2a(void *argv)
 {
+    (void)argv;
     t6_m1a.lock();
     seq.add('2');
     Thread::sleep(100);
@@ -1221,6 +1232,7 @@ static void t6_p2a(void *argv)
 
 static void t6_p3a(void *argv)
 {
+    (void)argv;
     t6_m1a.lock();
     seq.add('3');
     Thread::sleep(100);
@@ -1229,6 +1241,7 @@ static void t6_p3a(void *argv)
 
 static void t6_p4a(void *argv)
 {
+    (void)argv;
     t6_m1a.lock();
     for(;;)
     {
@@ -1251,6 +1264,7 @@ static FastMutex t6_m2a;
 
 static void t6_p5(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -1266,6 +1280,7 @@ static void t6_p5(void *argv)
 
 static void t6_p5a(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -1305,6 +1320,7 @@ static FastMutex t6_m5a(FastMutex::RECURSIVE);
 
 static void *t6_p7(void *argv)
 {
+    (void)argv;
     if(t6_m5.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked
     t6_m5.unlock();
     return reinterpret_cast<void*>(0); // 0 = unlocked
@@ -1320,6 +1336,7 @@ bool checkIft6_m5IsLocked()
 
 static void *t6_p7a(void *argv)
 {
+    (void)argv;
     if(t6_m5a.tryLock()==false) return reinterpret_cast<void*>(1); // 1 = locked
     t6_m5a.unlock();
     return reinterpret_cast<void*>(0); // 0 = unlocked
@@ -1741,6 +1758,7 @@ static Queue<char,4> t8_q2;
 
 static void t8_p1(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
@@ -1985,6 +2003,7 @@ void t10_f2()
 
 void t10_p1(void *argv)
 {
+    (void)argv;
     t10_f2();
     fail("Exception not thrown");
 }
@@ -2011,6 +2030,7 @@ static volatile unsigned int t11_v1;//Free heap after spawning thread
 
 void t11_p1(void *argv)
 {
+    (void)argv;
     if(MemoryProfiling::getStackSize()!=STACK_SMALL) fail("getStackSize (2)");
     //Check that getCurrentFreeHeap returns the same value from different
     //threads if no heap allocation happened in between
@@ -2032,7 +2052,8 @@ void test_11()
         fail("getAbsoluteFreeStack (1)");
     if(MemoryProfiling::getAbsoluteFreeStack()>curFreeStack-4)
         fail("getAbsoluteFreeStack (2)");
-        unsigned int heapSize=MemoryProfiling::getHeapSize();
+    
+    unsigned int heapSize=MemoryProfiling::getHeapSize();
     if(MemoryProfiling::getCurrentFreeHeap()>heapSize)
         fail("getCurrentFreeHeap");
     if(MemoryProfiling::getAbsoluteFreeHeap()>heapSize)
@@ -2057,12 +2078,14 @@ Mutex t12_m2;
 
 void t12_p1(void *argv)
 {
+    (void)argv;
     Lock<Mutex> l1(t12_m1);
     Lock<Mutex> l2(t12_m2);
 }
 
 void t12_p2(void *argv)
 {
+    (void)argv;
     Lock<Mutex> l(t12_m1);
 }
 
@@ -2265,6 +2288,7 @@ static Mutex t15_m1;
 
 void t15_p1(void *argv)
 {
+    (void)argv;
     for(int i=0;i<10;i++)
     {
         Lock<Mutex> l(t15_m1);
@@ -2276,6 +2300,7 @@ void t15_p1(void *argv)
 
 void t15_p2(void *argv)
 {
+    (void)argv;
     for(int i=0;i<10;i++)
     {
         Lock<Mutex> l(t15_m1);
@@ -2395,6 +2420,7 @@ pthread_cond_t t16_c1=PTHREAD_COND_INITIALIZER;
 
 void *t16_p3(void *argv)
 {
+    (void)argv;
     Thread::sleep(30);
     if(pthread_mutex_trylock(&t16_m1)!=0)
         fail("cond_wait did not release mutex"); //<---
@@ -2408,6 +2434,7 @@ pthread_cond_t t16_c2;
 
 void *t16_p4(void *argv)
 {
+    (void)argv;
     Thread::sleep(30);
     if(pthread_mutex_trylock(&t16_m1)!=0)
         fail("cond_wait did not release mutex (2)"); //<---
@@ -2791,6 +2818,7 @@ static void be()
 
 static void t19_p1(void *argv)
 {
+    (void)argv;
     Thread::sleep(50);
     {
         FastInterruptDisableLock dLock;
@@ -3232,6 +3260,7 @@ struct t22_s1
 
 static void t22_t2(void *argv)
 {
+    (void)argv;
     while(Thread::testTerminate()==false)
     {
         t22_v5=true;
@@ -3958,6 +3987,7 @@ static volatile bool fs_1_error;
 
 static void fs_t1_p1(void *argv)
 {
+    (void)argv;
     FILE *f;
     if((f=fopen("/sd/testdir/file_1.txt","w"))==NULL)
     {
@@ -3990,6 +4020,7 @@ static void fs_t1_p1(void *argv)
 
 static void fs_t1_p2(void *argv)
 {
+    (void)argv;
     FILE *f;
     if((f=fopen("/sd/testdir/file_2.txt","w"))==NULL)
     {
@@ -4022,6 +4053,7 @@ static void fs_t1_p2(void *argv)
 
 static void fs_t1_p3(void *argv)
 {
+    (void)argv;
     FILE *f;
     if((f=fopen("/sd/testdir/file_3.txt","w"))==NULL)
     {
@@ -4558,6 +4590,7 @@ context switch speed
 
 static void b2_p1(void *argv)
 {
+    (void)argv;
     for(;;)
     {
         if(Thread::testTerminate()) break;
diff --git a/src/entrypoints/sdcard-benchmark.cpp b/src/entrypoints/sdcard-benchmark.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..57b745d233b209cb7793932ba96a2ae9f807ef30
--- /dev/null
+++ b/src/entrypoints/sdcard-benchmark.cpp
@@ -0,0 +1,141 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+// #include "drivers/HardwareTimer.h"
+#include <array>
+// #include <chrono>
+#include <cstdio>
+#include <ctime>
+#include <iostream>
+// #include <thread>
+#include <vector>
+#include "math/Stats.h"
+#include <miosix.h>
+
+using namespace miosix;
+using namespace std;
+
+const unsigned int NUM_WRITES = 5000;
+
+vector<unsigned int> BUF_SIZES{128,  256,      512,       1024,      2048,
+                               4096, 1024 * 8};
+
+// Fills a buffer with random bytes
+void rndFill(uint8_t* buf, unsigned int size)
+{
+    for (unsigned int i = 0; i < size; i++)
+    {
+        *buf = rand() % 256;
+        ++buf;
+    }
+}
+
+bool benchmark(unsigned int buf_size, array<float, NUM_WRITES>& results)
+{
+    bool success = false;
+
+    uint8_t* buf = new uint8_t[buf_size];
+
+    FILE* f = fopen("/sd/buf.dat", "w");
+
+    if (f == NULL)
+    {
+        printf("Error opening file!\n");
+        goto clean;
+    }
+
+    for (unsigned int i = 0; i < NUM_WRITES; i++)
+    {
+        rndFill(buf, buf_size);
+        long long t = getTick();
+        int w       = fwrite(buf, 1, buf_size, f);
+        t = getTick() - t;
+
+        if (w != (int)buf_size)
+        {
+            printf("fwrite error: %d\n", ferror(f));
+            goto clean;
+        }
+        results[i] = t;
+    }
+
+    success = true;
+    
+clean:
+    // Cleanup
+    fclose(f);
+    remove("buf.dat");
+    delete[] buf;
+
+    return success;
+}
+
+void printResults(unsigned int buf_size, array<float, NUM_WRITES>& results)
+{
+    Stats s;
+    for (float f : results)
+    {
+        s.add(f);
+    }
+    StatsResult res = s.getStats();
+
+    cout << "***BUF SIZE: " << buf_size << "\n";
+    cout << "Times: \n";
+    cout << "- mean: " << res.mean << " ms \n";
+    cout << "- stddev: " << res.stdev << " ms \n";
+    cout << "- min: " << res.minValue << " ms \n";
+    cout << "- max: " << res.maxValue << " ms \n";
+
+    cout << "Speeds: \n";
+    cout << "- mean: " << buf_size / (res.mean * 1024) << " KiB/s \n";
+    cout << "- min: " << buf_size / (res.maxValue * 1024) << " KiB/s \n";
+    cout << "- max: " << buf_size / (res.minValue * 1024) << " KiB/s \n";
+
+    cout << "\n\n";
+}
+
+array<float, NUM_WRITES> data;
+
+int main()
+{
+    srand(time(NULL));
+
+    for (unsigned int s : BUF_SIZES)
+    {
+        if (benchmark(s, data))
+        {
+            printResults(s, data);
+        }
+        else
+        {
+            cout << "Error (buf_size: " << s << ").\nAborting.\n";
+        }
+    }
+    for(;;)
+    {
+        Thread::sleep(60000);
+        cout << "Aborted!\n";
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/shared/ActiveObject.h b/src/shared/ActiveObject.h
index f3cd2a415a3e608fa5c29c21b9b14b288a934470..574373009200c431aebd8c0d19e324898a62e18a 100644
--- a/src/shared/ActiveObject.h
+++ b/src/shared/ActiveObject.h
@@ -1,7 +1,5 @@
-/* Event scheduler
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Author: Federico Terraneo, Matteo Michele Piazzolla
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Federico Terraneo, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -26,6 +24,7 @@
 #define SRC_SHARED_ACTIVEOBJECT_H
 
 #include <Common.h>
+
 #include "diagnostic/SkywardStack.h"
 
 /**
@@ -84,9 +83,12 @@ public:
      */
     virtual void stop()
     {
-        should_stop = true;
-        thread->join();
-        stopped = true;
+        if (isRunning())
+        {
+            should_stop = true;
+            thread->join();
+            stopped = true;
+        }
     }
 
     bool isStarted() { return started; }
diff --git a/src/shared/Common.h b/src/shared/Common.h
index eee2fe4f0c60c8b985fc7d56fabf101ca0f2ac55..3d2b0ce8212d71fe557ee124a61800657a4f3dfc 100644
--- a/src/shared/Common.h
+++ b/src/shared/Common.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -42,6 +42,8 @@
 
 #include "Constants.h"
 
+#include "TimestampTimer.h"
+
 #include "Debug.h"
 
 #define UNUSED(x) (void)(x)
diff --git a/src/shared/Constants.h b/src/shared/Constants.h
index 0d3bbd309d7e8d6e109299fc13023c20ff2f8dd0..fbc8d55c5c144e0d9f5e43cedb5303628fa9333f 100644
--- a/src/shared/Constants.h
+++ b/src/shared/Constants.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -25,7 +25,11 @@
 
 static constexpr const float PI                 = 3.14159265f;
 static constexpr const float EARTH_GRAVITY      = 9.80665f;
+static constexpr const float EARTH_RADIUS       = 6371.0f * 1000.0f;  // [m]
 static constexpr const float DEGREES_TO_RADIANS = PI / 180.0f;
 static constexpr const float RADIANS_TO_DEGREES = 180.0f / PI;
+static constexpr const float KNOTS_TO_MPS       = 0.514444;
+static constexpr const float MSL_PRESSURE       = 101325.0f;  // [Pa]
+static constexpr const float MSL_TEMPERATURE    = 288.15f;    // [Kelvin]
 
 #endif
diff --git a/src/shared/Debug.cpp b/src/shared/Debug.cpp
index 483b54b7598c33ffb343d408c35fe86b070c24c2..9f9824a9b04dbc4ee68d5c670a1135ca61745521 100644
--- a/src/shared/Debug.cpp
+++ b/src/shared/Debug.cpp
@@ -1,20 +1,19 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/Debug.h b/src/shared/Debug.h
index 5121949f24a5c15e2edf084e616cf21f021bd830..4df4929db0610334a104fa26d0e69f9710f89dd9 100644
--- a/src/shared/Debug.h
+++ b/src/shared/Debug.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/Singleton.h b/src/shared/Singleton.h
index 715babfc8879dfd1e3ace64f339eb0b8b65820fd..b49b098af147ea22c9a1fce73ebc6925da1a7f5e 100644
--- a/src/shared/Singleton.h
+++ b/src/shared/Singleton.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Illya Dudchenko, Matteo Michele Piazzolla, Federico Terraneo
+ * Authors: Illya Dudchenko, Matteo Piazzolla, Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/TimestampTimer.cpp b/src/shared/TimestampTimer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..080e81c325a8d0f93c61cdb70433323f903e7379
--- /dev/null
+++ b/src/shared/TimestampTimer.cpp
@@ -0,0 +1,70 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "TimestampTimer.h"
+
+#include <Debug.h>
+
+namespace TimestampTimer
+{
+
+#ifdef _ARCH_CORTEXM3_STM32
+HardwareTimer<uint32_t, TimerMode::Chain> initHardwareTimer()
+{
+    // VERY IMPORTANT! ALWAYS ENABLE CLOCKS BEFORE CONFIGURING THE TIMERS!!
+    {
+        miosix::FastInterruptDisableLock dLock;
+        // Enable TIM2 + TIM3 peripheral clock
+        RCC->APB1ENR |= RCC_APB1ENR_TIM2EN | RCC_APB1ENR_TIM3EN;
+    }
+
+    // chain two 16-bits timers
+    return HardwareTimer<uint32_t, TimerMode::Chain>(
+        TIM2, TIM3, TimerTrigger::ITR1,
+        TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1));
+}
+
+HardwareTimer<uint32_t, TimerMode::Chain> timestamp_timer = initHardwareTimer();
+#else
+HardwareTimer<uint32_t, TimerMode::Single> initHardwareTimer()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+        // Enable TIM2 peripheral clock
+        RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
+    }
+
+    return HardwareTimer<uint32_t, TimerMode::Single>(
+        TIM2,
+        TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1));
+}
+
+HardwareTimer<uint32_t, TimerMode::Single> timestamp_timer = initHardwareTimer();
+#endif
+
+void enableTimestampTimer(uint8_t prescaler)
+{
+    timestamp_timer.setPrescaler(prescaler);
+    timestamp_timer.start();
+}
+
+}  // namespace TimestampTimer
\ No newline at end of file
diff --git a/src/shared/TimestampTimer.h b/src/shared/TimestampTimer.h
new file mode 100644
index 0000000000000000000000000000000000000000..c280ac5cab19d1216333a4064b96cfd2b5d00a36
--- /dev/null
+++ b/src/shared/TimestampTimer.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <math.h>
+
+#include <Debug.h>
+#include "drivers/HardwareTimer.h"
+
+namespace TimestampTimer
+{
+
+/**
+ * For timer resolution and duration refer to :
+ * https://docs.google.com/spreadsheets/d/1B44bN6m2vnldQx9XVxZaBP8bDHqoLPREyHoaLh8s0UA/edit#gid=0
+ *
+ */
+static const uint8_t PRESCALER_VALUE = 255;
+
+#ifdef _ARCH_CORTEXM3_STM32
+extern HardwareTimer<uint32_t, TimerMode::Chain> timestamp_timer;
+#else
+extern HardwareTimer<uint32_t, TimerMode::Single> timestamp_timer;
+#endif
+
+/**
+ * @brief Enables and starts the timer peripheral, which can be
+ * accessed via the timestamp_timer object.
+ */
+void enableTimestampTimer(uint8_t prescaler = PRESCALER_VALUE);
+
+/**
+ * @return the current timestamp in microseconds
+ */
+inline uint64_t getTimestamp()
+{
+    return timestamp_timer.toIntMicroSeconds(timestamp_timer.tick());
+}
+
+}  // namespace TimestampTimer
\ No newline at end of file
diff --git a/src/shared/diagnostic/CpuMeter.cpp b/src/shared/diagnostic/CpuMeter.cpp
index e81c45d6d271849b20df3f24165b19c0b9a0cb25..b448cf8aa94397245230e8ea6ceb28baf97bbcf4 100644
--- a/src/shared/diagnostic/CpuMeter.cpp
+++ b/src/shared/diagnostic/CpuMeter.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/CpuMeter.h b/src/shared/diagnostic/CpuMeter.h
index 9b470c6186637dbc0c646ffca137d78985974c3c..8b577251d426e6a868b12759ef15e0f13bae2aa2 100644
--- a/src/shared/diagnostic/CpuMeter.h
+++ b/src/shared/diagnostic/CpuMeter.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/FaultCounter.h b/src/shared/diagnostic/FaultCounter.h
index 7d49d31481b77d0b2cc53180b0dc99c95b9ff86a..fd597022aaf2ccd239d4b0e1ba8bc8f2537250df 100644
--- a/src/shared/diagnostic/FaultCounter.h
+++ b/src/shared/diagnostic/FaultCounter.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/FaultCounterData.h b/src/shared/diagnostic/FaultCounterData.h
index 5b63d1c605918770d7683c58113d2d8b5f91cc9f..ef73f4e7fda3b3d2b4169c6fffe72ed2aed2651a 100644
--- a/src/shared/diagnostic/FaultCounterData.h
+++ b/src/shared/diagnostic/FaultCounterData.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/Log.h b/src/shared/diagnostic/Log.h
deleted file mode 100644
index 061053657263f7f0b14bf76e3a40944d26f48b69..0000000000000000000000000000000000000000
--- a/src/shared/diagnostic/Log.h
+++ /dev/null
@@ -1,178 +0,0 @@
-/* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#ifndef SHARED_LOG_H
-#define SHARED_LOG_H
-
-#include <ActiveObject.h>
-#include <Common.h>
-#include <Singleton.h>
-#include <drivers/Leds.h>
-#include <fcntl.h>
-#include <math/Vec3.h>
-#include <termios.h>
-#include <unistd.h>
-#include <queue>
-
-class Log : public Singleton<Log>, public ActiveObject
-{
-    enum DataType
-    {
-        DATA_VEC3        = 0,
-        DATA_QUAT        = 1,
-        DATA_FLOAT       = 2,
-        DATA_INT         = 3,
-        DATA_STRING      = 4,
-        DATA_LIMITED_INT = 5,
-        DATA_UINT32      = 6,
-    };
-
-    friend class Singleton<Log>;
-
-public:
-    ~Log() {}
-
-    void logString(const std::string& str)
-    {
-        std::vector<uint8_t> buf(1 + str.length());
-        buf[0] = DATA_STRING;
-        for (size_t i  = 0; i < str.length(); i++)
-            buf[1 + i] = str[i];
-
-        queue(std::move(buf));
-    }
-
-    void logSensorFloat(uint8_t id, float data)
-    {
-        std::vector<uint8_t> buf(2 + sizeof(float));
-        buf[0]             = DATA_FLOAT;
-        buf[1]             = id;
-        *((float*)&buf[2]) = data;
-
-        queue(std::move(buf));
-    }
-
-    void logUInt32(uint8_t id, uint32_t data)
-    {
-        std::vector<uint8_t> buf(2 + sizeof(uint32_t));
-        buf[0] = DATA_UINT32;
-        buf[1] = id;
-        memcpy(&buf[2], &data, sizeof(uint32_t));
-
-        queue(std::move(buf));
-    }
-
-    void logLimitedInt(uint8_t id, int min, int max, int data)
-    {
-        assert(max > min);
-        uint32_t v = (data - min) * 256 / (max - min);
-
-        std::vector<uint8_t> buf(2 + 1);
-        buf[0] = DATA_LIMITED_INT;
-        buf[1] = id;
-        buf[2] = v & 0xff;
-
-        queue(std::move(buf));
-    }
-
-    void logSensorVec3(uint8_t id, const Vec3& data)
-    {
-        float tmp;
-        std::vector<uint8_t> buf(2 + 3 * sizeof(float));
-        buf[0] = DATA_VEC3;
-        buf[1] = id;
-
-        tmp                 = data.getX();
-        *((float*)&buf[2])  = tmp;
-        tmp                 = data.getY();
-        *((float*)&buf[6])  = tmp;
-        tmp                 = data.getZ();
-        *((float*)&buf[10]) = tmp;
-
-        queue(std::move(buf));
-    }
-
-    uint32_t getLogQueueSize() const { return mQueue.size(); }
-
-protected:
-    void run() override
-    {
-        miosix::Lock<miosix::FastMutex> lock(mMutex);
-        while (1)
-        {
-            Leds::set(4, 1);
-            while (mQueue.empty())
-                mCondVar.wait(mMutex);
-
-            Leds::set(4, 0);
-            while (!mQueue.empty())
-            {
-                const std::vector<uint8_t>& el = mQueue.front();
-                write(el);
-                mQueue.pop();
-            }
-            mWaitVar.broadcast();
-        }
-    }
-
-private:
-    std::queue<std::vector<uint8_t>> mQueue;
-    miosix::FastMutex mMutex;
-    miosix::ConditionVariable mCondVar, mWaitVar;
-
-    Log() : ActiveObject()
-    {
-        /*
-        struct termios t;
-        tcgetattr(STDIN_FILENO, &t);
-        t.c_lflag &= ~(ISIG | ICANON | ECHO);
-        tcsetattr(STDIN_FILENO,TCSANOW, &t);
-        */
-    }
-
-    void write(const std::vector<uint8_t>& data)
-    {
-        static const char map[] = "0123456789abcdef";
-        for (size_t i = 0; i < data.size(); i++)
-        {
-            const char& di = data[i];
-            char out[2];
-            out[0] = map[(di >> 4) & 0x0f];
-            out[1] = map[di & 0x0f];
-            ::write(1, out, 2);
-        }
-        ::write(1, "\r\n", 2);
-    }
-
-    void queue(std::vector<uint8_t>&& data)
-    {
-        miosix::Lock<miosix::FastMutex> lock(mMutex);
-        while (mQueue.size() > 100)
-            mWaitVar.wait(lock);
-        mQueue.push(std::move(data));
-        mCondVar.signal();
-    }
-};
-
-#define sLog Log::getInstance()
-
-#endif /* ifndef SHARED_LOG_H */
diff --git a/src/shared/diagnostic/LogSink.h b/src/shared/diagnostic/LogSink.h
new file mode 100644
index 0000000000000000000000000000000000000000..531af475540fb23202b708586f5ed76ef24f34be
--- /dev/null
+++ b/src/shared/diagnostic/LogSink.h
@@ -0,0 +1,97 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <logger/Logger.h>
+#include <miosix.h>
+
+#include "PrintLoggerData.h"
+
+using miosix::FastMutex;
+
+class LogSink
+{
+public:
+    LogSink() {}
+    LogSink(const LogSink&) = delete;
+    LogSink& operator=(const LogSink&) = delete;
+
+    void log(const LogRecord& record);
+
+    void enable() { enabled = true; }
+
+    void disable() { enabled = false; }
+
+    bool isEnabled() { return enabled; }
+
+    void setLevel(uint8_t level) { min_level = level; }
+
+    int getLevel() { return min_level; }
+
+    void setFormatString(std::string format) { this->format = format; }
+
+protected:
+    virtual void logImpl(std::string l) = 0;
+
+private:
+    bool enabled       = true;  // enabled by the default when created
+    uint8_t min_level  = LOGL_NOTSET;
+    std::string format = "{ts} {file}:{line} {fun} {lvl} [{name}] {msg}\n";
+};
+
+/**
+ * This class directly outputs the log to file.
+ */
+class FileLogSink : public LogSink
+{
+public:
+    FileLogSink(FILE* f) : f(f) {}
+
+    FileLogSink() {}
+
+    void setFile(FILE* f_) { f = f_; }
+
+protected:
+    void logImpl(std::string l);
+
+    FILE* f;
+    FastMutex mutex;
+};
+
+/**
+ * This class uses the skyward-boardcore logger in order to output
+ * strings to filesystem. It can be used for example to log error strings
+ * during flight, but use it carefully.
+ */
+class FileLogSinkBuffered : public FileLogSink
+{
+public:
+    FileLogSinkBuffered() : logger(Logger::instance()) {}
+
+protected:
+    void logImpl(std::string l);
+
+private:
+    Logger& logger;
+    FastMutex mutex;
+};
\ No newline at end of file
diff --git a/src/shared/diagnostic/PrintLogger.cpp b/src/shared/diagnostic/PrintLogger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c76b8a9d865ed56fd34bc7230c0a4db6c91b4040
--- /dev/null
+++ b/src/shared/diagnostic/PrintLogger.cpp
@@ -0,0 +1,158 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "PrintLogger.h"
+
+using miosix::Lock;
+
+static string getLevelString(uint8_t level)
+{
+    switch (level)
+    {
+        case LOGL_DEBUG:
+            return "DEBUG";
+        case LOGL_INFO:
+            return "INFO";
+        case LOGL_WARNING:
+            return "WARNING";
+        case LOGL_ERROR:
+            return "ERROR";
+        case LOGL_CRITICAL:
+            return "CRITICAL";
+        default:
+            return std::to_string(level);
+    }
+}
+void LogSink::log(const LogRecord& record)
+{
+    using namespace fmt::literals;
+    if (record.level >= min_level)
+    {
+        float ts      = miosix::getTick() / 1000.0f;
+        int min       = ts / 60;
+        string ts_str = fmt::format("{:02d}:{:06.3f}", min, (ts - min * 60));
+        logImpl(fmt::format(format, "ts"_a = ts_str, "file"_a = record.file,
+                            "line"_a = record.line, "fun"_a = record.function,
+                            "lvl"_a  = getLevelString(record.level),
+                            "name"_a = record.name, "msg"_a = record.message));
+    }
+}
+
+void FileLogSink::logImpl(string l)
+{
+    Lock<FastMutex> lock(mutex);
+    fwrite(l.c_str(), sizeof(char), l.length(), f);
+}
+
+void FileLogSinkBuffered::logImpl(string l)
+{
+    Lock<FastMutex> lock(mutex);
+    LoggingString s;
+    strncpy(s.log_string, l.c_str(), MAX_LOG_STRING_SIZE-1);
+    logger.log(s);
+}
+
+PrintLogger PrintLogger::getChild(string name)
+{
+    return PrintLogger(parent, this->name + "." + name);
+}
+
+LogRecord PrintLogger::buildLogRecord(uint8_t level, string function,
+                                      string file, int line,
+                                      fmt::string_view format,
+                                      fmt::format_args args)
+{
+    LogRecord record;
+    record.level    = level;
+    record.function = function;
+    record.file     = file;
+    record.line     = line;
+    record.name     = name;
+    try
+    {
+        record.message = fmt::vformat(format, args);
+    }
+    catch (const std::exception& e)
+    {
+        level          = ERROR;
+        record.message = "FMT Formatting error! " + string(e.what());
+    }
+
+    return record;
+}
+
+void PrintLogger::vlog(uint8_t level, string function, string file, int line,
+                       fmt::string_view format, fmt::format_args args)
+{
+    parent.log(buildLogRecord(level, function, file, line, format, args));
+}
+
+void PrintLogger::vlogAsync(uint8_t level, string function, string file,
+                            int line, fmt::string_view format,
+                            fmt::format_args args)
+{
+    parent.logAsync(buildLogRecord(level, function, file, line, format, args));
+}
+
+void Logging::log(const LogRecord& record)
+{
+    for (auto& s : sinks)
+    {
+        if (s->isEnabled())
+        {
+            s->log(record);
+        }
+    }
+}
+
+void Logging::logAsync(const LogRecord& record) { async_log.log(record); }
+
+Logging::AsyncLogger::AsyncLogger(Logging& parent) : parent(parent) {}
+
+void Logging::AsyncLogger::log(const LogRecord& record)
+{
+    {
+        Lock<FastMutex> l(mutex);
+        records.put(record);
+    }
+
+    cv.signal();
+}
+
+void Logging::AsyncLogger::run()
+{
+    while (!shouldStop())
+    {
+        LogRecord rec;
+        {
+            Lock<FastMutex> l(mutex);
+            while (records.isEmpty())
+            {
+                cv.wait(mutex);
+            }
+
+            rec = records.pop();
+        }
+
+        parent.log(rec);
+    }
+}
\ No newline at end of file
diff --git a/src/shared/diagnostic/PrintLogger.h b/src/shared/diagnostic/PrintLogger.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b8b06ff02b4f072f311341f7c3a062f80f4574e
--- /dev/null
+++ b/src/shared/diagnostic/PrintLogger.h
@@ -0,0 +1,170 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <fmt/format.h>
+#include <miosix.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ActiveObject.h"
+#include "LogSink.h"
+#include "PrintLoggerData.h"
+#include "Singleton.h"
+#include "logger/Logger.h"
+#include "utils/collections/CircularBuffer.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+using miosix::ConditionVariable;
+using miosix::FastMutex;
+
+#ifndef DEFAULT_STDOUT_LOG_LEVEL
+#define DEFAULT_STDOUT_LOG_LEVEL 0
+#endif
+
+static constexpr unsigned int ASYNC_LOG_BUFFER_SIZE = 100;
+
+class Logging;
+
+class PrintLogger
+{
+public:
+    PrintLogger(Logging& logging, string name) : parent(logging), name(name) {}
+
+    PrintLogger getChild(string name);
+
+    template <typename... Args>
+    void log(uint8_t level, string function, string file, int line,
+             string format, Args&&... args)
+    {
+        vlog(level, function, file, line, format,
+             fmt::make_args_checked<Args...>(format, args...));
+    }
+
+    template <typename... Args>
+    void logAsync(uint8_t level, string function, string file, int line,
+                  string format, Args&&... args)
+    {
+        vlogAsync(level, function, file, line, format,
+                  fmt::make_args_checked<Args...>(format, args...));
+    }
+
+private:
+    void vlog(uint8_t level, string function, string file, int line,
+              fmt::string_view format, fmt::format_args args);
+    void vlogAsync(uint8_t level, string function, string file, int line,
+                   fmt::string_view format, fmt::format_args args);
+
+    LogRecord buildLogRecord(uint8_t level, string function, string file,
+                             int line, fmt::string_view format,
+                             fmt::format_args args);
+
+    Logging& parent;
+    string name;
+};
+
+class Logging : public Singleton<Logging>
+{
+    friend class Singleton<Logging>;
+    friend class PrintLogger;
+
+public:
+    static PrintLogger getLogger(string name)
+    {
+        return PrintLogger(*getInstance(), name);
+    }
+
+    static void addLogSink(unique_ptr<LogSink>& sink)
+    {
+        getInstance()->sinks.push_back(std::move(sink));
+    }
+
+    static LogSink& getStdOutLogSink() { return *getInstance()->sinks.at(0); }
+
+    static void startAsyncLogger() { getInstance()->async_log.start(); }
+
+private:
+    void log(const LogRecord& record);
+    void logAsync(const LogRecord& record);
+
+    class AsyncLogger : public ActiveObject
+    {
+    public:
+        AsyncLogger(Logging& parent);
+        void log(const LogRecord& record);
+
+    protected:
+        void run() override;
+
+    private:
+        Logging& parent;
+        CircularBuffer<LogRecord, ASYNC_LOG_BUFFER_SIZE> records;
+        FastMutex mutex;
+        ConditionVariable cv;
+    };
+
+    Logging() : async_log(*this)
+    {
+        unique_ptr<FileLogSink> serial = std::make_unique<FileLogSink>(stdout);
+        serial->setLevel(DEFAULT_STDOUT_LOG_LEVEL);
+#ifndef DEBUG  // do not output to serial if not in DEBUG mode
+        serial->disable();
+#endif
+        sinks.push_back(std::move(serial));
+    }
+
+    AsyncLogger async_log;
+    vector<unique_ptr<LogSink>> sinks;
+};
+
+#define LOG(logger, level, ...) \
+    logger.log(level, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+
+#define LOG_DEBUG(logger, ...) LOG(logger, LOGL_DEBUG, __VA_ARGS__)
+
+#define LOG_INFO(logger, ...) LOG(logger, LOGL_INFO, __VA_ARGS__)
+
+#define LOG_WARN(logger, ...) LOG(logger, LOGL_WARNING, __VA_ARGS__)
+
+#define LOG_ERR(logger, ...) LOG(logger, LOGL_ERROR, __VA_ARGS__)
+
+#define LOG_CRIT(logger, ...) LOG(logger, LOGL_CRITICAL, __VA_ARGS__)
+
+#define LOG_ASYNC(logger, level, ...) \
+    logger.logAsync(level, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+
+#define LOG_DEBUG_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_DEBUG, __VA_ARGS__)
+
+#define LOG_INFO_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_INFO, __VA_ARGS__)
+
+#define LOG_WARN_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_WARNING, __VA_ARGS__)
+
+#define LOG_ERR_ASYNC(logger, ...) LOG_ASYNC(logger, LOGL_ERROR, __VA_ARGS__)
+
+#define LOG_CRIT_ASYNC(logger, ...) \
+    LOG_ASYNC(logger, LOGL_CRITICAL, __VA_ARGS__)
diff --git a/src/shared/diagnostic/PrintLoggerData.h b/src/shared/diagnostic/PrintLoggerData.h
new file mode 100644
index 0000000000000000000000000000000000000000..03ad85bf4245016fa3d5eb9db945ce6b62ce9d0b
--- /dev/null
+++ b/src/shared/diagnostic/PrintLoggerData.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <string>
+
+static constexpr unsigned int MAX_LOG_STRING_SIZE =
+    250; /**< Max length of messages to be logged through the buffered logger */
+
+struct LogRecord
+{
+    int level;
+    std::string function;
+    std::string file;
+    int line;
+    std::string name;
+    std::string message;
+};
+
+enum LogLevel : uint8_t
+{
+    LOGL_NOTSET   = 0,
+    LOGL_DEBUG    = 10,
+    LOGL_INFO     = 20,
+    LOGL_WARNING  = 30,
+    LOGL_ERROR    = 40,
+    LOGL_CRITICAL = 50
+};
+
+/**
+ * This class is used to output to filesystem (SD card) log messages
+ * through the buffered logger (src/shared/logger/Logger.h).
+ */
+struct LoggingString
+{
+    char log_string[MAX_LOG_STRING_SIZE];
+
+    static std::string header() { return "log_string\n"; }
+
+    void print(std::ostream& os) const { os << log_string << "\n"; }
+};
diff --git a/src/shared/diagnostic/SkywardStack.h b/src/shared/diagnostic/SkywardStack.h
index 11134a161cde691dc48204e07af230199b919c73..38fa133cb0aae41323b8169dcb9224e91bf7b91d 100644
--- a/src/shared/diagnostic/SkywardStack.h
+++ b/src/shared/diagnostic/SkywardStack.h
@@ -1,20 +1,19 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
- * 
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/StackData.h b/src/shared/diagnostic/StackData.h
index c3f93b86c4269dfce2039e61a2f7fcb66fb7724e..65bb636e9c8c09717bedb38cc1744a40acea0c72 100644
--- a/src/shared/diagnostic/StackData.h
+++ b/src/shared/diagnostic/StackData.h
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -31,7 +30,7 @@ enum ThreadId : uint8_t
     THID_MAV_RECEIVER,
     THID_MAV_SENDER,
     THID_XBEE,
-    THID_PIKSI,
+    THID_GPS,
     THID_EVT_BROKER,
     THID_LOGGER_PACK,
     THID_LOGGER_WRITE,
diff --git a/src/shared/diagnostic/StackLogger.h b/src/shared/diagnostic/StackLogger.h
index bd2cbdf46510cc985e643d39b680e3edaade1683..fe2797db32a7906b5d4945468c999dd0c30bab92 100644
--- a/src/shared/diagnostic/StackLogger.h
+++ b/src/shared/diagnostic/StackLogger.h
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/diagnostic/logger.hpp b/src/shared/diagnostic/logger.hpp
deleted file mode 100644
index e71c70ea9f0c3b5a993432458ba3d3adef54ba8b..0000000000000000000000000000000000000000
--- a/src/shared/diagnostic/logger.hpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/* Logger
- * Inspired by http://www.drdobbs.com/cpp/a-lightweight-logger-for-c/240147505.
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Gabriele Farina
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#ifndef LOGGER_HPP
-#define LOGGER_HPP
-
-#include <iostream>
-#include <string>
-#include <sstream>
-#include <ctime>
-#include <iomanip>
-
-namespace logging {
-
-class LogLevel {
- public:
-  LogLevel(int numeric_value, const std::string& string_value) :
-      _numeric_value(numeric_value), _string_value(string_value) {}
-
-  int getNumericValue() const {
-    return _numeric_value;
-  }
-
-  const std::string& getStringValue() const {
-    return _string_value;
-  }
-
-  bool operator<(const LogLevel& ref) const {
-    return _numeric_value < ref._numeric_value;
-  }
-
- private:
-  int _numeric_value;
-  std::string _string_value;
-};
-
-// Define some sugary constants
-auto INFO = LogLevel(1, "INFO");
-auto WARNING = LogLevel(2, "WARN");
-auto ERROR = LogLevel(3, "ERR!");
-auto CRITICAL = LogLevel(4, "CRIT");
-
-class Logger {
- public:
-  void setLogLevel(const LogLevel& new_level) {
-    _log_level = &new_level;
-  }
-  void setOstream(std::ostream* new_ostream) {
-    _out_stream = new_ostream;
-  }
-
-  // Print function. Use it like this:
-  //   .print<logging::WARNING>(arg1, arg2, arg3)
-  template <const LogLevel& level, typename... Args>
-  void log(Args...args) {
-    if (level < *_log_level)
-      return;
-
-    std::stringstream stream;
-
-    stream << "[" << level.getStringValue() << "] ";
-    _printOnSStream(&stream, args...);
-    *_out_stream << stream.str() << std::endl;
-  }
-
- private:
-  void _printOnSStream(std::stringstream*) {}
-
-  template <typename FirstArg, typename... Args>
-  void _printOnSStream(std::stringstream* stream,
-                       const FirstArg& arg, Args...args) {
-    *stream << arg;
-    _printOnSStream(stream, args...);
-  }
-
-  std::ostream* _out_stream = &std::cout;
-  const LogLevel* _log_level = &INFO;
-};
-
-}  // namespace logging
-
-#endif  // LOGGER_HPP
diff --git a/src/shared/diagnostic/logger_example.cpp b/src/shared/diagnostic/logger_example.cpp
deleted file mode 100644
index 351c2841b34305233a8d93d9351aa4dc894e3a59..0000000000000000000000000000000000000000
--- a/src/shared/diagnostic/logger_example.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Gabriele Farina
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#include "logger.hpp"
-
-int main()
-{
-    int i       = 42;
-    auto Logger = logging::Logger();
-
-    Logger.log<logging::INFO>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")");
-
-    // Set severity to >= WARNING.
-    Logger.setLogLevel(logging::WARNING);
-    Logger.log<logging::INFO>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")");
-
-    // Set severity to >= ERROR.
-    Logger.setLogLevel(logging::ERROR);
-    Logger.log<logging::INFO>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::WARNING>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::ERROR>("Some warning using variable i (=", i, ")");
-    Logger.log<logging::CRITICAL>("Some warning using variable i (=", i, ")");
-}
diff --git a/src/shared/drivers/BusTemplate.h b/src/shared/drivers/BusTemplate.h
index 0652a840bf017fa009fbc6883e85947ac5454318..a7df169be307effc63ba74b4726ce22121d8c6f9 100644
--- a/src/shared/drivers/BusTemplate.h
+++ b/src/shared/drivers/BusTemplate.h
@@ -1,8 +1,5 @@
-/* Bus base class
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Illya Dudchenko, Matteo Michele Piazzolla, Silvano Seva,
- *          Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Illya Dudchenko, Matteo Piazzolla, Silvano Seva, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -16,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -110,6 +107,9 @@ private:
 
     inline void _write(uint8_t byte) const
     {
+        // Wait until the peripheral is ready to transmit
+        while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0)
+            ;
         getSPIAddr(N)->DR = byte;
         while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0)
             ;
@@ -127,6 +127,9 @@ private:
 
     inline uint8_t _read() const
     {
+        // Wait until the peripheral is ready to transmit
+        while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0)
+            ;
         getSPIAddr(N)->DR = 0;
         while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0)
             ;
@@ -157,6 +160,9 @@ private:
      */
     inline uint8_t _transfer(uint8_t* byte)
     {
+        // Wait until the peripheral is ready to transmit
+        while ((getSPIAddr(N)->SR & SPI_SR_TXE) == 0)
+            ;
         getSPIAddr(N)->DR = *byte;
         while ((getSPIAddr(N)->SR & SPI_SR_RXNE) == 0)
             ;
@@ -210,7 +216,7 @@ private:
         return n_spi == 1 || n_spi == 2 ? 5 : 6;
     }
 
-    constexpr SPI_TypeDef* getSPIAddr(unsigned n)
+    constexpr SPI_TypeDef* getSPIAddr(unsigned n) const
     {
         return n == 1 ? SPI1 : n == 2 ? SPI2 : SPI3;
     }
diff --git a/src/shared/drivers/HardwareTimer.h b/src/shared/drivers/HardwareTimer.h
index f983b78a4d7245b062aa6281a5f497bbd4c72d63..11cc6d5fb10151e38c80eb9ede984e4ef5cc07f9 100644
--- a/src/shared/drivers/HardwareTimer.h
+++ b/src/shared/drivers/HardwareTimer.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018-2021 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Davide Mor
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -21,19 +20,11 @@
  * THE SOFTWARE.
  */
 
-#ifndef SRC_SHARED_DRIVERS_HARDWARETIMER_H
-#define SRC_SHARED_DRIVERS_HARDWARETIMER_H
+#pragma once
 
-#include <kernel/scheduler/scheduler.h>
 #include <miosix.h>
-#include <cassert>
-#include "Debug.h"
 
-#if !defined _ARCH_CORTEXM4_STM32F4
-#error "Unsupported architecture!"
-#endif
-
-using miosix::FastInterruptDisableLock;
+#include <type_traits>
 
 class TimerUtils
 {
@@ -48,22 +39,22 @@ public:
      * @brief returns the timer clock frequency before the prescaler.
      * Function borrowed from the SyncronizedServo class in Miosix.
      *
-     * @return unsigned int Prescaler input frequency
+     * @return Prescaler input frequency
      */
-    static unsigned int getPrescalerInputFrequency(InputClock input_clock)
+    static uint32_t getPrescalerInputFrequency(InputClock input_clock)
     {
         // The global variable SystemCoreClock from ARM's CMSIS allows to
         // know
         // the CPU frequency.
-        unsigned int freq = SystemCoreClock;
+        uint32_t freq = SystemCoreClock;
 
 // The position of the PPRE1 bit in RCC->CFGR is different in some stm32
 #ifdef _ARCH_CORTEXM3_STM32
-        const unsigned int ppre1 = 8;
-#error "Architecture not currently supported"
+        const uint32_t ppre1 = 8;
+        const uint32_t ppre2 = 11;
 #else  // stm32f2 and f4
-        const unsigned int ppre1 = 10;
-        const unsigned int ppre2 = 13;
+        const uint32_t ppre1 = 10;
+        const uint32_t ppre2 = 13;
 #endif
         // The timer frequency may however be a submultiple of the CPU
         // frequency,
@@ -89,59 +80,181 @@ public:
 
         return freq;
     }
-};
 
-template <typename T, unsigned Tim>
-class HardwareTimer
-{
-    using TimerType = HardwareTimer<T, Tim>;
+    template <typename Type>
+    static float toMicroSeconds(Type &tim, typename Type::Output ticks)
+    {
+        return (1.0f * ticks * 1000000 * (1 + tim.prescaler)) /
+               tim.prescaler_freq;
+    }
 
-    static_assert(std::is_same<T, uint32_t>::value ||
-                      std::is_same<T, uint16_t>::value,
-                  "Timer output type must be either uint16_t or uint32_t");
+    template <typename Type>
+    static uint64_t toIntMicroSeconds(Type &tim, typename Type::Output ticks)
+    {
+        return ((uint64_t)ticks * 1000000 * (uint64_t)(1 + tim.prescaler)) /
+               (uint64_t)tim.prescaler_freq;
+    }
 
-    static_assert(!((Tim == 2 || Tim == 5) && std::is_same<T, uint16_t>::value),
-                  "Tim2 and Tim5 are 32 bit timers!");
+    template <typename Type>
+    static float toMilliSeconds(Type &tim, typename Type::Output ticks)
+    {
+        return (1.0f * ticks * 1000 * (1 + tim.prescaler)) / tim.prescaler_freq;
+    }
 
-public:
-    inline static TimerType& instance()
+    template <typename Type>
+    static float toSeconds(Type &tim, typename Type::Output ticks)
     {
-        static TimerType timer;
-        return timer;
+        return (1.0f * ticks * (1 + tim.prescaler)) / tim.prescaler_freq;
     }
 
-    inline T start()
+    template <typename Type>
+    static float getResolution(Type &tim)
     {
-        if (!ticking)
-        {
-            ticking  = true;
-            TIM->CNT = 0;  // Reset the counter
+        // Resolution in us = number microseconds in one tick
+        return toMicroSeconds(tim, 1);
+    }
 
-            TIM->CR1 |= TIM_CR1_CEN;
-            return 0;
-        }
-        else
-        {
-            return tick();
-        }
+    template <typename Type>
+    static float getMaxDuration(Type &tim)
+    {
+        // Maximum duration = number of seconds to count to the auto_reload
+        // (reset) value.
+        return toSeconds(tim, tim.auto_reload);
     }
+};
 
-    inline T tick() { return TIM->CNT; }
+/**
+ * @brief Selects to operating mode of the timer.
+ */
+enum class TimerMode
+{
+    Single,  //< Use a single timer as a source of time.
+    Chain    //< Chain two timers as a source of time.
+};
 
-    inline T stop()
-    {
-        if (ticking)
-        {
-            T tick = TIM->CNT;
-            TIM->CR1 &= ~TIM_CR1_CEN;
-            ticking = false;
-            return tick;
-        }
+/**
+ * @brief Which trigger connection to use.
+ *
+ * This dictates the trigger for the slave timer,
+ * this must be choosen depending on the master slave.
+ *
+ * Here you can see a table with all the possible combinations
+ * taken directly from the STM32 datasheet, on the top row you can
+ * see the trigger pin, while on the left there is the choosen slave timer,
+ * by looking at the intersection you can find the connected master timer.
+ *
+ * PS: The datasheet where this comes from is for:
+ * - STM32F101xx
+ * - STM32F102xx
+ * - STM32F103xx
+ * - STM32F105xx
+ * - STM32F107xx
+ *
+ * ```
+ *       ITR0  ITR1  ITR2  ITR3
+ * TIM1  TIM5  TIM2  TIM3  TIM4
+ * TIM2  TIM1  TIM8  TIM3  TIM4
+ * TIM3  TIM1  TIM2  TIM5  TIM4
+ * TIM4  TIM1  TIM2  TIM3  TIM8
+ * TIM5  TIM2  TIM3  TIM4  TIM8
+ * TIM6  ----  ----  ----  ----
+ * TIM7  ----  ----  ----  ----
+ * TIM8  TIM1  TIM2  TIM4  TIM5
+ * TIM9  TIM2  TIM3  TIM10 TIM11
+ * TIM10 ----  ----  ----  ----
+ * TIM11 ----  ----  ----  ----
+ * TIM12 TIM4  TIM5  TIM13 TIM14
+ * TIM13 ----  ----  ----  ----
+ * TIM14 ----  ----  ----  ----
+ * ```
+ *
+ */
+enum class TimerTrigger
+{
+    ITR0 = 0,
+    ITR1 = TIM_SMCR_TS_0,
+    ITR2 = TIM_SMCR_TS_1,
+    ITR3 = TIM_SMCR_TS_0 | TIM_SMCR_TS_1
+};
 
-        return 0;
+/**
+ * @brief Class for handling Hardware Timers and perform time unit conversions
+ *
+ * @tparam Type type to use to access timer register (uint32_t if 32 bit timer,
+ * uint16_t if 16 bit timer)
+ * @tparam Operating mode of the timer (see TimerMode)
+ */
+template <typename Type, TimerMode Mode = TimerMode::Single>
+class HardwareTimer;
+
+template <typename Type>
+class HardwareTimer<Type, TimerMode::Single>
+{
+    friend class TimerUtils;
+
+    static_assert(std::is_same<Type, uint32_t>::value ||
+                      std::is_same<Type, uint16_t>::value,
+                  "Type must be either uint32_t or uint16_t.");
+
+public:
+    using Output = Type;
+
+    /**
+     * @brief Creates a new HardwareTimer instance
+     *
+     * @param    timer     The timer to use (pointer to timer registers struct)
+     * @param    psc_input_freq Input frequency of the timer's prescaler, see
+     *                          TimerUtils::getPrescalerInputFrequency()
+     */
+    HardwareTimer(TIM_TypeDef *timer, uint32_t psc_input_freq)
+        : tim(timer), prescaler_freq(psc_input_freq)
+    {
     }
 
-    bool isTicking() { return ticking; }
+    /**
+     * @brief Starts the timer returns the current tick
+     *
+     * @return Current tick
+     */
+    Type start();
+
+    /**
+     * @brief Stops the timer if already started
+     *
+     * @return Current tick or 0 if already stopped
+     */
+    Type stop();
+
+    /**
+     * @brief Returns current tick
+     */
+    Type tick();
+
+    /**
+     * @brief Sets the prescaler value.
+     * The tick frequency is defined as the clock frequency value of the timer
+     * divided by the prescaler. See the datasheet for further information.
+     */
+    void setPrescaler(uint16_t prescaler);
+
+    /**
+     * @brief Set the auto reload value.
+     * The auto reload value is the maximum value the timer can count to.
+     * After this value is reached, the timers counts back from 0.
+     */
+    void setAutoReload(Type auto_reload);
+
+    /**
+     * @brief Converts from ticks to microseconds, using the current prescaler
+     * setting
+     *
+     * @param ticks
+     * @return uint64_t
+     */
+    uint64_t toIntMicroSeconds(Type ticks)
+    {
+        return TimerUtils::toIntMicroSeconds(*this, ticks);
+    }
 
     /**
      * @brief Converts from ticks to microseconds, using the current prescaler
@@ -150,10 +263,9 @@ public:
      * @param ticks
      * @return float
      */
-    float toMicroSeconds(T ticks)
+    float toMicroSeconds(Type ticks)
     {
-        return (1.0f * ticks * 1000000 * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
+        return TimerUtils::toMicroSeconds(*this, ticks);
     }
 
     /**
@@ -163,10 +275,9 @@ public:
      * @param ticks
      * @return float
      */
-    float toMilliSeconds(T ticks)
+    float toMilliSeconds(Type ticks)
     {
-        return (1.0f * ticks * 1000 * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
+        return TimerUtils::toMilliSeconds(*this, ticks);
     }
 
     /**
@@ -176,154 +287,231 @@ public:
      * @param ticks
      * @return float
      */
-    float toSeconds(T ticks)
-    {
-        return (1.0f * ticks * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
-    }
+    float toSeconds(Type ticks) { return TimerUtils::toSeconds(*this, ticks); }
 
-    /*static float toSeconds(T tick) {}
-    static float toMilliseconds(T tick) {}
-    static float toMicroSeconds(T tick) {}*/
+    /**
+     * @brief Returns the current resolution of the timer in microseconds.
+     * @return Resolution in microseconds
+     */
+    float getResolution() { return TimerUtils::getResolution(*this); }
 
     /**
-     * @brief Sets the prescaler value.
-     * The tick frequency is defined as the clock frequency value of the timer
-     * divided by the prescaler. See the datasheet for further information.
+     * @brief Returns the maximum time the timer can measure before restarting
+     * from 0, in seconds.
+     *
+     * @return Maximum timer duration in seconds.
      */
-    void setPrescaler(uint16_t prescaler)
+    float getMaxDuration() { return TimerUtils::getMaxDuration(*this); }
+
+private:
+    TIM_TypeDef *tim;
+    uint32_t prescaler_freq;
+
+    bool ticking       = false;
+    uint16_t prescaler = 0;
+    Type auto_reload =
+        static_cast<Type>(-1);  // Max value of Type (Type is unsigned)
+};
+
+template <typename Type>
+inline Type HardwareTimer<Type>::start()
+{
+    if (!ticking)
+    {
+        ticking  = true;
+        tim->CNT = 0;  // Reset the counter
+
+        tim->CR1 |= TIM_CR1_CEN;
+        return 0;
+    }
+    else
     {
-        // reset();
-        TIM->PSC = prescaler;
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new prescaler
-        // value
-        this->prescaler = prescaler;
+        return tick();
     }
+}
+
+template <typename Type>
+inline Type HardwareTimer<Type>::stop()
+{
+    if (ticking)
+    {
+        tim->CR1 &= ~TIM_CR1_CEN;
+        ticking = false;
+        return tim->CNT;
+    }
+
+    return 0;
+}
+
+template <typename Type>
+inline Type HardwareTimer<Type>::tick()
+{
+    return tim->CNT;
+}
+
+template <typename Type>
+void HardwareTimer<Type>::setPrescaler(uint16_t prescaler)
+{
+    this->prescaler = prescaler;
+    tim->PSC        = prescaler;
+    tim->EGR = TIM_EGR_UG;  // Send an update event to load the new prescaler
+                            // value
+}
+
+template <typename Type>
+void HardwareTimer<Type>::setAutoReload(Type auto_reload)
+{
+    this->auto_reload = auto_reload;
+    tim->ARR          = auto_reload;
+    tim->EGR = TIM_EGR_UG;  // Send an update event to load the new auto-reload
+}
+
+template <typename Type>
+class HardwareTimer<Type, TimerMode::Chain>
+{
+    friend class TimerUtils;
+
+    // TODO: Does this still work with two 32bit timers?
+    static_assert(std::is_same<Type, uint32_t>::value,
+                  "Type must be uint32_t.");
+
+public:
+    using Output = Type;
 
     /**
-     * @brief Set the auto reload value.
-     * The auto reload value is the maximum value the timer can count to.
-     * After this value is reached, the timers counts back from 0.
+     * @brief Creates a new HardwareTimer instance
+     *
+     * @param tim The timer to use as master (pointer to timer registers
+     * struct)
+     * @param slave The timer to use as slave (pointer to timer registers
+     * struct)
+     * @param trigger The slave input trigger, should be choosen according to
+     * master and slave (see TimerTrigger)
+     * @param prescaler_freq Input frequency of the timer's prescaler, see
+     * TimerUtils::getPrescalerInputFrequency()
      */
-    void setAutoReload(T auto_reload)
+    HardwareTimer(TIM_TypeDef *tim, TIM_TypeDef *slave, TimerTrigger trigger,
+                  uint32_t prescaler_freq);
+
+    Type start();
+
+    Type stop();
+
+    Type tick();
+
+    void setPrescaler(uint16_t prescaler);
+
+    void setAutoReload(Type auto_reload);
+
+    uint64_t toIntMicroSeconds(Type ticks)
     {
-        if (ticking)
-        {
-            stop();
-        }
-        this->auto_reload = auto_reload;
-        TIM->ARR          = auto_reload;
+        return TimerUtils::toIntMicroSeconds(*this, ticks);
+    }
 
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new auto-reload
+    float toMicroSeconds(Type ticks)
+    {
+        return TimerUtils::toMicroSeconds(*this, ticks);
     }
 
-private:
-    HardwareTimer()
+    float toMilliSeconds(Type ticks)
     {
-        switch (Tim)
-        {
-            case 1:
-                TIM_EN = RCC_APB2ENR_TIM1EN;
-                TIM    = TIM1;
-                break;
-            default:  // Use TIM2 as default
-                TRACE("Wrong timer selected. Using TIM2.\n");
-            case 2:
-                TIM_EN = RCC_APB1ENR_TIM2EN;
-                TIM    = TIM2;
-                break;
-            case 3:
-                TIM_EN = RCC_APB1ENR_TIM3EN;
-                TIM    = TIM3;
-                break;
-            case 4:
-                TIM_EN = RCC_APB1ENR_TIM4EN;
-                TIM    = TIM4;
-                break;
-            case 5:
-                TIM_EN = RCC_APB1ENR_TIM5EN;
-                TIM    = TIM5;
-                break;
-            case 6:
-                TIM_EN = RCC_APB1ENR_TIM6EN;
-                TIM    = TIM6;
-                break;
-            case 7:
-                TIM_EN = RCC_APB1ENR_TIM7EN;
-                TIM    = TIM7;
-                break;
-            case 8:
-                TIM_EN = RCC_APB2ENR_TIM8EN;
-                TIM    = TIM8;
-                break;
-            case 9:
-                TIM_EN = RCC_APB2ENR_TIM9EN;
-                TIM    = TIM9;
-                break;
-            case 10:
-                TIM_EN = RCC_APB2ENR_TIM10EN;
-                TIM    = TIM10;
-                break;
-            case 11:
-                TIM_EN = RCC_APB2ENR_TIM11EN;
-                TIM    = TIM11;
-                break;
-            case 12:
-                TIM_EN = RCC_APB1ENR_TIM12EN;
-                TIM    = TIM12;
-                break;
-            case 13:
-                TIM_EN = RCC_APB1ENR_TIM13EN;
-                TIM    = TIM13;
-                break;
-            case 14:
-                TIM_EN = RCC_APB1ENR_TIM14EN;
-                TIM    = TIM14;
-                break;
-        }
+        return TimerUtils::toMilliSeconds(*this, ticks);
+    }
 
-        if (Tim == 1 || (Tim >= 8 && Tim <= 11))
-        {
-            clk = TimerUtils::InputClock::APB2;
+    float toSeconds(Type ticks) { return TimerUtils::toSeconds(*this, ticks); }
 
-            FastInterruptDisableLock dLock;
-            RCC->APB2ENR |= TIM_EN;
-            RCC_SYNC();
-        }
-        else
-        {
-            clk = TimerUtils::InputClock::APB1;
+    float getResolution() { return toMicroSeconds(1); }
 
-            FastInterruptDisableLock dLock;
-            RCC->APB1ENR |= TIM_EN;
-            RCC_SYNC();
-        }
+    float getMaxDuration() { return toSeconds(auto_reload); }
 
-        // Reset control registers
-        TIM->CR1 = 0;
-        TIM->CR2 = 0;
+private:
+    TIM_TypeDef *tim, *slave;
+    uint32_t prescaler_freq;
+
+    bool ticking       = false;
+    uint16_t prescaler = 0;
+    Type auto_reload =
+        static_cast<Type>(-1);  // Max value of Type (Type is unsigned)
+};
 
-        auto_reload = static_cast<T>(-1);  // Max value of T (unsigned int)
-        TIM->ARR    = auto_reload;
+template <typename Type>
+inline HardwareTimer<Type, TimerMode::Chain>::HardwareTimer(
+    TIM_TypeDef *tim, TIM_TypeDef *slave, TimerTrigger trigger,
+    uint32_t prescaler_freq)
+    : tim(tim), slave(slave), prescaler_freq(prescaler_freq)
+{
+    // Trigger selection from parameter
+    const uint8_t ts = static_cast<uint8_t>(trigger);
+    // Slave mode selection 0b111 -> External Clock mode 1
+    const uint8_t sms = TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_2;
+    // Master mode selection 0b010 -> Update
+    const uint8_t mms = TIM_CR2_MMS_1;
+
+    // Configure timer1 as master and timer2 as slave
+    tim->CR2 &= ~TIM_CR2_MMS;
+    tim->CR2 |= mms;
+
+    slave->SMCR &= ~(TIM_SMCR_TS | TIM_SMCR_SMS);
+    slave->SMCR |= ts | sms;
+}
+
+template <typename Type>
+inline Type HardwareTimer<Type, TimerMode::Chain>::start()
+{
+    if (!ticking)
+    {
+        ticking = true;
 
-        prescaler = 0;
-        TIM->PSC  = prescaler;
+        slave->CNT = tim->CNT = 0;
 
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new prescaler and
-        // auto reload values
+        slave->CR1 |= TIM_CR1_CEN;
+        tim->CR1 |= TIM_CR1_CEN;
+        return 0;
     }
+    else
+    {
+        return tick();
+    }
+}
 
-    TIM_TypeDef* TIM;
-    uint32_t TIM_EN;
-    TimerUtils::InputClock clk;
+template <typename Type>
+inline Type HardwareTimer<Type, TimerMode::Chain>::stop()
+{
+    if (ticking)
+    {
+        slave->CR1 &= ~TIM_CR1_CEN;
+        tim->CR1 &= ~TIM_CR1_CEN;
 
-    T auto_reload;
-    uint16_t prescaler;
+        ticking = false;
+        return tick();
+    }
+    else
+    {
+        return 0;
+    }
+}
 
-    bool ticking = false;
-};
+template <typename Type>
+inline Type HardwareTimer<Type, TimerMode::Chain>::tick()
+{
+    return (Type)tim->CNT | ((Type)slave->CNT << (sizeof(Type) * 8 / 2));
+}
 
-#endif /* SRC_SHARED_DRIVERS_HARDWARETIMER_H */
+template <typename Type>
+void HardwareTimer<Type, TimerMode::Chain>::setPrescaler(uint16_t prescaler)
+{
+    this->prescaler = prescaler;
+    tim->PSC        = prescaler;
+    tim->EGR = TIM_EGR_UG;  // Send an update event to load the new prescaler
+                            // value
+}
+
+template <typename Type>
+void HardwareTimer<Type, TimerMode::Chain>::setAutoReload(Type auto_reload)
+{
+    this->auto_reload = auto_reload;
+    // We only care about the higher bits
+    slave->ARR = auto_reload >> (sizeof(Type) * 8 / 2);
+    slave->EGR = TIM_EGR_UG;  // Send an update event to load the new 
+                              // auto-reload
+}
\ No newline at end of file
diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp
index 271b50f53b6d74fd2bf05b95bfb8f186c040a9e1..fd10b0a2c5233929b1ad65e119991bf914b591eb 100644
--- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp
+++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.cpp
@@ -1,23 +1,23 @@
-/*
- * Inter Stormtrooper Boards communication protocol through STM32's 
- * USART2 interface
- * 
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2018 Skyward Experimental Rocketry
  * Author: Silvano Seva
- * 
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
 #include "IsbProtocol_serial2.h"
diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h
index c031fe1402c1815b936ddd45504152414a39846d..261a42fa1fc7d01345f5e00a0cf0e71b05760f37 100644
--- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h
+++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial2.h
@@ -1,23 +1,23 @@
-/*
- * Inter Stormtrooper Boards communication protocol through STM32's 
- * USART2 interface
- * 
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
- * 
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
 #ifndef ISBPROTOCOL_SERIAL2_H
@@ -39,7 +39,7 @@
  * response (if expected). Owing to this model, if two stormtrooper slaves need
  * to exchange data they have to rely on the stormtrooper master acting as a
  * router between them.
- * 
+ *
  * The packet structure is this:
  * 
  * +------+-----+------+-----+
diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp
index ffe44e566ab9d0f8606653fce6e315e4966dedca..4222b8496705ad6adf0c624beb0374b9ab5ae6a5 100644
--- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp
+++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.cpp
@@ -20,6 +20,28 @@
  * 
  */
 
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Silvano Seva
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #include "IsbProtocol_serial3.h"
 
 using namespace std;
diff --git a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h
index 04738688b5f2237fb54dbcd24b54196aa12265c3..c0d850ce08590a8afad64fae432ec25c7f865187 100644
--- a/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h
+++ b/src/shared/drivers/ISB_protocol/IsbProtocol_serial3.h
@@ -1,23 +1,23 @@
-/*
- * Inter Stormtrooper Boards communication protocol through STM32's 
- * USART3 interface
- * 
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
- * 
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
 #ifndef ISBPROTOCOL_SERIAL3_H
@@ -39,7 +39,7 @@
  * response (if expected). Owing to this model, if two stormtrooper slaves need
  * to exchange data they have to rely on the stormtrooper master acting as a
  * router between them.
- * 
+ *
  * The packet structure is this:
  * 
  * +------+-----+------+-----+
diff --git a/src/shared/drivers/Leds.cpp b/src/shared/drivers/Leds.cpp
index 08bab0f88f4777210cc58e023eb72e3e538d945d..2ec4677a10026890df5834c84723b3a37fb45455 100644
--- a/src/shared/drivers/Leds.cpp
+++ b/src/shared/drivers/Leds.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/Leds.h b/src/shared/drivers/Leds.h
index a1c1522a02a6d325ae9930560bd3e137d6c18cdc..0cef4fa338b3ebe6a693107b4701a4c1815fecab 100644
--- a/src/shared/drivers/Leds.h
+++ b/src/shared/drivers/Leds.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla, Federico Terraneo
+ * Authors: Alain Carlucci, Matteo Piazzolla, Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/SoftwareI2CAdapter.h b/src/shared/drivers/SoftwareI2CAdapter.h
index 60931e79dbd181150a1124ec8d3950c412d3622e..219aad3a727c40d1cdbcf2eefcb295ff94e40478 100644
--- a/src/shared/drivers/SoftwareI2CAdapter.h
+++ b/src/shared/drivers/SoftwareI2CAdapter.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/Transceiver.h b/src/shared/drivers/Transceiver.h
index 3bfd631a8ee7e878f3ef8293af771ffc3005caaf..f1a3456d8f11bc0f630a73c286d538fc601841d5 100644
--- a/src/shared/drivers/Transceiver.h
+++ b/src/shared/drivers/Transceiver.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,8 +23,6 @@
 #ifndef TRANSCEIVER_H
 #define TRANSCEIVER_H
 
-#include <Common.h>
-
 class Transceiver
 {
 public:
diff --git a/src/shared/drivers/Xbee/APIFrameParser.cpp b/src/shared/drivers/Xbee/APIFrameParser.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..44751d263e81bc2aa6841114782830bfdd592329
--- /dev/null
+++ b/src/shared/drivers/Xbee/APIFrameParser.cpp
@@ -0,0 +1,110 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "APIFrameParser.h"
+
+#include "Debug.h"
+
+namespace Xbee
+{
+
+APIFrameParser::APIFrameParser() {}
+
+APIFrameParser::ParseResult APIFrameParser::parse(uint8_t byte, APIFrame* frame)
+{
+    switch (parser_state)
+    {
+        // Look for the start of frame delimiter
+        case ParserState::FIND_START:
+
+            if (byte == START_DELIMITER)
+            {
+                parser_state = ParserState::READ_LENGTH_1;
+            }
+            break;
+        // Read most significant byte of the length
+        case ParserState::READ_LENGTH_1:
+            frame->length = byte;
+            parser_state  = ParserState::READ_LENGTH_2;
+
+            break;
+        // Read least significant byte of the length
+        case ParserState::READ_LENGTH_2:
+            frame->length |= ((uint16_t)byte << 8) & 0xFF00;
+            // At least two frame data bytes (frame_type and a payload)
+            if (swapBytes16(frame->length) < 2)
+            {
+                parser_state = ParserState::FIND_START;
+                return ParseResult::FAIL;
+            }
+
+            if (frame->getFrameDataLength() > FRAME_DATA_SIZE)
+            {
+                parser_state = ParserState::FIND_START;
+                return ParseResult::FAIL;
+            }
+
+            parser_state = ParserState::READ_FRAME_TYPE;
+            break;
+        // Read frame type
+        case ParserState::READ_FRAME_TYPE:
+            frame->frame_type = byte;
+            parser_state      = ParserState::READ_FRAME_DATA;
+            break;
+        // Read the data frame
+        case ParserState::READ_FRAME_DATA:
+            frame->frame_data[current_frame_data_index++] = byte;
+
+            if (current_frame_data_index == frame->getFrameDataLength())
+            {
+                current_frame_data_index = 0;
+                parser_state             = ParserState::READ_CHECKSUM;
+            }
+            break;
+        // Read & verify checksum
+        case ParserState::READ_CHECKSUM:
+            frame->checksum = byte;
+            parser_state    = ParserState::FIND_START;
+
+            if (frame->verifyChecksum())
+            {
+                return ParseResult::SUCCESS;
+            }
+            else
+            {
+                LOG_ERR(logger, "Wrong packet checksum!");
+                return ParseResult::FAIL;
+            }
+            break;
+    }
+
+    if (parser_state != ParserState::FIND_START)
+    {
+        return ParseResult::PARSING;
+    }
+    else
+    {
+        return ParseResult::IDLE;
+    }
+}
+
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/APIFrameParser.h b/src/shared/drivers/Xbee/APIFrameParser.h
new file mode 100644
index 0000000000000000000000000000000000000000..4a1f5abc2dde7d6ac62544e5199ddc58d9226be6
--- /dev/null
+++ b/src/shared/drivers/Xbee/APIFrameParser.h
@@ -0,0 +1,90 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+
+#include <cstdint>
+
+#include "APIFrames.h"
+
+namespace Xbee
+{
+
+/**
+ * @brief Parses a byte sequence into an Xbee APIFrame
+ */
+class APIFrameParser
+{
+public:
+    /**
+     * @brief Current state of the parser internal state machine
+     */
+    enum class ParserState
+    {
+        FIND_START,
+        READ_LENGTH_1,
+        READ_LENGTH_2,
+        READ_FRAME_TYPE,
+        READ_FRAME_DATA,
+        READ_CHECKSUM
+    };
+
+    /**
+     * @brief Result of the last parse operation
+     *
+     */
+    enum class ParseResult : uint8_t
+    {
+        IDLE = 0,  // No frame has been found yet
+        PARSING,   // Currently paring a frame
+        SUCCESS,   // A frame has been parsed successfully
+        FAIL  // The parsed frame was invalid (eg wrong length, wrong checksum)
+    };
+
+    APIFrameParser();
+
+    /**
+     * @brief Parses a single byte. When this function returns
+     * ParseResult:SUCESS, @p frame contains a valid APIFrame
+     *
+     * @param byte Byte to parse
+     * @param frame Frame to be constructed from the parsed data.
+     * @return ParseResult If SUCCESS is returned, then the provided frame is
+     * valid and can be used
+     */
+    ParseResult parse(uint8_t byte, APIFrame* frame);
+
+    /**
+     * @brief Returns the current state of the parser
+     */
+    ParserState getParserState() { return parser_state; }
+
+private:
+    ParserState parser_state          = ParserState::FIND_START;
+    uint16_t current_frame_data_index = 0;
+
+    PrintLogger logger = Logging::getLogger("apiframeparser");
+};
+
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/APIFrames.h b/src/shared/drivers/Xbee/APIFrames.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca7cc9209869b13480be4767af23c3c7aed972aa
--- /dev/null
+++ b/src/shared/drivers/Xbee/APIFrames.h
@@ -0,0 +1,474 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <ostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#ifndef COMPILE_FOR_X86
+#include <interfaces/endianness.h>
+#elif defined(__GNUC__)
+
+#define swapBytes64 __builtin_bswap64
+
+static uint16_t swapBytes16(uint16_t x)
+{
+    return (x & 0xFF00) >> 8 | (x & 0x00FF) << 8;
+}
+#endif
+
+using std::min;
+using std::string;
+
+namespace Xbee
+{
+/// Maximum length for a TX/RX frame payload. User-configurable using the "NP"
+/// AT command or the Xbee configuration software (see Xbee datasheet)
+static constexpr size_t MIN_RX_PACKET_FRAME_SIZE   = 11;
+static constexpr size_t MIN_TX_REQUEST_FRAME_SIZE  = 13;
+static constexpr size_t MIN_AT_COMMAND_FRAME_SIZE  = 3;
+static constexpr size_t MIN_AT_RESPONSE_FRAME_SIZE = 4;
+
+static constexpr size_t TX_STATUS_FRAME_SIZE    = 6;
+static constexpr size_t MODEM_STATUS_FRAME_SIZE = 1;
+
+static constexpr uint16_t MAX_PACKET_PAYLOAD_LENGTH      = 256;
+static constexpr uint16_t MAX_AT_COMMAND_PARAMS_LENGTH   = 20;
+static constexpr uint16_t MAX_AT_COMMAND_RESPONSE_LENGTH = 30;
+
+static constexpr size_t FRAME_DATA_SIZE =
+    MAX_PACKET_PAYLOAD_LENGTH + MIN_TX_REQUEST_FRAME_SIZE;
+
+static constexpr size_t MAX_API_FRAME_SIZE = FRAME_DATA_SIZE + 5;
+static constexpr size_t MIN_API_FRAME_SIZE = 5;
+
+static constexpr uint64_t ADDRESS_BROADCAST = 0xFFFF;
+static constexpr uint8_t START_DELIMITER    = 0x7E;
+
+enum FrameType : uint8_t
+{
+    FTYPE_AT_COMMAND          = 0x08,
+    FTYPE_AT_COMMAND_QUEUE    = 0x09,
+    FTYPE_TX_REQUEST          = 0x10,
+    FTYPE_AT_COMMAND_RESPONSE = 0x88,
+    FTYPE_MODEM_STATUS        = 0x8A,
+    FTYPE_TX_STATUS           = 0x8B,
+    FTYPE_RX_PACKET_FRAME     = 0x90,
+};
+
+enum TransmitOptionsBitfield : uint8_t
+{
+    TO_DISABLE_ACK = 0x01,
+    TO_DISABLE_RD  = 0x02,
+    TO_NACK        = 0x04,
+    TO_TRACE_ROUTE = 0x08,
+
+    TO_DM_POINT_MULTIPOINT = 0x40,
+    TO_DM_REPEATER_MODE    = 0x80,
+    TO_DM_DIGIMESH         = 0xC0
+};
+
+enum CommandStatusBitfield : uint8_t
+{
+    CS_OK                = 0x00,
+    CS_ERROR             = 0x01,
+    CS_INVALID_COMMAND   = 0x02,
+    CS_INVALID_PARAMETER = 0x03,
+    CS_RSSI_INVALID      = 0x40,
+    CS_IS_REMOTE_COMMAND = 0x80
+};
+
+enum ModemStatus : uint8_t
+{
+    MS_HARDWARE_RESET       = 0x00,
+    MS_WATCHDOG_TIMER_RESET = 0x01,
+    MS_NETWORK_WOKE_UP      = 0x0B,
+    MS_NETWORK_WENT_SLEEP   = 0x0C,
+};
+
+enum DeliveryStatus : uint8_t
+{
+    DELS_SUCCESS               = 0x00,
+    DELS_MAC_ACK_FAILURE       = 0x01,
+    DELS_COLL_AVOID_FAILURE    = 0x02,
+    DELS_NO_SPECTRUM_AVAILABLE = 0x03,
+    DELS_NET_ACK_FAILURE       = 0x21,
+    DELS_ROUTE_NOT_FOUND       = 0x25,
+    DELS_INT_RESOURCE_ERR      = 0x31,
+    DELS_INTERNAL_ERROR        = 0x32,
+    DELS_PAYLOAD_TOO_LARGE     = 0x74,
+    DELS_INDIRECT_MSG_REQ      = 0x75,
+};
+
+enum DiscoveryStatus : uint8_t
+{
+    DISCS_NO_DISC_OVERHEAD = 0x00,
+    DISCS_ROUTE_DISCOVERY  = 0x02
+};
+
+enum ReceiveOptions : uint8_t
+{
+    RO_PACKET_ACK          = 0x00,
+    RO_PACKET_IS_BROADCAST = 0x01,
+    RO_POINT_MULTIPOINT    = 0x40,
+    RO_REPEATER_MODE       = 0x80,
+    RO_DIGIMESH            = 0xC0,
+};
+
+#pragma pack(1)
+struct APIFrame
+{
+    uint8_t start_del  = 0x7E;
+    uint16_t length    = 0;
+    uint8_t frame_type = 0;
+    uint8_t frame_data[FRAME_DATA_SIZE];
+
+    uint8_t checksum = 0;
+    // Used for logging, not part of the standard Xbee API Frame
+    long long timestamp = 0;
+
+    APIFrame() { memset(frame_data, 0, FRAME_DATA_SIZE); }
+
+    uint16_t getFrameDataLength() const
+    {
+        size_t len = swapBytes16(length) - 1;
+        assert(len <= FRAME_DATA_SIZE);
+
+        return min(len, FRAME_DATA_SIZE);
+    }
+
+    void setFrameDataLength(uint16_t len)
+    {
+        assert(len <= FRAME_DATA_SIZE);
+        length = swapBytes16(min((size_t)(len + 1), FRAME_DATA_SIZE + 1));
+    }
+
+    bool verifyChecksum() const
+    {
+        assert(getFrameDataLength() <= FRAME_DATA_SIZE);
+        // Sum all the bytes including checksum and frame type.
+        // The sum can be stored in a uint8_t since we only care about the least
+        // significant byte.
+        uint8_t sum = checksum + frame_type;
+        for (uint16_t i = 0; i < getFrameDataLength(); ++i)
+        {
+            sum += frame_data[i];
+        }
+        return sum == 0xFF;
+    }
+
+    void calcChecksum()
+    {
+        assert(getFrameDataLength() <= FRAME_DATA_SIZE);
+        checksum = frame_type;
+        for (uint16_t i = 0; i < getFrameDataLength(); ++i)
+        {
+            checksum += frame_data[i];
+        }
+
+        checksum = 0xFF - checksum;
+    }
+
+    template <typename FrameType>
+    FrameType* toFrameType()
+    {
+        static_assert(
+            std::is_base_of<APIFrame, FrameType>::value ||
+                std::is_same<APIFrame, FrameType>::value,
+            "FrameType must be derived from APIFrame or be an APIFrame");
+
+        return reinterpret_cast<FrameType*>(this);
+    }
+
+    size_t toBytes(uint8_t* bytes)
+    {
+        memcpy(bytes, this, 4 + getFrameDataLength());
+
+        bytes[4 + getFrameDataLength()] = checksum;
+        return 5 + getFrameDataLength();
+    }
+
+    static bool fromBytes(uint8_t* bytes, size_t size, APIFrame* f)
+    {
+        if (size >= MIN_API_FRAME_SIZE && size <= MAX_API_FRAME_SIZE)
+        {
+            memcpy(f, bytes, size - 1);
+            f->checksum = bytes[size - 1];
+
+            return true;
+        }
+        return false;
+    }
+};
+#pragma pack()
+struct ATCommandFrame : public APIFrame
+{
+    ATCommandFrame() : APIFrame()
+    {
+        frame_type = FTYPE_AT_COMMAND;
+        setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE);
+    }
+
+    uint8_t getFrameID() const { return frame_data[0]; }
+
+    void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; }
+
+    const char* getATCommand() const
+    {
+        return reinterpret_cast<const char*>(&frame_data[1]);
+    }
+
+    void setATCommand(const char* at)
+    {
+        frame_data[1] = at[0];
+        frame_data[2] = at[1];
+    }
+
+    uint8_t* getCommandDataPointer() { return &frame_data[3]; }
+
+    uint16_t getCommandDataLength() const
+    {
+        return getFrameDataLength() - MIN_AT_COMMAND_FRAME_SIZE;
+    }
+
+    void setParameterSize(uint16_t size)
+    {
+        assert(size <= FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE);
+        size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_COMMAND_FRAME_SIZE);
+
+        setFrameDataLength(MIN_AT_COMMAND_FRAME_SIZE + size);
+    }
+};
+static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+
+struct ATCommandResponseFrame : public APIFrame
+{
+    ATCommandResponseFrame() : APIFrame()
+    {
+        frame_type = FTYPE_AT_COMMAND_RESPONSE;
+        setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE);
+    }
+
+    uint8_t getFrameID() const { return frame_data[0]; }
+
+    void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; }
+
+    const char* getATCommand() const
+    {
+        return reinterpret_cast<const char*>(&frame_data[1]);
+    }
+
+    void setATCommand(const char* at)
+    {
+        frame_data[1] = at[0];
+        frame_data[2] = at[1];
+    }
+
+    uint8_t getCommandStatus() const { return frame_data[3]; }
+
+    void setCommandStatus(uint8_t cs) { frame_data[3] = cs; }
+
+    uint8_t* getCommandDataPointer() { return &frame_data[4]; }
+
+    uint16_t getCommandDataLength() const
+    {
+        return getFrameDataLength() - MIN_AT_RESPONSE_FRAME_SIZE;
+    }
+
+    void setCommandDataSize(uint16_t size)
+    {
+        assert(size <= FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE);
+        size = min((size_t)size, FRAME_DATA_SIZE - MIN_AT_RESPONSE_FRAME_SIZE);
+
+        setFrameDataLength(MIN_AT_RESPONSE_FRAME_SIZE + size);
+    }
+};
+static_assert(sizeof(ATCommandFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+
+struct TXRequestFrame : public APIFrame
+{
+    TXRequestFrame() : APIFrame()
+    {
+        frame_type = FTYPE_TX_REQUEST;
+        setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE);
+
+        // Reserved bytes
+        frame_data[9]  = 0xFF;
+        frame_data[10] = 0xFE;
+    }
+
+    uint8_t getFrameID() const { return frame_data[0]; }
+
+    void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; }
+
+    uint64_t getDestAddress() const
+    {
+        uint64_t addr;
+        memcpy(&addr, &frame_data[1], sizeof(uint64_t));
+        return swapBytes64(addr);
+    }
+
+    void setDestAddress(uint64_t address)
+    {
+        address = swapBytes64(address);
+        memcpy(&frame_data[1], &address, 8);
+    }
+
+    uint8_t getBroadcastRadius() const { return frame_data[11]; }
+
+    void setBroadcastRadius(uint8_t br) { frame_data[11] = br; }
+
+    uint8_t getTrasmitOptions() const { return frame_data[12]; }
+
+    void setTransmitOptions(uint8_t br) { frame_data[12] = br; }
+
+    uint8_t* getRFDataPointer() { return &frame_data[13]; }
+
+    uint16_t getRFDataLength() const
+    {
+        return min((size_t)(getFrameDataLength() - MIN_TX_REQUEST_FRAME_SIZE),
+                   (size_t)MAX_PACKET_PAYLOAD_LENGTH);
+    }
+
+    void setRFDataLength(uint16_t size)
+    {
+        assert(size <= MAX_PACKET_PAYLOAD_LENGTH);
+        size = min(size, MAX_PACKET_PAYLOAD_LENGTH);
+
+        setFrameDataLength(MIN_TX_REQUEST_FRAME_SIZE + size);
+    }
+};
+static_assert(sizeof(TXRequestFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+
+struct ModemStatusFrame : public APIFrame
+{
+    ModemStatusFrame() : APIFrame()
+    {
+        frame_type = FTYPE_MODEM_STATUS;
+        setFrameDataLength(MODEM_STATUS_FRAME_SIZE);
+    }
+
+    uint8_t getStatus() const { return frame_data[0]; }
+
+    void setStatus(uint8_t status) { frame_data[0] = status; }
+};
+static_assert(sizeof(ModemStatusFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+
+struct TXStatusFrame : public APIFrame
+{
+    TXStatusFrame() : APIFrame()
+    {
+        frame_type = FTYPE_TX_STATUS;
+        setFrameDataLength(TX_STATUS_FRAME_SIZE);
+
+        // Reserved bytes
+        frame_data[1] = 0xFF;
+        frame_data[2] = 0xFE;
+    }
+
+    uint8_t getFrameID() const { return frame_data[0]; }
+
+    void setFrameID(uint8_t frame_id) { frame_data[0] = frame_id; }
+
+    uint8_t getTransmitRetryCount() const { return frame_data[3]; }
+
+    void setTransmitRetryCount(uint8_t trc) { frame_data[3] = trc; }
+
+    uint8_t getDeliveryStatus() const { return frame_data[4]; }
+
+    void setDeliveryStatus(uint8_t ds) { frame_data[4] = ds; }
+
+    uint8_t getDiscoveryStatus() const { return frame_data[5]; }
+
+    void setDiscoveryStatus(uint8_t ds) { frame_data[5] = ds; }
+};
+
+static_assert(sizeof(TXStatusFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+
+struct RXPacketFrame : public APIFrame
+{
+    RXPacketFrame() : APIFrame()
+    {
+        frame_type = FTYPE_RX_PACKET_FRAME;
+        setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE);
+
+        frame_data[8] = 0xFF;
+        frame_data[9] = 0xFE;
+    }
+
+    uint64_t getSourceAddress() const
+    {
+        uint64_t addr;
+        memcpy(&addr, &frame_data[0], sizeof(uint64_t));
+        return swapBytes64(addr);
+    }
+
+    void setSourceAddress(uint64_t address)
+    {
+        address       = swapBytes64(address);
+        uint8_t* addr = reinterpret_cast<uint8_t*>(&address);
+
+        memcpy(&frame_data[0], addr, 8);
+    }
+
+    uint8_t getReceiveOptions() const { return frame_data[10]; }
+
+    void setReceiveOptions(uint8_t ro) { frame_data[10] = ro; }
+
+    uint8_t* getRXDataPointer() { return &frame_data[11]; }
+
+    uint16_t getRXDataLength() const
+    {
+        return min((size_t)(getFrameDataLength() - MIN_RX_PACKET_FRAME_SIZE),
+                   (size_t)MAX_PACKET_PAYLOAD_LENGTH);
+    }
+
+    void setRXDataLength(uint16_t size)
+    {
+        assert(size <= MAX_PACKET_PAYLOAD_LENGTH);
+        size = min(size, MAX_PACKET_PAYLOAD_LENGTH);
+
+        setFrameDataLength(MIN_RX_PACKET_FRAME_SIZE + size);
+    }
+};
+
+static_assert(sizeof(RXPacketFrame) == sizeof(APIFrame),
+              "Size of derived classes must be the same as APIFrame class (no "
+              "additional members & no virtual functions)");
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/APIFramesLog.h b/src/shared/drivers/Xbee/APIFramesLog.h
new file mode 100644
index 0000000000000000000000000000000000000000..f7a86ca1ea64b4db813608edb7b9334bd59c1344
--- /dev/null
+++ b/src/shared/drivers/Xbee/APIFramesLog.h
@@ -0,0 +1,393 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "APIFrames.h"
+
+using std::min;
+
+/**
+ * Classes used if you want to log an APIFrame, but do not want to log the base
+ * APIFrame class, which is quite heavy on memory
+ */
+
+namespace Xbee
+{
+
+struct APIFrameLog
+{
+    long long timestamp;
+    uint8_t frame_type;
+    uint16_t frame_data_length;
+    uint8_t frame_data[FRAME_DATA_SIZE];
+
+    static bool fromAPIFrame(APIFrame& api, APIFrameLog* dest)
+    {
+        dest->timestamp         = api.timestamp;
+        dest->frame_type        = api.frame_type;
+        dest->frame_data_length = api.getFrameDataLength();
+
+        memcpy(dest->frame_data, api.frame_data, dest->frame_data_length);
+
+        return true;
+    }
+
+    static string header() { return "timestamp,length,frame_type\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << frame_data_length << "," << frame_type
+           << "\n";
+    }
+};
+
+struct ATCommandFrameLog
+{
+    long long timestamp;
+    uint8_t frame_id = 0;
+    char at_command[2];
+
+    uint8_t command_data[MAX_AT_COMMAND_PARAMS_LENGTH];
+    uint16_t command_data_length = 0;
+
+    static bool toFrameType(APIFrame& api, ATCommandFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_AT_COMMAND)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE ||
+            api.getFrameDataLength() >
+                MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_PARAMS_LENGTH)
+        {
+            return false;
+        }
+        ATCommandFrame* at = api.toFrameType<ATCommandFrame>();
+
+        dest->timestamp = at->timestamp;
+        dest->frame_id  = at->getFrameID();
+
+        memcpy(dest->at_command, at->getATCommand(), 2);
+
+        dest->command_data_length = at->getCommandDataLength();
+        memcpy(dest->command_data, at->getCommandDataPointer(),
+               dest->command_data_length);
+
+        return true;
+    }
+
+    static string header() { return "timestamp,id,cmd,param_size,params\n"; }
+
+    void print(std::ostream& os) const
+    {
+        char cmd[3];
+
+        strncpy(cmd, at_command, 2);
+        cmd[2] = '\0';
+
+        os << timestamp << "," << (int)frame_id << "," << cmd << ","
+           << command_data_length << ",";
+
+        if (command_data_length > 0)
+        {
+            for (uint16_t i = 0; i < command_data_length; i++)
+            {
+                os << (int)command_data[i] << " ";
+            }
+        }
+        else
+        {
+            os << "-";
+        }
+
+        os << "\n";
+    }
+};
+
+struct TXRequestFrameLog
+{
+    long long timestamp = 0;
+    uint8_t frame_id    = 0;
+
+    uint64_t dest_address = 0;
+
+    uint8_t broadcast_radius = 0;
+    uint8_t transmit_options = 0;
+
+    uint8_t rf_data[MAX_PACKET_PAYLOAD_LENGTH];
+    uint16_t rf_data_length = 0;
+
+    static bool toFrameType(APIFrame& api, TXRequestFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_TX_REQUEST)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() < MIN_TX_REQUEST_FRAME_SIZE ||
+            api.getFrameDataLength() >
+                MIN_TX_REQUEST_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH)
+        {
+            return false;
+        }
+
+        TXRequestFrame* tx = api.toFrameType<TXRequestFrame>();
+
+        dest->timestamp = api.timestamp;
+        dest->frame_id  = tx->getFrameID();
+
+        dest->dest_address = tx->getDestAddress();
+
+        dest->broadcast_radius = tx->getBroadcastRadius();
+        dest->transmit_options = tx->getTrasmitOptions();
+
+        dest->rf_data_length = tx->getRFDataLength();
+        memcpy(dest->rf_data, tx->getRFDataPointer(), dest->rf_data_length);
+
+        return true;
+    }
+
+    static string header()
+    {
+        return "timestamp,id,dest_addr,broadcast_radius,tx_options,rf_data_"
+               "len,first_uint32\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        uint32_t d;
+        memcpy(&d, rf_data, sizeof(d));
+        os << timestamp << "," << (int)frame_id << "," << dest_address << ","
+           << (int)broadcast_radius << "," << (int)transmit_options << ","
+           << rf_data_length << "," << d << "\n";
+    }
+};
+
+struct ATCommandResponseFrameLog
+{
+    long long timestamp = 0;
+    uint8_t frame_id    = 0;
+    char at_command[2];
+    uint8_t command_status = 0;
+
+    uint8_t command_data[MAX_AT_COMMAND_RESPONSE_LENGTH];
+    uint16_t command_data_length = 0;
+
+    static bool toFrameType(APIFrame& api, ATCommandResponseFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_AT_COMMAND_RESPONSE)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() < MIN_AT_COMMAND_FRAME_SIZE ||
+            api.getFrameDataLength() >
+                MIN_AT_COMMAND_FRAME_SIZE + MAX_AT_COMMAND_RESPONSE_LENGTH)
+        {
+            return false;
+        }
+
+        ATCommandResponseFrame* at = api.toFrameType<ATCommandResponseFrame>();
+
+        dest->timestamp = api.timestamp;
+        dest->frame_id  = at->getFrameID();
+        memcpy(dest->at_command, at->getATCommand(), 2);
+
+        dest->command_status = at->getCommandStatus();
+
+        dest->command_data_length = at->getCommandDataLength();
+        memcpy(dest->command_data, at->getCommandDataPointer(),
+               dest->command_data_length);
+
+        return true;
+    }
+
+    static string header()
+    {
+        return "timestamp,id,cmd,status,param_size,params\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        char cmd[3];
+
+        strncpy(cmd, at_command, 2);
+        cmd[2] = '\0';
+
+        os << timestamp << "," << (int)frame_id << "," << cmd << ","
+           << (int)command_status << "," << command_data_length << ",";
+
+        if (command_data_length > 0)
+        {
+            for (uint16_t i = 0; i < command_data_length; i++)
+            {
+                os << (int)command_data[i] << " ";
+            }
+        }
+        else
+        {
+            os << "-";
+        }
+
+        os << "\n";
+    }
+};
+
+struct ModemStatusFrameLog
+{
+    long long timestamp  = 0;
+    uint8_t modem_status = 0;
+
+    static bool toFrameType(APIFrame& api, ModemStatusFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_MODEM_STATUS)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() != MODEM_STATUS_FRAME_SIZE)
+        {
+            return false;
+        }
+
+        ModemStatusFrame* modem = api.toFrameType<ModemStatusFrame>();
+
+        dest->timestamp    = api.timestamp;
+        dest->modem_status = modem->getStatus();
+
+        return true;
+    }
+
+    static string header() { return "timestamp,status\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)modem_status << "\n";
+    }
+};
+
+struct TXStatusFrameLog
+{
+    long long timestamp      = 0;
+    uint8_t frame_id         = 0;
+    uint8_t tx_retry_count   = 0;
+    uint8_t delivery_status  = 0;
+    uint8_t discovery_status = 0;
+
+    static bool toFrameType(APIFrame& api, TXStatusFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_TX_STATUS)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() != TX_STATUS_FRAME_SIZE)
+        {
+            return false;
+        }
+
+        TXStatusFrame* tx = api.toFrameType<TXStatusFrame>();
+
+        dest->timestamp = api.timestamp;
+        dest->frame_id  = tx->getFrameID();
+
+        dest->tx_retry_count   = tx->getTransmitRetryCount();
+        dest->delivery_status  = tx->getDeliveryStatus();
+        dest->discovery_status = tx->getDiscoveryStatus();
+
+        return true;
+    }
+
+    static string header()
+    {
+        return "timestamp,id,tx_retries,delivery_status,discovery_status\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (int)frame_id << "," << (int)tx_retry_count
+           << "," << (int)delivery_status << "," << (int)discovery_status
+           << "\n";
+    }
+};
+
+struct RXPacketFrameLog
+{
+    long long timestamp = 0;
+
+    uint64_t source_address = 0;
+
+    uint8_t receive_options = 0;
+
+    uint8_t rx_data[MAX_PACKET_PAYLOAD_LENGTH];
+    uint16_t rx_data_length = 0;
+
+    static bool toFrameType(APIFrame& api, RXPacketFrameLog* dest)
+    {
+        if (api.frame_type != FTYPE_RX_PACKET_FRAME)
+        {
+            return false;
+        }
+
+        if (api.getFrameDataLength() < MIN_RX_PACKET_FRAME_SIZE ||
+            api.getFrameDataLength() >
+                MIN_RX_PACKET_FRAME_SIZE + MAX_PACKET_PAYLOAD_LENGTH)
+        {
+            return false;
+        }
+
+        RXPacketFrame* rx = api.toFrameType<RXPacketFrame>();
+
+        dest->timestamp      = api.timestamp;
+        dest->source_address = rx->getSourceAddress();
+
+        dest->receive_options = rx->getReceiveOptions();
+
+        dest->rx_data_length = rx->getRXDataLength();
+        memcpy(dest->rx_data, rx->getRXDataPointer(), dest->rx_data_length);
+
+        return true;
+    }
+
+    static string header()
+    {
+        return "timestamp,src_addr,rx_options,rx_data_size,payload32_0,payload32_1\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        uint32_t data[2];
+        memcpy(&data, rx_data, min(sizeof(uint32_t)*2, (size_t)rx_data_length));
+        os << timestamp << "," << source_address << "," << (int)receive_options
+           << "," << rx_data_length << "," << data[0] << "," << data[1] << "\n";
+    }
+};
+
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/ATCommands.h b/src/shared/drivers/Xbee/ATCommands.h
new file mode 100644
index 0000000000000000000000000000000000000000..6db0c915b7dcef0a022530f1a78f9c8ae4d649e2
--- /dev/null
+++ b/src/shared/drivers/Xbee/ATCommands.h
@@ -0,0 +1,154 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "APIFrames.h"
+#include "Xbee.h"
+
+namespace Xbee
+{
+
+/**
+ * @brief Enables or disable the specified channels. Channel 9 and 24 are always
+ * disabled due to regulatory limitations.
+ * See datasheet for the list of channel frequencies.
+ *
+ * @param xbee Reference to an xbee object
+ * @param channels channel mask
+ * @return True if the command was executed successfully
+ */
+bool setChannelMask(Xbee& xbee, uint32_t mask, unsigned int timeout = 1000)
+{
+    ATCommandResponseFrame response;
+
+    mask = swapBytes32(mask);
+
+    return xbee.sendATCommand("CM", &response,
+                              reinterpret_cast<uint8_t*>(&mask), 4, timeout);
+}
+
+/**
+ * @brief Enables or disable the specified channels. Channel 9 and 24 are always
+ * disabled due to regulatory limitations.
+ * See datasheet for the list of channel frequencies.
+ *
+ * @param xbee Reference to an xbee object
+ * @param channels channel mask array
+ *
+ * @return True if the command was executed successfully
+ */
+bool setChannelMask(Xbee& xbee, bool channels[30], unsigned int timeout = 1000)
+{
+    uint32_t val = 0;
+
+    for (int i = 0; i < 30; i++)
+    {
+        val &= ((uint32_t)channels[i]) << i;
+    }
+
+    return setChannelMask(xbee, val, timeout);
+}
+
+/**
+ * @brief Enable communication on all available channels. Equivalent to calling
+ * setChannelMask(xbee, 0x3EFFFDFF).
+ *
+ * @param xbee Reference to an xbee object
+ * @return True if the command was executed successfully
+ */
+bool enableAllChannels(Xbee& xbee, unsigned int timeout = 1000)
+{
+    return setChannelMask(xbee, 0x3EFFFDFF, timeout);
+}
+
+/**
+ * @brief Disables frequency hopping by allowing communication only on channel
+ * #29. Equivalent to calling setChannelMask(xbee, 0x20000000).
+ *
+ * @param xbee Reference to an xbee object
+ */
+bool disableFrequencyHopping(Xbee& xbee, unsigned int timeout = 1000)
+{
+    return setChannelMask(xbee, 0x20000000, timeout);
+}
+
+/**
+ * @brief Configures the desired xbee data rate.
+ *
+ * @param xbee Reference to an xbee object
+ * @param data_rate_80kbps true for 80kbps, false for 10kbps
+ * @return True if the command was executed successfully
+ */
+bool setDataRate(Xbee& xbee, bool data_rate_80kbps, unsigned int timeout = 1000)
+{
+    uint8_t param = (uint8_t)data_rate_80kbps;
+    ATCommandResponseFrame response;
+
+    return xbee.sendATCommand("BR", &response, &param, 1, timeout);
+}
+
+/**
+ * @brief Writes parameter values to non-volatile memory so that parameter
+ * modifications persist through subsequent resets.
+ *
+ * @param xbee Reference to an xbee object
+ * @return True if the command was executed successfully
+ */
+bool writeToMemory(Xbee& xbee, unsigned int timeout = 1000)
+{
+    ATCommandResponseFrame response;
+    return xbee.sendATCommand("WR", &response, nullptr, 0, timeout);
+}
+
+/**
+ * @brief Performs an energy detect scan on all channels
+ *
+ * @param xbee Reference to an xbee object
+ * @param energy_detect_data Pointer to a 30-bytes buffer where energy levels
+ * will be stored. Energy levels are in -dBm units.
+ * @param duration Scan duration in ms
+ * @return True if the command was executed successfully and data stored in
+ * energy_detect_data is valid
+ */
+bool energyDetect(Xbee& xbee, int* energy_detect_data, uint8_t duration,
+                  unsigned int timeout = 1000)
+{
+    ATCommandResponseFrame response;
+    if (xbee.sendATCommand("ED", &response, &duration, 1, timeout))
+    {
+        uint16_t resp_len = response.getCommandDataLength();
+        if (resp_len == 30)
+        {
+            for (int i = 0; i < 30; i++)
+            {
+                energy_detect_data[i] =
+                    (int)(*(response.getCommandDataPointer() + i));
+            }
+
+            return true;
+        }
+    }
+    return false;
+}
+
+}  // namespace Xbee
diff --git a/src/shared/drivers/Xbee/Xbee.cpp b/src/shared/drivers/Xbee/Xbee.cpp
index 4cc53fb6b2ba165fd28aa69da0a2877b28f2f986..a361e3894bc63cdbd5e2ac9e40fce44f0e42947a 100644
--- a/src/shared/drivers/Xbee/Xbee.cpp
+++ b/src/shared/drivers/Xbee/Xbee.cpp
@@ -1,20 +1,19 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,7 +22,512 @@
 
 #include "Xbee.h"
 
+#include <Debug.h>
+#include <kernel/scheduler/scheduler.h>
+#include <miosix.h>
+#include <diagnostic/StackLogger.h>
+#include <algorithm>
+
+using miosix::FastMutex;
+using miosix::Lock;
+using miosix::Unlock;
+using std::min;
+
 namespace Xbee
 {
-miosix::Thread* waiting = nullptr;
-}
\ No newline at end of file
+
+Xbee::Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst,
+           long long tx_timeout)
+    : Xbee(bus, {}, cs, attn, rst, tx_timeout)
+{
+    spi_xbee.config.clock_div = SPIClockDivider::DIV128;
+}
+
+Xbee::Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs,
+           GpioType attn, GpioType rst, long long tx_timeout)
+    : spi_xbee(bus, cs, config), attn(attn), rst(rst), tx_timeout(tx_timeout)
+{
+    reset();
+}
+
+Xbee::~Xbee() { wakeReceiver(true); }
+
+bool Xbee::send(uint8_t* pkt, size_t pkt_len)
+{
+    if (pkt_len > MAX_PACKET_PAYLOAD_LENGTH || pkt_len == 0)
+    {
+        LOG_ERR(logger, "Invalid packet length (0< {} <= {})", pkt_len,
+                MAX_PACKET_PAYLOAD_LENGTH);
+        return false;
+    }
+    long long start_tick = miosix::getTick();
+
+    TXRequestFrame tx_req;
+    uint8_t tx_frame_id = buildTXRequestFrame(tx_req, pkt, pkt_len);
+    bool success        = false;
+    bool status_timeout = true;
+    {
+        Lock<FastMutex> l(mutex_xbee_comm);
+        writeFrame(tx_req);
+
+        // Wait for a TX Status frame
+        long long timeout_tick = miosix::getTick() + tx_timeout;
+
+        while (waitForFrame(FTYPE_TX_STATUS, FRAME_POLL_INTERVAL, timeout_tick))
+        {
+            TXStatusFrame* f = parsing_api_frame.toFrameType<TXStatusFrame>();
+
+            if (f->getFrameID() == tx_frame_id)
+            {
+                success        = f->getDeliveryStatus() == DELS_SUCCESS;
+                status_timeout = false;
+                break;
+            }
+            else
+            {
+                LOG_ERR(logger, "Wrong tx_status ID");
+            }
+        }
+
+        // Ak for total transmitted byte count for logging  purposes
+        // sendATCommandInternal("BC");
+    }
+
+    // If there is more data to receive, wake the receive() thread
+    if (attn.value() == 0)
+    {
+        wakeReceiver();
+    }
+    if (status_timeout)
+    {
+        ++status.tx_timeout_count;
+        LOG_ERR(logger, "TX_STATUS timeout");
+    }
+    time_to_send_stats.add(miosix::getTick() - start_tick);
+
+    StackLogger::getInstance()->updateStack(THID_XBEE);
+
+    return success;
+}
+
+ssize_t Xbee::receive(uint8_t* buf, size_t buf_max_size)
+{
+    while (true)
+    {
+        // We have data in the buffer pending to be returned
+        if (!rx_frames_buf.isEmpty() || curr_rx_payload_ptr >= 0)
+        {
+            return fillReceiveBuf(buf, buf_max_size);
+        }
+        // No data in the buffer, but the xbee has data to return via SPI
+        else if (attn.value() == 0)
+        {
+            Lock<FastMutex> l(mutex_xbee_comm);
+            if (readRXFrame())
+            {
+                sendATCommandInternal("DB");  // Query last packet RSSI
+                sendATCommandInternal("ER");  // Query receive error count
+            }
+        }
+        // No data available at all: sleep until we have something to do
+        else
+        {
+            {
+                miosix::FastInterruptDisableLock dLock;
+                receive_thread = miosix::Thread::getCurrentThread();
+
+                while (receive_thread != 0)  // Avoid spurious wakeups
+                {
+                    receive_thread->IRQwait();
+                    {
+                        miosix::FastInterruptEnableLock eLock(dLock);
+                        miosix::Thread::yield();
+                    }
+                }
+            }
+
+            // Forcefully return without receiving anything
+            if (force_rcv_return)
+            {
+                force_rcv_return = false;
+                return -1;
+            }
+        }
+    }
+}
+
+XbeeStatus Xbee::getStatus()
+{
+    status.timestamp          = miosix::getTick();
+    status.time_to_send_stats = time_to_send_stats.getStats();
+    return status;
+}
+
+void Xbee::reset()
+{
+    Lock<FastMutex> l(mutex_xbee_comm);
+    {
+        miosix::FastInterruptDisableLock dLock();
+        rst.mode(miosix::Mode::OPEN_DRAIN);
+    }
+    rst.low();
+    miosix::delayUs(50);
+    rst.high();
+
+    // When the xbee is ready, we should assert SSEL to tell it to use
+    // SPI, and it should provide us with a modem status frame
+    long long timeout = miosix::getTick() + 1000;
+    do
+    {
+        // Assert SSEL on every iteration as we don't exactly know when the
+        // xbee will be ready.
+        {
+            SPIAcquireLock acq(spi_xbee);
+            spi_xbee.cs.low();
+            miosix::delayUs(10);
+            spi_xbee.cs.high();
+        }
+
+        miosix::delayUs(50);
+
+        if (attn.value() == 0 && readOneFrame() == ParseResult::SUCCESS)
+        {
+            handleFrame(parsing_api_frame);
+
+            if (parsing_api_frame.frame_type == FTYPE_MODEM_STATUS)
+            {
+                break;
+            }
+        }
+        miosix::Thread::sleep(5);
+    } while (miosix::getTick() < timeout);
+}
+
+void Xbee::wakeReceiver(bool force_return)
+{
+    force_rcv_return = force_return;
+    miosix::FastInterruptDisableLock dLock;
+
+    if (receive_thread)
+    {
+        receive_thread->IRQwakeup();
+        receive_thread = 0;
+    }
+}
+
+void Xbee::handleATTNInterrupt()
+{
+    if (receive_thread)
+    {
+        receive_thread->IRQwakeup();
+        if (receive_thread->IRQgetPriority() >
+            miosix::Thread::IRQgetCurrentThread()->IRQgetPriority())
+        {
+            miosix::Scheduler::IRQfindNextThread();
+        }
+        receive_thread = 0;
+    }
+}
+
+size_t Xbee::fillReceiveBuf(uint8_t* buf, size_t buf_max_size)
+{
+    if (!rx_frames_buf.isEmpty() && curr_rx_payload_ptr == -1)
+    {
+        Lock<FastMutex> l(mutex_rx_frames);
+        curr_rx_frame       = rx_frames_buf.pop();
+        curr_rx_payload_ptr = 0;
+    }
+
+    if (curr_rx_payload_ptr >= 0)
+    {
+        size_t len_remaining_data =
+            curr_rx_frame.getRXDataLength() - curr_rx_payload_ptr;
+
+        // The buffer may be smaller than the data we need to return
+        size_t len_to_copy = min(buf_max_size, len_remaining_data);
+
+        memcpy(buf, curr_rx_frame.getRXDataPointer() + curr_rx_payload_ptr,
+               len_to_copy);
+
+        curr_rx_payload_ptr += len_to_copy;
+
+        if (curr_rx_payload_ptr == curr_rx_frame.getRXDataLength())
+        {
+            // We've emptied the current frame
+            curr_rx_payload_ptr = -1;
+        }
+
+        return len_to_copy;
+    }
+
+    return 0;
+}
+
+ParseResult Xbee::readOneFrame()
+{
+    SPIAcquireLock acq(spi_xbee);
+    SPISelectLock sel(spi_xbee);
+
+    ParseResult result = ParseResult::IDLE;
+    do
+    {
+        result = parser.parse(spi_xbee.bus.read(), &parsing_api_frame);
+    } while (attn.value() == 0 && result == ParseResult::PARSING);
+
+    return result;
+}
+
+bool Xbee::readRXFrame()
+{
+    while (attn.value() == 0)
+    {
+        if (readOneFrame() == ParseResult::SUCCESS)
+        {
+            handleFrame(parsing_api_frame);
+
+            if (parsing_api_frame.frame_type == FTYPE_RX_PACKET_FRAME)
+            {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void Xbee::writeFrame(APIFrame& frame)
+{
+    frame.timestamp = miosix::getTick();  // Only for logging purposes
+
+    // Serialize the frame
+    uint8_t tx_buf[MAX_API_FRAME_SIZE];
+    size_t tx_buf_size = frame.toBytes(tx_buf);
+
+    {
+        SPITransaction spi(spi_xbee);
+        spi.transfer(tx_buf, tx_buf_size);
+    }
+
+    // Pass the frame we just sent to the listener
+    if (frame_listener)
+    {
+        frame_listener(frame);
+    }
+
+    // Full duplex spi transfer: we may have received valid data while we
+    // were sending the frame.
+    for (unsigned int i = 0; i < tx_buf_size; i++)
+    {
+        ParseResult res = parser.parse(tx_buf[i], &parsing_api_frame);
+        if (res == ParseResult::SUCCESS)
+        {
+            handleFrame(parsing_api_frame);
+        }
+    }
+}
+
+bool Xbee::waitForFrame(uint8_t frame_type, unsigned int poll_interval,
+                        long long timeout_tick)
+{
+    do
+    {
+        if (attn.value() == 0)
+        {
+            if (readOneFrame() == ParseResult::SUCCESS)
+            {
+                handleFrame(parsing_api_frame);
+
+                if (parsing_api_frame.frame_type == frame_type)
+                {
+                    return true;
+                }
+            }
+        }
+        else
+        {
+            miosix::Thread::sleep(poll_interval);
+        }
+    } while (miosix::getTick() < timeout_tick);
+
+    return false;
+}
+
+uint8_t Xbee::buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt,
+                                  size_t pkt_len)
+{
+    memcpy(tx_req.getRFDataPointer(), pkt, pkt_len);
+    tx_req.setRFDataLength(pkt_len);
+
+    uint8_t tx_frame_id = getNewFrameID();
+
+    tx_req.setFrameID(tx_frame_id);
+
+    tx_req.setDestAddress(ADDRESS_BROADCAST);
+    tx_req.setBroadcastRadius(0);  // 0 = max hops, but we don't really care as
+                                   // it does not apply to point-multipoint mode
+    // Point-multipoint mode, disable ack, disable route discovery
+    tx_req.setTransmitOptions(TO_DM_POINT_MULTIPOINT | TO_DISABLE_ACK |
+                              TO_DISABLE_RD);
+    tx_req.calcChecksum();
+
+    return tx_frame_id;
+}
+
+void Xbee::sendATCommand(const char* cmd, uint8_t* params, size_t params_len)
+{
+    Lock<FastMutex> l(mutex_xbee_comm);
+
+    sendATCommandInternal(0, cmd, params, params_len);
+}
+
+bool Xbee::sendATCommand(const char* cmd, ATCommandResponseFrame* response,
+                         uint8_t* params, size_t params_len,
+                         unsigned int timeout)
+{
+    Lock<FastMutex> l(mutex_xbee_comm);
+
+    uint8_t tx_frame_id = sendATCommandInternal(cmd, params, params_len);
+
+    bool success = false;
+
+    long long timeout_tick = miosix::getTick() + timeout;
+
+    while (waitForFrame(FTYPE_AT_COMMAND_RESPONSE, FRAME_POLL_INTERVAL,
+                        timeout_tick))
+    {
+        ATCommandResponseFrame* f =
+            parsing_api_frame.toFrameType<ATCommandResponseFrame>();
+
+        if (f->getFrameID() == tx_frame_id &&
+            strncmp(cmd, f->getATCommand(), 2) == 0)
+        {
+            memcpy(response, f, sizeof(ATCommandResponseFrame));
+            success = true;
+            break;
+        }
+    }
+
+    return success;
+}
+
+uint8_t Xbee::sendATCommandInternal(const char* cmd, uint8_t* params,
+                                    size_t params_len)
+{
+    return sendATCommandInternal(getNewFrameID(), cmd, params, params_len);
+}
+
+uint8_t Xbee::sendATCommandInternal(uint8_t tx_frame_id, const char* cmd,
+                                    uint8_t* params, size_t params_len)
+{
+    // Build the AT command
+    ATCommandFrame at;
+    at.setATCommand(cmd);
+    at.setFrameID(tx_frame_id);
+    if (params_len > 0)
+    {
+        if (params_len > MAX_AT_COMMAND_PARAMS_LENGTH)
+        {
+            LOG_ERR(logger, "AT Command payload too large, it was truncated");
+            params_len = MAX_AT_COMMAND_PARAMS_LENGTH;
+        }
+        at.setParameterSize(params_len);
+        memcpy(at.getCommandDataPointer(), params, params_len);
+    }
+
+    at.calcChecksum();
+
+    // Send it
+    writeFrame(at);
+
+    return tx_frame_id;
+}
+
+void Xbee::handleFrame(APIFrame& frame)
+{
+    // Set the timestamp to the frame
+    frame.timestamp = miosix::getTick();
+
+    switch (frame.frame_type)
+    {
+        case FTYPE_RX_PACKET_FRAME:
+        {
+            RXPacketFrame* pf = frame.toFrameType<RXPacketFrame>();
+            {
+                Lock<FastMutex> l(mutex_rx_frames);
+
+                if (rx_frames_buf.isFull())
+                {
+                    ++status.rx_dropped_buffers;
+                }
+                rx_frames_buf.put(*pf);
+                if (rx_frames_buf.count() > status.frame_buf_max_length)
+                {
+                    status.frame_buf_max_length = rx_frames_buf.count();
+                }
+            }
+            wakeReceiver();
+            break;
+        }
+        case FTYPE_MODEM_STATUS:
+        {
+            ModemStatusFrame* ms = frame.toFrameType<ModemStatusFrame>();
+
+            switch (ms->getStatus())
+            {
+                case MS_HARDWARE_RESET:
+                    LOG_DEBUG(logger, "Modem status: Hardware reset");
+                    break;
+                case MS_WATCHDOG_TIMER_RESET:
+                    LOG_DEBUG(logger, "Modem status: Watchdog timer reset");
+                    break;
+            }
+            break;
+        }
+        case FTYPE_TX_STATUS:
+        {
+            TXStatusFrame* ts     = frame.toFrameType<TXStatusFrame>();
+            status.last_tx_status = ts->getDeliveryStatus();
+            if (status.last_tx_status != DELS_SUCCESS)
+            {
+                status.last_tx_status_error = status.last_tx_status;
+            }
+            switch (status.last_tx_status)
+            {
+                case DELS_SUCCESS:
+                    break;
+                default:
+                    LOG_ERR(
+                        logger, "TX Status Error: {} (retries: {}, RD: {})",
+                        status.last_tx_status, ts->getTransmitRetryCount(),
+                        ts->getDiscoveryStatus() == 2 ? "Enabled" : "Disabled");
+                    break;
+            }
+            break;
+        }
+        default:
+            break;
+    }
+
+    // Pass the frame to the listener
+    if (frame_listener)
+    {
+        frame_listener(frame);
+    }
+}
+
+void Xbee::setOnFrameReceivedListener(OnFrameReceivedListener listener)
+{
+    frame_listener = listener;
+}
+
+uint8_t Xbee::getNewFrameID()
+{
+    uint8_t tx_frame_id = frame_id_counter++;
+
+    // Any value != 0 implies that we DO want a tx status response
+    if (frame_id_counter == 0)
+        frame_id_counter = 1;
+
+    return tx_frame_id;
+}
+
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/Xbee.h b/src/shared/drivers/Xbee/Xbee.h
index 163089b0b7745512d289c034ce17ecca519c0f5e..b84f062eec5f683971e028cc1f65046c7ce117c9 100644
--- a/src/shared/drivers/Xbee/Xbee.h
+++ b/src/shared/drivers/Xbee/Xbee.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2015-2019 Skyward Experimental Rocketry
- * Authors: Andrea Milluzzo, Artem Glukhov, Alvise de Faveri Tron, Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -22,594 +22,262 @@
 
 #pragma once
 
-#include <Common.h>
-#include <drivers/BusTemplate.h>
-#include <drivers/Transceiver.h>
-#include <algorithm>
-#include <vector>
-
+#include <diagnostic/PrintLogger.h>
 #include <miosix.h>
-#include "ActiveObject.h"
-#include "XbeeStatus.h"
-#include "diagnostic/StackLogger.h"
 
-using miosix::FastInterruptDisableLock;
-using miosix::FastInterruptEnableLock;
-using miosix::Thread;
+#include <functional>
+
+#include "XbeeStatus.h"
+#include "drivers/Transceiver.h"
+#include "drivers/Xbee/APIFrameParser.h"
+#include "drivers/spi/SPIDriver.h"
+#include "utils/collections/CircularBuffer.h"
 
-using miosix::ConditionVariable;
 using miosix::FastMutex;
-using miosix::Lock;
-using miosix::Unlock;
 
-using std::vector;
+#ifndef USE_MOCK_PERIPHERALS
+using GpioType = GpioPin;
+#else
+#include "utils/testutils/MockGpioPin.h"
+using GpioType = MockGpioPin;
+#endif
 
 namespace Xbee
 {
-// Constants
-
-// How often should we check if we received a transmit status response.
-static constexpr unsigned int SEND_STATUS_POLL_INTERVAL = 10;  // ms
-
-static constexpr uint16_t MAX_PAYLOAD_LEN = 0xFFFF;
-
-static constexpr uint8_t START_DELIMITER            = 0x7E;
-static constexpr uint64_t BROADCAST_ADDR            = 0xFFFF;
-static constexpr uint8_t ID_ACK_DISABLE             = 0x00;
-static constexpr uint8_t MAX_BROADCAST_HOPS         = 0x00;
-static constexpr uint8_t TX_STATUS_DELIVERY_SUCCESS = 0x00;
-
-// NOTE: 0x43 = Point-Multipoint, No Ack, No route discovery. See transmit
-// request frame bit options
-static constexpr uint8_t TRANSMIT_OPTIONS = 0x43;
-
-// Frame Sizes
-static constexpr uint8_t API_HEADER_SIZE = 3;
-
-static constexpr uint8_t TX_FRAME_HEADER_SIZE    = 14;
-static constexpr uint8_t RX_FRAME_HEADER_SIZE    = 12;
-static constexpr uint8_t CHECKSUM_SIZE           = 1;
-static constexpr uint8_t TX_STATUS_FRAME_SIZE    = 7;
-static constexpr uint8_t MODEM_STATUS_FRAME_SIZE = 2;
-
-// Bit definitions for status frame
-static constexpr uint8_t BIT_STATUS_RETRY_COUNT = 4;
-static constexpr uint8_t BIT_STATUS_DELIVERY    = 5;
-static constexpr uint8_t BIT_STATUS_DISCOVERY   = 6;
-
-// Waiting thread to be woken when something has been received or must be sent.
-// Defined extern so it can be accessed from multiple translation units
-extern miosix::Thread* waiting;
-
-/**
- * @brief Handler for the ATTN interrupt: wakes up the thread.
- * The ATTN (ATTention Needed) pin is raised when the transceiver has
- * received something.
- * Call this function from inside an IRQHandler, after you have
- * enabled the corresponding interrupt in the NVIC vector.
- */
-static void __attribute__((used)) handleATTNInterrupt()
-{
-    if (waiting)
-    {
-        // Wake
-        waiting->IRQwakeup();
-
-        if (waiting->IRQgetPriority() >
-            miosix::Thread::IRQgetCurrentThread()->IRQgetPriority())
-        {
-            miosix::Scheduler::IRQfindNextThread();
-        }
-
-        waiting = nullptr;
-    }
-}
-
-/**
- * WARNING: An IRQ linked with the ATTN pin of the Xbee module must be enabled
- * before using this class. See test/misc/xbee-bitrate for an example.
- */
-template <typename Bus, class CS, class ATTN, class RST>
-class Xbee : public Transceiver, public ActiveObject
+using ParseResult = APIFrameParser::ParseResult;
+
+static constexpr unsigned int FRAME_POLL_INTERVAL = 10;    // ms
+static constexpr unsigned int DEFAULT_TX_TIMEOUT  = 5000;  // ms
+
+// Size (in frames) of the receive circular buffer
+static constexpr unsigned int RX_FRAMES_BUF_SIZE = 3;
+
+class Xbee : public Transceiver
 {
 public:
-    Xbee(unsigned int send_timeout) : send_timeout(send_timeout)
-    {
-        reset();
-        miosix::Thread::sleep(10);
-
-        CS::low();
-        miosix::Thread::sleep(1);
-        CS::high();
-        miosix::Thread::sleep(1);
-    }
-
-    Xbee() : Xbee(1000) {}
-    /*
-     * Send a message through the XBee
-     * Blocks until the message is sent (successfully or not)
+    using OnFrameReceivedListener = std::function<void(APIFrame& frame)>;
+
+    /**
+     * @brief Constructs a new instance of the Xbee driver.
      *
-     * @param msg               Message to be sent
-     * @param pkt_len           Lenght of the message
-     * @return true             The message was sent correctly.
-     * @return false            There was an error sending the message.
+     * @param bus The SPI bus where the xbee is connected
+     * @param cs Xbee SPI Chip Select Pin
+     * @param attn Xbee ATTN Pin
+     * @param rst Xbee RST PIN
+     * @param tx_timeout How long to wait to for a TX Status
      */
-    bool send(uint8_t* msg, size_t msg_len) override
-    {
-        if (msg == nullptr || msg_len == 0)
-        {
-            return false;
-        }
-
-        // Create a TX API packet
-        vector<uint8_t> tx_pkt;
-        buildTxPacket(tx_pkt, msg, msg_len);
-
-        // Send the packet
-        setTxBuf(tx_pkt.data(), tx_pkt.size());
-
-        // Wake the runner thread in order to send the data
-        wakeThread();
-
-        // Wait until the XBee sends the message or a timeout expires
-        // It would be cool to have condition variables with timeout to avoid
-        // polling...
-        unsigned int timeout = 0;
-        while (!received_tx_status)
-        {
-            if (timeout > send_timeout)
-            {
-                // Timeout. Return error
-                TRACE("[Xbee] Send Timeout!\n");
-                ++status.tx_timeout_count;
-                return false;
-            }
-            Thread::sleep(SEND_STATUS_POLL_INTERVAL);
-            timeout += SEND_STATUS_POLL_INTERVAL;
-        }
-
-        received_tx_status = false;
-        if (status.tx_delivery_status != TX_STATUS_DELIVERY_SUCCESS)
-        {
-            TRACE("[Xbee] Error: %02X\n", status.tx_delivery_status);
-        }
-        return status.tx_delivery_status == TX_STATUS_DELIVERY_SUCCESS;
-    }
-
-    /*
-     * Wait for a new message to be received by the XBee
-     * @param rcv_buf Buffer to store the received message
-     * @param rcv_buf_len Maximum length of the message to be received
-     * @return true A message was successfully received
-     * @return false Received invalid message
+    Xbee(SPIBusInterface& bus, GpioType cs, GpioType attn, GpioType rst,
+         long long tx_timeout = DEFAULT_TX_TIMEOUT);
+
+    /**
+     * @brief Constructs a new instance of the Xbee driver.
+     *
+     * @param bus The SPI bus where the xbee is connected
+     * @param config Custom SPI bus configuration
+     * @param cs Xbee SPI Chip Select Pin
+     * @param attn Xbee ATTN Pin
+     * @param rst Xbee RST PIN
+     * @param tx_timeout How long to wait to for a TX Status
+     *
+     */
+    Xbee(SPIBusInterface& bus, SPIBusConfig config, GpioType cs, GpioType attn,
+         GpioType rst, long long tx_timeout = DEFAULT_TX_TIMEOUT);
+
+    ~Xbee();
+
+    /**
+     * @brief Sends a packet.
+     * The function blocks until the packet is sent to the peripheral, but does
+     * not wait for an ACK or send confirmation from the Xbee. Thus, it always
+     * returns true.
+     *
+     * @param pkt       Pointer to the packet (needs to be at least pkt_len
+     * bytes).
+     * @param pkt_len   Lenght of the packet to be sent.
+     * @return          Always true
      */
-    ssize_t receive(uint8_t* rcv_buf, size_t rcv_buf_len) override
-    {
-        Lock<FastMutex> l(rx_mutex);
-
-        while (rx_frame.size() == 0)
-        {
-            rx_cond.wait(l);
-        }
-
-        if (rx_frame.size() > rcv_buf_len)
-        {
-            return -1;
-        }
-        else
-        {
-            memcpy(rcv_buf, rx_frame.data(), rx_frame.size());
-            rcv_buf_len = rx_frame.size();
-
-            rx_frame.clear();
-
-            return rcv_buf_len;
-        }
-    }
-
-    void stop() override
-    {
-        should_stop = true;
-        wakeThread();
-        ActiveObject::stop();
-    }
-
-    XbeeStatus getStatus() { return status; }
-
-protected:
+    bool send(uint8_t* pkt, size_t pkt_len) override;
+
     /**
-     * Sends and receives data, puts the thread to sleep if nothing to
-     * send/receive.
+     * @brief Waits until a new packet is received.
+     *
+     * @param buf            Buffer to store the received packet into.
+     * @param buf_max_size   Maximum length of the received data.
+     * @return               Size of the data received or -1 if failure
      */
-    void run() override
-    {
-        while (!shouldStop())
-        {
-            // Wait for RX or TX request
-            {
-                miosix::FastInterruptDisableLock dLock;
-
-                // Check if we have data to send (tx_buf.size > 0) or receive
-                // (attn == 0) with disabled interrupts to avoid race conditions
-                if (ATTN::value() != 0 && tx_buf.size() == 0)
-                {
-                    // If we have nothing to receive or send, wait.
-                    waiting = miosix::Thread::getCurrentThread();
-
-                    while (waiting != 0)
-                    {
-                        waiting->IRQwait();
-                        {
-                            miosix::FastInterruptEnableLock eLock(dLock);
-                            miosix::Thread::yield();
-                        }
-                    }
-                }
-
-                StackLogger::getInstance()->updateStack(THID_XBEE);
-            }
-            // Transfer any data on the tx buffer and receive any incoming data
-            transferData();
-        }
-    }
+    ssize_t receive(uint8_t* buf, size_t buf_max_size) override;
 
-private:
-    enum class ParseResult : uint8_t
-    {
-        IDLE,
-        PARSING,
-        SUCCESS,
-        FAIL
-    };
-
-    enum class ParserState
-    {
-        FIND_START,
-        READ_LENGTH_1,
-        READ_LENGTH_2,
-        READ_FRAME,
-        READ_CHECKSUM
-    };
-
-    enum FrameType : uint8_t
-    {
-        FRAMETYPE_AT_COMMAND                    = 0x08,
-        FRAMETYPE_AT_QUEUE_PARAM_VALUE          = 0x09,
-        FRAMETYPE_TX_REQUEST                    = 0x10,
-        FRAMETYPE_EXPICIT_TX_REQUEST            = 0x11,
-        FRAMETYPE_REMOTE_AT_REQUEST             = 0x17,
-        FRAMETYPE_AT_COMMAND_RESPONSE           = 0x88,
-        FRAMETYPE_MODEM_STATUS                  = 0x8A,
-        FRAMETYPE_TRANSMIT_STATUS               = 0x8B,
-        FRAMETYPE_ROUTE_INFO_PACKET             = 0x8D,
-        FRAMETYPE_AGGREGATE_ADRESSING_UPDATE    = 0x8E,
-        FRAMETYPE_RX_PACKET                     = 0x90,
-        FRAMETYPE_EXPLICIT_RX_PACKET            = 0x91,
-        FRAMETYPE_DATA_SAMPLE_RX_INDICATOR      = 0x92,
-        FRAMETYPE_NODE_IDENTIFICATION_INDICATOR = 0x95,
-        FRAMETYPE_REMOTE_COMMAND_RESPONSE       = 0x97
-    };
-
-    void reset()
-    {
-        RST::mode(miosix::Mode::OPEN_DRAIN);
-        RST::low();
-        miosix::delayUs(500);
-        RST::high();
-    }
+    /**
+     * @brief Hardware resets the Xbee.
+     */
+    void reset();
 
     /**
-     * Wake the AO thread from another thread.
-     * Cannot be called with disabled interrupts.
+     * @brief Signals the receive() function that there is new data available.
+     * Call this from the ATTN pin interrupt servirce routine, and nowhere else
+     * plese.
+     *
      */
-    void wakeThread()
-    {
-        FastInterruptDisableLock dLock;
+    void handleATTNInterrupt();
 
-        if (waiting)
-        {
-            waiting->IRQwakeup();
-            waiting = nullptr;
-        }
-    }
+    /**
+     * @brief Wakes the receive function without needing an interrupt
+     *
+     * @param force_return Wether receive(..) should return even if it has not
+     * received any data
+     */
+    void wakeReceiver(bool force_return = false);
 
     /**
-     * Transfer data to/from the Xbee.
-     * Performs a full duplex transaction.
+     * @brief Sends an AT Command to the Xbee (see datasheet) without waiting
+     * for a response
+     *
+     * @param cmd Two character string with the name of the command (eg "DB")
+     * @param params Optional command parameters
+     * @param params_len Length in bytes of the params array
      */
-    void transferData()
-    {
-        ParseResult result = ParseResult::IDLE;
-
-        CS::low();
-        vector<uint8_t> data;
-
-        {
-            // Make a local copy of the TX buffer
-            FastInterruptDisableLock dLock;
-            data = tx_buf;
-
-            // Clear the tx buffer
-            tx_buf.clear();
-        }
-
-        // If there is data to send
-        if (data.size() > 0)
-        {
-            // Full duplex transfer, the data vector is replaced with received
-            // data, if any.
-            Bus::transfer(data.data(), data.size());
-
-            // Parse the received data
-            for (uint8_t rx : data)
-            {
-                result = parse(rx);
-
-                // We received something
-                if (result == ParseResult::SUCCESS)
-                {
-                    handleFrame(parser_buf);
-                }
-            }
-
-            // If there's nothing more to parse, return
-            if (result != ParseResult::PARSING)
-            {
-                CS::high();
-                return;
-            }
-
-            // If there is more data to be received, continue below.
-        }
-
-        // Read until we have received a packet (or no packet is found)
-        do
-        {
-            result = parse(Bus::read());
-        } while (result == ParseResult::PARSING);
-
-        if (result == ParseResult::SUCCESS)
-        {
-            handleFrame(parser_buf);
-        }
-        else if (result == ParseResult::FAIL)
-        {
-            TRACE("[Xbee] Read failed. Parser result: %d\n", (int)result);
-        }
-
-        CS::high();
-    }
+    void sendATCommand(const char* cmd, uint8_t* params = nullptr,
+                       size_t params_len = 0);
 
     /**
-     * Parse received data one byte at a time, storing it in the parser_buf.
-     * When a full packet is received, returns ParseResult::SUCCESS.
-     * Returns ParseResult::FAIL if a full packet is received but checksum
-     * verification fails.
-     * In both cases, the frame is stored in parser_buf.
+     * @brief Sends an AT Command to the Xbee and wait for a response (see
+     * datasheet)
      *
-     * Returns ParseResult::IDLE if no frame start delimiter has been found yet.
-     * Returns ParseResult::PARSING if a start delimiter was found and a packet
-     * is being parsed.
+     * @param cmd Two character string with the name of the command (eg "DB")
+     * @param params Optional command parameters
+     * @param params_len Length in bytes of the params array
+     * @param response Where to store the response
+     * @param timeout Maximum time to wait for the response
+     * @return true if response received before the timeout, false otherwise
+     */
+    bool sendATCommand(const char* cmd, ATCommandResponseFrame* response,
+                       uint8_t* params = nullptr, size_t params_len = 0,
+                       unsigned int timeout = 1000);
+
+    /**
+     * @brief Set the frame received listener, called each time a new APIFrame
+     * is received from the device
      *
-     * @param byte byte to be parsed
-     * @return
+     * @param listener The listener
      */
-    ParseResult parse(uint8_t byte)
-    {
-        switch (parser_state)
-        {
-            // Look for the start of frame delimiter
-            case ParserState::FIND_START:
-                if (byte == START_DELIMITER)
-                {
-                    parser_state = ParserState::READ_LENGTH_1;
-
-                    // Frame start found, clear old rx buf
-                    parser_buf.clear();
-                    parser_packet_length = 0;
-                }
-                else
-                {
-                    return ParseResult::IDLE;
-                }
-                break;
-            // Read most significant byte of the length
-            case ParserState::READ_LENGTH_1:
-                parser_packet_length = byte << 8;
-                parser_state         = ParserState::READ_LENGTH_2;
-                break;
-            // Read least significant byte of the length
-            case ParserState::READ_LENGTH_2:
-                parser_packet_length += byte;
-
-                parser_state = ParserState::READ_FRAME;
-
-                // Now that we know how long the packet is, reserve memory to
-                // store it
-                parser_buf.reserve(parser_packet_length);
-                break;
-            // Read the data frame
-            case ParserState::READ_FRAME:
-                parser_buf.push_back(byte);
-
-                if (parser_buf.size() == parser_packet_length)
-                {
-                    parser_state = ParserState::READ_CHECKSUM;
-                }
-                break;
-            // Read & verify checksum
-            case ParserState::READ_CHECKSUM:
-                parser_state = ParserState::FIND_START;
-
-                if (verifyChecksum(parser_buf, byte))
-                {
-                    return ParseResult::SUCCESS;
-                }
-                else
-                {
-                    ++status.rx_wrong_checksum;
-                    TRACE("[Xbee] Rx checksum verification failed\n");
-                    return ParseResult::FAIL;
-                }
-                break;
-        }
-
-        return ParseResult::PARSING;
-    }
-
-    void handleFrame(const vector<uint8_t>& frame)
-    {
-        if (frame.size() == 0)
-        {
-            return;
-        }
-        // The first byte indicates the frame type
-        switch (frame[0])
-        {
-            case FRAMETYPE_RX_PACKET:
-            {
-                size_t payload_size = frame.size() - RX_FRAME_HEADER_SIZE;
-
-                if (payload_size > 0)
-                {
-                    {
-                        Lock<FastMutex> l(rx_mutex);
-                        rx_frame.clear();
-                        rx_frame.insert(rx_frame.end(),
-                                        frame.begin() + RX_FRAME_HEADER_SIZE,
-                                        frame.end());
-                    }
-                    rx_cond.signal();
-                }
-                break;
-            }
-            case FRAMETYPE_TRANSMIT_STATUS:
-            {
-                if (frame.size() == TX_STATUS_FRAME_SIZE)
-                {
-                    status.tx_retry_count      = frame[BIT_STATUS_RETRY_COUNT];
-                    status.tx_delivery_status  = frame[BIT_STATUS_DELIVERY];
-                    status.tx_discovery_status = frame[BIT_STATUS_DISCOVERY];
-
-                    received_tx_status = true;
-                }
-                else
-                {
-                    TRACE("[Xbee] Wrong TX status frame size.\n");
-                }
-                break;
-            }
-            case FRAMETYPE_MODEM_STATUS:
-            {
-                if (frame.size() == MODEM_STATUS_FRAME_SIZE)
-                {
-                    TRACE("[Xbee] Modem status: %d\n", frame[1]);
-                }
-                else
-                {
-                    TRACE("[Xbee] Wrong MODEM status frame size.\n");
-                }
-                break;
-            }
-            default:
-                break;
-        }
-    }
-
-    void buildTxPacket(vector<uint8_t>& tx_pkt, uint8_t* msg, size_t msg_len)
-    {
-        tx_pkt.reserve(API_HEADER_SIZE + TX_FRAME_HEADER_SIZE + msg_len +
-                       CHECKSUM_SIZE);
-
-        tx_pkt.push_back(START_DELIMITER);
-
-        uint16_t frame_len = msg_len + TX_FRAME_HEADER_SIZE;
-        // invert pkt_len bytes order
-        tx_pkt.push_back((frame_len & 0xff00) >> 8);
-        tx_pkt.push_back(frame_len & 0xff);
-
-        tx_pkt.push_back(FRAMETYPE_TX_REQUEST);
-
-        // Any value != 0 indicates that we want a tx status response
-        tx_pkt.push_back(0x01);
-
-        // Split the address in bytes & add to the buffer
-        uint8_t addr_buffer[8];
-        memcpy(addr_buffer, &BROADCAST_ADDR, 8);
-
-        for (int i = 7; i >= 0; i--)
-        {
-            tx_pkt.push_back(addr_buffer[i]);
-        }
-
-        // Reserved bytes
-        tx_pkt.push_back(0xff);
-        tx_pkt.push_back(0xfe);
-
-        tx_pkt.push_back(MAX_BROADCAST_HOPS);
-
-        tx_pkt.push_back(TRANSMIT_OPTIONS);
-
-        // payload
-        for (uint32_t i = 0; i < msg_len; i++)
-        {
-            tx_pkt.push_back(*(msg + i));
-        }
-
-        // Calculate & add checksum
-        uint32_t checksum = 0;
-        for (uint32_t i = 3; i < tx_pkt.size(); i++)
-        {
-            checksum += tx_pkt.at(i);
-        }
-
-        tx_pkt.push_back(0xff - (checksum & 0xff));
-    }
+    void setOnFrameReceivedListener(OnFrameReceivedListener listener);
 
+    XbeeStatus getStatus();
+
+private:
     /**
-     * Set the tx buffer with the provided data.
+     * @brief Handles an API Frame
+     * @attention  mutex_xbee_comm must be locked before calling this function.
      */
-    void setTxBuf(uint8_t* buf, size_t size)
-    {
-        // We need to disable interrupts (instead of using a mutex) becouse we
-        // need to synchronize the access to the tx buf in the waiting thread
-        // before putting it to sleep (which requires disabling interrupts)
-        FastInterruptDisableLock dLock;
-
-        // Copy buf into tx_buf, removing old content
-        tx_buf.clear();
-        tx_buf.insert(tx_buf.end(), buf, buf + size);
-    }
-
-    bool verifyChecksum(vector<uint8_t> frame, uint8_t checksum)
-    {
-        // Sum all the bytes including checksum.
-        // The sum can be stored in a uint8_t since we only care about the least
-        // significant bits.
-        uint8_t sum = checksum;
-        for (size_t i = 0; i < frame.size(); ++i)
-        {
-            sum += frame[i];
-        }
-        return sum == 0xFF;
-    }
-
-    // How long to wait for a transmit status response.
-    unsigned int send_timeout;
-
-    vector<uint8_t> tx_buf;
-
-    bool received_tx_status = false;
-
-    FastMutex rx_mutex;
-    vector<uint8_t> rx_frame;
-    ConditionVariable rx_cond;
-
-    ParserState parser_state    = ParserState::FIND_START;
-    size_t parser_packet_length = 0;
-    vector<uint8_t> parser_buf;
+    void handleFrame(APIFrame& frame);
 
+    /**
+     * @brief Sends an AT command to the devices
+     * @attention  mutex_xbee_comm must be locked before calling this function.
+     */
+    uint8_t sendATCommandInternal(const char* cmd, uint8_t* params = nullptr,
+                                  size_t params_len = 0);
+
+    /**
+     * @brief Sends an AT command to the devices
+     * @attention  mutex_xbee_comm must be locked before calling this function.
+     */
+    uint8_t sendATCommandInternal(uint8_t frame_id, const char* cmd,
+                                  uint8_t* params   = nullptr,
+                                  size_t params_len = 0);
+
+    uint8_t buildTXRequestFrame(TXRequestFrame& tx_req, uint8_t* pkt,
+                                size_t pkt_len);
+
+    /**
+     * @brief Fills the provided buffer with data contained in rx_frames_buf
+     */
+    size_t fillReceiveBuf(uint8_t* buf, size_t buf_max_size);
+
+    /**
+     * @brief Reads a single frame from the SPI bus.
+     * @attention mutex_xbee_comm must be locked before calling this function.
+     */
+    ParseResult readOneFrame();
+
+    /**
+     * @brief Reads the SPI bus until an RXPacketFrame is encountered or no more
+     * data is available
+     * @attention  mutex_xbee_comm must be locked before calling this function.
+     *
+     * @return true RXPacketFrame received
+     * @return false No more data available on SPI and RXPacketFrame not
+     * received
+     */
+    bool readRXFrame();
+
+    /**
+     * @brief Writes a frame from the SPI bus.
+     * @attention  mutex_xbee_comm must be locked before calling this function.
+     */
+    void writeFrame(APIFrame& frame);
+
+    uint8_t getNewFrameID();
+
+    /**
+     * @brief Waits until an APIFrame with frame type \p frame_type is received
+     * on the SPI or until the timeouts expires. The frame is then stored in the
+     * parsing_api_frame local class varibale. Any frame received by this
+     * function is automatically passed to the handleFrame() function
+     * @attention mutex_xbee_comm must be locked before calling this function.
+     *
+     * @param poll_interval Polling interval for the attn pin in milliseconds
+     * @param timeout_tick Tick after which the function should stop waiting for
+     * frames
+     * @return true If a frame with type equal to \p frame_type is received
+     * @return false If the timeout_tick reached
+     */
+    bool waitForFrame(uint8_t frame_type, unsigned int poll_interval,
+                      long long timeout_tick);
+
+    // Synchronizes all communications to the xbee, as they may happen on
+    // multiple threads
+    FastMutex mutex_xbee_comm;
+
+    APIFrameParser parser;
+    // Frame being parsed. Only valid if last call to
+    // parser.parse() returned ParseResult::SUCCESS
+    APIFrame parsing_api_frame;
+
+    // Temporary storage for RX packet frames, waiting to be returned by
+    // receive()
+    CircularBuffer<RXPacketFrame, RX_FRAMES_BUF_SIZE> rx_frames_buf;
+    FastMutex mutex_rx_frames;
+    // RX Packet currently being returned by receive()
+    RXPacketFrame curr_rx_frame;
+    // Index of the first byte of the payload of curr_rx_frame that will be
+    // returned on the next call to receive()
+    int curr_rx_payload_ptr = -1;
+
+    // SPI defs
+    SPISlave spi_xbee;
+    GpioType attn;
+    GpioType rst;
+
+    // How long to wait for a TX Status
+    long long tx_timeout;
+
+    OnFrameReceivedListener frame_listener;
+
+    // Used to generate a unique frame id
+    uint8_t frame_id_counter = 1;
+
+    // Forces receive() to return even if there is no new data
+    bool force_rcv_return = false;
+
+    // Status structs
     XbeeStatus status;
-};  // namespace Xbee
+    Stats time_to_send_stats;
+
+    // Waiting thread to be woken when something has been received.
+    miosix::Thread* receive_thread = 0;
+
+    PrintLogger logger = Logging::getLogger("xbee");
+};
 
 }  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/XbeeStatus.h b/src/shared/drivers/Xbee/XbeeStatus.h
index a3bfcaa44f8e38d951da2aa1bd1b48b2cb296d71..8a246debcd1cb8dad6217e8baebdfd3638c5ca08 100644
--- a/src/shared/drivers/Xbee/XbeeStatus.h
+++ b/src/shared/drivers/Xbee/XbeeStatus.h
@@ -1,20 +1,19 @@
-/* 
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -25,27 +24,43 @@
 
 #include <cstdint>
 #include <cstdio>
+#include <ostream>
+#include <string>
 
+#include "math/Stats.h"
 namespace Xbee
 {
-    struct XbeeStatus
+struct XbeeStatus
+{
+    long long timestamp = 0LL;
+
+    uint8_t last_tx_status_error = 0;
+    uint8_t last_tx_status       = 0;
+
+    StatsResult time_to_send_stats;
+
+    unsigned int tx_timeout_count = 0;
+
+    unsigned int rx_dropped_buffers = 0;
+
+    unsigned int frame_buf_max_length = 0;
+
+    static std::string header()
+    {
+        return "timestamp,last_tx_status_error,last_tx_status,tts_stats.min,"
+               "tts_stats.max,tts_stats.mean,tts_stats.stddev,tts_stats.n_"
+               "samples,tx_timeout_count,rx_dropped_buffers,frame_buf_max_"
+               "length\n";
+    }
+
+    void print(std::ostream& os) const
     {
-        uint8_t tx_retry_count = 0;
-        uint8_t tx_delivery_status = 0;
-        uint8_t tx_discovery_status = 0;
-        unsigned int tx_timeout_count = 0;
-
-        unsigned int rx_dropped_buffers = 0;
-        unsigned int rx_wrong_checksum = 0;
-
-        void print()
-        {
-            TRACE("+++XBEE STATUS+++");
-            TRACE("TX: Timeouts: %d\n", tx_timeout_count);
-            TRACE("TXSTATUS: Retries: %d, Delivery: %02X, Discovery: %02X\n",
-                   tx_retry_count, tx_delivery_status, tx_discovery_status);
-            TRACE("RX: Dropped: %d, Checksum: %d\n", rx_dropped_buffers, rx_wrong_checksum);
-                
-        }
-    };
-}
\ No newline at end of file
+        os << timestamp << "," << (int)last_tx_status_error << ","
+           << (int)last_tx_status << "," << time_to_send_stats.minValue << ","
+           << time_to_send_stats.maxValue << "," << time_to_send_stats.mean
+           << "," << time_to_send_stats.stdev << ","
+           << time_to_send_stats.nSamples << "," << tx_timeout_count << ","
+           << rx_dropped_buffers << "," << frame_buf_max_length << "\n";
+    }
+};
+}  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/adc/AD7994Data.h b/src/shared/drivers/adc/AD7994Data.h
deleted file mode 100644
index d921ed9196609ad4a5c7abc2414671e935d72afa..0000000000000000000000000000000000000000
--- a/src/shared/drivers/adc/AD7994Data.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef SRC_SHARED_DRIVERS_ADC_AD7994DATA_H
-#define SRC_SHARED_DRIVERS_ADC_AD7994DATA_H
-
-#include <cstdint>
-#include <ostream>
-#include <string>
-
-struct AD7994Sample
-{
-    long long timestamp;
-
-    uint8_t channel_id; // [1-4]
-    bool alert_flag;
-    uint16_t value;
-
-    // Functions used to deserialize the binary logs into csv files
-
-    static std::string header() { return "timestamp,ch_id,value,alert_flag\n"; }
-
-    void print(std::ostream& os) const
-    {
-        os << timestamp << "," << channel_id << "," << value << ","
-           << (int)alert_flag << "\n";
-    }
-};
-
-#endif
\ No newline at end of file
diff --git a/src/shared/drivers/adc/ADC.h b/src/shared/drivers/adc/ADC.h
deleted file mode 100644
index 4eaa76818c779336ac117475f8555c5b9add9fc8..0000000000000000000000000000000000000000
--- a/src/shared/drivers/adc/ADC.h
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Luca Erbetta
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-#pragma once
-#include "Common.h"
-
-/**
- * @brief Driver for stm32 internal ADC.
- * Allows for conversion on multiple channels, one at a time (no SCAN mode),
- * with per-channel sample time.
- * This class is not thread-safe. All conversions must be executed on the same
- * thread.
- * @tparam ADC_num Which ADC to use: [1,2,3] on stm32f429zi
- */
-template <unsigned ADC_num>
-class SensorADC
-{
-public:
-    /**
-     * @brief One ADC channel
-     */
-    enum class Channel : uint8_t
-    {
-        CH1 = 1,
-        CH2,
-        CH3,
-        CH4,
-        CH5,
-        CH6,
-        CH7,
-        CH8,
-        CH9,
-        CH10,
-        CH11,
-        CH12,
-        CH13,
-        CH14,
-        CH15,
-        CH16,
-        CH17,
-        CH18,
-        CH19
-    };
-
-    /**
-     * @brief Conversion sample time. See datasheet.
-     * 
-     */
-    enum class SampleTime : uint8_t
-    {
-        CYCLES_3   = 0x0,
-        CYCLES_15  = 0x1,
-        CYCLES_28  = 0x2,
-        CYCLES_56  = 0x3,
-        CYCLES_84  = 0x4,
-        CYCLES_112 = 0x5,
-        CYCLES_144 = 0x6,
-        CYCLES_480 = 0x7
-    };
-
-    SensorADC()
-    {
-        adc = getADC(ADC_num);
-        enableADC();
-
-        adc->SQR1 = 0x0;  // One conversion
-
-        adc->CR2 = ADC_CR2_ADON;
-        // CCR pre scaler
-        ADC->CCR = 0x3 << 16;
-    }
-
-    ~SensorADC() { disableADC(); }
-
-    /**
-     * @brief Converts the specified channel
-     * 
-     * @param channel The channel to convert
-     * @param sample_time Conversion sample time
-     * @return uint16_t Measured value (12 bits)
-     */
-    uint16_t convertChannel(Channel channel,
-                            SampleTime sample_time = SampleTime::CYCLES_15)
-    {
-        // Select the channel
-        adc->SQR3 = static_cast<uint32_t>(channel);
-        // Set sample time
-        adc->SMPR2 = static_cast<uint32_t>(sample_time);
-
-        // Start conversion
-        adc->CR2 |= ADC_CR2_SWSTART;
-
-        // Wait for conversion to finish
-        const int TIMEOUT = 50;  // Make sure the loop terminates eventually
-        int i             = 0;
-        while ((adc->SR & ADC_SR_EOC) == 0 && i < TIMEOUT)
-        {
-            ++i;
-        }
-        adc->SR = 0; // Clear the status register
-
-        // Conversion terminated, read result
-        return static_cast<uint16_t>(adc->DR & 0x0000FFFF);
-    }
-
-private:
-    void enableADC()
-    {
-        miosix::FastInterruptDisableLock dLock;
-
-        if (adc == ADC1)
-        {
-            RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-        }
-        else if (adc == ADC2)
-        {
-            RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
-        }
-        else if (adc == ADC3)
-        {
-            RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
-        }
-
-        RCC_SYNC();
-    }
-
-    void disableADC()
-    {
-        miosix::FastInterruptDisableLock dLock;
-
-        if (adc == ADC1)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
-        }
-        else if (adc == ADC2)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN;
-        }
-        else if (adc == ADC3)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN;
-        }
-
-        RCC_SYNC();
-    }
-
-    constexpr ADC_TypeDef* getADC(unsigned n)
-    {
-        return n == 1 ? ADC1 : n == 2 ? ADC2 : ADC3;
-    }
-
-    ADC_TypeDef* adc;
-};
diff --git a/src/shared/drivers/adc/ADS1118/ADS1118.cpp b/src/shared/drivers/adc/ADS1118/ADS1118.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f19aeb1cbb548ef943a68b39bb1adb8516f5e45d
--- /dev/null
+++ b/src/shared/drivers/adc/ADS1118/ADS1118.cpp
@@ -0,0 +1,318 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "ADS1118.h"
+
+#include <interfaces/endianness.h>
+
+#include "TimestampTimer.h"
+
+const ADS1118::ADS1118Config ADS1118::ADS1118_DEFAULT_CONFIG = {
+    SINGLE_SHOT_MODE, FSR_2_048,  MUX_AIN0_AIN1, 0,     0,
+    VALID_OPERATION,  PULL_UP_EN, ADC_MODE,      DR_128};
+
+ADS1118::ADS1118(SPIBusInterface &bus, GpioPin cs, ADS1118Config config_,
+                 SPIBusConfig spiConfig)
+    : ADS1118(SPISlave(bus, cs, spiConfig), config_, false)
+{
+}
+
+ADS1118::ADS1118(SPISlave spiSlave_, ADS1118Config config_, bool busyWait_,
+                 int16_t tempDivider_)
+    : spiSlave(spiSlave_), baseConfig(config_), busyWait(busyWait_),
+      tempDivider(tempDivider_)
+
+{
+    // Initialize to 0 all the channels
+    for (auto i = 0; i < NUM_OF_CHANNELS; i++)
+    {
+        channelsConfig[i].word = 0;
+
+        // Set channel id for each ADS1118Data object in values array
+        values[i].channel_id = i;
+    }
+
+    // Reset the last written config value
+    lastConfig.word = 0;
+    lastConfigIndex = 0;
+}
+
+SPIBusConfig ADS1118::getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.clock_div = SPIClockDivider::DIV32;
+    spiConfig.mode      = SPIMode::MODE1;
+    return spiConfig;
+}
+
+bool ADS1118::init() { return true; }
+
+void ADS1118::enableInput(ADS1118Mux mux)
+{
+    enableInput(mux, baseConfig.bits.rate, baseConfig.bits.pga);
+}
+
+void ADS1118::enableInput(ADS1118Mux mux, ADS1118DataRate rate, ADS1118Pga pga)
+{
+    channelsConfig[mux].bits.mode       = baseConfig.bits.mode;
+    channelsConfig[mux].bits.pga        = pga;
+    channelsConfig[mux].bits.mux        = mux;
+    channelsConfig[mux].bits.singleShot = 1;
+    channelsConfig[mux].bits.noOp       = VALID_OPERATION;
+    channelsConfig[mux].bits.pullUp     = baseConfig.bits.pullUp;
+    channelsConfig[mux].bits.tempMode   = ADC_MODE;
+    channelsConfig[mux].bits.rate       = rate;
+
+    // Decrement the sample counter in order to read the temperature earlier on
+    sampleCounter--;
+}
+
+void ADS1118::disableInput(ADS1118Mux mux) { channelsConfig[mux].word = 0; }
+
+void ADS1118::disableAllInputs()
+{
+    for (auto i = 0; i < NUM_OF_CHANNELS; i++)
+    {
+        channelsConfig[i].word = 0;
+    }
+}
+
+void ADS1118::enableTemperature()
+{
+    channelsConfig[TEMP_CHANNEL].word = TEMP_CONFIG;
+}
+
+void ADS1118::disableTemperature() { channelsConfig[TEMP_CHANNEL].word = 0; }
+
+void ADS1118::enablePullUpResistor() { baseConfig.bits.pullUp = PULL_UP_EN; }
+
+void ADS1118::disablePullUpResistor() { baseConfig.bits.pullUp = PULL_UP_DIS; }
+
+void ADS1118::enableConfigCheck() { configCheck = true; }
+
+void ADS1118::disableConfigCheck() { configCheck = false; }
+
+ADS1118Data ADS1118::readInputAndWait(ADS1118Mux mux)
+{
+    readChannel(mux);
+    return getVoltage(mux);
+}
+
+TemperatureData ADS1118::readTemperatureAndWait()
+{
+    readChannel(TEMP_CHANNEL);
+    return getTemperature();
+}
+
+ADS1118Data ADS1118::getVoltage(ADS1118Mux mux) { return values[mux]; }
+
+TemperatureData ADS1118::getTemperature()
+{
+    return TemperatureData{values[TEMP_CHANNEL].adc_timestamp,
+                           values[TEMP_CHANNEL].voltage};
+}
+
+int ADS1118::getConversionTime(int8_t channel)
+{
+    if (channel >= 0 && channel <= TEMP_CHANNEL)
+    {
+        return CONV_TIME[channelsConfig[channel].bits.rate];
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+bool ADS1118::selfTest()
+{
+    // Save the current configuration
+    bool prevConfigCheck    = configCheck;
+    uint16_t prevTempConfig = channelsConfig[TEMP_CHANNEL].word;
+
+    // Enable temperature config in case it is not, just in case no other
+    // configuration is enabled
+    channelsConfig[TEMP_CHANNEL].word = TEMP_CONFIG;
+
+    // Enable configuration check
+    configCheck = true;
+
+    // Check the communication by reading the temperature channel
+    readChannel(TEMP_CHANNEL, INVALID_CHANNEL);
+
+    // Restore the configuration
+    configCheck                       = prevConfigCheck;
+    channelsConfig[TEMP_CHANNEL].word = prevTempConfig;
+
+    // Return false if an error occurred
+    return last_error == 0;  // The sensor class misses a constant for no error
+}
+
+/**
+ * The sampling of all the enabled inputs is performed in sequence starting from
+ * the input with the lowest mux value.
+ *
+ * The first configuration is written and then, at the next call, read back the
+ * result while transmitting the next configuration
+ */
+ADS1118Data ADS1118::sampleImpl()
+{
+    int8_t i = findNextEnabledChannel(lastConfigIndex + 1);
+
+    // Write the next config and read the value (only if lastConfig is valid)
+    readChannel(i, lastConfig.word != 0 ? lastConfigIndex : INVALID_CHANNEL);
+
+    // Save index and config for the next read
+    lastConfig.word = channelsConfig[i].word;
+    lastConfigIndex = i;
+
+    // Increment the sample counter
+    sampleCounter++;
+
+    // Regardless of the readChannel result, return the value stored
+    return values[lastConfigIndex];
+}
+
+void ADS1118::readChannel(int8_t nextChannel, int8_t prevChannel)
+{
+    int16_t rawValue;
+    uint32_t writeData, transferData;
+
+    last_error = NO_ERRORS;
+
+    // Prepare the next configuration data
+    if (nextChannel > INVALID_CHANNEL && nextChannel < NUM_OF_CHANNELS)
+    {
+        writeData = channelsConfig[nextChannel].word;
+    }
+    else
+    {
+        // A valid configuration will always be not equal to 0 since the valid
+        // operation bits must be 0b01
+        writeData = 0x0;
+    }
+
+    // Write next configuration and read previous value if necessary
+    transferData = writeData;
+    {
+        SPITransaction transaction(spiSlave);
+        transaction.transfer((uint8_t *)&transferData, configCheck ? 4 : 2);
+    }
+
+    // If enabled and a valid configuration has just been written, check the
+    // read back configuration
+    if (configCheck && writeData)
+    {
+        // Compare the configuration with the second 16 bit word read
+        if ((channelsConfig[nextChannel].word & CONFIG_MASK) !=
+            (transferData >> 16 & CONFIG_MASK))
+        {
+            // Save the error
+            last_error = BUS_FAULT;
+
+            // Disable next value conversion
+            lastConfig.word = 0;
+        }
+    }
+
+    // Convert and save the value if last written configuration is valid
+    if (prevChannel >= 0)
+    {
+        rawValue = swapBytes16(transferData);
+
+        // TODO: the timestamp should be taken when the configuration is
+        // written, now we could be reading the value after some time!
+        values[prevChannel].adc_timestamp = TimestampTimer::getTimestamp();
+
+        if (prevChannel != 8)  // Voltage value
+        {
+            values[prevChannel].voltage =
+                rawValue * PGA_LSB_SIZE[channelsConfig[prevChannel].bits.pga] /
+                1000;
+        }
+        else  // Temperature value
+        {
+            values[TEMP_CHANNEL].voltage = (rawValue / 4) * TEMP_LSB_SIZE;
+        }
+    }
+}
+
+/**
+ * To read an input we'll first write the appropriate configuration, wait for
+ * the sample accordingly to the channel data rate and then read back the result
+ */
+void ADS1118::readChannel(int8_t channel)
+{
+    readChannel(channel, INVALID_CHANNEL);
+
+    if (busyWait)
+    {
+        // Use a busy wait loop to be as precise as possible
+        miosix::delayUs(getConversionTime(channel));
+    }
+    else
+    {
+        // Converto to milliseconds and increment by one to prevent premature
+        // readings
+        miosix::Thread::sleep(getConversionTime(channel) / 1000 + 1);
+    }
+
+    readChannel(INVALID_CHANNEL, channel);
+}
+
+/**
+ * Search the next enabled channel starting from the specified channel and
+ * returns it's index. If no channels are enabled return INVALID_CHANNEL
+ */
+int8_t ADS1118::findNextEnabledChannel(int8_t startChannel)
+{
+    int8_t &channel = startChannel;  // Just a change of name
+
+    for (auto i = 0; i < 2; i++)
+    {
+        // Go to the first channel if channel is too big
+        if (channel >= NUM_OF_CHANNELS)
+        {
+            channel = 0;
+        }
+
+        // Find next enabled mux config
+        for (; channelsConfig[channel].word == 0 && channel < NUM_OF_CHANNELS;
+             channel++)
+            ;
+
+        // Check if the channel is valid and, for the temperature channel, if we
+        // have to read it based on sampleCounter and tempDivider
+        // If invalid try to search again starting from the first channel
+        if (channel == TEMP_CHANNEL && sampleCounter % tempDivider != 0)
+        {
+            continue;
+        }
+        if (channel < NUM_OF_CHANNELS)
+        {
+            return channel;
+        }
+    }
+
+    // If no valid channel has been fount return an invalid channel
+    return INVALID_CHANNEL;
+}
\ No newline at end of file
diff --git a/src/shared/drivers/adc/ADS1118/ADS1118.h b/src/shared/drivers/adc/ADS1118/ADS1118.h
new file mode 100644
index 0000000000000000000000000000000000000000..25c600e0c53f1adc66cc31b20aad6b50d98d8d02
--- /dev/null
+++ b/src/shared/drivers/adc/ADS1118/ADS1118.h
@@ -0,0 +1,368 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/**
+ * @brief Driver for ADS1118 adc
+ *
+ * The ADS1118 is a 16-bit delta-sigma analog-to-digital converter.
+ * It measures the difference of two inputs configured by a MUX. The device
+ * allows also to read each of the four pins in reference to GND. A temperature
+ * sensor is also included.
+ *
+ * The communication uses the spi protocol and the maximum allowed frequency is
+ * 4MHz. Configuration is applied by writing a 16bit value to the device.
+ * Sampled voltages or temperature readings are obtained also by reading a 16bit
+ * value while writing the configuration.
+ *
+ * Data rate between 8Hz and 860Hz can be programmed and an internal
+ * programmable gain aplifier can be set with a sensitivity range from ±0.256V
+ * to ±6.144V (note that the inputs must remain between VCC or GND).
+ *
+ * The data rate should be choosen as low as possible to allow the delta-sigma
+ * adc to average the input voltage (this allows a less noisy reading).
+ *
+ * The device can work in two modes:
+ * - CONTIN_CONV_MODE: Continuosly read the last configured channel, when you
+ * make a read you'll obtain the lates reading
+ * - SINGLE_SHOT_MODE: A single conversion is performed when the configuration
+ * is written
+ *
+ * The ADS1118 is a simple device, it has a single data register where it can
+ * store the reading, therefore it can sample a single input at a time. sample()
+ * cylces through the enabled channels one at a time and writes it's
+ * configuration while reading the value of the previous written one.
+ *
+ * As an example if you need to read 4 inputs at 50Hz you should set all the
+ * data rates at 250Hz (50Hz x * 4 = 200Hz) and call sample() at a rate of
+ * 200Hz.
+ *
+ * An example for how to use the driver can be found in the test code
+ * (src/tests/drivers/test-ads1118.cpp)
+ */
+
+#pragma once
+
+#include "ADS1118Data.h"
+#include "drivers/spi/SPIDriver.h"
+#include "sensors/Sensor.h"
+
+/**
+ * @brief Driver class for ADS1118 adc
+ *
+ * This class allows to mange the device configuration.
+ */
+class ADS1118 : public Sensor<ADS1118Data>
+{
+public:
+    enum ADS1118Mux
+    {
+        MUX_AIN0_AIN1 = 0x0,  ///< AINp is AIN0 and AINn is AIN1 (default)
+        MUX_AIN0_AIN3 = 0x1,  ///< AINp is AIN0 and AINn is AIN3
+        MUX_AIN1_AIN3 = 0x2,  ///< AINp is AIN1 and AINn is AIN3
+        MUX_AIN2_AIN3 = 0x3,  ///< AINp is AIN2 and AINn is AIN3
+        MUX_AIN0_GND  = 0x4,  ///< AINp is AIN0 and AINn is GND
+        MUX_AIN1_GND  = 0x5,  ///< AINp is AIN1 and AINn is GND
+        MUX_AIN2_GND  = 0x6,  ///< AINp is AIN2 and AINn is GND
+        MUX_AIN3_GND  = 0x7   ///< AINp is AIN3 and AINn is GND
+    };                        ///< Multiplexer values
+
+    enum ADS1118Pga
+    {
+        FSR_6_144 = 0x0,  ///< FSR is ±6.144 V
+        FSR_4_096 = 0x1,  ///< FSR is ±4.096 V
+        FSR_2_048 = 0x2,  ///< FSR is ±2.048 V (default)
+        FSR_1_024 = 0x3,  ///< FSR is ±1.024 V
+        FSR_0_512 = 0x4,  ///< FSR is ±0.512 V
+        FSR_0_256 = 0x5   ///< FSR is ±0.256 V
+    };                    ///< Programmable gain amplifier values
+
+    enum ADS1118Mode
+    {
+        CONTIN_CONV_MODE = 0x0,  ///< Continuous-conversion mode
+        SINGLE_SHOT_MODE = 0x1   ///< Power-down and single-shot mode (default)
+    };                           ///< Conversione mode values
+
+    enum ADS1118DataRate
+    {
+        DR_8   = 0x0,  ///< 8 SPS
+        DR_16  = 0x1,  ///< 16 SPS
+        DR_32  = 0x2,  ///< 32 SPS
+        DR_64  = 0x3,  ///< 64 SPS
+        DR_128 = 0x4,  ///< 128 SPS (default)
+        DR_250 = 0x5,  ///< 250 SPS
+        DR_475 = 0x6,  ///< 475 SPS
+        DR_860 = 0x7   ///< 860 SPS
+    };                 ///< Data rate configuration values
+
+    enum ADS1118TempMode
+    {
+        ADC_MODE         = 0x0,  ///< ADC mode (default)
+        TEMP_SENSOR_MODE = 0x1   ///< Temperature sensor mode
+    };                           ///< Temeprature or ADC mode values
+
+    enum ADS1118PullUp
+    {
+        PULL_UP_DIS = 0x0,  ///< Pullup resistor disabled on DOUT pin
+        PULL_UP_EN  = 0x1   ///< Pullup resistor enabled on DOUT pin (default)
+    };                      ///< Pull up enable or disable values
+
+    union ADS1118Config
+    {
+        struct
+        {
+            ADS1118Mode mode : 1;  ///< Device operating mode
+            ADS1118Pga pga : 3;  ///< Programmable gain amplifier configuration
+            ADS1118Mux mux : 3;  ///< Input multiplexer configuration
+            uint8_t singleShot : 1;        ///< Single-shot conversion start
+            uint8_t reserved : 1;          ///< Reserved, doesn't matter
+            uint8_t noOp : 2;              ///< No operation
+            ADS1118PullUp pullUp : 1;      ///< Pullup enable
+            ADS1118TempMode tempMode : 1;  ///< Temperature sensor mode
+            ADS1118DataRate rate : 3;      ///< Data rate
+        } bits;  ///< Includes all the configuration bits
+        struct
+        {
+            uint8_t msb;  ///< Byte MSB
+            uint8_t lsb;  ///< Byte LSB
+        } byte;           ///< Includes the msb and lsb bytes
+        uint16_t word;    ///< Representation in word (16-bits) format
+    };                    ///< Structure of configuration word
+
+    struct ADS1118InputConfig
+    {
+        ADS1118Mux mux       = MUX_AIN0_AIN1;  ///< Input's mux configuration
+        ADS1118DataRate rate = DR_128;  ///< Input's data rate configuration
+    };                                  ///< Driver's input config
+
+    static constexpr uint8_t VALID_OPERATION =
+        0x1;  ///< Indicates a valid configuration
+
+    static const ADS1118Config
+        ADS1118_DEFAULT_CONFIG;  ///< Default configuration
+
+    static constexpr int8_t TEMP_CHANNEL = 8;  ///< Temperature channel number
+
+    static constexpr int8_t NUM_OF_CHANNELS = 9;
+
+    static constexpr int8_t INVALID_CHANNEL = -1;
+
+    /**
+     * @brief Construct a new ADS1118 object specifing spi bus, spi config and
+     * cs pin as well as device configuration
+     */
+    ADS1118(SPIBusInterface &bus, GpioPin cs, ADS1118Config config_,
+            SPIBusConfig spiConfig = getDefaultSPIConfig());
+
+    /**
+     * @brief Construct a new ADS1118 object
+     *
+     * @param spiSlave_ Spi slave configured with spi interface, spi config and
+     * ss pin
+     * @param config_ Device main configuration used as default while enabling
+     * channels
+     * @param busyWait_ Enable busy wait instead normal wait (uses `delayUs`
+     * instead of `sleep`), only useful when sampling at close to the maximum
+     * frequency!
+     * @param tempDivider_ Specify how many onSimpleUpdate calls between each
+     * temperature reading
+     */
+    ADS1118(SPISlave spiSlave_, ADS1118Config config_ = ADS1118_DEFAULT_CONFIG,
+            bool busyWait_ = false, int16_t tempDivider_ = 100);
+
+    /**
+     * Constructs the default config for SPI Bus.
+     *
+     * @returns the default SPIBusConfig
+     */
+    static SPIBusConfig getDefaultSPIConfig();
+
+    /**
+     * @brief Initialize the configuration
+     *
+     * It resets all the channels thus you must call init() before enabling any
+     * channel
+     */
+    bool init() override;
+
+    /**
+     * @brief Enables the sapling of a specific mux configuration with the main
+     * configuration specified in the constructor
+     *
+     * @param mux Mux configuration to enable
+     */
+    void enableInput(ADS1118Mux mux);
+
+    /**
+     * @brief Enables the sapling of a specific mux configuration
+     *
+     * @param mux Mux configuration to enable
+     * @param rate Data rate for this specific channel
+     * @param pga Gain for this specific channel
+     */
+    void enableInput(ADS1118Mux mux, ADS1118DataRate rate, ADS1118Pga pga);
+
+    /**
+     * @brief Disables the specified mux configuration
+     *
+     * @param mux Mux configuration to disable
+     */
+    void disableInput(ADS1118Mux mux);
+
+    /**
+     * @brief Disables all the inputs
+     */
+    void disableAllInputs();
+
+    /**
+     * @brief Enables temperature readings
+     */
+    void enableTemperature();
+
+    /**
+     * @brief Disables temperature readings
+     */
+    void disableTemperature();
+
+    /**
+     * @brief Enables the pull up resistor on dout/miso
+     */
+    void enablePullUpResistor();
+
+    /**
+     * @brief Disables the pull up resistor on dout/miso
+     */
+    void disablePullUpResistor();
+
+    /**
+     * @brief Enables the configuration check after it's writing to the device
+     */
+    void enableConfigCheck();
+
+    /**
+     * @brief Disables the configuration check after it's writing to the device
+     */
+    void disableConfigCheck();
+
+    /**
+     * @brief Reads on the fly the specified input
+     *
+     * @param mux Mux configuration to read
+     * @return Voltage value sampled from the channel in mV
+     */
+    ADS1118Data readInputAndWait(ADS1118Mux mux);
+
+    /**
+     * @brief Reads on the fly the temperature
+     *
+     * @return Temperature in degree
+     */
+    TemperatureData readTemperatureAndWait();
+
+    /**
+     * @brief Returns the last read voltage value for the specified channel
+     */
+    ADS1118Data getVoltage(ADS1118Mux mux);
+
+    /**
+     * @brief Returns the last temperature value
+     */
+    TemperatureData getTemperature();
+
+    /**
+     * @brief Returns the conversion time in us for the specified channel
+     */
+    int getConversionTime(int8_t channel);
+
+    /**
+     * @brief Writes the temperature configuration and check if it is read back
+     * correctly
+     *
+     * @return True if everything ok
+     */
+    bool selfTest() override;
+
+private:
+    /**
+     * @brief Reads the previously configured channel while writing the next
+     * enabled configuration.
+     *
+     * Multiple calls are needed to read all the enabled channels.
+     */
+    ADS1118Data sampleImpl() override;
+
+    /**
+     * @brief Writes the configuration specified, reads the previous written
+     * configuration's value and stores it. If enabled checks also that the
+     * configuration has been written correctly
+     *
+     * @param nextChannel Channel number to write the configuration of
+     * @param prevChannel Channel number to read the configuration of
+     * @return True if everything ok
+     */
+    void readChannel(int8_t nextChannel, int8_t prevChannel);
+
+    /**
+     * @brief Reads on the fly the speficied channel
+     *
+     * @param channel
+     */
+    void readChannel(int8_t channel);
+
+    int8_t findNextEnabledChannel(int8_t startChannel);
+
+    const SPISlave spiSlave;
+    ADS1118Config baseConfig;
+
+    ///< Read the written configuration on each transaction and checks it
+    bool configCheck = false;
+
+    ///< Use `delayUs` instead of `sleep`
+    const bool busyWait;
+
+    ADS1118Config channelsConfig[NUM_OF_CHANNELS];  ///< Channels configuration
+    ADS1118Data values[NUM_OF_CHANNELS];            ///< Voltage values in mV
+
+    ADS1118Config lastConfig;     ///< Last written configuration
+    uint8_t lastConfigIndex = 0;  ///< Last written configuration's index
+
+    ///< Rate of sample calls on which the temperature read
+    const uint16_t tempDivider;
+
+    uint16_t sampleCounter = 0;  ///< Counts the number of samples made
+
+    ///< Digit value in mV for each pga configurations
+    const float PGA_LSB_SIZE[6] = {0.187,   0.125,    0.0625,
+                                   0.03125, 0.015625, 0.0078125};
+
+    ///< Conversion times in us + 100us
+    const int CONV_TIME[8] = {125000, 62500, 31250, 15625,
+                              7813,   4000,  2106,  1163};
+
+    static constexpr float TEMP_LSB_SIZE  = 0.03125;
+    static constexpr uint16_t TEMP_CONFIG = 0xF281;
+
+    /**
+     * This masks excludes 2 bits from the configuration, the reserved bit and
+     * the sigle shot bit
+     */
+    static constexpr uint16_t CONFIG_MASK = 0xFE7F;
+};
\ No newline at end of file
diff --git a/src/shared/drivers/adc/ADS1118/ADS1118Data.h b/src/shared/drivers/adc/ADS1118/ADS1118Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..3bbc933f589ab47ebf2b07ac66d58333aadcffe4
--- /dev/null
+++ b/src/shared/drivers/adc/ADS1118/ADS1118Data.h
@@ -0,0 +1,43 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct ADS1118Data : public ADCData
+{
+    ADS1118Data() : ADCData{0, 0, 0.0} {}
+
+    ADS1118Data(uint64_t t, uint8_t channel_id, float voltage)
+        : ADCData{t, channel_id, voltage}
+
+    {
+    }
+    
+    static std::string header() { return "adc_timestamp,channel_id,voltage\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << adc_timestamp << "," << (int)channel_id << "," << voltage << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/drivers/adc/InternalADC.h b/src/shared/drivers/adc/InternalADC.h
deleted file mode 100644
index 9d0558b4a1ff8ddd824646a55c507f63c604cc3d..0000000000000000000000000000000000000000
--- a/src/shared/drivers/adc/InternalADC.h
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (c) 2015-2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta, Matteo Michele Piazzolla
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#pragma once
-
-#include <utility>
-#include <vector>
-#include "Common.h"
-#include "Debug.h"
-#include "InternalADCData.h"
-
-using std::pair;
-using std::vector;
-
-/**
- * +++ THIS CLASS IS NOT FINISHED. THIS WILL NOT WORK +++
- * @brief SensorADC class to sample data from multiple stm32 adc channels.
- * Supports converting multiple channels with per-channel sample time.
- * All channels are converted togheter, so the sampling frequency is the same
- * for all channels.
- * 
- * DMA STREAMS for stm32f429zi: 
- * ADC1: DMA2_Stream0, DMA2_Stream4 
- * ADC2: DMA2_Stream2, DMA2_Stream3
- * ADC3: DMA2_Stream0, DMA2_Stream1 
- * 
- * @tparam ADC_n Which adc to use (1,2,3 for stm32f429zi)
- * @tparam DMA_Stream Which dma stream to use (see above)
- */
-template <unsigned ADC_n, unsigned DMA_Stream>
-class SensorADC
-{
-public:
-    enum class Channel : uint8_t
-    {
-        CH1 = 1,
-        CH2,
-        CH3,
-        CH4,
-        CH5,
-        CH6,
-        CH7,
-        CH8,
-        CH9,
-        CH10,
-        CH11,
-        CH12,
-        CH13,
-        CH14,
-        CH15,
-        CH16,
-        CH17,
-        CH18,
-        CH19
-    };
-
-    enum class SampleTime : uint8_t
-    {
-        CYCLES_3   = 0x0,
-        CYCLES_15  = 0x1,
-        CYCLES_28  = 0x2,
-        CYCLES_56  = 0x3,
-        CYCLES_84  = 0x4,
-        CYCLES_112 = 0x5,
-        CYCLES_144 = 0x6,
-        CYCLES_480 = 0x7
-    };
-
-    SensorADC() : adc(getADC(ADC_n))
-    {
-
-        {
-            miosix::FastInterruptDisableLock dLock;
-
-            if (adc == ADC1)
-            {
-                RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-            }
-            else if (adc == ADC2)
-            {
-                RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
-            }
-            else if (adc == ADC3)
-            {
-                RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
-            }
-
-            RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
-
-            RCC_SYNC();
-        }
-        adc->CR2 = ADC_CR2_ADON;
-        // CCR pre scaler
-        ADC->CCR = 0x3 << 16;
-
-        // DMA Setup
-        setupDMA();
-    }
-
-    ~SensorADC() { disableADC(); }
-
-    bool enableChannel(Channel ch, SampleTime sample_time)
-    {
-        if (channels.size() == 16)
-        {
-            return false;
-        }
-
-        for (auto& i : channels)
-        {
-            if (i.first == ch)
-            {
-                // channel already enabled
-                return false;
-            }
-        }
-
-        channels.push_back({ch, sample_time});
-
-        updateChannels();
-
-        return true;
-    }
-
-    void disableChannel(Channel ch)
-    {
-        for (auto it = channels.begin(); it < channels.end(); ++it)
-        {
-            if ((*it).first == ch)
-            {
-                channels.erase(it);
-
-                updateChannels();
-                return;
-            }
-        }
-    }
-
-    void update()
-    {
-        adc->CR2 |= ADC_CR2_SWSTART;
-        int i = 0;
-        while (adc->SR & ADC_SR_EOC == 0)
-        {
-            ++i;
-        }
-        adc->SR = 0;
-        printf("Waited %d cycles.  ", i);
-    }
-
-    uint16_t get() { return static_cast<uint16_t>(adc->DR & 0x0000FFFF); }
-
-private:
-    constexpr ADC_TypeDef* getADC(unsigned n)
-    {
-        return n == 1 ? ADC1 : n == 2 ? ADC2 : ADC3;
-    }
-
-    void enableADC() {}
-
-    void disableADC()
-    {
-        miosix::FastInterruptDisableLock dLock;
-
-        if (adc == ADC1)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
-        }
-        else if (adc == ADC2)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN;
-        }
-        else if (adc == ADC3)
-        {
-            RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN;
-        }
-
-        RCC_SYNC();
-    }
-
-    void setupDMA() {}
-
-    void updateChannels()
-    {
-        // Clear the registers
-        adc->SQR1  = 0;
-        adc->SQR2  = 0;
-        adc->SQR3  = 0;
-        adc->SMPR1 = 0;
-        adc->SMPR2 = 0;
-
-        if (channels.size() == 0)
-        {
-            // If no channel is enabled, clear the registers and return.
-            return;
-        }
-
-        // Store the channel sequence in the SQRx registers
-        uint8_t L = channels.size() - 1;
-
-        volatile uint32_t* sqr_ptr =
-            &(adc->SQR3);  // Pointer to the SQR3 register, defining the first 6
-                           // channels in the sequence. If more channels are to
-                           // be converted, this pointer will be updated to
-                           // point the register that stores the sequence for
-                           // subsequent channels.
-
-        unsigned int offset = 0;
-        for (unsigned int i = 0; i < channels.size(); i++)
-        {
-            if (i == 6)
-            {
-                sqr_ptr = &(adc->SQR2);
-                offset  = 6;
-            }
-            else if (i == 12)
-            {
-                sqr_ptr = &(adc->SQR1);
-                offset  = 12;
-            }
-
-            *sqr_ptr |= static_cast<uint8_t>(channels[i].first)
-                        << ((i - offset) * 5);
-        }
-
-        adc->SQR1 |= L << 20;
-
-        // Now update sample times for each channel
-
-        volatile uint32_t* SMPR;
-        for (auto& i : channels)
-        {
-            uint8_t ch = static_cast<uint8_t>(i.first) - 1;  // CH1 --> 0
-            uint8_t sample_time = static_cast<uint8_t>(i.second);
-            if (ch >= 10)
-            {
-                SMPR   = &(adc->SMPR1);
-                offset = 10;
-            }
-            else
-            {
-                SMPR   = &(adc->SMPR1);
-                offset = 0;
-            }
-
-            *SMPR |= static_cast<uint32_t>(sample_time) << ((ch - offset) * 3);
-        }
-    }
-
-    ADC_TypeDef* adc;
-    // Channels to convert, in order of conversion. size() must not exceed 16.
-    vector<pair<Channel, SampleTime>> channels;
-};
\ No newline at end of file
diff --git a/src/shared/drivers/adc/InternalADC/InternalADC.cpp b/src/shared/drivers/adc/InternalADC/InternalADC.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5432a14eaa64d7c85aeafb9aeaeec270f1d2b20b
--- /dev/null
+++ b/src/shared/drivers/adc/InternalADC/InternalADC.cpp
@@ -0,0 +1,386 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "InternalADC.h"
+
+#include "TimestampTimer.h"
+
+InternalADC::InternalADC(ADC_TypeDef& ADCx_, const float V_SUPPLY_,
+                         const bool isUsingDMA_,
+                         DMA_Stream_TypeDef* DMAx_Streamx_)
+    : ADCx(ADCx_), V_SUPPLY(V_SUPPLY_), isUsingDMA(isUsingDMA_),
+      DMAx_Streamx(DMAx_Streamx_)
+{
+    resetRegisters();
+    enableADCClock();
+
+    // Init indexMap
+    for (auto i = 0; i < CH_NUM; i++)
+    {
+        indexMap[i] = -1;
+    }
+}
+
+InternalADC::~InternalADC()
+{
+    resetRegisters();
+    disableADCClock();
+}
+
+bool InternalADC::init()
+{
+    // Turn on the ADC
+    ADCx.CR2 |= ADC_CR2_ADON;
+
+    // Set single conversion mode
+    ADCx.CR2 &= ~ADC_CR2_CONT;
+
+    // Set scan mode
+    ADCx.CR1 |= ADC_CR1_SCAN;
+
+    // Data alignment
+    ADCx.CR2 &= ~ADC_CR2_ALIGN;  // right
+
+    if (isUsingDMA)
+    {
+        // Set the DMA peripheral address
+        DMAx_Streamx->PAR = (uint32_t) & (ADCx.DR);
+
+        // Set the DMA memory address
+        DMAx_Streamx->M0AR = (uint32_t)values;
+
+        // Enable DMA on ADC
+        ADCx.CR2 |= ADC_CR2_DMA;
+
+        // Enable DMA stream
+        DMAx_Streamx->CR |= DMA_SxCR_EN;
+
+        // Check if we are using the DMA2 controller, otherwise it's DMA1
+        if (((uint32_t)&DMAx_Streamx & ~0xFF) == (uint32_t)DMA2_BASE)
+        {
+            DMAx = DMA2;
+        }
+
+        // Find the DMA stream number
+        streamNum = ((((uint32_t)&DMAx_Streamx) & 0xFF) - 0x10) / 0x18;
+
+        // Check which registers to use
+        statusReg    = (streamNum < 4 ? &(DMAx->LISR) : &(DMAx->HISR));
+        clearFlagReg = (streamNum < 4 ? &(DMAx->LIFCR) : &(DMAx->HIFCR));
+
+        // Create the masks for the status bits (this will do for both high and
+        // low registers as well as status and reset status registers)
+        switch (streamNum % 4)
+        {
+            case 0:
+                transferCompleteMask = DMA_LISR_TCIF0;
+                transferErrorMask    = DMA_LISR_TEIF0;
+                break;
+            case 1:
+                transferCompleteMask = DMA_LISR_TCIF1;
+                transferErrorMask    = DMA_LISR_TEIF1;
+                break;
+            case 2:
+                transferCompleteMask = DMA_LISR_TCIF2;
+                transferErrorMask    = DMA_LISR_TEIF2;
+                break;
+            case 3:
+                transferCompleteMask = DMA_LISR_TCIF3;
+                transferErrorMask    = DMA_LISR_TEIF3;
+                break;
+        }
+
+        // All this is because the status bits are not stored in the
+        // DMAx_Streamx registers but in the main DMA controller status
+        // register. We could not care about the transfer status but the
+        // timestamp would not match the values read after calling sample().
+
+        // We calculate this values to avoid doing it every time in
+        // sampleImpl().
+    }
+
+    return true;
+}
+
+bool InternalADC::enableChannel(Channel channel)
+{
+    return enableChannel(channel, CYCLES_3);
+}
+
+bool InternalADC::enableChannel(Channel channel, SampleTime sampleTime)
+{
+    // Check channel number
+    if (channel < CH0 || channel >= CH_NUM)
+    {
+        return false;
+    }
+
+    // Add channel to the sequence
+    if (!isUsingDMA)
+    {
+        if (!addInjectedChannel(channel))
+        {
+            return false;
+        }
+    }
+    else
+    {
+        if (!addRegularChannel(channel))
+        {
+            return false;
+        }
+
+        // Update the DMA number of data
+        DMAx_Streamx->NDTR = activeChannels;
+    }
+
+    // Set channel's sample time
+    setChannelSampleTime(channel, sampleTime);
+
+    return true;
+}
+
+ADCData InternalADC::getVoltage(Channel channel)
+{
+    float voltage = 0;
+
+    if (indexMap[channel] >= 0 && indexMap[channel] < CH_NUM)
+    {
+        voltage = values[indexMap[channel]] * V_SUPPLY / RESOLUTION;
+    }
+
+    return ADCData{timestamp, (uint8_t)channel, voltage};
+}
+
+bool InternalADC::selfTest()
+{
+    // Try a single sample and check for error
+    sample();
+
+    if (last_error != NO_ERRORS)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+ADCData InternalADC::sampleImpl()
+{
+    if (!isUsingDMA)
+    {
+        startInjectedConversion();
+
+        // Wait for end of conversion
+        while (!(ADCx.SR & ADC_SR_JEOC))
+            ;
+
+        // Read all 4 channels (faster than read only the enabled ones)
+        values[0] = ADCx.JDR1;
+        values[1] = ADCx.JDR2;
+        values[2] = ADCx.JDR3;
+        values[3] = ADCx.JDR4;
+    }
+    else
+    {
+        // Rewrite the DMA bit in ADC CR2 (reference manual chapter 13.8.1)
+        ADCx.CR2 &= ~ADC_CR2_DMA;
+        ADCx.CR2 |= ADC_CR2_DMA;
+
+        startRegularConversion();
+
+        // This should trigger the DMA stream for each channel's conversion
+
+        // Wait for tranfer end
+        while (!(*statusReg & (transferCompleteMask | transferErrorMask)))
+            ;
+
+        // Clear the transfer complete flag
+        *clearFlagReg |= transferCompleteMask;
+
+        // If and error has occurred (probaly due to a higher priority stream)
+        // don't update the timestamp, the values should not have been updated
+        if (*statusReg & transferErrorMask)
+        {
+            // Clear the transfer error flag
+            *clearFlagReg |= transferErrorMask;
+
+            // Signal an error
+            last_error = DMA_ERROR;
+            return last_sample;
+        }
+    }
+
+    timestamp = TimestampTimer::getTimestamp();
+
+    return last_sample;
+}
+
+inline void InternalADC::resetRegisters()
+{
+    // Reset the ADC configuration
+    ADCx.CR1   = 0;
+    ADCx.CR2   = 0;
+    ADCx.SMPR1 = 0;
+    ADCx.SMPR2 = 0;
+    ADCx.JOFR1 = 0;
+    ADCx.JOFR2 = 0;
+    ADCx.JOFR3 = 0;
+    ADCx.JOFR4 = 0;
+    ADCx.HTR   = 0;
+    ADCx.LTR   = 0;
+    ADCx.SQR1  = 0;
+    ADCx.SQR2  = 0;
+    ADCx.SQR3  = 0;
+    ADCx.JSQR  = 0;
+}
+
+inline void InternalADC::startInjectedConversion()
+{
+    ADCx.CR2 |= ADC_CR2_JSWSTART;
+}
+
+inline void InternalADC::startRegularConversion()
+{
+    ADCx.CR2 |= ADC_CR2_SWSTART;
+}
+
+inline bool InternalADC::addInjectedChannel(Channel channel)
+{
+    // Check active channels number
+    if (activeChannels >= 4)
+    {
+        return false;
+    }
+
+    // Add the channel to the sequence, starting from position 4
+    ADCx.JSQR |= channel << (15 - activeChannels * 5);
+
+    // Update the channels number in the register
+    ADCx.JSQR &= 0x000FFFFF;
+    ADCx.JSQR |= activeChannels << 20;
+
+    // Increment the index of all enabled channels
+    for (auto i = 0; i < CH_NUM; i++)
+    {
+        if (indexMap[i] >= 0)
+        {
+            indexMap[i]++;
+        }
+    }
+
+    // Set this channel index to 0
+    indexMap[channel] = 0;
+
+    // Update the counter
+    activeChannels++;
+
+    return true;
+}
+
+inline bool InternalADC::addRegularChannel(Channel channel)
+{
+    // Check active channels number
+    if (activeChannels >= 16)
+    {
+        return false;
+    }
+
+    // Add the channel to the sequence
+    volatile uint32_t* sqrPtr;
+    switch (activeChannels % 6)
+    {
+        case 1:
+            sqrPtr = &(ADCx.SQR3);
+            break;
+        case 2:
+            sqrPtr = &(ADCx.SQR2);
+            break;
+        default:
+            sqrPtr = &(ADCx.SQR1);
+    }
+    *sqrPtr = channel << ((activeChannels % 6) * 5);
+
+    // Update the channels number in the register
+    ADCx.SQR1 |= activeChannels << 20;
+
+    // Save the index of the channel in the ADC's regular sequence
+    indexMap[channel] = activeChannels;
+
+    // Update the counter
+    activeChannels++;
+
+    return true;
+}
+
+inline void InternalADC::setChannelSampleTime(Channel channel,
+                                              SampleTime sampleTime)
+{
+    volatile uint32_t* smprPtr;
+    if (channel <= 9)
+    {
+        smprPtr = &(ADCx.SMPR2);
+    }
+    else
+    {
+        smprPtr = &(ADCx.SMPR1);
+    }
+    *smprPtr = sampleTime << (channel * 3);
+}
+
+inline void InternalADC::enableADCClock()
+{
+    miosix::FastInterruptDisableLock dLock;
+
+    if (&ADCx == ADC1)
+    {
+        RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
+    }
+    else if (&ADCx == ADC2)
+    {
+        RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
+    }
+    else if (&ADCx == ADC3)
+    {
+        RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
+    }
+
+    RCC_SYNC();
+}
+
+inline void InternalADC::disableADCClock()
+{
+    miosix::FastInterruptDisableLock dLock;
+
+    if (&ADCx == ADC1)
+    {
+        RCC->APB2ENR &= ~RCC_APB2ENR_ADC1EN;
+    }
+    else if (&ADCx == ADC2)
+    {
+        RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN;
+    }
+    else if (&ADCx == ADC3)
+    {
+        RCC->APB2ENR &= ~RCC_APB2ENR_ADC3EN;
+    }
+}
\ No newline at end of file
diff --git a/src/shared/drivers/adc/InternalADC/InternalADC.h b/src/shared/drivers/adc/InternalADC/InternalADC.h
new file mode 100644
index 0000000000000000000000000000000000000000..0343176c5254240de174ca3ac1345768c38c053d
--- /dev/null
+++ b/src/shared/drivers/adc/InternalADC/InternalADC.h
@@ -0,0 +1,181 @@
+/* Copyright (c) 2015-2020 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Luca Erbetta, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <miosix.h>
+
+#include "InternalADCData.h"
+#include "sensors/Sensor.h"
+
+/**
+ * @brief Driver for stm32 internal ADC
+ *
+ * Allows conversions on multiple channels with per-channel sample time.
+ *
+ * The driver configures the ADC by itself, including the clock.
+ *
+ * This driver implements the following ADC's features:
+ * - injected channels: a list of up to 4 channels that can be converted
+ * - scan mode with regular channels: up to 16 channels converted in the same
+ * sequence as they are enabled
+ * - DMA: Used in scan mode to transfer data from the peripheral data register
+ *
+ * When you don't use the DMA, the driver works with the injected channels.
+ * When a conversion is started the values are stored in 4 specific data
+ * registes, whithout the need to use DMA.
+ *
+ * If you need more than 4 channels you must use DMA. When the regular group
+ * of channels is converted, the data will be stored sequentially in a single
+ * data register and DMA is needed to save the value between each conversion in
+ * another memory location.
+ *
+ * When using DMA the driver will configure only the memory addresses, you must
+ * configure the stream properly!
+ *
+ * Examples of how to use the internal ADC driver and set up the DMA stream
+ * can be found in the test files (test-internal-adc*).
+ *
+ * The ADC has also other features as value offsets for injected channels,
+ * interrupts (such as dma transfer and watchdog), conversion triggering with
+ * timers and multi adc mode.
+ * This features are not implemented to keep the driver simple.
+ */
+class InternalADC : public Sensor<ADCData>
+{
+public:
+    /**
+     * @brief ADC channels enumeration
+     */
+    enum Channel : uint8_t
+    {
+        CH0 = 0,
+        CH1,
+        CH2,
+        CH3,
+        CH4,
+        CH5,
+        CH6,
+        CH7,
+        CH8,
+        CH9,
+        CH10,
+        CH11,
+        CH12,
+        CH13,
+        CH14,
+        CH15,
+        CH_NUM
+    };
+
+    /**
+     * @brief Conversion sample time. See reference manual.
+     */
+    enum SampleTime : uint8_t
+    {
+        CYCLES_3   = 0x0,
+        CYCLES_15  = 0x1,
+        CYCLES_28  = 0x2,
+        CYCLES_56  = 0x3,
+        CYCLES_84  = 0x4,
+        CYCLES_112 = 0x5,
+        CYCLES_144 = 0x6,
+        CYCLES_480 = 0x7
+    };
+
+    InternalADC(ADC_TypeDef& ADCx_, const float V_SUPPLY_ = 5.0,
+                const bool isUsingDMA_    = false,
+                DMA_Stream_TypeDef* DMAx_ = DMA1_Stream0);
+
+    ~InternalADC();
+
+    /**
+     * @brief ADC Initialization
+     *
+     * The ADC clock must be set before hand as well as GPIO configuration
+     * and DMA if used. Also the clock for the analog circuitry should be set
+     * accordingly to the device datasheet.
+     */
+    bool init() override;
+
+    bool enableChannel(Channel channel);
+
+    bool enableChannel(Channel channel, SampleTime sampleTime);
+
+    ADCData getVoltage(Channel channel);
+
+    bool selfTest() override;
+
+    ADCData sampleImpl() override;
+
+private:
+    inline void resetRegisters();
+
+    inline void startInjectedConversion();
+
+    inline void startRegularConversion();
+
+    inline bool addInjectedChannel(Channel channel);
+
+    inline bool addRegularChannel(Channel channel);
+
+    inline void setChannelSampleTime(Channel channel, SampleTime sampleTime);
+
+    inline void enableADCClock();
+
+    inline void disableADCClock();
+
+    ADC_TypeDef& ADCx;
+    const float V_SUPPLY = 5.0;
+
+    uint8_t activeChannels = 0;
+    uint64_t timestamp;
+
+    // Raw value used by DMA
+    uint16_t values[CH_NUM] = {};
+
+    // Maps the channel number with the index in the ADC's regular sequence
+    int8_t indexMap[CH_NUM];
+
+    /**
+     * @brief Determines whether to use regular channels or injected channels
+     *
+     * We'll use up to 4 injected channel by dafult and up to 16 channels when
+     * using DMA.
+     *
+     * The differantiation is necessary because whitout DMA it is much more
+     * simplier to use injected channel for multichannel readings. Otherwise we
+     * would need to handle each channel's end of conversion interrupt or go
+     * through
+     */
+    const bool isUsingDMA;
+    DMA_Stream_TypeDef* DMAx_Streamx;
+    DMA_TypeDef* DMAx = DMA1;
+    uint8_t streamNum = 0;
+    uint32_t transferCompleteMask;
+    uint32_t transferErrorMask;
+    volatile uint32_t* statusReg;
+    volatile uint32_t* clearFlagReg;
+
+    static constexpr int INJECTED_CHANNEL_N = 4;
+    static constexpr int RESOLUTION         = 4096;  ///< 12 bits
+};
\ No newline at end of file
diff --git a/src/shared/drivers/adc/InternalADC/InternalADCData.h b/src/shared/drivers/adc/InternalADC/InternalADCData.h
new file mode 100644
index 0000000000000000000000000000000000000000..5aa751b3bf11801e6b27670471fea339337aa9e6
--- /dev/null
+++ b/src/shared/drivers/adc/InternalADC/InternalADCData.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct InternalADCData : public ADCData
+{
+    InternalADCData() : ADCData{0, 0, 0.0} {}
+
+    InternalADCData(uint64_t t, uint8_t channel_id, float voltage)
+        : ADCData{t, channel_id, voltage}
+    {
+    }
+
+    static std::string header() { return "adc_timestamp,channel_id,voltage\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << adc_timestamp << "," << (int)channel_id << "," << voltage << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/drivers/canbus/CanBus.cpp b/src/shared/drivers/canbus/CanBus.cpp
index a57cfec668c55c4444c061ab9398992b09621fbc..d6c6bd89ecd8dfb6d05bd6aab440fb12cc7fa100 100644
--- a/src/shared/drivers/canbus/CanBus.cpp
+++ b/src/shared/drivers/canbus/CanBus.cpp
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -32,6 +30,7 @@ TODO:
 */
 
 #include "CanBus.h"
+
 #include "CanManager.h"
 #include "CanUtils.h"
 
@@ -41,7 +40,8 @@ using std::set;
 // Transmit mailbox request
 #define TMIDxR_TXRQ ((uint32_t)0x00000001)
 
-CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id, CanDispatcher dispatcher)
+CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id,
+               CanDispatcher dispatcher)
     : CANx(bus), manager(manager), id(can_id), dispatchMessage(dispatcher)
 {
     this->canSetup();
@@ -49,8 +49,8 @@ CanBus::CanBus(CAN_TypeDef *bus, CanManager *manager, const int can_id, CanDispa
 }
 
 /**
-* Funzione eseguita dall'ActiveObject per ricevere messaggi
-*/
+ * Funzione eseguita dall'ActiveObject per ricevere messaggi
+ */
 void CanBus::rcvFunction()
 {
     while (!should_stop)
@@ -60,7 +60,7 @@ void CanBus::rcvFunction()
         rcvQueue.waitUntilNotEmpty();  // blocks
 
         rcvQueue.get(message);
-        TRACE("[CanBus] message received\n");
+        LOG_DEBUG(logger, "Message received");
 
         /* Check message */
         uint32_t filter_id;
@@ -72,7 +72,7 @@ void CanBus::rcvFunction()
 
         if (filter_id >= (uint32_t)CanManager::filter_max_id)
         {
-            TRACE("[CanBus] Unsupported message\n");
+            LOG_ERR(logger, "Unsupported message");
             continue;
         }
 
@@ -84,7 +84,7 @@ void CanBus::rcvFunction()
             // Update status
             Lock<FastMutex> l(statusMutex);
             status.n_rcv++;
-            status.last_rcv = (uint8_t)filter_id;
+            status.last_rcv    = (uint8_t)filter_id;
             status.last_rcv_ts = miosix::getTick();
         }
 
@@ -92,7 +92,7 @@ void CanBus::rcvFunction()
         dispatchMessage(message, status);
     }
 
-    TRACE("Canbus thread exit\n");
+    LOG_DEBUG(logger, "Thread exit");
 }
 
 /*
@@ -169,7 +169,7 @@ bool CanBus::send(uint16_t id, const uint8_t *message, uint8_t len)
         // Update status
         Lock<FastMutex> l(statusMutex);
         status.n_sent++;
-        status.last_sent = (uint8_t)id;
+        status.last_sent    = (uint8_t)id;
         status.last_sent_ts = miosix::getTick();
     }
 
@@ -231,8 +231,8 @@ void CanBus::canSetup()
      *       =>   42M / (14 * 6) = 500kBit/s
      */
     // Synchronization with jump (CAN_BRT SJW)
-    uint8_t CAN_SJW = CAN_SJW_1tq;  
-    // Bit Segment 1 = sample point (CAN_BRT TS1)   
+    uint8_t CAN_SJW = CAN_SJW_1tq;
+    // Bit Segment 1 = sample point (CAN_BRT TS1)
     uint32_t CAN_BS1 = CAN_BS1_6tq;
     // Bit Segment 2 = transmit point (CAN_BRT TS2)
     uint32_t CAN_BS2 = CAN_BS2_7tq;
diff --git a/src/shared/drivers/canbus/CanBus.h b/src/shared/drivers/canbus/CanBus.h
index 6e281166d31249c786f7301b0b65d85a2edf0f13..7d2e71911fb284ea75607285ffb6ea3fb6ce2269 100644
--- a/src/shared/drivers/canbus/CanBus.h
+++ b/src/shared/drivers/canbus/CanBus.h
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -27,10 +25,12 @@
 
 #include <ActiveObject.h>
 #include <Common.h>
+#include <diagnostic/PrintLogger.h>
+
 #include "CanUtils.h"
 
 /* Alias for the function to be executed on receive */
-using CanDispatcher = std::function<void(CanMsg,CanStatus)>;
+using CanDispatcher = std::function<void(CanMsg, CanStatus)>;
 
 class CanManager;
 
@@ -45,15 +45,16 @@ public:
      * @param bus        the physical pins to be used
      * @param manager    who created this bus object
      * @param can_id     the id of the currently created canbus (CAN1, CAN2...)
-     * @param dispatcher function to be executed on receive 
+     * @param dispatcher function to be executed on receive
      */
-    CanBus(CAN_TypeDef* bus, CanManager* manager, const int can_id, CanDispatcher dispatcher);
+    CanBus(CAN_TypeDef* bus, CanManager* manager, const int can_id,
+           CanDispatcher dispatcher);
     ~CanBus() {}
 
     /* Receiving function executed by the ActiveObject */
     void rcvFunction();
 
-    /* Sender function 
+    /* Sender function
      * @param id       Id of the message (aka topic)
      * @para message   message as byte array
      * @param len      length of the message (max 8 bytes, truncated if grater)
@@ -69,7 +70,7 @@ public:
     {
         should_stop = true;
 
-        //Wake the thread posting an empty can msg
+        // Wake the thread posting an empty can msg
         CanMsg empty_msg;
         empty_msg.StdId = 0xFFFFFFFF;
         empty_msg.ExtId = 0xFFFFFFFF;
@@ -78,8 +79,6 @@ public:
         ActiveObject::stop();
     }
 
-
-
     /* Rule of five */
     CanBus(const CanBus&)  = delete;
     CanBus(const CanBus&&) = delete;
@@ -90,9 +89,7 @@ public:
 
 protected:
     /* Inherited from ActiveObject: executes the receiving function */
-    void run() override {
-        rcvFunction();
-    }
+    void run() override { rcvFunction(); }
 
 private:
     void canSetup();
@@ -106,6 +103,8 @@ private:
 
     CanDispatcher dispatchMessage;
     CanStatus status;
+
+    PrintLogger logger = Logging::getLogger("canbus");
 };
 
 #endif /* CANBUS_H */
diff --git a/src/shared/drivers/canbus/CanInterrupt.cpp b/src/shared/drivers/canbus/CanInterrupt.cpp
index aac8ecd04b8bbeba217d40537915134d534ace91..3e9773393615a9db9d538c2cc397214e8969a8dc 100644
--- a/src/shared/drivers/canbus/CanInterrupt.cpp
+++ b/src/shared/drivers/canbus/CanInterrupt.cpp
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/canbus/CanManager.cpp b/src/shared/drivers/canbus/CanManager.cpp
index c2627a9ae587afd1d7bfc2dfda0d9048b77d82d0..6278ea231997f9f60b7076bbe1a152fdbfb14f0b 100644
--- a/src/shared/drivers/canbus/CanManager.cpp
+++ b/src/shared/drivers/canbus/CanManager.cpp
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/canbus/CanManager.h b/src/shared/drivers/canbus/CanManager.h
index 6aa9c980c33d4949d2284e36f8ae4167fc5cf665..4b6fc0a01f507384a640a3917c1d5cff4668ea7d 100644
--- a/src/shared/drivers/canbus/CanManager.h
+++ b/src/shared/drivers/canbus/CanManager.h
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -125,10 +123,18 @@ public:
     {
         // Disable interrupts
         // TODO: Only disable the interrupts that we enabled
+#ifdef CAN1_RX0_IRQn
         NVIC_DisableIRQ(CAN1_RX0_IRQn);
+#endif
+#ifdef CAN1_RX1_IRQn
         NVIC_DisableIRQ(CAN1_RX1_IRQn);
+#endif
+#ifdef CAN2_RX0_IRQn
         NVIC_DisableIRQ(CAN2_RX0_IRQn);
+#endif
+#ifdef CAN2_RX1_IRQn
         NVIC_DisableIRQ(CAN2_RX1_IRQn);
+#endif
 
         global_bus_ptr[0] = NULL;
         global_bus_ptr[1] = NULL;
diff --git a/src/shared/drivers/canbus/CanUtils.h b/src/shared/drivers/canbus/CanUtils.h
index a55c52459c489141c9eae06a534950a4ec208ad1..ebdefec449976aa27b7f3119f3ca2e403b81e239 100644
--- a/src/shared/drivers/canbus/CanUtils.h
+++ b/src/shared/drivers/canbus/CanUtils.h
@@ -1,7 +1,5 @@
-/* CAN-Bus Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/PacketBuffer.cpp b/src/shared/drivers/ethernet/PacketBuffer.cpp
index 9766d9d3e0bd6e46189f05dff2af0f1e1b8271f0..aad856f35b0eabcf06e80ffe30b6f230729428fe 100644
--- a/src/shared/drivers/ethernet/PacketBuffer.cpp
+++ b/src/shared/drivers/ethernet/PacketBuffer.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/PacketBuffer.h b/src/shared/drivers/ethernet/PacketBuffer.h
index 92a36823c81864ed6235da84789e4fb7fa454ac1..738573708633d9e46e3e0c060a46b5e5d38382a3 100644
--- a/src/shared/drivers/ethernet/PacketBuffer.h
+++ b/src/shared/drivers/ethernet/PacketBuffer.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/UdpManager.cpp b/src/shared/drivers/ethernet/UdpManager.cpp
index f4751d6b0f2e52c0b6f13a5f9a98b27b765dab73..2f562144d936a773bc177bd9c71145be4fe477bb 100644
--- a/src/shared/drivers/ethernet/UdpManager.cpp
+++ b/src/shared/drivers/ethernet/UdpManager.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/UdpManager.h b/src/shared/drivers/ethernet/UdpManager.h
index 3a2c790ae73f786254545a3f0b6bb6038d5a80f6..f3207bd62ab7e390d25c90b364c4bde8ac0d7d8c 100644
--- a/src/shared/drivers/ethernet/UdpManager.h
+++ b/src/shared/drivers/ethernet/UdpManager.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/W5200/spi_impl.cpp b/src/shared/drivers/ethernet/W5200/spi_impl.cpp
index 42be4428ae27c94b089d943eaf3b9b80fb06284a..5585500554449111b801ff6bcf40b0541961a42d 100644
--- a/src/shared/drivers/ethernet/W5200/spi_impl.cpp
+++ b/src/shared/drivers/ethernet/W5200/spi_impl.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/W5200/spi_impl.h b/src/shared/drivers/ethernet/W5200/spi_impl.h
index fa21a9fc8b2fd85d9924a9721c003af0ce919d45..d82d961c24d0190e535aacf5bf4b353d6ff91a64 100644
--- a/src/shared/drivers/ethernet/W5200/spi_impl.h
+++ b/src/shared/drivers/ethernet/W5200/spi_impl.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/W5200/w5200.cpp b/src/shared/drivers/ethernet/W5200/w5200.cpp
index fedcbf88f17ca919c16a3dc8fe20ba7ebf0b2f3e..e9328c0cf5bf8c167298d0d6648cee0a52576578 100644
--- a/src/shared/drivers/ethernet/W5200/w5200.cpp
+++ b/src/shared/drivers/ethernet/W5200/w5200.cpp
@@ -1,7 +1,5 @@
-/* Driver class for Wiznet W5200 chip
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/W5200/w5200.h b/src/shared/drivers/ethernet/W5200/w5200.h
index bb01a77bab32f1461b088573e2add225472ace5c..d7e024941abd1468b8a3c6599cf8ed9ee1f9c4e0 100644
--- a/src/shared/drivers/ethernet/W5200/w5200.h
+++ b/src/shared/drivers/ethernet/W5200/w5200.h
@@ -1,7 +1,5 @@
-/* Driver class for Wiznet W5200 chip
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/W5200/w5200_defs.h b/src/shared/drivers/ethernet/W5200/w5200_defs.h
index d07638d69d5296d54cffe93634bd1720c69fedbb..3455191efff0cee0afe0a977db3255e182527e7b 100644
--- a/src/shared/drivers/ethernet/W5200/w5200_defs.h
+++ b/src/shared/drivers/ethernet/W5200/w5200_defs.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/WatchdogTimer.cpp b/src/shared/drivers/ethernet/WatchdogTimer.cpp
index 2074be2ec5123d7e67cb74c5bbb66e907f252741..3868ac03bf142c5462207ef720509bec0d8b5476 100644
--- a/src/shared/drivers/ethernet/WatchdogTimer.cpp
+++ b/src/shared/drivers/ethernet/WatchdogTimer.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/WatchdogTimer.h b/src/shared/drivers/ethernet/WatchdogTimer.h
index 19fb921f8b587bfacb1711941930005dfa8c9ead..f170a92fec0991941f01e4fa44a22adb2d1908d8 100644
--- a/src/shared/drivers/ethernet/WatchdogTimer.h
+++ b/src/shared/drivers/ethernet/WatchdogTimer.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/ethernet/packet.h b/src/shared/drivers/ethernet/packet.h
index aca1bfc8b2ecdf0673d84231c29b9bac1539a76e..9a7479848db468d2041fbc5f8e942eb4d81cfe08 100644
--- a/src/shared/drivers/ethernet/packet.h
+++ b/src/shared/drivers/ethernet/packet.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/gamma868/Gamma868.cpp b/src/shared/drivers/gamma868/Gamma868.cpp
index e3cf3cdec90d324485a6ba9144c483bbfaa2c2c5..30476976ccbaed5a16d758a686f7c30448a5336b 100644
--- a/src/shared/drivers/gamma868/Gamma868.cpp
+++ b/src/shared/drivers/gamma868/Gamma868.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -28,15 +28,15 @@ using namespace miosix;
  * to be passed to the object in order to communicate with the device.
  *
  */
-Gamma868::Gamma868(const char *serialPath, const uint16_t multiplier)
-                            : send_timeout_multiplier(multiplier), conf{}, gammaSwitch{}
+Gamma868::Gamma868(const char* serialPath, const uint16_t multiplier)
+    : send_timeout_multiplier(multiplier), conf{}, gammaSwitch{}
 {
     conf_enabled = false;
-    fd = open(serialPath, O_RDWR);
+    fd           = open(serialPath, O_RDWR);
 
     if (fd < 0)
-        TRACE("[Gamma868] Cannot open %s\n", serialPath);
-        // TODO: Signal an error if we fail here?
+        LOG_ERR(logger, "Cannot open {}", serialPath);
+    // TODO: Signal an error if we fail here?
 }
 
 /*
@@ -44,20 +44,18 @@ Gamma868::Gamma868(const char *serialPath, const uint16_t multiplier)
  * to be passed to the object in order to communicate with the device.
  *
  */
-Gamma868::Gamma868(const char *serialPath, GpioPin* lrn_pin, const uint16_t multiplier) 
-                                                : Gamma868(serialPath, multiplier)
+Gamma868::Gamma868(const char* serialPath, GpioPin* lrn_pin,
+                   const uint16_t multiplier)
+    : Gamma868(serialPath, multiplier)
 {
-    gammaSwitch = lrn_pin;
+    gammaSwitch  = lrn_pin;
     conf_enabled = true;
 
     gammaSwitch->mode(Mode::OUTPUT);
     gammaSwitch->high();
 }
 
-Gamma868::~Gamma868()
-{
-    close(fd);
-}
+Gamma868::~Gamma868() { close(fd); }
 
 /*
  * Immediately sends command (blocking).
@@ -74,7 +72,7 @@ bool Gamma868::send(uint8_t* pkt, size_t pkt_len)
  */
 ssize_t Gamma868::receive(uint8_t* pkt, size_t pkt_len)
 {
-    if(pkt_len > 0)
+    if (pkt_len > 0)
         return read(fd, pkt, 1);
     else
         return 0;
@@ -85,13 +83,16 @@ ssize_t Gamma868::receive(uint8_t* pkt, size_t pkt_len)
  */
 GammaConf Gamma868::readConfig()
 {
-    if(!conf_enabled) {
+    if (!conf_enabled)
+    {
         conf.is_valid = false;
-    } else {
+    }
+    else
+    {
         enterLearnMode();
 
         bool ok = updateConfig();
-        if(!ok)
+        if (!ok)
             conf.is_valid = false;
 
         exitLearnMode();
@@ -108,7 +109,7 @@ bool Gamma868::configure(const GammaConf& newConf)
 {
     bool retValue;
 
-    if(!conf_enabled)
+    if (!conf_enabled)
         return false;
 
     enterLearnMode();
@@ -117,19 +118,21 @@ bool Gamma868::configure(const GammaConf& newConf)
     memcpy(&oldConf, &conf, sizeof(GammaConf));
 
     // Write the new configuration
-    TRACE("[Gamma868] Writing new configuration.. \n");
+    LOG_DEBUG(logger, "Writing new configuration...");
     writeConfig(newConf);
 
     // Check the current configuration
     bool ok = updateConfig();
 
-    if(ok && conf == newConf){
-        TRACE("[Gamma868] Config Ok\n");
+    if (ok && conf == newConf)
+    {
+        LOG_DEBUG(logger, "Config ok");
         retValue = true;
     }
-    else{
-        TRACE("[Gamma868] Config error\n");
-        memcpy(&conf, &oldConf, sizeof(GammaConf)); // rollback
+    else
+    {
+        LOG_ERR(logger, "Config error");
+        memcpy(&conf, &oldConf, sizeof(GammaConf));  // rollback
         retValue = false;
     }
 
@@ -146,7 +149,7 @@ void Gamma868::enterLearnMode()
     // TODO: switch baudrate to 9600
 
     // Enter learn mode
-    TRACE("[Gamma868] Entering learn mode ... \n");
+    LOG_DEBUG(logger, "Entering learn mode...");
     gammaSwitch->low();
 
     // Wait 5 seconds
@@ -162,7 +165,7 @@ void Gamma868::exitLearnMode()
 {
     // TODO: switch baudrate according to configuration
 
-    TRACE("[Gamma868] Exiting learn mode. \n");
+    LOG_DEBUG(logger, "Exiting learn mode");
     write(fd, "#Q", 2);
 }
 
@@ -203,7 +206,7 @@ void Gamma868::writeConfig(const GammaConf& newConf)
  */
 bool Gamma868::updateConfig()
 {
-    if(!conf_enabled)
+    if (!conf_enabled)
         return false;
 
     gamma_msg msg;
@@ -213,9 +216,8 @@ bool Gamma868::updateConfig()
     read(fd, &(msg.buf), sizeof(gamma_msg));
 
     // Check values validity
-    if( msg.conf.lora_mode >= LAST_SF
-        || msg.conf.lora_power >= LAST_POWER
-        || msg.conf.baudrate >= LAST_BAUDRATE ) 
+    if (msg.conf.lora_mode >= LAST_SF || msg.conf.lora_power >= LAST_POWER ||
+        msg.conf.baudrate >= LAST_BAUDRATE)
     {
         return false;
     }
@@ -225,7 +227,7 @@ bool Gamma868::updateConfig()
 
     // Addresses
     memcpy(&conf.local_addr, msg.conf.local_addr, 3);
-    memcpy(&conf.dest_addr,  msg.conf.dest_addr,  3);
+    memcpy(&conf.dest_addr, msg.conf.dest_addr, 3);
 
     // LoRa values
     conf.lora_sf    = static_cast<GammaSF>(msg.conf.lora_mode);
@@ -244,6 +246,6 @@ void Gamma868::waitForOk()
 {
     char reply[3];
     read(fd, reply, 3);
-    TRACE("[Gamma868] device replied: %s\n", reply);
+    LOG_DEBUG(logger, "Device replied: {}", reply);
     Thread::sleep(100);
 }
\ No newline at end of file
diff --git a/src/shared/drivers/gamma868/Gamma868.h b/src/shared/drivers/gamma868/Gamma868.h
index e04ea39d4609d5f0bd59538ed9a389872b2b391c..40db04a673c96f5c72b307352a3e000f84a050c4 100644
--- a/src/shared/drivers/gamma868/Gamma868.h
+++ b/src/shared/drivers/gamma868/Gamma868.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -24,10 +24,11 @@
 #define GAMMA868_H
 
 #include <Common.h>
+#include <diagnostic/PrintLogger.h>
+#include <drivers/Transceiver.h>
 #include <fcntl.h>
 
 #include "GammaTypes.h"
-#include <drivers/Transceiver.h>
 
 class Gamma868 : public Transceiver
 {
@@ -37,7 +38,8 @@ public:
      * Create a Gamma868 object using the given path as the serial port to use.
      * @param serialPath        Name of the serial port (es. /dev/tty)
      * @param multiplier        If defined, after each send the send function
-     *                          will block for multiplier*n_bytes_sent milliseconds.
+     *                          will block for multiplier*n_bytes_sent
+     * milliseconds.
      */
     explicit Gamma868(const char* serialPath, const uint16_t multiplier = 0);
 
@@ -46,53 +48,56 @@ public:
      * @param serialPath        Name of the serial port (es. /dev/tty)
      * @param lrn_pin           pin connected to the Learn Mode pin of the Gamma
      * @param multiplier        If defined, after each send the send() function
-     *                          will block for multiplier*n_bytes_sent milliseconds.
+     *                          will block for multiplier*n_bytes_sent
+     * milliseconds.
      */
-    Gamma868(const char* serialPath, miosix::GpioPin* lrn_pin, const uint16_t multiplier = 0);
+    Gamma868(const char* serialPath, miosix::GpioPin* lrn_pin,
+             const uint16_t multiplier = 0);
 
     ~Gamma868();
 
     /*
      * Send a message through the serial port to the gamma868 module (blocking).
-     * @param pkt               Pointer to the packet (needs to be at least pkt_len bytes).
+     * @param pkt               Pointer to the packet (needs to be at least
+     * pkt_len bytes).
      * @param pkt_len           Lenght of the packet to be sent.
      * @return                  True if the message was sent correctly.
      */
     bool send(uint8_t* pkt, size_t pkt_len) override;
 
     /*
-     * Receive a message through the serial port to the gamma868 module (blocking).
-     * @param pkt               Pointer to the buffer (needs to be at least pkt_len bytes).
+     * Receive a message through the serial port to the gamma868 module
+     * (blocking).
+     * @param pkt               Pointer to the buffer (needs to be at least
+     * pkt_len bytes).
      * @param pkt_len           Maximum lenght of the packet to be received.
      * @return                  Size of the data received or -1 if failure
      */
     ssize_t receive(uint8_t* pkt, size_t pkt_len) override;
 
     /*
-     * Set a new configuration to the gamma868 module. Can be done only if the 
+     * Set a new configuration to the gamma868 module. Can be done only if the
      * learn pin has been specified in the constructor.
      * @return       True if the configuration was set correctly.
      */
-    bool configure(const GammaConf& newConf); 
+    bool configure(const GammaConf& newConf);
 
     /*
-     * Reads the configuration from the device, updates the internal configuration
-     * variable and returns it. 
-     * Meaningful only if the learn pin has been specified in the constructor.
+     * Reads the configuration from the device, updates the internal
+     * configuration variable and returns it. Meaningful only if the learn pin
+     * has been specified in the constructor.
      * @return       The current configuration.
      */
     GammaConf readConfig();
 
     /*
-     * Immediately returns the value of the configuration variable, without reading it from
-     * the device.
+     * Immediately returns the value of the configuration variable, without
+     * reading it from the device.
      * @return       The current configuration.
-     * @warning      check the is_valid bit to see if the returned configuration is meaningful.
+     * @warning      check the is_valid bit to see if the returned configuration
+     * is meaningful.
      */
-    GammaConf getConfig() 
-    {
-        return conf;
-    }
+    GammaConf getConfig() { return conf; }
 
 private:
     int fd;
@@ -108,6 +113,8 @@ private:
     void writeConfig(const GammaConf& conf);
     bool updateConfig();
     void waitForOk();
+
+    PrintLogger logger = Logging::getLogger("gamma868");
 };
 
 #endif /* GAMMA868_H */
\ No newline at end of file
diff --git a/src/shared/drivers/gamma868/GammaTypes.h b/src/shared/drivers/gamma868/GammaTypes.h
index e5a63ac5c915f2e8bf36db360da64185584b5d1e..af07d445734e72820b8cf86ec67c23bfd6de77c3 100644
--- a/src/shared/drivers/gamma868/GammaTypes.h
+++ b/src/shared/drivers/gamma868/GammaTypes.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/gps/nmea/nmea.c b/src/shared/drivers/gps/nmea/nmea.c
new file mode 100644
index 0000000000000000000000000000000000000000..68d659f5f85a3ff4a6644140db615a5fd3684f03
--- /dev/null
+++ b/src/shared/drivers/gps/nmea/nmea.c
@@ -0,0 +1,613 @@
+/* Copyright © 2014 Kosma Moczek <kosma@cloudyourcar.com>
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+#include <drivers/gps/nmea/nmea.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h>
+
+#define boolstr(s) ((s) ? "true" : "false")
+
+static int hex2int(char c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    if (c >= 'A' && c <= 'F')
+        return c - 'A' + 10;
+    if (c >= 'a' && c <= 'f')
+        return c - 'a' + 10;
+    return -1;
+}
+
+uint8_t minmea_checksum(const char *sentence)
+{
+    // Support senteces with or without the starting dollar sign.
+    if (*sentence == '$')
+        sentence++;
+
+    uint8_t checksum = 0x00;
+
+    // The optional checksum is an XOR of all bytes between "$" and "*".
+    while (*sentence && *sentence != '*')
+        checksum ^= *sentence++;
+
+    return checksum;
+}
+
+bool minmea_check(const char *sentence, bool strict)
+{
+    uint8_t checksum = 0x00;
+
+    // Sequence length is limited.
+    if (strlen(sentence) > MINMEA_MAX_LENGTH + 3)
+        return false;
+
+    // A valid sentence starts with "$".
+    if (*sentence++ != '$')
+        return false;
+
+    // The optional checksum is an XOR of all bytes between "$" and "*".
+    while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence))
+        checksum ^= *sentence++;
+
+    // If checksum is present...
+    if (*sentence == '*') {
+        // Extract checksum.
+        sentence++;
+        int upper = hex2int(*sentence++);
+        if (upper == -1)
+            return false;
+        int lower = hex2int(*sentence++);
+        if (lower == -1)
+            return false;
+        int expected = upper << 4 | lower;
+
+        // Check for checksum mismatch.
+        if (checksum != expected)
+            return false;
+    } else if (strict) {
+        // Discard non-checksummed frames in strict mode.
+        return false;
+    }
+
+    // The only stuff allowed at this point is a newline.
+    if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n"))
+        return false;
+
+    return true;
+}
+
+static inline bool minmea_isfield(char c) {
+    return isprint((unsigned char) c) && c != ',' && c != '*';
+}
+
+bool minmea_scan(const char *sentence, const char *format, ...)
+{
+    bool result = false;
+    bool optional = false;
+    va_list ap;
+    va_start(ap, format);
+
+    const char *field = sentence;
+#define next_field() \
+    do { \
+        /* Progress to the next field. */ \
+        while (minmea_isfield(*sentence)) \
+            sentence++; \
+        /* Make sure there is a field there. */ \
+        if (*sentence == ',') { \
+            sentence++; \
+            field = sentence; \
+        } else { \
+            field = NULL; \
+        } \
+    } while (0)
+
+    while (*format) {
+        char type = *format++;
+
+        if (type == ';') {
+            // All further fields are optional.
+            optional = true;
+            continue;
+        }
+
+        if (!field && !optional) {
+            // Field requested but we ran out if input. Bail out.
+            goto parse_error;
+        }
+
+        switch (type) {
+            case 'c': { // Single character field (char).
+                char value = '\0';
+
+                if (field && minmea_isfield(*field))
+                    value = *field;
+
+                *va_arg(ap, char *) = value;
+            } break;
+
+            case 'd': { // Single character direction field (int).
+                int value = 0;
+
+                if (field && minmea_isfield(*field)) {
+                    switch (*field) {
+                        case 'N':
+                        case 'E':
+                            value = 1;
+                            break;
+                        case 'S':
+                        case 'W':
+                            value = -1;
+                            break;
+                        default:
+                            goto parse_error;
+                    }
+                }
+
+                *va_arg(ap, int *) = value;
+            } break;
+
+            case 'f': { // Fractional value with scale (struct minmea_float).
+                int sign = 0;
+                int_least32_t value = -1;
+                int_least32_t scale = 0;
+
+                if (field) {
+                    while (minmea_isfield(*field)) {
+                        if (*field == '+' && !sign && value == -1) {
+                            sign = 1;
+                        } else if (*field == '-' && !sign && value == -1) {
+                            sign = -1;
+                        } else if (isdigit((unsigned char) *field)) {
+                            int digit = *field - '0';
+                            if (value == -1)
+                                value = 0;
+                            if (value > (INT_LEAST32_MAX-digit) / 10) {
+                                /* we ran out of bits, what do we do? */
+                                if (scale) {
+                                    /* truncate extra precision */
+                                    break;
+                                } else {
+                                    /* integer overflow. bail out. */
+                                    goto parse_error;
+                                }
+                            }
+                            value = (10 * value) + digit;
+                            if (scale)
+                                scale *= 10;
+                        } else if (*field == '.' && scale == 0) {
+                            scale = 1;
+                        } else if (*field == ' ') {
+                            /* Allow spaces at the start of the field. Not NMEA
+                             * conformant, but some modules do this. */
+                            if (sign != 0 || value != -1 || scale != 0)
+                                goto parse_error;
+                        } else {
+                            goto parse_error;
+                        }
+                        field++;
+                    }
+                }
+
+                if ((sign || scale) && value == -1)
+                    goto parse_error;
+
+                if (value == -1) {
+                    /* No digits were scanned. */
+                    value = 0;
+                    scale = 0;
+                } else if (scale == 0) {
+                    /* No decimal point. */
+                    scale = 1;
+                }
+                if (sign)
+                    value *= sign;
+
+                *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale};
+            } break;
+
+            case 'i': { // Integer value, default 0 (int).
+                int value = 0;
+
+                if (field) {
+                    char *endptr;
+                    value = strtol(field, &endptr, 10);
+                    if (minmea_isfield(*endptr))
+                        goto parse_error;
+                }
+
+                *va_arg(ap, int *) = value;
+            } break;
+
+            case 's': { // String value (char *).
+                char *buf = va_arg(ap, char *);
+
+                if (field) {
+                    while (minmea_isfield(*field))
+                        *buf++ = *field++;
+                }
+
+                *buf = '\0';
+            } break;
+
+            case 't': { // NMEA talker+sentence identifier (char *).
+                // This field is always mandatory.
+                if (!field)
+                    goto parse_error;
+
+                if (field[0] != '$')
+                    goto parse_error;
+                int f=0;
+                for (f=0; f<5; f++)
+                    if (!minmea_isfield(field[1+f]))
+                        goto parse_error;
+
+                char *buf = va_arg(ap, char *);
+                memcpy(buf, field+1, 5);
+                buf[5] = '\0';
+            } break;
+
+            case 'D': { // Date (int, int, int), -1 if empty.
+                struct minmea_date *date = va_arg(ap, struct minmea_date *);
+
+                int d = -1, m = -1, y = -1;
+
+                if (field && minmea_isfield(*field)) {
+                    // Always six digits.
+                    int f=0;
+                    for (f=0; f<6; f++)
+                        if (!isdigit((unsigned char) field[f]))
+                            goto parse_error;
+
+                    char dArr[] = {field[0], field[1], '\0'};
+                    char mArr[] = {field[2], field[3], '\0'};
+                    char yArr[] = {field[4], field[5], '\0'};
+                    d = strtol(dArr, NULL, 10);
+                    m = strtol(mArr, NULL, 10);
+                    y = strtol(yArr, NULL, 10);
+                }
+
+                date->day = d;
+                date->month = m;
+                date->year = y;
+            } break;
+
+            case 'T': { // Time (int, int, int, int), -1 if empty.
+                struct minmea_time *time_ = va_arg(ap, struct minmea_time *);
+
+                int h = -1, i = -1, s = -1, u = -1;
+
+                if (field && minmea_isfield(*field)) {
+                    // Minimum required: integer time.
+                    int f=0;
+                    for (f=0; f<6; f++)
+                        if (!isdigit((unsigned char) field[f]))
+                            goto parse_error;
+
+                    char hArr[] = {field[0], field[1], '\0'};
+                    char iArr[] = {field[2], field[3], '\0'};
+                    char sArr[] = {field[4], field[5], '\0'};
+                    h = strtol(hArr, NULL, 10);
+                    i = strtol(iArr, NULL, 10);
+                    s = strtol(sArr, NULL, 10);
+                    field += 6;
+
+                    // Extra: fractional time. Saved as microseconds.
+                    if (*field++ == '.') {
+                        int value = 0;
+                        int scale = 1000000;
+                        while (isdigit((unsigned char) *field) && scale > 1) {
+                            value = (value * 10) + (*field++ - '0');
+                            scale /= 10;
+                        }
+                        u = value * scale;
+                    } else {
+                        u = 0;
+                    }
+                }
+
+                time_->hours = h;
+                time_->minutes = i;
+                time_->seconds = s;
+                time_->microseconds = u;
+            } break;
+
+            case '_': { // Ignore the field.
+            } break;
+
+            default: { // Unknown.
+                goto parse_error;
+            } break;
+        }
+
+        next_field();
+    }
+
+    result = true;
+
+parse_error:
+    va_end(ap);
+    return result;
+}
+
+bool minmea_talker_id(char talker[3], const char *sentence)
+{
+    char type[6];
+    if (!minmea_scan(sentence, "t", type))
+        return false;
+
+    talker[0] = type[0];
+    talker[1] = type[1];
+    talker[2] = '\0';
+
+    return true;
+}
+
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict)
+{
+    if (!minmea_check(sentence, strict))
+        return MINMEA_INVALID;
+
+    char type[6];
+    if (!minmea_scan(sentence, "t", type))
+        return MINMEA_INVALID;
+
+    if (!strcmp(type+2, "RMC"))
+        return MINMEA_SENTENCE_RMC;
+    if (!strcmp(type+2, "GGA"))
+        return MINMEA_SENTENCE_GGA;
+    if (!strcmp(type+2, "GSA"))
+        return MINMEA_SENTENCE_GSA;
+    if (!strcmp(type+2, "GLL"))
+        return MINMEA_SENTENCE_GLL;
+    if (!strcmp(type+2, "GST"))
+        return MINMEA_SENTENCE_GST;
+    if (!strcmp(type+2, "GSV"))
+        return MINMEA_SENTENCE_GSV;
+    if (!strcmp(type+2, "VTG"))
+        return MINMEA_SENTENCE_VTG;
+
+    return MINMEA_UNKNOWN;
+}
+
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence)
+{
+    // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+    char type[6];
+    char validity;
+    int latitude_direction;
+    int longitude_direction;
+    int variation_direction;
+    if (!minmea_scan(sentence, "tTcfdfdffDfd",
+            type,
+            &frame->time,
+            &validity,
+            &frame->latitude, &latitude_direction,
+            &frame->longitude, &longitude_direction,
+            &frame->speed,
+            &frame->course,
+            &frame->date,
+            &frame->variation, &variation_direction))
+        return false;
+    if (strcmp(type+2, "RMC"))
+        return false;
+
+    frame->valid = (validity == 'A');
+    frame->latitude.value *= latitude_direction;
+    frame->longitude.value *= longitude_direction;
+    frame->variation.value *= variation_direction;
+
+    return true;
+}
+
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence)
+{
+    // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
+    char type[6];
+    int latitude_direction;
+    int longitude_direction;
+
+    if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
+            type,
+            &frame->time,
+            &frame->latitude, &latitude_direction,
+            &frame->longitude, &longitude_direction,
+            &frame->fix_quality,
+            &frame->satellites_tracked,
+            &frame->hdop,
+            &frame->altitude, &frame->altitude_units,
+            &frame->height, &frame->height_units,
+            &frame->dgps_age))
+        return false;
+    if (strcmp(type+2, "GGA"))
+        return false;
+
+    frame->latitude.value *= latitude_direction;
+    frame->longitude.value *= longitude_direction;
+
+    return true;
+}
+
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence)
+{
+    // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
+    char type[6];
+
+    if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff",
+            type,
+            &frame->mode,
+            &frame->fix_type,
+            &frame->sats[0],
+            &frame->sats[1],
+            &frame->sats[2],
+            &frame->sats[3],
+            &frame->sats[4],
+            &frame->sats[5],
+            &frame->sats[6],
+            &frame->sats[7],
+            &frame->sats[8],
+            &frame->sats[9],
+            &frame->sats[10],
+            &frame->sats[11],
+            &frame->pdop,
+            &frame->hdop,
+            &frame->vdop))
+        return false;
+    if (strcmp(type+2, "GSA"))
+        return false;
+
+    return true;
+}
+
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence)
+{
+    // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$;
+    char type[6];
+    int latitude_direction;
+    int longitude_direction;
+
+    if (!minmea_scan(sentence, "tfdfdTc;c",
+            type,
+            &frame->latitude, &latitude_direction,
+            &frame->longitude, &longitude_direction,
+            &frame->time,
+            &frame->status,
+            &frame->mode))
+        return false;
+    if (strcmp(type+2, "GLL"))
+        return false;
+
+    frame->latitude.value *= latitude_direction;
+    frame->longitude.value *= longitude_direction;
+
+    return true;
+}
+
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence)
+{
+    // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
+    char type[6];
+
+    if (!minmea_scan(sentence, "tTfffffff",
+            type,
+            &frame->time,
+            &frame->rms_deviation,
+            &frame->semi_major_deviation,
+            &frame->semi_minor_deviation,
+            &frame->semi_major_orientation,
+            &frame->latitude_error_deviation,
+            &frame->longitude_error_deviation,
+            &frame->altitude_error_deviation))
+        return false;
+    if (strcmp(type+2, "GST"))
+        return false;
+
+    return true;
+}
+
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence)
+{
+    // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
+    // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
+    // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75
+    // $GPGSV,4,4,13,39,31,170,27*40
+    // $GPGSV,4,4,13*7B
+    char type[6];
+
+    if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii",
+            type,
+            &frame->total_msgs,
+            &frame->msg_nr,
+            &frame->total_sats,
+            &frame->sats[0].nr,
+            &frame->sats[0].elevation,
+            &frame->sats[0].azimuth,
+            &frame->sats[0].snr,
+            &frame->sats[1].nr,
+            &frame->sats[1].elevation,
+            &frame->sats[1].azimuth,
+            &frame->sats[1].snr,
+            &frame->sats[2].nr,
+            &frame->sats[2].elevation,
+            &frame->sats[2].azimuth,
+            &frame->sats[2].snr,
+            &frame->sats[3].nr,
+            &frame->sats[3].elevation,
+            &frame->sats[3].azimuth,
+            &frame->sats[3].snr
+            )) {
+        return false;
+    }
+    if (strcmp(type+2, "GSV"))
+        return false;
+
+    return true;
+}
+
+bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence)
+{
+    // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48
+    // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41
+    // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22
+    // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F
+    char type[6];
+    char c_true, c_magnetic, c_knots, c_kph, c_faa_mode;
+
+    if (!minmea_scan(sentence, "tfcfcfcfc;c",
+            type,
+            &frame->true_track_degrees,
+            &c_true,
+            &frame->magnetic_track_degrees,
+            &c_magnetic,
+            &frame->speed_knots,
+            &c_knots,
+            &frame->speed_kph,
+            &c_kph,
+            &c_faa_mode))
+        return false;
+    if (strcmp(type+2, "VTG"))
+        return false;
+    // check chars
+    if (c_true != 'T' ||
+        c_magnetic != 'M' ||
+        c_knots != 'N' ||
+        c_kph != 'K')
+        return false;
+    //frame->faa_mode = c_faa_mode;
+
+    return true;
+}
+/*
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_)
+{
+    if (date->year == -1 || time_->hours == -1)
+        return -1;
+
+    struct tm tm;
+    memset(&tm, 0, sizeof(tm));
+    tm.tm_year = 2000 + date->year - 1900;
+    tm.tm_mon = date->month - 1;
+    tm.tm_mday = date->day;
+    tm.tm_hour = time_->hours;
+    tm.tm_min = time_->minutes;
+    tm.tm_sec = time_->seconds;
+
+    time_t timestamp = timegm(&tm); //See README.md if your system lacks timegm().
+    if (timestamp != -1) {
+        ts->tv_sec = timestamp;
+        ts->tv_nsec = time_->microseconds * 1000;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+*/
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/shared/drivers/gps/nmea/nmea.h b/src/shared/drivers/gps/nmea/nmea.h
new file mode 100644
index 0000000000000000000000000000000000000000..afa6aa77c9c97638c824df550db764a898a144ca
--- /dev/null
+++ b/src/shared/drivers/gps/nmea/nmea.h
@@ -0,0 +1,263 @@
+/* Copyright (c) 2014 Skyward Experimental Rocketry
+ * Author: Kosma Moczek
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef MINMEA_H
+#define MINMEA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+
+#define MINMEA_MAX_LENGTH 80
+
+enum minmea_sentence_id {
+    MINMEA_INVALID = -1,
+    MINMEA_UNKNOWN = 0,
+    MINMEA_SENTENCE_RMC,
+    MINMEA_SENTENCE_GGA,
+    MINMEA_SENTENCE_GSA,
+    MINMEA_SENTENCE_GLL,
+    MINMEA_SENTENCE_GST,
+    MINMEA_SENTENCE_GSV,
+    MINMEA_SENTENCE_VTG,
+};
+
+struct minmea_float {
+    int_least32_t value;
+    int_least32_t scale;
+};
+
+struct minmea_date {
+    int day;
+    int month;
+    int year;
+};
+
+struct minmea_time {
+    int hours;
+    int minutes;
+    int seconds;
+    int microseconds;
+};
+
+struct minmea_sentence_rmc {
+    struct minmea_time time;
+    bool valid;
+    struct minmea_float latitude;
+    struct minmea_float longitude;
+    struct minmea_float speed;
+    struct minmea_float course;
+    struct minmea_date date;
+    struct minmea_float variation;
+};
+
+struct minmea_sentence_gga {
+    struct minmea_time time;
+    struct minmea_float latitude;
+    struct minmea_float longitude;
+    int fix_quality;
+    int satellites_tracked;
+    struct minmea_float hdop;
+    struct minmea_float altitude; char altitude_units;
+    struct minmea_float height; char height_units;
+    int dgps_age;
+};
+
+enum minmea_gll_status {
+    MINMEA_GLL_STATUS_DATA_VALID = 'A',
+    MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V',
+};
+
+// FAA mode added to some fields in NMEA 2.3.
+enum minmea_faa_mode {
+    MINMEA_FAA_MODE_AUTONOMOUS = 'A',
+    MINMEA_FAA_MODE_DIFFERENTIAL = 'D',
+    MINMEA_FAA_MODE_ESTIMATED = 'E',
+    MINMEA_FAA_MODE_MANUAL = 'M',
+    MINMEA_FAA_MODE_SIMULATED = 'S',
+    MINMEA_FAA_MODE_NOT_VALID = 'N',
+    MINMEA_FAA_MODE_PRECISE = 'P',
+};
+
+struct minmea_sentence_gll {
+    struct minmea_float latitude;
+    struct minmea_float longitude;
+    struct minmea_time time;
+    char status;
+    char mode;
+};
+
+struct minmea_sentence_gst {
+    struct minmea_time time;
+    struct minmea_float rms_deviation;
+    struct minmea_float semi_major_deviation;
+    struct minmea_float semi_minor_deviation;
+    struct minmea_float semi_major_orientation;
+    struct minmea_float latitude_error_deviation;
+    struct minmea_float longitude_error_deviation;
+    struct minmea_float altitude_error_deviation;
+};
+
+enum minmea_gsa_mode {
+    MINMEA_GPGSA_MODE_AUTO = 'A',
+    MINMEA_GPGSA_MODE_FORCED = 'M',
+};
+
+enum minmea_gsa_fix_type {
+    MINMEA_GPGSA_FIX_NONE = 1,
+    MINMEA_GPGSA_FIX_2D = 2,
+    MINMEA_GPGSA_FIX_3D = 3,
+};
+
+struct minmea_sentence_gsa {
+    char mode;
+    int fix_type;
+    int sats[12];
+    struct minmea_float pdop;
+    struct minmea_float hdop;
+    struct minmea_float vdop;
+};
+
+struct minmea_sat_info {
+    int nr;
+    int elevation;
+    int azimuth;
+    int snr;
+};
+
+struct minmea_sentence_gsv {
+    int total_msgs;
+    int msg_nr;
+    int total_sats;
+    struct minmea_sat_info sats[4];
+};
+
+struct minmea_sentence_vtg {
+    struct minmea_float true_track_degrees;
+    struct minmea_float magnetic_track_degrees;
+    struct minmea_float speed_knots;
+    struct minmea_float speed_kph;
+    enum minmea_faa_mode faa_mode;
+};
+
+/**
+ * Calculate raw sentence checksum. Does not check sentence integrity.
+ */
+uint8_t minmea_checksum(const char *sentence);
+
+/**
+ * Check sentence validity and checksum. Returns true for valid sentences.
+ */
+bool minmea_check(const char *sentence, bool strict);
+
+/**
+ * Determine talker identifier.
+ */
+bool minmea_talker_id(char talker[3], const char *sentence);
+
+/**
+ * Determine sentence identifier.
+ */
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict);
+
+/**
+ * Scanf-like processor for NMEA sentences. Supports the following formats:
+ * c - single character (char *)
+ * d - direction, returned as 1/-1, default 0 (int *)
+ * f - fractional, returned as value + scale (int *, int *)
+ * i - decimal, default zero (int *)
+ * s - string (char *)
+ * t - talker identifier and type (char *)
+ * T - date/time stamp (int *, int *, int *)
+ * Returns true on success. See library source code for details.
+ */
+bool minmea_scan(const char *sentence, const char *format, ...);
+
+/*
+ * Parse a specific type of sentence. Return true on success.
+ */
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence);
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence);
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence);
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence);
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence);
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence);
+bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence);
+
+/**
+ * Convert GPS UTC date/time representation to a UNIX timestamp.
+ */
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_);
+
+/**
+ * Rescale a fixed-point value to a different scale. Rounds towards zero.
+ */
+static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale)
+{
+    if (f->scale == 0)
+        return 0;
+    if (f->scale == new_scale)
+        return f->value;
+    if (f->scale > new_scale)
+        return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale);
+    else
+        return f->value * (new_scale/f->scale);
+}
+
+/**
+ * Convert a fixed-point value to a floating-point value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tofloat(struct minmea_float *f)
+{
+    if (f->scale == 0)
+        return NAN;
+    return (float) f->value / (float) f->scale;
+}
+
+/**
+ * Convert a raw coordinate to a floating point DD.DDD... value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tocoord(struct minmea_float *f)
+{
+    if (f->scale == 0)
+        return NAN;
+    int_least32_t degrees = f->value / (f->scale * 100);
+    int_least32_t minutes = f->value % (f->scale * 100);
+    return (float) degrees + (float) minutes / (60 * f->scale);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINMEA_H */
+
+/* vim: set ts=4 sw=4 et: */
diff --git a/src/shared/drivers/gps/ublox/UbloxGPS.cpp b/src/shared/drivers/gps/ublox/UbloxGPS.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..392be263e908aba8bb618febc96929b262a48171
--- /dev/null
+++ b/src/shared/drivers/gps/ublox/UbloxGPS.cpp
@@ -0,0 +1,532 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "UbloxGPS.h"
+
+#include <fcntl.h>
+
+#include "drivers/serial.h"
+#include "filesystem/file_access.h"
+
+#include "diagnostic/StackLogger.h"
+
+using namespace miosix;
+
+UbloxGPS::UbloxGPS(int baudrate_, uint8_t sampleRate_, int serialPortNum_,
+                   const char* serialPortName_, int defaultBaudrate_)
+    : baudrate(baudrate_), sampleRate(sampleRate_),
+      serialPortNumber(serialPortNum_), serialPortName(serialPortName_),
+      defaultBaudrate(defaultBaudrate_)
+{
+    // Prepare the gps file path with the specified name
+    strcpy(gpsFilePath, "/dev/");
+    strcat(gpsFilePath, serialPortName);
+}
+
+bool UbloxGPS::init()
+{
+    // Change the baud rate from the default value
+    if (!serialCommuinicationSetup())
+    {
+        return false;
+    }
+
+    Thread::sleep(10);
+
+    // Reset configuration to default
+    // TODO: maybe move this on serial communication setup
+    if (!resetConfiguration())
+    {
+        return false;
+    }
+
+    // Disable NMEA messages
+    if (!disableNMEAMessages())
+    {
+        return false;
+    }
+
+    Thread::sleep(100);
+
+    // Set GNSS configuration
+    if (!setGNSSConfiguration())
+    {
+        return false;
+    }
+
+    Thread::sleep(100);
+
+    // Enable UBX messages
+    if (!enableUBXMessages())
+    {
+        return false;
+    }
+
+    Thread::sleep(100);
+
+    // Set rate
+    if (!setRate())
+    {
+        return false;
+    }
+
+    return true;
+}
+
+bool UbloxGPS::selfTest() { return true; }
+
+UbloxGPSData UbloxGPS::sampleImpl()
+{
+    Lock<FastMutex> l(mutex);
+    return threadSample;
+}
+
+void UbloxGPS::run()
+{
+    /**
+     * UBX message structure:
+     * - 2B: Preamble
+     * - 1B: Message class
+     * - 1B: Message id
+     * - 2B: Payload length
+     * - lB: Payload
+     * - 2B: Checksum
+     */
+    uint8_t message[6 + UBX_MAX_PAYLOAD_LENGTH + 2];
+    uint16_t payloadLength;
+
+    while (!shouldStop())
+    {
+        StackLogger::getInstance()->updateStack(THID_GPS);
+
+        // Try to read the message
+        if (!readUBXMessage(message, payloadLength))
+        {
+            LOG_ERR(logger, "Unable to read a UBX message");
+            continue;
+        }
+
+        // Parse the message
+        if (!parseUBXMessage(message))
+        {
+            LOG_ERR(logger,
+                    "UBX message not recognized (class:0x{02x}, id: 0x{02x})",
+                    message[2], message[3]);
+        }
+    }
+}
+
+void UbloxGPS::ubxChecksum(uint8_t* msg, int len)
+{
+    uint8_t ck_a = 0, ck_b = 0;
+
+    // The length must be valid, at least 8 bytes (preamble, msg, length,
+    // checksum)
+    if (len <= 8)
+    {
+        return;
+    }
+
+    // Checksum calculation from byte 2 to end of payload
+    for (int i = 2; i < len - 2; i++)
+    {
+        ck_a = ck_a + msg[i];
+        ck_b = ck_b + ck_a;
+    }
+
+    msg[len - 2] = ck_a;
+    msg[len - 1] = ck_b;
+}
+
+bool UbloxGPS::writeUBXMessage(uint8_t* message, int length)
+{
+    // Compute the checksum
+    ubxChecksum(message, length);
+
+    // Write configuration
+    if (write(gpsFile, message, length) < 0)
+    {
+        LOG_ERR(logger,
+                "Failed to write ubx message (class:0x{02x}, id: 0x{02x})",
+                message[2], message[3]);
+        return false;
+    }
+
+    return true;
+}
+
+bool UbloxGPS::serialCommuinicationSetup()
+{
+    intrusive_ref_ptr<DevFs> devFs = FilesystemManager::instance().getDevFs();
+
+    // Change the baudrate only if it is different than the default
+    if (baudrate != defaultBaudrate)
+    {
+        // Close the gps file if already opened
+        devFs->remove(serialPortName);
+
+        // Open the serial port device with the default boudrate
+        if (!devFs->addDevice(serialPortName,
+                              intrusive_ref_ptr<Device>(new STM32Serial(
+                                  serialPortNumber, defaultBaudrate))))
+        {
+            LOG_ERR(logger,
+                    "[gps] Faild to open serial port {0} with baudrate {1} as "
+                    "file {2}",
+                    serialPortNumber, defaultBaudrate, serialPortName);
+            return false;
+        }
+
+        // Open the gps file
+        if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0)
+        {
+            LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath);
+            return false;
+        }
+
+        // Change boudrate
+        if (!setBaudrate())
+        {
+            return false;
+        };
+
+        // Close the gps file
+        if (close(gpsFile) < 0)
+        {
+            LOG_ERR(logger, "Failed to close gps file {}", gpsFilePath);
+            return false;
+        }
+
+        // Close the serial port
+        if (!devFs->remove(serialPortName))
+        {
+            LOG_ERR(logger, "Failed to close serial port {} as file {}",
+                    serialPortNumber, serialPortName);
+            return false;
+        }
+    }
+
+    // Reopen the serial port with the configured boudrate
+    if (!devFs->addDevice(serialPortName,
+                          intrusive_ref_ptr<Device>(
+                              new STM32Serial(serialPortNumber, baudrate))))
+    {
+        LOG_ERR(logger,
+                "Faild to open serial port {} with baudrate {} as file {}\n",
+                serialPortNumber, defaultBaudrate, serialPortName);
+        return false;
+    }
+
+    // Reopen the gps file
+    if ((gpsFile = open(gpsFilePath, O_RDWR)) < 0)
+    {
+        LOG_ERR(logger, "Failed to open gps file {}", gpsFilePath);
+        return false;
+    }
+
+    return true;
+}
+
+bool UbloxGPS::resetConfiguration()
+{
+    uint8_t ubx_cfg_prt[RESET_CONFIG_MSG_LEN] = {
+        0Xb5, 0x62,  // Preamble
+        0x06, 0x04,  // Message UBX-CFG-RST
+        0x04, 0x00,  // Length
+        0x00, 0x00,  // navBbrMask (Hot start)
+        0x00,        // Hardware reset immediately
+        0x00,        // Reserved
+        0xff, 0xff   // Checksum
+    };
+
+    return writeUBXMessage(ubx_cfg_prt, RESET_CONFIG_MSG_LEN);
+}
+
+bool UbloxGPS::setBaudrate()
+{
+    uint8_t ubx_cfg_prt[SET_BAUDRATE_MSG_LEN] = {
+        0Xb5, 0x62,              // Preamble
+        0x06, 0x8a,              // Message UBX-CFG-VALSET
+        0x0c, 0x00,              // Length
+        0x00,                    // Version
+        0xff,                    // All layers
+        0x00, 0x00,              // Reserved
+        0x01, 0x00, 0x52, 0x40,  // Configuration item key ID
+        0xff, 0xff, 0xff, 0xff,  // Value
+        0xff, 0xff               // Checksum
+    };
+
+    // Prepare boud rate
+    ubx_cfg_prt[14] = baudrate;
+    ubx_cfg_prt[15] = baudrate >> 8;
+    ubx_cfg_prt[16] = baudrate >> 16;
+    ubx_cfg_prt[17] = baudrate >> 24;
+
+    return writeUBXMessage(ubx_cfg_prt, SET_BAUDRATE_MSG_LEN);
+}
+
+bool UbloxGPS::disableNMEAMessages()
+{
+    uint8_t ubx_cfg_valset[DISABLE_NMEA_MESSAGES_MSG_LEN] = {
+        0Xb5, 0x62,              // Preamble
+        0x06, 0x8a,              // Message UBX-CFG-VALSET
+        0x22, 0x00,              // Length
+        0x00,                    // Version
+        0xff,                    // All layers
+        0x00, 0x00,              // Reserved
+        0xbb, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_GGA_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_GGA_UART1 value
+        0xca, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_GLL_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_GLL_UART1 value
+        0xc0, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_GSA_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_GSA_UART1 value
+        0xc5, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_GSV_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_GSV_UART1 value
+        0xac, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_RMC_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_RMC_UART1 value
+        0xb1, 0x00, 0x91, 0x20,  // CFG-MSGOUT-NMEA_ID_VTG_UART1 key ID
+        0x00,                    // CFG-MSGOUT-NMEA_ID_VTG_UART1 value
+        0xff, 0xff               // Checksum
+    };
+
+    return writeUBXMessage(ubx_cfg_valset, DISABLE_NMEA_MESSAGES_MSG_LEN);
+}
+
+bool UbloxGPS::setGNSSConfiguration()
+{
+    uint8_t ubx_cfg_valset[SET_GNSS_CONF_LEN] = {
+        0Xb5, 0x62,              // Preamble
+        0x06, 0x8a,              // Message UBX-CFG-VALSET
+        0x09, 0x00,              // Length
+        0x00,                    // Version
+        0x07,                    // All layers
+        0x00, 0x00,              // Reserved
+        0x21, 0x00, 0x11, 0x20,  // CFG-NAVSPG-DYNMODEL key ID
+        0x08,                    // CFG-NAVSPG-DYNMODEL value
+        0xff, 0xff               // Checksum
+    };
+
+    return writeUBXMessage(ubx_cfg_valset, SET_GNSS_CONF_LEN);
+}
+
+bool UbloxGPS::enableUBXMessages()
+{
+    uint8_t ubx_cfg_valset[ENABLE_UBX_MESSAGES_MSG_LEN] = {
+        0Xb5, 0x62,              // Preamble
+        0x06, 0x8a,              // Message UBX-CFG-VALSET
+        0x09, 0x00,              // Length
+        0x00,                    // Version
+        0xff,                    // All layers
+        0x00, 0x00,              // Reserved
+        0x07, 0x00, 0x91, 0x20,  // CFG-MSGOUT-UBX_NAV_PVT_UART1 key ID
+        0x01,                    // CFG-MSGOUT-UBX_NAV_PVT_UART1 value
+        0xff, 0xff               // Checksum
+    };
+
+    return writeUBXMessage(ubx_cfg_valset, ENABLE_UBX_MESSAGES_MSG_LEN);
+}
+
+bool UbloxGPS::setRate()
+{
+    uint16_t rate = 1000 / sampleRate;
+    LOG_DEBUG(logger, "Rate: {}", rate);
+
+    uint8_t ubx_cfg_valset[SET_RATE_MSG_LEN] = {
+        0Xb5, 0x62,              // Preamble
+        0x06, 0x8a,              // Message UBX-CFG-VALSET
+        0x0a, 0x00,              // Length
+        0x00,                    // Version
+        0x07,                    // All layers
+        0x00, 0x00,              // Reserved
+        0x01, 0x00, 0x21, 0x30,  // CFG-RATE-MEAS key ID
+        0xff, 0xff,              // CFG-RATE-MEAS value
+        0xff, 0xff               // Checksum
+    };
+
+    // Prepare rate
+    ubx_cfg_valset[14] = rate;
+    ubx_cfg_valset[15] = rate >> 8;
+
+    return writeUBXMessage(ubx_cfg_valset, SET_RATE_MSG_LEN);
+}
+
+bool UbloxGPS::readUBXMessage(uint8_t* message, uint16_t& payloadLength)
+{
+    // Read preamble
+    do
+    {
+        // Read util the first byte of the preamble
+        do
+        {
+            if (read(gpsFile, &message[0], 1) <= 0)  // No more data available
+            {
+                return false;
+            }
+        } while (message[0] != PREAMBLE[0]);
+
+        // Read the next byte
+        if (read(gpsFile, &message[1], 1) <= 0)  // No more data available
+        {
+            return false;
+        }
+    } while (message[1] != PREAMBLE[1]);  // Continue
+
+    // Read message class and ID
+    if (read(gpsFile, &message[2], 1) <= 0)
+    {
+        return false;
+    }
+    if (read(gpsFile, &message[3], 1) <= 0)
+    {
+        return false;
+    }
+
+    // Read length
+    if (read(gpsFile, &message[4], 2) <= 0)
+    {
+        return false;
+    }
+    payloadLength = message[4] | (message[5] << 8);
+    if (payloadLength > UBX_MAX_PAYLOAD_LENGTH)
+    {
+        return false;
+    }
+
+    // Read paylaod and checksum
+    for (auto i = 0; i < payloadLength + 2; i++)
+    {
+        if (read(gpsFile, &message[6 + i], 1) <= 0)
+        {
+            return false;
+        }
+    }
+
+    // Verify the checksum
+    uint8_t msgChecksum1 = message[6 + payloadLength];
+    uint8_t msgChecksum2 = message[6 + payloadLength + 1];
+    ubxChecksum(message, 6 + payloadLength + 2);
+    if (msgChecksum1 != message[6 + payloadLength] ||
+        msgChecksum2 != message[6 + payloadLength + 1])
+    {
+        LOG_ERR(logger, "Message checksum verification failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool UbloxGPS::parseUBXMessage(uint8_t* message)
+{
+    switch (message[2])  // Message class
+    {
+        case 0x01:  // UBX-NAV
+            return parseUBXNAVMessage(message);
+        case 0x05:  // UBX-ACK
+            return parseUBXACKMessage(message);
+    }
+    return false;
+}
+
+bool UbloxGPS::parseUBXNAVMessage(uint8_t* message)
+{
+    switch (message[3])  // Message id
+    {
+        case 0x07:  // UBX-NAV-PVT
+            // Lock the threadSample variable
+            Lock<FastMutex> l(mutex);
+
+            // Latitude
+            int32_t rawLatitude = message[6 + 28] | message[6 + 29] << 8 |
+                                  message[6 + 30] << 16 | message[6 + 31] << 24;
+            threadSample.latitude = (float)rawLatitude / 1e7;
+
+            // Longitude
+            int32_t rawLongitude = message[6 + 24] | message[6 + 25] << 8 |
+                                   message[6 + 26] << 16 |
+                                   message[6 + 27] << 24;
+            threadSample.longitude = (float)rawLongitude / 1e7;
+
+            // Height
+            int32_t rawHeight = message[6 + 32] | message[6 + 33] << 8 |
+                                message[6 + 34] << 16 | message[6 + 35] << 24;
+            threadSample.height = (float)rawHeight / 1e3;
+
+            // Velocity north
+            int32_t rawVelocityNorth = message[6 + 48] | message[6 + 49] << 8 |
+                                       message[6 + 50] << 16 |
+                                       message[6 + 51] << 24;
+            threadSample.velocity_north = (float)rawVelocityNorth / 1e3;
+
+            // Velocity east
+            int32_t rawVelocityEast = message[6 + 52] | message[6 + 53] << 8 |
+                                      message[6 + 54] << 16 |
+                                      message[6 + 55] << 24;
+            threadSample.velocity_east = (float)rawVelocityEast / 1e3;
+
+            // Velocity down
+            int32_t rawVelocityDown = message[6 + 56] | message[6 + 57] << 8 |
+                                      message[6 + 58] << 16 |
+                                      message[6 + 59] << 24;
+            threadSample.velocity_down = (float)rawVelocityDown / 1e3;
+
+            // Speed
+            int32_t rawSpeed = message[6 + 60] | message[6 + 61] << 8 |
+                               message[6 + 62] << 16 | message[6 + 63] << 24;
+            threadSample.speed = (float)rawSpeed / 1e3;
+
+            // Track (heading of motion)
+            int32_t rawTrack = message[6 + 64] | message[6 + 65] << 8 |
+                               message[6 + 66] << 16 | message[6 + 67] << 24;
+            threadSample.track = (float)rawTrack / 1e5;
+
+            // Number of satellite
+            threadSample.num_satellites = (uint8_t)message[6 + 23];
+
+            // Fix (every type of fix accepted)
+            threadSample.fix = message[6 + 20] != 0;
+
+            // Timestamp
+            threadSample.gps_timestamp = TimestampTimer::getTimestamp();
+
+            return true;
+    }
+
+    return false;
+}
+
+bool UbloxGPS::parseUBXACKMessage(uint8_t* message)
+{
+    switch (message[3])  // Message id
+    {
+        case 0x00:  // UBX-ACK-NAC
+            LOG_DEBUG(logger,
+                      "Received NAC for message (class:0x{02x}, id: 0x{02x})",
+                      message[6], message[7]);
+            return true;
+        case 0x01:  // UBX-ACK-ACK
+            LOG_DEBUG(logger,
+                      "Received ACK for message (class:0x{02x}, id: 0x{02x})",
+                      message[6], message[7]);
+            return true;
+    }
+    return false;
+}
diff --git a/src/shared/drivers/gps/ublox/UbloxGPS.h b/src/shared/drivers/gps/ublox/UbloxGPS.h
new file mode 100644
index 0000000000000000000000000000000000000000..a50e19fd8417324e8aa924dc051a27e8f966a422
--- /dev/null
+++ b/src/shared/drivers/gps/ublox/UbloxGPS.h
@@ -0,0 +1,146 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+#include <miosix.h>
+
+#include "ActiveObject.h"
+#include "UbloxGPSData.h"
+#include "sensors/Sensor.h"
+
+/**
+ * @brief Driver for Ublox GPSs
+ *
+ * This driver handles communication and setup with Ublox GPSs. It uses the
+ * binary UBX protocol to retrieve and parse navigation data quicker than using
+ * the string based NMEA.
+ *
+ * At initialization it configures the device with the specified baudrate, reset
+ * the configuration and sets up UBX messages and GNSS parameters.
+ *
+ * Communication with the device is performed through a file, the driver opens
+ * the serial port under the filepath /dev/<serialPortName>.
+ * There is no need for the file to be setted up beforhand.
+ *
+ * This driver was written for a NEO-M9N gps with the latest version of UBX.
+ */
+class UbloxGPS : public Sensor<UbloxGPSData>, public ActiveObject
+{
+public:
+    /**
+     * @brief Construct a new UbloxGPS object
+     *
+     * @param boudrate_ Baudrate to communicate with the device (max: 921600,
+     * min: 4800 for NEO-M9N)
+     * @param sampleRate_ GPS sample rate (max: 25 for NEO-M9N)
+     * @param serialPortName_ Name of the file for the gps device
+     * @param defaultBaudrate_ Startup baudrate (38400 for NEO-M9N)
+     */
+    UbloxGPS(int boudrate_ = 921600, uint8_t sampleRate_ = 10,
+             int serialPortNumber_ = 2, const char *serialPortName_ = "gps",
+             int defaultBaudrate_ = 38400);
+
+    /**
+     * @brief Sets up the serial port baudrate, disables the NMEA messages,
+     * configures GNSS options and enables UBX-PVT message
+     *
+     * @return True if the operation succeeded
+     */
+    bool init() override;
+
+    /**
+     * @brief Read a single message form the GPS, waits 2 sample cycle
+     *
+     * @return True if a message was sampled
+     */
+    bool selfTest() override;
+
+private:
+    UbloxGPSData sampleImpl() override;
+
+    void run() override;
+
+    void ubxChecksum(uint8_t *msg, int len);
+
+    /**
+     * @brief Compute the checksum and write the message to the device
+     */
+    bool writeUBXMessage(uint8_t *message, int length);
+
+    /**
+     * @brief Sets up the serial port with the correct baudrate
+     *
+     * Opens the serial port with the defaul baudrate and changes it to
+     * the value specified in the constructor, then it reopens the serial port.
+     * If the device is already using the correct baudrate this won't have
+     * effect. However if the gps is using a different baudrate the diver won't
+     * be able to communicate.
+     */
+    bool serialCommuinicationSetup();
+
+    bool resetConfiguration();
+
+    bool setBaudrate();
+
+    bool disableNMEAMessages();
+
+    bool setGNSSConfiguration();
+
+    bool enableUBXMessages();
+
+    bool setRate();
+
+    bool readUBXMessage(uint8_t *message, uint16_t &payloadLength);
+
+    bool parseUBXMessage(uint8_t *message);
+
+    bool parseUBXNAVMessage(uint8_t *message);
+
+    bool parseUBXACKMessage(uint8_t *message);
+
+    const int baudrate;        // [baud]
+    const uint8_t sampleRate;  // [Hz]
+    const int serialPortNumber;
+    const char *serialPortName;
+    const int defaultBaudrate;  // [baud]
+
+    char gpsFilePath[16];  ///< Allows for a filename of up to 10 characters
+    int gpsFile;
+
+    mutable miosix::FastMutex mutex;
+    UbloxGPSData threadSample{};
+
+    static constexpr int SET_BAUDRATE_MSG_LEN          = 20;
+    static constexpr int RESET_CONFIG_MSG_LEN          = 12;
+    static constexpr int DISABLE_NMEA_MESSAGES_MSG_LEN = 42;
+    static constexpr int SET_GNSS_CONF_LEN             = 17;
+    static constexpr int ENABLE_UBX_MESSAGES_MSG_LEN   = 17;
+    static constexpr int SET_RATE_MSG_LEN              = 18;
+
+    static constexpr uint8_t PREAMBLE[] = {0xb5, 0x62};
+
+    static constexpr int UBX_MAX_PAYLOAD_LENGTH = 92;
+
+    PrintLogger logger = Logging::getLogger("ubloxgps");
+};
diff --git a/src/shared/drivers/gps/ublox/UbloxGPSData.h b/src/shared/drivers/gps/ublox/UbloxGPSData.h
new file mode 100644
index 0000000000000000000000000000000000000000..86272a6ba38ac0ccc27af41775869de4bc3d726f
--- /dev/null
+++ b/src/shared/drivers/gps/ublox/UbloxGPSData.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Bonomini
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct UbloxGPSData : public GPSData
+{
+    static std::string header()
+    {
+        return "gps_timestamp,latitude,longitude,height,velocity_north,"
+               "velocity_east,velocity_down,speed,track,num_satellites,fix\n";
+    }
+
+    void print(std::ostream &os) const
+    {
+        os << gps_timestamp << "," << latitude << "," << longitude << ","
+           << height << "," << velocity_north << "," << velocity_east << ","
+           << velocity_down << "," << speed << "," << track << ","
+           << (int)num_satellites << "," << (int)fix << "\n";
+    }
+};
diff --git a/src/shared/drivers/hbridge/HBridge.cpp b/src/shared/drivers/hbridge/HBridge.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fc279273d68952f7a113143fb1d39b899462bb52
--- /dev/null
+++ b/src/shared/drivers/hbridge/HBridge.cpp
@@ -0,0 +1,86 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "HBridge.h"
+
+HBridge::HBridge(miosix::GpioPin inhibit, PWM::Timer timer, PWMChannel channel,
+                 uint32_t frequency, float duty_cycle,
+                 uint16_t disable_delay_ms)
+    : pin_inh(inhibit), pwm(timer, frequency), channel(channel),
+      duty_cycle(duty_cycle), disable_delay_ms(disable_delay_ms)
+{
+    pin_inh.low();
+
+    status.timestamp = TimestampTimer::getTimestamp();
+    status.state     = HBridgeState::DISABLED;
+
+    // Start PWM with 0 duty cycle to keep IN pins low
+    pwm.enableChannel(channel, 0.0f);
+
+    pwm.start();
+}
+
+HBridge::~HBridge()
+{
+    disable();
+
+    pwm.stop();
+}
+
+void HBridge::disable()
+{
+    if (status.state == HBridgeState::ENABLED)
+    {
+        pwm.setDutyCycle(channel, 0.0f);  // Set duty cycle to 0 to leave the IN
+                                          // pin of the h-bridge low
+
+        Thread::sleep(disable_delay_ms);  // Wait a short delay
+
+        pin_inh.low();  // Disable h-bridge
+
+        status.timestamp = TimestampTimer::getTimestamp();
+        status.state     = HBridgeState::DISABLED;
+    }
+}
+
+void HBridge::enable() { enableHBridge(channel, pin_inh, duty_cycle); }
+
+void HBridge::enableTest(float test_duty_cycle)
+{
+    enableHBridge(channel, pin_inh, test_duty_cycle);
+}
+
+HBridgeStatus HBridge::getStatus() { return status; }
+
+void HBridge::enableHBridge(PWMChannel channel, GpioPin& inh, float duty_cycle)
+{
+    if (status.state == HBridgeState::DISABLED)
+    {
+        // Enable PWM Generation
+        pwm.setDutyCycle(channel, duty_cycle);
+        // enable h-bridge
+        inh.high();
+
+        status.timestamp = TimestampTimer::getTimestamp();
+        status.state     = HBridgeState::ENABLED;
+    }
+}
diff --git a/src/shared/drivers/hbridge/HBridge.h b/src/shared/drivers/hbridge/HBridge.h
new file mode 100644
index 0000000000000000000000000000000000000000..d21fbb3f80dc9266e917d66101e136dccc772da0
--- /dev/null
+++ b/src/shared/drivers/hbridge/HBridge.h
@@ -0,0 +1,106 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <miosix.h>
+
+#include "HBridgeData.h"
+#include "drivers/pwm/pwm.h"
+#include "TimestampTimer.h"
+
+using miosix::GpioPin;
+using miosix::Thread;
+
+/**
+ * @brief Interface class to operate an h-bridge.
+ *
+ * Provides the ability to enable an h-bridge using a "nominal" duty cycle or
+ * using a "test" duty cycle.
+ */
+class HBridge
+{
+public:
+    /**
+     * @brief Create a new HBridge
+     *
+     * @param   inhibit            Inhibit pin of the h-bridge
+     * @param   timer              Timer peripheral used for the PWM signal
+     * @param   channel            PWM channel
+     * @param   frequency          Frequency of the PWM driving the h-bridge
+     * @param   duty_cycle         Duty cycle of the PWM (from 0.0 to 1.0)
+     * @param   disable_delay_ms   Period of time where the IN must be kept
+     *                             low before bringing ENA/INH low
+     */
+    HBridge(GpioPin inhibit, PWM::Timer timer, PWMChannel channel,
+            uint32_t frequency, float duty_cycle,
+            uint16_t disable_delay_ms = 50);
+
+    ~HBridge();
+
+    /**
+     * @brief Deactivates the h-bridge
+     */
+    void disable();
+
+    /**
+     * @brief Enables the h-bridge
+     *
+     * call disable() to stop
+     */
+    void enable();
+
+    /**
+     * @brief Enables the h-bridge using the "test" duty cycle.
+     *        Call disable() to stop
+     *
+     * @param test_duty_cycle   Duty cycle to be used when testing the
+     *                          h-bridge for continuity
+     */
+    void enableTest(float test_duty_cycle);
+
+    /**
+     * @return the HBridge object status
+     */
+    HBridgeStatus getStatus();
+
+private:
+    /**
+     * @brief Enables the h-bridge.
+     *
+     * @param channel       the h-bridge PWM channel
+     * @param ena_pin       enable pin of the h-bridge
+     * @param duty_cycle    the duty cycle to be used
+     */
+    void enableHBridge(PWMChannel channel, GpioPin& ena_pin, float duty_cycle);
+
+    HBridge(const HBridge& hb) = delete;
+
+    GpioPin pin_inh;
+    PWM pwm;
+    PWMChannel channel;
+
+    float duty_cycle;
+    uint16_t disable_delay_ms;
+
+    HBridgeStatus status;
+};
diff --git a/src/shared/drivers/hbridge/HBridgeData.h b/src/shared/drivers/hbridge/HBridgeData.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc78294aaabb02383c0738ae5565001554311302
--- /dev/null
+++ b/src/shared/drivers/hbridge/HBridgeData.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <ostream>
+#include <string>
+
+enum class HBridgeState : uint8_t
+{
+    DISABLED = 0,
+    ENABLED  = 1
+};
+
+struct HBridgeStatus
+{
+    uint64_t timestamp;
+    HBridgeState state = HBridgeState::DISABLED;
+
+    static std::string header() { return "timestamp,state\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << (uint8_t)state << "\n";
+    }
+};
diff --git a/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp b/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
index e2c97970da71ad2f3b0dd7edc5ef62d29f82f6ec..f33431c79b0fc72bbeb3e7a4ba326c5ac58a333b 100644
--- a/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
+++ b/src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
@@ -1,29 +1,25 @@
-/***************************************************************************
- *   Copyright (C) 2013 by Terraneo Federico and Silvano Seva              *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2013 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 
 #include "stm32f2_f4_i2c.h"
 #include <kernel/scheduler/scheduler.h>
diff --git a/src/shared/drivers/i2c/stm32f2_f4_i2c.h b/src/shared/drivers/i2c/stm32f2_f4_i2c.h
index c493ef935b5589d8349b6f7d185562d2a5828d1d..80882b524c17f0bad804a71b1eed21879314af33 100644
--- a/src/shared/drivers/i2c/stm32f2_f4_i2c.h
+++ b/src/shared/drivers/i2c/stm32f2_f4_i2c.h
@@ -1,29 +1,25 @@
-/***************************************************************************
- *   Copyright (C) 2013 by Terraneo Federico and Silvano Seva              *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2013 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 
 #ifndef STM32F2_I2C_H
 #define STM32F2_I2C_H
diff --git a/src/shared/drivers/interrupt/InterruptManager.cpp b/src/shared/drivers/interrupt/InterruptManager.cpp
index 8b5f22c26169a354eba73c6a6144db24b1f2c081..261db7df50e3fd5fae482f5115763eaf05acbf9e 100755
--- a/src/shared/drivers/interrupt/InterruptManager.cpp
+++ b/src/shared/drivers/interrupt/InterruptManager.cpp
@@ -1,3 +1,25 @@
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Author: Matteo Piazzolla
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #include "InterruptManager.h"
 #include "miosix.h"
 #include <e20/e20.h>
diff --git a/src/shared/drivers/interrupt/InterruptManager.h b/src/shared/drivers/interrupt/InterruptManager.h
index 87e956bee598f813063b10f3256eaadfc724d89a..322a005c54a3ced5c489676f876352b4f4c60c02 100755
--- a/src/shared/drivers/interrupt/InterruptManager.h
+++ b/src/shared/drivers/interrupt/InterruptManager.h
@@ -1,3 +1,25 @@
+/* Copyright (c) 2016-2018 Skyward Experimental Rocketry
+ * Author: Matteo Piazzolla
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #ifndef INTERRUPTMANAGER_H
 #define INTERRUPTMANAGER_H
 
diff --git a/src/shared/drivers/interrupt/external_interrupts.cpp b/src/shared/drivers/interrupt/external_interrupts.cpp
index 7b58d7c3d19e8538713881fb2526df62b1bfc588..69592402a4d8a5bd2bd3bb64a1dfd298ff4a677c 100644
--- a/src/shared/drivers/interrupt/external_interrupts.cpp
+++ b/src/shared/drivers/interrupt/external_interrupts.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Alvise de' Faveri Tron
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Alvise de'Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,13 +13,14 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
+#include "external_interrupts.h"
 #include <miosix.h>
 using namespace miosix;
 
@@ -31,7 +31,7 @@ using namespace miosix;
  *
  * In particular, an EXTIxx_IRQHandlerImpl() function is declared for every
  * interrupt from 5 to 15, and defined in a similar fashion as miosix's
- * 'stage_1_boot', i.e. handlers are "weak" and, if not overridden, will be 
+ * 'stage_1_boot', i.e. handlers are "weak" and, if not overridden, will be
  * assigned to a default handler.
  *
  * NOTE: you will still have to enable the corresponding interrupt if you
@@ -61,33 +61,40 @@ using namespace miosix;
 
 // All unused interrupts call this function.
 extern void unexpectedInterrupt();
-extern "C" void Default_EXTI_Handler() 
-{
-    unexpectedInterrupt();
-}
+extern "C" void Default_EXTI_Handler() { unexpectedInterrupt(); }
 
 /**
  * Declaration of a separate IRQHandler for each external
  * interrupt. If no further implementatio is provided, the
  * Default_Handler will be called.
  */
-void __attribute__((weak))  EXTI5_IRQHandlerImpl();
-void __attribute__((weak))  EXTI6_IRQHandlerImpl();
-void __attribute__((weak))  EXTI7_IRQHandlerImpl();
-void __attribute__((weak))  EXTI8_IRQHandlerImpl();
-void __attribute__((weak))  EXTI9_IRQHandlerImpl();
-void __attribute__((weak))  EXTI10_IRQHandlerImpl();
-void __attribute__((weak))  EXTI11_IRQHandlerImpl();
-void __attribute__((weak))  EXTI12_IRQHandlerImpl();
-void __attribute__((weak))  EXTI13_IRQHandlerImpl();
-void __attribute__((weak))  EXTI14_IRQHandlerImpl();
-void __attribute__((weak))  EXTI15_IRQHandlerImpl();
-
-#pragma weak EXTI5_IRQHandlerImpl = Default_EXTI_Handler
-#pragma weak EXTI6_IRQHandlerImpl = Default_EXTI_Handler
-#pragma weak EXTI7_IRQHandlerImpl = Default_EXTI_Handler
-#pragma weak EXTI8_IRQHandlerImpl = Default_EXTI_Handler
-#pragma weak EXTI9_IRQHandlerImpl = Default_EXTI_Handler
+void __attribute__((weak)) EXTI0_IRQHandlerImpl();
+void __attribute__((weak)) EXTI1_IRQHandlerImpl();
+void __attribute__((weak)) EXTI2_IRQHandlerImpl();
+void __attribute__((weak)) EXTI3_IRQHandlerImpl();
+void __attribute__((weak)) EXTI4_IRQHandlerImpl();
+void __attribute__((weak)) EXTI5_IRQHandlerImpl();
+void __attribute__((weak)) EXTI6_IRQHandlerImpl();
+void __attribute__((weak)) EXTI7_IRQHandlerImpl();
+void __attribute__((weak)) EXTI8_IRQHandlerImpl();
+void __attribute__((weak)) EXTI9_IRQHandlerImpl();
+void __attribute__((weak)) EXTI10_IRQHandlerImpl();
+void __attribute__((weak)) EXTI11_IRQHandlerImpl();
+void __attribute__((weak)) EXTI12_IRQHandlerImpl();
+void __attribute__((weak)) EXTI13_IRQHandlerImpl();
+void __attribute__((weak)) EXTI14_IRQHandlerImpl();
+void __attribute__((weak)) EXTI15_IRQHandlerImpl();
+
+#pragma weak EXTI0_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI1_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI2_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI3_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI4_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI5_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI6_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI7_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI8_IRQHandlerImpl  = Default_EXTI_Handler
+#pragma weak EXTI9_IRQHandlerImpl  = Default_EXTI_Handler
 #pragma weak EXTI10_IRQHandlerImpl = Default_EXTI_Handler
 #pragma weak EXTI11_IRQHandlerImpl = Default_EXTI_Handler
 #pragma weak EXTI12_IRQHandlerImpl = Default_EXTI_Handler
@@ -97,7 +104,67 @@ void __attribute__((weak))  EXTI15_IRQHandlerImpl();
 
 /**
  * Implementation of the IRQHandler that is triggered when
- * any external interrupt between 5 and 9 is raised. 
+ * external interrupt 0 is raised.
+ */
+void __attribute__((naked)) EXTI0_IRQHandler()
+{
+    saveContext();
+    EXTI->PR = EXTI_PR_PR0;
+    asm volatile("bl _Z20EXTI0_IRQHandlerImplv");
+    restoreContext();
+}
+
+/**
+ * Implementation of the IRQHandler that is triggered when
+ * external interrupt 1 is raised.
+ */
+void __attribute__((naked)) EXTI1_IRQHandler()
+{
+    saveContext();
+    EXTI->PR = EXTI_PR_PR1;
+    asm volatile("bl _Z20EXTI1_IRQHandlerImplv");
+    restoreContext();
+}
+
+/**
+ * Implementation of the IRQHandler that is triggered when
+ * external interrupt 2 is raised.
+ */
+void __attribute__((naked)) EXTI2_IRQHandler()
+{
+    saveContext();
+    EXTI->PR = EXTI_PR_PR2;
+    asm volatile("bl _Z20EXTI2_IRQHandlerImplv");
+    restoreContext();
+}
+
+/**
+ * Implementation of the IRQHandler that is triggered when
+ * external interrupt 3 is raised.
+ */
+void __attribute__((naked)) EXTI3_IRQHandler()
+{
+    saveContext();
+    EXTI->PR = EXTI_PR_PR3;
+    asm volatile("bl _Z20EXTI3_IRQHandlerImplv");
+    restoreContext();
+}
+
+/**
+ * Implementation of the IRQHandler that is triggered when
+ * external interrupt 4 is raised.
+ */
+void __attribute__((naked)) EXTI4_IRQHandler()
+{
+    saveContext();
+    EXTI->PR = EXTI_PR_PR4;
+    asm volatile("bl _Z20EXTI4_IRQHandlerImplv");
+    restoreContext();
+}
+
+/**
+ * Implementation of the IRQHandler that is triggered when
+ * any external interrupt between 5 and 9 is raised.
  */
 void __attribute__((naked)) EXTI9_5_IRQHandler()
 {
@@ -108,7 +175,7 @@ void __attribute__((naked)) EXTI9_5_IRQHandler()
 
 /**
  * Implementation of the IRQHandler that is triggered when
- * any external interrupt between 10 and 15 is raised. 
+ * any external interrupt between 10 and 15 is raised.
  */
 void __attribute__((naked)) EXTI15_10_IRQHandler()
 {
@@ -118,78 +185,148 @@ void __attribute__((naked)) EXTI15_10_IRQHandler()
 }
 
 /**
- * Read from the PR register which interrupt is pending 
+ * Read from the PR register which interrupt is pending
  * and call the corresponding IRQHandler.
- * If no flag in the range covered by this IRQ is set, 
+ * If no flag in the range covered by this IRQ is set,
  * call default handler.
  */
 void __attribute__((used)) EXTI9_5_IRQHandlerImpl()
 {
-    if(EXTI->PR & EXTI_PR_PR5)
+    if (EXTI->PR & EXTI_PR_PR5)
     {
-        EXTI->PR |= EXTI_PR_PR5;   // Clear pending flag
+        EXTI->PR = EXTI_PR_PR5;  // Clear pending flag
         EXTI5_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR6)
+    else if (EXTI->PR & EXTI_PR_PR6)
     {
-        EXTI->PR |= EXTI_PR_PR6;
+        EXTI->PR = EXTI_PR_PR6;
         EXTI6_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR7)
+    else if (EXTI->PR & EXTI_PR_PR7)
     {
-        EXTI->PR |= EXTI_PR_PR7;
+        EXTI->PR = EXTI_PR_PR7;
         EXTI7_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR8)
+    else if (EXTI->PR & EXTI_PR_PR8)
     {
-        EXTI->PR |= EXTI_PR_PR8;
+        EXTI->PR = EXTI_PR_PR8;
         EXTI8_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR9)
+    else if (EXTI->PR & EXTI_PR_PR9)
     {
-        EXTI->PR |= EXTI_PR_PR9;
+        EXTI->PR = EXTI_PR_PR9;
         EXTI9_IRQHandlerImpl();
     }
     else
     {
+#ifndef FLIGHT
         Default_EXTI_Handler();
+#endif
     }
 }
 
 void __attribute__((used)) EXTI15_10_IRQHandlerImpl()
 {
-    if(EXTI->PR & EXTI_PR_PR10)
+    if (EXTI->PR & EXTI_PR_PR10)
     {
-        EXTI->PR |= EXTI_PR_PR10;   // Clear pending flag
+        EXTI->PR = EXTI_PR_PR10;  // Clear pending flag
         EXTI10_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR11)
+    else if (EXTI->PR & EXTI_PR_PR11)
     {
-        EXTI->PR |= EXTI_PR_PR11;
+        EXTI->PR = EXTI_PR_PR11;
         EXTI11_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR12)
+    else if (EXTI->PR & EXTI_PR_PR12)
     {
-        EXTI->PR |= EXTI_PR_PR12;
+        EXTI->PR = EXTI_PR_PR12;
         EXTI12_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR13)
+    else if (EXTI->PR & EXTI_PR_PR13)
     {
-        EXTI->PR |= EXTI_PR_PR13;
+        EXTI->PR = EXTI_PR_PR13;
         EXTI13_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR14)
+    else if (EXTI->PR & EXTI_PR_PR14)
     {
-        EXTI->PR |= EXTI_PR_PR14;
+        EXTI->PR = EXTI_PR_PR14;
         EXTI14_IRQHandlerImpl();
     }
-    else if(EXTI->PR & EXTI_PR_PR15)
+    else if (EXTI->PR & EXTI_PR_PR15)
     {
-        EXTI->PR |= EXTI_PR_PR15;
+        EXTI->PR = EXTI_PR_PR15;
         EXTI15_IRQHandlerImpl();
     }
     else
     {
+#ifndef FLIGHT
         Default_EXTI_Handler();
+#endif
     }
 }
+
+constexpr unsigned ConvertGPIO_BASEtoUnsiged(unsigned P)
+{
+    // clang-format off
+    return  P == GPIOA_BASE? 0 :
+            P == GPIOB_BASE? 1 :
+            P == GPIOC_BASE? 2 :
+            P == GPIOD_BASE? 3 :
+            P == GPIOE_BASE? 4 :
+            P == GPIOF_BASE? 5 :
+            P == GPIOG_BASE? 6 :
+            P == GPIOH_BASE? 7 :
+            P == GPIOI_BASE? 8 :
+#if (defined (STM32F427xx) || defined (STM32F437xx) || defined (STM32F429xx) || defined (STM32F439xx))
+            P == GPIOJ_BASE? 9 :
+            P == GPIOK_BASE? 10 :
+#endif                
+            0;
+    // clang-format on
+}
+
+constexpr unsigned GetEXTI_IRQn(unsigned N)
+{
+    // clang-format off
+
+    return N==0? EXTI0_IRQn :
+           N==1? EXTI1_IRQn :
+           N==2? EXTI2_IRQn :
+           N==3? EXTI3_IRQn :
+           N==4? EXTI4_IRQn :
+           (N>=5&&N<=9)? EXTI9_5_IRQn :
+           EXTI15_10_IRQn;
+
+    // clang-format on
+}
+
+constexpr unsigned GetEXTICR_register_value(unsigned P, unsigned N)
+{
+    return (ConvertGPIO_BASEtoUnsiged(P) << ((N % 4) * 4));
+}
+
+void enableExternalInterrupt(unsigned int gpio_port, unsigned int gpio_num,
+                             InterruptTrigger trigger, unsigned int priority)
+{
+    auto exitcr_reg_value = GetEXTICR_register_value(gpio_port, gpio_num);
+
+    {
+        FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
+        SYSCFG->EXTICR[int(gpio_num / 4)] |= exitcr_reg_value;
+    }
+
+    EXTI->IMR |= 1 << gpio_num;
+
+    if (trigger == InterruptTrigger::RISING_EDGE ||
+        trigger == InterruptTrigger::RISING_FALLING_EDGE)
+        EXTI->RTSR |= 1 << gpio_num;
+
+    if (trigger == InterruptTrigger::FALLING_EDGE ||
+        trigger == InterruptTrigger::RISING_FALLING_EDGE)
+        EXTI->FTSR |= 1 << gpio_num;
+
+    NVIC_EnableIRQ(static_cast<IRQn_Type>(GetEXTI_IRQn(gpio_num)));
+    NVIC_SetPriority(static_cast<IRQn_Type>(GetEXTI_IRQn(gpio_num)), priority);
+}
\ No newline at end of file
diff --git a/src/shared/drivers/interrupt/external_interrupts.h b/src/shared/drivers/interrupt/external_interrupts.h
new file mode 100644
index 0000000000000000000000000000000000000000..02dce4f69a48f013614762ef6a7eda67834c79fb
--- /dev/null
+++ b/src/shared/drivers/interrupt/external_interrupts.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+enum class InterruptTrigger
+{
+    RISING_EDGE,
+    FALLING_EDGE,
+    RISING_FALLING_EDGE
+};
+
+/**
+ * @brief Enables external interrupts on the provided pin.
+ * Remember to set the GPIO to input mode!
+ * 
+ * @param gpio_port Port of the pin (eg:  GPIOC_BASE)
+ * @param gpio_num Pin number (eg: 4 for PC4)
+ * @param trigger Interrupt detection trigger (rising edge, falling or both)
+ * @param priority Interrupt priority [0-15], 0 = Highest priority
+ */
+void enableExternalInterrupt(unsigned int gpio_port, unsigned int gpio_num,
+                     InterruptTrigger trigger, unsigned int priority = 15);
\ No newline at end of file
diff --git a/src/shared/drivers/mavlink/MavlinkDriver.h b/src/shared/drivers/mavlink/MavlinkDriver.h
index e89ebd3e1f4fe8a65fc11ed349144e00c7de5f51..e2c025f735c5187b76f4a6d22f4a8389fb1aa31a 100644
--- a/src/shared/drivers/mavlink/MavlinkDriver.h
+++ b/src/shared/drivers/mavlink/MavlinkDriver.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2018-2019 Skyward Experimental Rocketry
- * Authors: Alvise de'Faveri Tron
+/* Copyright (c) 2018-2019 Skyward Experimental Rocketry
+ * Author: Alvise de'Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -24,6 +23,8 @@
 #pragma once
 
 #include <Common.h>
+#include <diagnostic/PrintLogger.h>
+
 #include <vector>
 
 /**
@@ -38,10 +39,10 @@
 implementation before including MavlinkDriver.h"
 #endif
 
-#include <mavlink_skyward_lib/mavlink_lib/mavlink_types.h>
-
 #include <drivers/Transceiver.h>
+#include <mavlink_skyward_lib/mavlink_lib/mavlink_types.h>
 #include <utils/collections/SyncPacketQueue.h>
+
 #include "MavlinkStatus.h"
 #include "diagnostic/SkywardStack.h"
 #include "diagnostic/StackLogger.h"
@@ -165,6 +166,8 @@ private:
 
     miosix::Thread* sndThread = nullptr;
     miosix::Thread* rcvThread = nullptr;
+
+    PrintLogger logger = Logging::getLogger("mavlinkdriver");
 };
 
 /**********************************************************************
@@ -199,7 +202,7 @@ bool MavlinkDriver<pkt_len, out_queue_size>::start()
         }
         else
         {
-            TRACE("[MAV] Could not start sender!\n");
+            LOG_ERR(logger, "Could not start sender!");
         }
     }
 
@@ -216,13 +219,13 @@ bool MavlinkDriver<pkt_len, out_queue_size>::start()
         }
         else
         {
-            TRACE("[MAV] Could not start receiver!\n");
+            LOG_ERR(logger, "Could not start receiver!");
         }
     }
 
     if (sndStarted && rcvStarted)
     {
-        TRACE("[MAV] Start ok (sender and receiver)\n");
+        LOG_DEBUG(logger, "Start ok (sender and receiver)\n");
     }
 
     return (sndStarted && rcvStarted);
@@ -262,7 +265,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::updateQueueStats(int dropped)
 
     if (dropped != 0)
     {
-        TRACE("[MAV] Buffer full. The oldest message has been discarded.\n");
+        LOG_ERR(logger, "Buffer full, the oldest message has been discarded");
         status.n_dropped_packets++;
     }
 
@@ -308,11 +311,10 @@ void MavlinkDriver<pkt_len, out_queue_size>::runReceiver()
                     // what could happen.
                     miosix::Unlock<miosix::FastMutex> unlock(l);
 
-                    TRACE(
-                        "[MAV] Received message with ID %d, sequence: %d "
-                        "from "
-                        "component %d of system %d\n",
-                        msg.msgid, msg.seq, msg.compid, msg.sysid);
+                    LOG_DEBUG(logger,
+                              "Received message with ID {}, sequence: {} from "
+                              "component {} of system {}",
+                              msg.msgid, msg.seq, msg.compid, msg.sysid);
 
                     // ... handle the command
                     onRcv(this, msg);
@@ -327,7 +329,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::runReceiver()
 template <unsigned int pkt_len, unsigned int out_queue_size>
 void MavlinkDriver<pkt_len, out_queue_size>::runSender()
 {
-    TRACE("[MAV] Sender is running\n");
+    LOG_DEBUG(logger, "Sender is running");
     Packet<pkt_len> pkt;
 
     while (!stop_flag)
@@ -345,8 +347,8 @@ void MavlinkDriver<pkt_len, out_queue_size>::runSender()
             {
                 out_queue.pop();  // remove from queue
 
-                // TRACE("[MAV] Sending packet. Size: %d (age: %d)\n", pkt.size(),
-                //       (int)age);
+                // LOG_DEBUG(logger, "Sending packet. Size: {} (age: {})",
+                //           pkt.size(), age);
 
                 bool sent = device->send(pkt.content.data(), pkt.size());
                 updateSenderStats(pkt.msgCount(), sent);
@@ -373,7 +375,7 @@ void MavlinkDriver<pkt_len, out_queue_size>::updateSenderStats(size_t msgCount,
         if (!sent)
         {
             status.n_send_errors++;
-            TRACE("[MAV] Error: could not send message\n");
+            LOG_ERR(logger, "Could not send message");
         }
     }
 
@@ -384,6 +386,7 @@ template <unsigned int pkt_len, unsigned int out_queue_size>
 MavlinkStatus MavlinkDriver<pkt_len, out_queue_size>::getStatus()
 {
     miosix::Lock<miosix::FastMutex> l(mtx_status);
+    status.timestamp = miosix::getTick();
     return status;
 }
 
diff --git a/src/shared/drivers/mavlink/MavlinkStatus.h b/src/shared/drivers/mavlink/MavlinkStatus.h
index a3b327cc1339da66afe975f84e12cb9dcc09f5f3..ed69cd1ef56b48eaa380fd083a769f03014ba04d 100644
--- a/src/shared/drivers/mavlink/MavlinkStatus.h
+++ b/src/shared/drivers/mavlink/MavlinkStatus.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Alvise de'Faveri Tron
+ * Author: Alvise de'Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/memory/FlashController.h b/src/shared/drivers/memory/FlashController.h
new file mode 100644
index 0000000000000000000000000000000000000000..7d442a742e164b6d3cf1bc65e5371f09b9a1e208
--- /dev/null
+++ b/src/shared/drivers/memory/FlashController.h
@@ -0,0 +1,670 @@
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Matteo Piazzolla
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef FLASHCONTROLLER_H
+#define FLASHCONTROLLER_H
+
+#include <drivers/memory/FlashDriverInclude.h>
+
+#include <miosix.h>
+#include <Singleton.h>
+#include <diagnostic/FaultCounter.h>
+#include <diagnostic/NewLogger.h>
+
+using logging::logger;
+
+//Forward declaration
+namespace testing
+{
+namespace flashmemorytests
+{
+template<typename >
+class FlashTest;
+}
+}
+
+namespace flashmemory
+{
+
+/*
+ 16 pagine da 256 byte
+ 1000000 pagine in un banco
+ 62500 sottosettori
+ */
+static const uint16_t CONTROLLER_VERSION = 1;
+static const uint16_t BOARD_ID = 0xAB;  //TBD
+
+//BLOCK_SIZE deve essere un divisore di SUBSECTOR_SIZE
+static const uint32_t BLOCK_SIZE = PAGE_SIZE;
+
+/** Number of flash data blocks that fit in each sector.
+ *  First block of each sector is reserved for the header.
+ *  In the first sector, the entire first subsector is reserved for the header.
+ */
+static const uint32_t BLOCKS_PER_SECTOR = SECTOR_SIZE / BLOCK_SIZE;
+static const uint32_t BLOCKS_NUM = BLOCKS_PER_SECTOR * SECTORS_NUM;
+
+static const uint32_t FIRST_BOOT_KEY = 0xF135B007;
+static const uint32_t READ_ONLY_KEY = 0x3EAD0271;
+
+//How many sectors should we attempt to write in before we give up?
+static const int MAX_SECTOR_ITERATIONS = 50;
+
+struct FlashHeader
+{
+  const uint16_t version = CONTROLLER_VERSION;
+  const uint16_t board_id = BOARD_ID;
+
+  uint32_t first_boot = FIRST_BOOT_KEY;
+  uint32_t start_index = 0;
+  uint32_t read_only = 0;
+
+  bool operator==(const FlashHeader& other) const
+  {
+    return memcmp(this, &other, sizeof(FlashHeader)) == 0;
+  }
+
+  bool operator!=(const FlashHeader& other) const
+  {
+    return memcmp(this, &other, sizeof(FlashHeader)) != 0;
+  }
+};
+
+static const uint8_t SECTOR_EMPTY = 0x55;
+static const uint8_t SECTOR_WRITTEN = 0x44;
+static const uint8_t SECTOR_CORRUPT = 0x00;
+
+struct SectorHeader
+{
+  uint8_t content = SECTOR_CORRUPT;  //Either one of SECTOR_EMPTY,
+  //SECTOR_WRITTEN, SECTOR_CORRUPT
+  bool operator==(const SectorHeader& other) const
+  {
+    return memcmp(this, &other, sizeof(SectorHeader)) == 0;
+  }
+};
+
+static const uint32_t FLASH_BUFFER_SIZE = (BLOCK_SIZE)
+    - (sizeof(uint32_t) * 2 + sizeof(uint16_t));
+
+struct FlashDataBlock
+{
+  uint32_t id;
+  uint32_t timestamp;
+  uint8_t buffer[FLASH_BUFFER_SIZE];
+  uint16_t crc;
+
+  bool operator==(const FlashDataBlock& other) const
+  {
+    return memcmp(this, &other, sizeof(FlashDataBlock)) == 0;
+  }
+
+  bool operator!=(const FlashDataBlock& other) const
+  {
+    return memcmp(this, &other, sizeof(FlashDataBlock)) != 0;
+  }
+};
+
+template<typename MemoryBus>
+class FlashController : Singleton<FlashController<MemoryBus>>
+{
+  friend class Singleton<FlashController<MemoryBus>>;
+
+  template<typename >
+  friend class testing::flashmemorytests::FlashTest;
+
+  typedef FlashController<MemoryBus> FlashControllerType;
+  typedef FlashDriver<MemoryBus> FlashDriverType;
+
+ public:
+  enum class Status
+  {
+    WRITE_READY = 0,
+    READ_ONLY = 1,
+    INITIALIZATION_ERROR = 2,
+    WRITE_ERROR = 3
+  };
+
+  void init()
+  {
+    status_ = Status::INITIALIZATION_ERROR;
+    block_index_ = SUBSECTOR_SIZE / BLOCK_SIZE;
+
+    FlashHeader header;
+
+    if (!readFlashHeader(&header))
+    {
+      logger.critical(logtag(),
+                      "Cannot read flash header.");
+      sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT);
+      return;
+    }
+
+    if (header.read_only == READ_ONLY_KEY)
+    {
+      flash_->setReadOnly();
+      status_ = Status::READ_ONLY;
+    } else
+    {
+      if (header.first_boot == FIRST_BOOT_KEY)
+      {
+        //Update the first boot key
+        header.first_boot = 0;
+
+        if (!writeFlashHeader(header, false))
+        {
+          logger.critical(logtag(),
+                                "Could not update first boot value.");
+          sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT);
+          return;
+        }
+
+        if (header.start_index >= block_index_)
+        {
+          block_index_ = header.start_index;
+        }
+        status_ = Status::WRITE_READY;
+      } else
+      {
+        if (recoverFromReboot())
+        {
+          status_ = Status::WRITE_READY;
+        } else
+        {
+          logger.critical(logtag(), "Couldn't find sector to write in.");
+          sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT);
+        }
+      }
+    }
+  }
+
+  FlashController::Status getStatus()
+  {
+    return status_;
+  }
+
+  uint32_t getBlockIndex()
+  {
+    return block_index_;
+  }
+
+  /**
+   * @brief Writes a block onto the flash memory.
+   *
+   * @param block The block to be written on the flash
+   */
+  bool writeDataBlock(FlashDataBlock* block_buf)
+  {
+
+    if (status_ != Status::WRITE_READY)
+    {
+      logger.error(logtag(), "Writing is not enabled.");
+      return false;
+    }
+
+    if (block_index_ >= BLOCKS_NUM)
+    {
+      logger.critical(logtag(), "Memory is full.");
+      sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT);
+      status_ = Status::WRITE_ERROR;
+      return false;
+    }
+
+    //Enforce size <= BLOCK_SIZE
+    size_t size = sizeof(FlashDataBlock) < BLOCK_SIZE ?
+        sizeof(FlashDataBlock) : BLOCK_SIZE;
+
+    //Beginning of a new sector. Check if we can write in it and update its header
+    if (block_index_ % BLOCKS_PER_SECTOR == 0)
+    {
+      if(!gotoNewSector()){
+        logger.critical(logtag(), "Could not find sector to write in.");
+        status_ = Status::WRITE_ERROR;
+        sFaultCounterMgr->Increment(Fault::F_MASTER_FLASH_CNTRL_FAULT);
+        return false;
+      }
+    }
+
+    uint8_t result;
+    flash_->write(&result, block_index_ * BLOCK_SIZE,
+                  reinterpret_cast<uint8_t*>(block_buf), size);
+    block_index_++;
+
+    return result == RESULT_OK;
+  }
+
+  static bool readFlashHeader(FlashHeader* header)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+    flash->read(&result, 0, (uint8_t*) header, sizeof(FlashHeader));
+    return result == RESULT_OK;
+  }
+
+  /**
+   * Saves (and overwrites) the header in the first address on the memory.
+   * @param header
+   */
+  static bool writeFlashHeader(const FlashHeader& header, bool erase = true)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+    if (erase)
+    {
+      flash->enableErase();
+      flash->eraseSubsector(&result, 0);
+      if (result != OpResultFlags::RESULT_OK)
+      {
+        return false;
+      }
+    }
+    uint32_t size =
+        sizeof(FlashHeader) > SUBSECTOR_SIZE ? SUBSECTOR_SIZE :
+                                               sizeof(FlashHeader);
+
+    flash->writeAndCheck(&result, 0, (uint8_t*) (&header), size);
+    return result == RESULT_OK;
+  }
+
+  static bool readBlock(uint32_t block_index, uint8_t* buffer)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+
+    flash->read(&result, block_index * BLOCK_SIZE, buffer, BLOCK_SIZE);
+    return result == RESULT_OK;
+  }
+
+  static bool readDataBlock(uint32_t block_index, FlashDataBlock* block)
+  {
+    //Flash header & sector headers are not *data* blocks.
+    if(block_index % BLOCKS_PER_SECTOR == 0
+        || block_index < SUBSECTOR_SIZE / BLOCK_SIZE)
+    {
+      return false;
+    }
+
+    readBlock(block_index, reinterpret_cast<uint8_t*>(block));
+    return true;
+  }
+
+  /**
+   * Saves (and overwrites) the header in the first address on the memory.
+   * @param header
+   */
+  static bool writeSectorHeader(uint32_t sector, const SectorHeader& header)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+
+    uint32_t size =
+        sizeof(SectorHeader) > BLOCK_SIZE ? BLOCK_SIZE : sizeof(SectorHeader);
+
+    flash->writeAndCheck(&result, sector * SECTOR_SIZE, (uint8_t*) (&header),
+                         size);
+
+    return result == RESULT_OK;
+  }
+
+  static bool readSectorHeader(uint32_t sector, SectorHeader* header)
+  {
+    return readSectorHeader(sector, (uint8_t*) header);
+  }
+
+  static bool readSectorHeader(uint32_t sector, uint8_t* header_buff)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+    flash->read(&result, SECTOR_SIZE * sector, header_buff,
+                sizeof(SectorHeader));
+    return result == RESULT_OK;
+  }
+
+  static bool format()
+  {
+    uint8_t result;
+    logger.warning(logtag(), "THE MEMORY WILL BE FORMATTED IN 5 SECONDS");
+    //Thread::sleep(5000);
+    logger.info(logtag(), "Erasing...");
+    //TODO: Flash leds for a few seconds as a warning
+    eraseMemory(&result);
+    if (result != OpResultFlags::RESULT_OK)
+    {
+      logger.error(logtag(), "Error: result=", logging::hex(result));
+      return false;
+    }
+    logger.info(logtag(), "Writing flash header...");
+    FlashHeader header = { };
+    header.first_boot = FIRST_BOOT_KEY;
+    //header.magic_word = MAGIC_WORD;
+
+    if (!writeFlashHeader(header, false))
+    {
+      logger.error(logtag(), "Error: result=", logging::hex(result));
+      return false;
+    }
+    logger.info(logtag(), "Writing sector headers...");
+    SectorHeader sheader;
+    sheader.content = SECTOR_EMPTY;
+
+    for (uint32_t sector = 1; sector < SECTORS_NUM; sector++)
+    {
+      if (!writeSectorHeader(sector, sheader))
+      {
+        logger.error(logtag(), "Error: result=", logging::hex(result),
+                     " sector: ", sector);
+        return false;
+      }
+    }
+    logger.info(logtag(), "Success!");
+
+    return true;
+  }
+
+  /**
+     * @brief Formats the memory.
+     * Use this only if the filesystem is in a good state: this fuction
+     * reads every sector header and erases only sector where
+     * header content != SECTOR_EMPTY
+     *
+     * @warning Use this only for testing!
+     */
+  static bool fastFormat()
+  {
+    logger.warning(logtag(), "!!!THE MEMORY IS ABOUT TO BE FORMATTED!!!\n");
+
+    logger.info(logtag(),"Formatting...\n");
+    //TODO: Flash leds for a few seconds as a warning
+    //Always erase the first sector
+    if(!erase(0, SUBSECTORS_PER_SECTOR)){
+      logger.error(logtag(), "Couldn't erase sector 0");
+      return false;
+    }
+
+    //Then erase only the written/broken sectors
+    uint32_t s;
+    for (s = 1; s < SECTORS_NUM; s++)
+    {
+      SectorHeader header;
+      bool read_result = readSectorHeader(s, &header);
+      if (read_result && header.content != SECTOR_EMPTY)
+      {
+        //Erase data in this sector
+        if(!erase(s * SUBSECTORS_PER_SECTOR, (s + 1) * SUBSECTORS_PER_SECTOR)){
+          logger.error(logtag(), "Couldn't erase sector ", s);
+          return false;
+        }
+
+        //Rewrite the header
+        SectorHeader sheader;
+        sheader.content = SECTOR_EMPTY;
+        if (!writeSectorHeader(s, sheader))
+        {
+          logger.error("Error writing sector header in sector ", s);
+          return false;
+        }
+      }else if(!read_result)
+      {
+        logger.error(logtag(), "Error reading sector header in sector ", s);
+        return false;
+      }
+    }
+
+    //Format the memory
+    FlashHeader header { };
+    header.first_boot = FIRST_BOOT_KEY;
+
+    if(!writeFlashHeader(header, false)){
+      logger.error(logtag(),"Couldn't write flash header.");
+      return false;
+    }
+
+    return true;
+  }
+
+  static bool eraseMemory(uint8_t* result)
+  {
+    return erase(0, SUBSECTORS_NUM);
+  }
+
+  /**
+   * Erases the memory between the given subsectors
+   * Only erases a subsector if data has been written into it,
+   * to avoid wasting erase cycles.
+   *
+   * @brief Erases The memory between the given bounds.
+   * @param result Erase successful or not
+   * @param from Address belonging to the first subsector to be erased
+   * @param to Address belonging to the last subsector to be erased
+   * @return
+   */
+  static bool erase(uint32_t from_subsector, uint32_t to_subsector)
+  {
+    uint8_t result;
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+    uint8_t* buffer = new uint8_t[PAGE_SIZE];
+
+    std::stringstream stream;  //To save strings to write in a single log line
+    stream << "Erasing subsectors: ";
+
+    //Erase from the start of the subsector corresponding to "from"
+    uint32_t from_addr = from_subsector * SUBSECTOR_SIZE;
+
+    //To the end of the subsector corresponding to "to".
+    uint32_t to_addr = to_subsector * SUBSECTOR_SIZE;
+
+    //Read memory in blocks of PAGE_SIZE, if a byte is != 0xFF, erase the
+    //subsector and start reading again from the next one.
+    while (from_addr < to_addr)
+    {
+      flash->read(&result, from_addr, buffer, PAGE_SIZE);
+
+      if (result == OpResultFlags::RESULT_OK)
+      {
+        bool erase = false;
+        for (uint32_t i = 0; i < PAGE_SIZE; i++)
+        {
+          erase = buffer[i] != 0xFF;
+
+          if (erase)
+            break;
+        }
+
+        if (erase)
+        {
+          stream << from_addr / SUBSECTOR_SIZE << ", ";
+
+          flash->enableErase();
+          flash->eraseSubsector(&result, from_addr);
+          if (result == OpResultFlags::RESULT_OK)
+          {
+            //Move to the beginning of the next subsector
+            from_addr = (from_addr / SUBSECTOR_SIZE + 1) * SUBSECTOR_SIZE;
+          } else
+          {
+            logger.error(logtag(), "Error erasing subsector: result=0x",
+                         logging::hex(result));
+            break;
+          }
+
+        } else
+        {
+          from_addr = from_addr + PAGE_SIZE;
+        }
+      } else
+      {
+        logger.error(logtag(), "Error reading memory: result=0x",
+                     logging::hex(result));
+        break;
+      }
+    }
+
+    stream << "--";
+
+    logger.info(logtag(), stream.str());
+    delete buffer;
+
+    return from_addr >= to_addr;
+  }
+
+  static bool isMemoryClean(uint8_t* result)
+  {
+    FlashDriverType* flash = Singleton<FlashDriverType>::getInstance();
+    uint8_t buffer[BLOCK_SIZE];
+
+    //Check from the start of the subsector corresponding to "from"
+    uint32_t addr = SUBSECTOR_SIZE;
+
+    bool clean = true;
+    while (addr < MEMORY_SIZE)
+    {
+      if (addr % SECTOR_SIZE == 0)
+      {
+        SectorHeader header;
+        bool read_result = readSectorHeader(addr / SECTOR_SIZE, &header);
+        if (!read_result || header.content != SECTOR_EMPTY)
+        {
+          logger.warning(logtag(), "Wrong sector header @", addr);
+          clean = false;
+        }
+      } else
+      {
+        flash->read(result, addr, buffer, BLOCK_SIZE);
+        if (*result == OpResultFlags::RESULT_OK)
+        {
+          for (uint32_t i = 0; i < BLOCK_SIZE; i++)
+          {
+            if (buffer[i] != 0xFF)
+            {
+              logger.warning(logtag(), "Dirty block @", addr);
+              clean = false;
+              break;
+            }
+          }
+        } else
+        {
+          logger.warning(logtag(), "Error reading at %d\n", addr);
+          break;
+        }
+      }
+
+      addr = addr + BLOCK_SIZE;
+    }
+
+    if (clean)
+    {
+      logger.info(logtag(), "Memory is clean.");
+    }
+    return clean;
+  }
+
+ private:
+
+  FlashController()
+  {
+    flash_ = Singleton<FlashDriverType>::getInstance();
+  }
+
+  /**
+   * @brief Restores the state after a reboot.
+   *
+   * Restores the state after a reboot.
+   * Sequentially search for the first empty sector and update block_index_
+   * accordingly
+   *
+   * @return Empty sector found or not
+   */
+  bool recoverFromReboot()
+  {
+    //Start from the second sector
+    uint32_t sector = 1;
+    SectorHeader header;
+
+    do
+    {
+      bool r = readSectorHeader(sector, &header);
+      if (r && header.content == SECTOR_EMPTY)
+      {
+        block_index_ = sector * BLOCKS_PER_SECTOR;
+        return true;
+      }
+    } while (++sector < SECTORS_NUM);
+
+    return false;
+  }
+
+  /**
+   * Updates block_index_ to point to the next available sector.
+   *
+   * @return True if a new sector is found
+   */
+  bool gotoNewSector()
+  {
+    uint32_t sector = block_index_ / BLOCKS_PER_SECTOR;
+    SectorHeader readHeader, writeHeader;
+
+    //Look for an empty sector
+    bool read_result;
+    int i = 0;
+    do
+    {
+      read_result = readSectorHeader(sector, &readHeader);
+
+    } while (((read_result && readHeader.content != SECTOR_EMPTY) || !read_result)
+        && ++sector < SECTORS_NUM && ++i < MAX_SECTOR_ITERATIONS);
+
+    block_index_ = sector * BLOCKS_PER_SECTOR;
+
+    if (read_result && readHeader.content == SECTOR_EMPTY)
+    {
+      writeHeader.content = SECTOR_WRITTEN;
+
+      if (!writeSectorHeader(sector, writeHeader))
+      {
+        logger.error(logtag(), "Error writing sector header.");
+        return false;
+      }
+      block_index_++;
+    } else
+    {
+      logger.error(logtag(), "Error: could not find empty sector to write in.");
+      return false;
+    }
+
+    return true;
+  }
+
+  static std::string logtag()
+  {
+    return "FlashCTRL";
+  }
+
+  FlashDriverType* flash_;
+  uint32_t block_index_ = 0;
+  //Value will be changed if initialization is successful
+  Status status_ = Status::INITIALIZATION_ERROR;
+
+};
+
+}   //namespace flashmemory
+
+#endif /* FLASHCONTROLLER_H */
diff --git a/src/shared/drivers/memory/FlashDriver.h b/src/shared/drivers/memory/FlashDriver.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f94ae7419afa544701531c64e0f326574239e1a
--- /dev/null
+++ b/src/shared/drivers/memory/FlashDriver.h
@@ -0,0 +1,615 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+#ifndef FLASHDRIVER_H
+#define FLASHDRIVER_H
+
+#include <miosix.h>
+#include <Singleton.h>
+#include <diagnostic/NewLogger.h>
+#include <string>
+
+using miosix::Thread;
+using miosix::Mode;
+
+using logging::logger;
+
+namespace flashmemory {
+
+static const uint32_t PAGES_PER_SUBSECTOR = 16U;
+static const uint32_t PAGES_PER_SECTOR = 256U;
+static const uint32_t SUBSECTORS_PER_SECTOR = 16U;
+
+static const uint32_t SUBSECTORS_NUM = 16384U;
+static const uint32_t SECTORS_NUM = 1024U;
+static const uint32_t PAGE_SIZE = 256U;
+
+static const uint32_t SUBSECTOR_SIZE = PAGE_SIZE*PAGES_PER_SUBSECTOR;
+static const uint32_t SECTOR_SIZE = PAGE_SIZE*PAGES_PER_SECTOR;
+
+static const uint32_t BANK_SIZE = SECTOR_SIZE*SECTORS_NUM/2;
+static const uint32_t MEMORY_SIZE = SECTOR_SIZE*SECTORS_NUM;
+
+
+/**
+ * 	@brief Definitions for results of various flash operations.
+ *
+ * 	Definitions for result flags of various flash operations.
+ */
+enum OpResultFlags
+{
+	RESULT_OK 				= 0x00,
+
+
+	RESULT_F_OUT_OF_MEMORY 	= 0x01,
+	RESULT_F_CHECK_FAIL 		= 0x04,
+
+
+	//Bits reserved for future use
+	RESULT_F_UNUSED_1			= 0x08,
+	RESULT_F_UNUSED_2 		= 0x40,
+	RESULT_F_UNUSED_3 		= 0x80,
+
+
+	/**
+	 * These bits are in the same positions of the corresponding bits in the
+	 * FLAG STATUS REGISTER
+	 */
+	RESULT_F_PROTECTION_ERROR = 0x02,
+	RESULT_F_PROGRAM_ERROR 	= 0x10,
+	RESULT_F_ERASE_ERROR		= 0x20
+};
+
+
+/*
+ * TODO:
+ * -Controlla significato bit Vpp nel Flag status register
+ * -Casi in cui eseguire _write_disable()
+ * -Velocizzare il clock software per il factory reset
+ * -Controlla se bisogna davvero leggere due volte il flag_status_register per
+ * 	vedere se una scrittura ad un registro e' stata completata
+ *
+ */
+
+template<typename Bus>
+class FlashDriver : Singleton<FlashDriver<Bus>>
+{
+  typedef Singleton<FlashDriver<Bus>> SingletonType;
+
+  friend class Singleton<FlashDriver<Bus>>;
+
+	template<typename>
+	friend class FlashDriverTest;
+
+public:
+
+	/**
+	 * @brief Read n bytes into a buffer, starting from the specified address
+	 *
+	 * @param result
+	 * @param address Starting address
+	 * @param buf Buffer to read data into
+	 * @param size Number of bytes to read
+	 */
+	void read(uint8_t *result, uint32_t address, uint8_t* buf, uint32_t size)
+	{
+		if(address + size > MEMORY_SIZE){
+			*result = RESULT_F_OUT_OF_MEMORY;
+			return;
+		}
+
+		uint32_t rsize;
+		if(address < BANK_SIZE)
+			rsize = std::min(size, BANK_SIZE - address);
+		else
+			rsize = size;
+
+		uint8_t addr_buf[4];
+		addrToBuf(addr_buf, address);
+
+		Bus::read(READ, addr_buf, buf, 4, rsize);
+
+		size -= rsize;
+
+		//This means that we reached the end of the first die but we still need
+		//to read some data.
+		if(size > 0)
+		{
+			address += rsize;
+			buf += rsize;
+
+			addrToBuf(addr_buf, address);
+
+			//Read the remaining bytes
+			Bus::read(READ, addr_buf, buf, 4, size);
+		}
+
+		*result = RESULT_OK;
+	}
+
+	/**
+	 * @brief Writes data on the flash starting from the specified address.
+	 *
+	 * @param address
+	 * @param data
+	 * @param size
+	 */
+	void write(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size)
+	{
+		//Do not write outside of the memory
+		if(address + size > MEMORY_SIZE)
+		{
+			*result = RESULT_F_OUT_OF_MEMORY;
+			return;
+		}
+
+		uint8_t addr_buf[4], status;
+
+		uint32_t next_page, wsize;
+
+		do {
+			next_page = (address / 256)*256 + 256;
+
+			wsize = std::min(next_page - address, size);
+
+			addrToBuf(addr_buf, address);
+			writeEnable();
+			Bus::write(PROGRAM_PAGE, addr_buf, data, 4, wsize);
+
+			do {
+				status = readFlagStatusReg();
+			}while((status & FSR_PRG_ERS_CTRL) == 0);
+
+			data += wsize;
+			size -= wsize;
+			address = next_page;
+
+			*result = status & (FSR_PROGRAM | FSR_PROTECTION);
+		}while(size > 0 && *result == RESULT_OK);
+
+		//Clear flag status register if an error occurred
+		if(*result != RESULT_OK)
+		{
+			clearFlagStatusReg();
+
+			//Manually reset write_enable latch
+			if((*result & RESULT_F_PROTECTION_ERROR) > 0)
+			{
+				writeDisable();
+			}
+		}
+	}
+
+	/**
+	 * @brief Writes data on the flash starting from the specified address, then
+	 * checks if everything was written correctly.
+	 *
+	 * @param address
+	 * @param data
+	 * @param size
+	 */
+	void writeAndCheck(uint8_t *result, uint32_t address, uint8_t* data,
+			uint32_t size){ //TODO: size_t
+		write(result,address,data,size);
+		if(*result == RESULT_OK){
+			uint8_t *check = new uint8_t[size];
+			read(result, address, check, size);
+			for(uint32_t i = 0; i < size; i++){
+				if(*check++ != *data++){
+					//Something was not written/read correctly.
+					*result = RESULT_F_CHECK_FAIL;
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * @brief Writes data on the flash starting from the specified address, only
+	 * if the provided data fits in a single page.
+	 *
+	 * Writes data on the flash starting from the specified address, only
+	 * if the provided data fits in a single page. If too much data is provided
+	 * nothing will be written and result will be set to RESULT_F_OUT_OF_MEMORY
+	 *
+	 * @param result
+	 * @param address
+	 * @param data
+	 * @param size
+	 */
+	void programPage(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size)
+	{
+		//Do now wrap around a page and do not try to write outside of the memory
+		uint32_t next_page = (address / 256)*256 + 256;
+		if(address + size > next_page || address > MEMORY_SIZE)
+		{
+			*result = RESULT_F_OUT_OF_MEMORY;
+			return;
+		}
+
+		uint8_t addr_buf[4], status;
+		addrToBuf(addr_buf, address);
+
+		writeEnable();
+		Bus::write(PROGRAM_PAGE, addr_buf, data, 4, size);
+
+		do {
+			status = readFlagStatusReg();
+		}while((status & FSR_PRG_ERS_CTRL) == 0);
+
+		*result = status & (FSR_PROGRAM | FSR_PROTECTION);
+
+		//Clear flag status register if an error has been encountered
+		if(*result != RESULT_OK)
+		{
+			clearFlagStatusReg();
+
+			//Manually reset write_enable latch
+			if((*result & RESULT_F_PROTECTION_ERROR) > 0)
+			{
+				writeDisable();
+			}
+		}
+	}
+
+	/**
+	 * @brief Enables or disables read only mode
+	 */
+	void setReadOnly(bool readonly = true)
+	{
+		read_only_ = readonly;
+	}
+
+
+	bool isReadOnly() const
+	{
+	  return read_only_;
+	}
+
+	/**
+	 * @brief Enable the next erase operation. Call this before every erase op.
+	 */
+	void enableErase()
+	{
+		writeEnable();
+	}
+
+	/**
+	 * @brief Erases the subsector containing the specified address. For the operation
+	 * to be successful, enable_erase_op must be called before this.
+	 *
+	 * @warning Estimated execution time is slightly less than 1 second.
+	 *
+	 * @param result
+	 * @param address
+	 */
+	void eraseSubsector(uint8_t* result, uint32_t address)
+	{
+		erase(result, SUBSECTOR_ERASE, address);
+	}
+
+	/**
+	 * @brief Erases the sector containing the specified address. For the operation
+	 * to be successful, enable_erase_op must be called before this.
+	 * @warning Estimated execution time is about 2 seconds.
+	 *
+	 * @param result
+	 * @param address
+	 */
+	void eraseSector(uint8_t* result, uint32_t address)
+	{
+		erase(result, SECTOR_ERASE, address);
+	}
+
+	/**
+	 * @brief Erases the entire die containing the specified address. For the operation
+	 * to be successful, enable_erase_op must be called before this.
+	 * @warning Estimated execution time is more than 4 minutes.
+	 *
+	 * @param result
+	 * @param die: 0 to erase the first die, 1 to erase the second.
+	 */
+	void eraseDie(uint8_t* result, uint8_t die)
+	{
+		if(die == 0){
+			erase(result, DIE_ERASE, 0);
+		}else if(die == 1){
+			erase(result, DIE_ERASE, BANK_SIZE);
+		}
+	}
+
+	/**
+	 * Reads id information for this flash memory.
+	 * @param buf Buffer with at least 20 bytes
+	 */
+	void readId(uint8_t* buf)
+	{
+		Bus::read(READ_ID, buf, 20);
+	}
+
+
+	/**
+	 * @brief Software reset for the memory. All volatile bits are set to their
+	 * default value.
+	 */
+	void softReset()
+	{
+		Bus::write(RESET_ENABLE);
+		usleep(5);
+		Bus::write(RESET_MEMORY);
+		waitUntilReady();
+	}
+
+	/**
+	 * If the flash memory is in a bad state and can't be recovered by any
+	 * other means, run this, then set CONFIG_REGISTER to 0xFFFF.
+	 */
+	template <class CS, class MOSI, class CLK>
+	static void factoryReset()
+	{
+		{
+			miosix::FastInterruptDisableLock dLock;
+			CS::mode(Mode::OUTPUT);
+			MOSI::mode(Mode::OUTPUT);
+			CLK::mode(Mode::OUTPUT);
+		}
+		MOSI::high();
+		Thread::sleep(20);
+
+
+		factoryResetSequence<CS, CLK>(7);
+		factoryResetSequence<CS, CLK>(13);
+		factoryResetSequence<CS, CLK>(17);
+		factoryResetSequence<CS, CLK>(25);
+		factoryResetSequence<CS, CLK>(33);
+
+		MOSI::low();
+		Thread::sleep(50);
+		MOSI::high();
+		factoryResetSequence<CS, CLK>(8);
+
+		MOSI::low();
+		CS::low();
+	}
+
+private:
+	FlashDriver(){
+	  waitUntilReady();
+
+    uint8_t buf[20];
+    readId(buf);
+
+    if(buf[0] != 0x20)
+    {
+      logger.critical(logtag(), "Wrong id on FlashDriver instantiation");
+    }
+
+    writeStatusReg(0x00);
+
+    clearFlagStatusReg();
+
+
+    uint16_t config = readConfigReg();
+
+    //Last bit to 0 to enable 4 byte address mode
+    if(config != 0xFFFE) writeConfigReg(0xFFFE);
+	}
+
+	void erase(uint8_t *result, uint8_t erase_cmd, uint32_t address)
+	{
+		if(address > MEMORY_SIZE)
+		{
+			*result = RESULT_F_OUT_OF_MEMORY;
+			return;
+		}
+
+		uint8_t addr_buf[4], status;
+		addrToBuf(addr_buf, address);
+		Bus::write(erase_cmd, addr_buf, 4);
+
+		do {
+			status = readFlagStatusReg();
+		}while((status & FSR_PRG_ERS_CTRL) == 0);
+
+		*result = status & (FSR_ERASE | FSR_PROTECTION);
+
+		//Clear flag status register if an error has been encountered
+		if(*result != RESULT_OK)
+		{
+			clearFlagStatusReg();
+
+			//Manually reset write_enable latch
+			if((*result & RESULT_F_PROTECTION_ERROR) > 0)
+			{
+				writeDisable();
+			}
+		}
+	}
+
+	/**
+   * @brief Checks if a program operation is still in progress.
+   * @param result
+   */
+  bool isBusy()
+  {
+    return readFlagStatusReg() & FSR_PRG_ERS_CTRL;
+  }
+
+  /**
+   * @brief Waits until the memory is ready to perform a new write/erase op.
+   * @param result
+   */
+  void waitUntilReady()
+  {
+    while((readFlagStatusReg() & FSR_PRG_ERS_CTRL) == 0)
+    {
+
+    }
+  }
+
+	uint8_t readStatusReg()
+	{
+		uint8_t ret = Bus::read(READ_STATUS_REG);
+		return ret;
+	}
+
+	void writeStatusReg(uint8_t val)
+	{
+		writeEnable();
+		Bus::write(WRITE_STATUS_REG, val);
+		writeDisable();
+		waitUntilReady();
+	}
+
+	uint8_t readFlagStatusReg()
+	{
+		uint8_t ret = Bus::read(READ_FLAG_STATUS_REG);
+		return ret;
+	}
+
+	void clearFlagStatusReg()
+	{
+		Bus::write(CLEAR_FLAG_STATUS_REG);
+	}
+
+	uint16_t readConfigReg()
+	{
+		uint8_t buf[2];
+		Bus::read(READ_NV_CONFIG_REG, buf, 2);
+
+		uint16_t res = buf[0] | (buf[1] << 8);
+
+		return res;
+	}
+
+	void writeConfigReg(uint16_t val)
+	{
+		/*
+		 * Mask some of the bits of the config register because
+		 * we don't want to change them by accident.
+		 */
+		val = val | 0x0FEC;
+
+		uint8_t buf[2];
+		buf[0] = val & 0xFF;
+		buf[1] = (val >> 8) & 0xFF;
+
+		writeEnable();
+		Bus::write(WRITE_NV_CONFIG_REG, buf, 2);
+		writeDisable();
+		waitUntilReady();
+	}
+
+	void writeEnable()
+	{
+		if(!read_only_) {
+			Bus::write(WRITE_ENABLE);
+		}
+	}
+
+	void writeDisable()
+	{
+		Bus::write(WRITE_DISABLE);
+	}
+
+	static void addrToBuf(uint8_t* buf, uint32_t addr)
+	{
+		for(int i = 0; i < 4; i++)
+		{
+			buf[3 - i] = (addr >> 8*i) & 0xFF;
+		}
+	}
+
+	template <class CS, class CLK>
+	static void factoryResetSequence(int cycles)
+	{
+		CS::low();
+		Thread::sleep(5);
+		for(int i = 0; i < cycles; i++)
+		{
+			CLK::high();
+			Thread::sleep(5);
+			CLK::low();
+			Thread::sleep(5);
+		}
+		CS::high();
+		Thread::sleep(20);
+	}
+
+	static std::string logtag()
+	{
+	  return "FLASH_DRIVER";
+	}
+
+	enum Commands
+	{
+		READ_ID = 0x9F,
+
+		WRITE_ENABLE = 0x06,
+		WRITE_DISABLE = 0x04,
+
+		READ_STATUS_REG = 0x05,
+		WRITE_STATUS_REG = 0x01,
+
+		READ_FLAG_STATUS_REG = 0x70,
+		CLEAR_FLAG_STATUS_REG = 0x50,
+
+		//non-volatile configuration register
+		READ_NV_CONFIG_REG = 0xB5,
+		WRITE_NV_CONFIG_REG = 0xB1,
+
+		//volatile configuration register
+		WRITE_VOL_CONFIG_REG = 0x85,
+		READ_VOL_CONFIG_REG = 0x81,
+
+		READ_EXT_ADDRESS_REG = 0xC8,
+		WRITE_EXT_ADDRESS_REG = 0xC5,
+
+		RESET_ENABLE = 0x66,
+		RESET_MEMORY = 0x99,
+
+		READ = 0x03,
+		PROGRAM_PAGE = 0x02,
+
+		SUBSECTOR_ERASE = 0x20,
+		SECTOR_ERASE = 0xD8,
+		DIE_ERASE = 0xC4
+	};
+
+	bool read_only_ = false;
+
+	/**
+	 * Flag status register bit definitions
+	 */
+	static const uint8_t FSR_ADDRESSING_MODE		= 0x01;
+	static const uint8_t FSR_PROTECTION 			= 0x02;
+	static const uint8_t FSR_PROGRAM_SUSPEND 		= 0x04;
+	static const uint8_t FSR_VPP 					= 0x08;
+	static const uint8_t FSR_PROGRAM 				= 0x10;
+	static const uint8_t FSR_ERASE 					= 0x20;
+	static const uint8_t FSR_ERASE_SUSPEND			= 0x40;
+	static const uint8_t FSR_PRG_ERS_CTRL	 		= 0x80;
+};
+
+}
+#endif /* FLASHDRIVER_H */
diff --git a/src/shared/drivers/memory/FlashDriverInclude.h b/src/shared/drivers/memory/FlashDriverInclude.h
new file mode 100644
index 0000000000000000000000000000000000000000..8770dcfebcea6fc187cc938064c50ec773219563
--- /dev/null
+++ b/src/shared/drivers/memory/FlashDriverInclude.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2015-2017 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+#ifndef SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_
+#define SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_
+
+
+#ifdef FLASH_TEST
+
+#include <flashmemory/mockup/FlashDriver.h>
+
+#else
+
+#include <drivers/memory/FlashDriver.h>
+
+#endif /* FLASH_TEST */
+
+
+#endif /* SRC_SHARED_DRIVERS_MEMORY_FLASHDRIVERINCLUDE_H_ */
diff --git a/src/shared/drivers/memory/MultiFlashController.cpp b/src/shared/drivers/memory/MultiFlashController.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f1bac737fe100aa5e37ae7c1e6f2c68e06d95857
--- /dev/null
+++ b/src/shared/drivers/memory/MultiFlashController.cpp
@@ -0,0 +1,106 @@
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "MultiFlashController.h"
+#include <Singleton.h>
+#include <diagnostic/NewLogger.h>
+
+using logging::logger;
+/* stub crc class */
+
+namespace flashmemory
+{
+class CRCHelper
+{
+ public:
+  static uint16_t calcCRC16(uint8_t* data, uint32_t size)
+  {
+    return 0xABCD;
+  }
+};
+
+//miosix::Queue<FlashDataBlock,20> MultiFlashController::block_queue_;
+
+MultiFlashController::MultiFlashController()
+    : ActiveObject::ActiveObject()
+{
+  flash0_ = Singleton<FlashController<MemoryBus0>>::getInstance();
+
+  //block_queue_ = new miosix::Queue<FlashDataBlock, 5>();
+}
+
+MultiFlashController::~MultiFlashController()
+{
+}
+
+/**
+ *   @brief  Infinite loop that process the block queue.
+ */
+void MultiFlashController::run()
+{
+  FlashDataBlock to_flash;
+  while (true)
+  {
+    block_queue_.waitUntilNotEmpty();
+
+    block_queue_.get(to_flash);
+
+    bool result = true;
+
+    result = result && flash0_->writeDataBlock(&to_flash);
+
+    //TODO: Also write on 2nd and 3rd flash memory
+    //result = result && flash1_->writeBlock(&to_flash);
+    //result = result && flash1_->writeBlock(&to_flash);
+
+    if (!result)
+    {
+      logger.error(logtag(), "Error writing blocks!");
+    }
+
+  }
+}
+
+/**
+ *   @brief  Insert into the fixed queue a new FlashDataBlock to save in flash.
+ *
+ *   @param  buffer data do be saved
+ *   @param  size the size of the data (must be < FLASH_BUFFER_SIZE)
+ */
+void MultiFlashController::addData(const uint8_t* buffer, size_t size)
+{
+  /* limit data to FLASH_BUFFER_SIZE */
+  uint16_t safe_size = (size <= FLASH_BUFFER_SIZE ? size : FLASH_BUFFER_SIZE);
+  FlashDataBlock to_save = { };
+
+  // create the Flash Data Block.
+  to_save.id = block_counter++;
+  to_save.timestamp = miosix::getTick();
+  memcpy((uint8_t*) to_save.buffer, (uint8_t*) buffer, safe_size);
+
+  //TODO crc of the whole block with toSave.crc = 0x0000?
+  to_save.crc = CRCHelper::calcCRC16((uint8_t*) to_save.buffer, safe_size);
+
+  block_queue_.put(to_save);
+}
+
+}  //namespace flashmemory
diff --git a/src/shared/drivers/memory/MultiFlashController.h b/src/shared/drivers/memory/MultiFlashController.h
new file mode 100644
index 0000000000000000000000000000000000000000..32d7031655389f60c56c09f32cf778ba1df4b8ff
--- /dev/null
+++ b/src/shared/drivers/memory/MultiFlashController.h
@@ -0,0 +1,88 @@
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+#ifndef MULTIFLASHCONTROLLER_H
+#define MULTIFLASHCONTROLLER_H
+
+#include <Common.h>
+#include <ActiveObject.h>
+#include <drivers/BusTemplate.h>
+#include "FlashController.h"
+
+using miosix::Gpio;
+
+//Forward declaration
+namespace testing
+{
+namespace flashmemorytests
+{
+template<typename >
+class FlashTest;
+}
+}
+
+namespace flashmemory
+{
+
+class MultiFlashController : public Singleton<MultiFlashController>, ActiveObject
+{
+  friend class Singleton<MultiFlashController>;
+
+  template<typename >
+  friend class testing::flashmemorytests::FlashTest;
+
+  typedef Gpio<GPIOA_BASE, 5> GpioSck;
+  typedef Gpio<GPIOA_BASE, 6> GpioMiso;
+  typedef Gpio<GPIOA_BASE, 7> GpioMosi;
+  typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> bus;
+
+  typedef Gpio<GPIOC_BASE, 15> CS_FLASH0;
+
+ public:
+  typedef ProtocolSPI<bus, CS_FLASH0> MemoryBus0;
+
+  virtual ~MultiFlashController();
+
+  void addData(const uint8_t* buffer, size_t size);
+
+ protected:
+
+  void run() override;
+
+ private:
+  MultiFlashController();
+
+  static std::string logtag()
+  {
+    return "MultiFlashCTRL";
+  }
+
+  uint32_t block_counter = 0;
+  miosix::Queue<FlashDataBlock,20> block_queue_;
+
+  FlashController<MemoryBus0>* flash0_;
+  //FlashController<spi_flash0> flash1; //Change to spi_flash1
+  //FlashController<spi_flash0> flash2; //Change to spi_flash2
+};
+
+}  //namespace flashmemory
+
+#endif /* MULTIFLASHCONTROLLER_H */
diff --git a/src/shared/drivers/modbus/ExceptionCodes.h b/src/shared/drivers/modbus/ExceptionCodes.h
index 1397f36f635e5c27badf6095e20af135da826310..0290fd125a3aab91ac2e14d480fa3b80a908450e 100644
--- a/src/shared/drivers/modbus/ExceptionCodes.h
+++ b/src/shared/drivers/modbus/ExceptionCodes.h
@@ -1,6 +1,4 @@
-/* Modbus protocol exception codes
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/HooksInterface.h b/src/shared/drivers/modbus/HooksInterface.h
index c40932bf89c0eafd231201a0e255a4210a68bbaa..375eef7d0213a0d7dbef88e860282abb4cd82d18 100644
--- a/src/shared/drivers/modbus/HooksInterface.h
+++ b/src/shared/drivers/modbus/HooksInterface.h
@@ -1,9 +1,4 @@
-/* Class that provides an interface made by a set of function interface used by
- * Modbus slave engine to handle the server requests.
- * By default the functions will return the error code "function unsupported",
- * user must provide a correct implementation by subclassing this class
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,13 +13,19 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
+/* Class that provides an interface made by a set of function interface used by
+ * Modbus slave engine to handle the server requests.
+ * By default the functions will return the error code "function unsupported",
+ * user must provide a correct implementation by subclassing this class
+ */
+
 #ifndef HOOKS_H
 #define HOOKS_H
 
diff --git a/src/shared/drivers/modbus/Modbus.h b/src/shared/drivers/modbus/Modbus.h
index 8f1b307d3346f833f08fb9316bf2898a29a091eb..ec01e2e3aba7a5eb2c98829065f6eea0bd9c12f1 100644
--- a/src/shared/drivers/modbus/Modbus.h
+++ b/src/shared/drivers/modbus/Modbus.h
@@ -1,6 +1,4 @@
-/* Container for all the headers, user has only to include this file
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/PDU.cpp b/src/shared/drivers/modbus/PDU.cpp
index e4ea5675db1dc7a35b7b1aca031b96f301f2a37c..afa98b4b6b68f4ca0df7e0eb80dd518de44b4f0e 100644
--- a/src/shared/drivers/modbus/PDU.cpp
+++ b/src/shared/drivers/modbus/PDU.cpp
@@ -1,6 +1,4 @@
-/* Modbus protocol PDU data structure
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/PDU.h b/src/shared/drivers/modbus/PDU.h
index 94e1df0f4d8befca6310689086a6cce1993fd330..c79cb7081a0f6c6a16a3785213417d7aa1adc753 100644
--- a/src/shared/drivers/modbus/PDU.h
+++ b/src/shared/drivers/modbus/PDU.h
@@ -1,6 +1,4 @@
-/* Modbus protocol PDU data structure
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/RtuSlave.cpp b/src/shared/drivers/modbus/slave/RtuSlave.cpp
index c48bb345a45ea23551e7121ce4c31d38aaa72907..661b850b11c8197652cc96841bca5cfa070be564 100644
--- a/src/shared/drivers/modbus/slave/RtuSlave.cpp
+++ b/src/shared/drivers/modbus/slave/RtuSlave.cpp
@@ -1,3 +1,25 @@
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Silvano Seva
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #include "RtuSlave.h"
 
 using namespace std;
diff --git a/src/shared/drivers/modbus/slave/RtuSlave.h b/src/shared/drivers/modbus/slave/RtuSlave.h
index ac8e301d35cd9d5b7bbd8071697e3689bf92e5f9..76fe486cb0d8dff4bee3faa8dddcfbb400dfe3d3 100644
--- a/src/shared/drivers/modbus/slave/RtuSlave.h
+++ b/src/shared/drivers/modbus/slave/RtuSlave.h
@@ -1,3 +1,25 @@
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Silvano Seva
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #ifndef RTUSLAVE_H
 #define RTUSLAVE_H
 
diff --git a/src/shared/drivers/modbus/slave/SlaveEngine.cpp b/src/shared/drivers/modbus/slave/SlaveEngine.cpp
index ad7e0dd6ad8cc38a4f8edb3256bba5d317b83565..96f825d141bd7cd82413bb897c50506462c49432 100644
--- a/src/shared/drivers/modbus/slave/SlaveEngine.cpp
+++ b/src/shared/drivers/modbus/slave/SlaveEngine.cpp
@@ -1,6 +1,4 @@
-/* Modbus protocol engine for slave modules
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/SlaveEngine.h b/src/shared/drivers/modbus/slave/SlaveEngine.h
index ca77fd62d8a86e4a05efe64acc0e7d8bf4ccec1c..c15111b3be5e6b59cba82e67227294b39c2c9f0d 100644
--- a/src/shared/drivers/modbus/slave/SlaveEngine.h
+++ b/src/shared/drivers/modbus/slave/SlaveEngine.h
@@ -1,6 +1,4 @@
-/* Modbus protocol engine for slave modules
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp b/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp
index f69174a654088d73a99301d75e00c0e7a013f845..d90b12457161b7f6a818b887f3234260f7e72073 100644
--- a/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp
+++ b/src/shared/drivers/modbus/slave/old/SlaveInterface.cpp
@@ -1,6 +1,4 @@
-/* Serial driver for Modbus RTU slave device
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/old/SlaveInterface.h b/src/shared/drivers/modbus/slave/old/SlaveInterface.h
index b4646457e8669d4e0d71d87c3c82a0740953ae35..63495bdd2826e460da5f574f64760203899e6b61 100644
--- a/src/shared/drivers/modbus/slave/old/SlaveInterface.h
+++ b/src/shared/drivers/modbus/slave/old/SlaveInterface.h
@@ -1,6 +1,4 @@
-/* Serial driver for Modbus RTU slave device
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/old/Timer.cpp b/src/shared/drivers/modbus/slave/old/Timer.cpp
index a888516d663ceca10c88b92116125f22acf436eb..dc421894a6215dd38601eaa8f9572532d4b6d7aa 100644
--- a/src/shared/drivers/modbus/slave/old/Timer.cpp
+++ b/src/shared/drivers/modbus/slave/old/Timer.cpp
@@ -1,6 +1,4 @@
-/* Serial driver for Modbus RTU slave device
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/modbus/slave/old/Timer.h b/src/shared/drivers/modbus/slave/old/Timer.h
index 1ffbaada525172ba354876a5601438f73da78755..1331e23769e5019d9fbceae4986e0b94d4760c54 100644
--- a/src/shared/drivers/modbus/slave/old/Timer.h
+++ b/src/shared/drivers/modbus/slave/old/Timer.h
@@ -1,6 +1,4 @@
-/* Serial driver for Modbus RTU slave device
- *
- * Copyright (c) 2017 Skyward Experimental Rocketry
+/* Copyright (c) 2017 Skyward Experimental Rocketry
  * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/adc/AD7994.h b/src/shared/drivers/old_examples/AD7994/AD7994.h
similarity index 98%
rename from src/shared/drivers/adc/AD7994.h
rename to src/shared/drivers/old_examples/AD7994/AD7994.h
index 35d8f1abd7395b15b61db44016c3bc4866a772fe..44c72574ba7b8a472400f5fbb42373f041a892d4 100644
--- a/src/shared/drivers/adc/AD7994.h
+++ b/src/shared/drivers/old_examples/AD7994/AD7994.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018-2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/old_examples/AD7994/AD7994Data.h b/src/shared/drivers/old_examples/AD7994/AD7994Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..b6d844ab8f2672e97ac19c7229eb635c49f608f9
--- /dev/null
+++ b/src/shared/drivers/old_examples/AD7994/AD7994Data.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef SRC_SHARED_DRIVERS_ADC_AD7994DATA_H
+#define SRC_SHARED_DRIVERS_ADC_AD7994DATA_H
+
+#include <cstdint>
+#include <ostream>
+#include <string>
+
+struct AD7994Sample
+{
+    long long timestamp;
+
+    uint8_t channel_id; // [1-4]
+    bool alert_flag;
+    uint16_t value;
+
+    // Functions used to deserialize the binary logs into csv files
+
+    static std::string header() { return "timestamp,ch_id,value,alert_flag\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << channel_id << "," << value << ","
+           << (int)alert_flag << "\n";
+    }
+};
+
+#endif
\ No newline at end of file
diff --git a/src/shared/drivers/piksi/piksi.cpp b/src/shared/drivers/old_examples/piksi/piksi.cpp
similarity index 94%
rename from src/shared/drivers/piksi/piksi.cpp
rename to src/shared/drivers/old_examples/piksi/piksi.cpp
index 356acc31b4912a82ffd0c479672fdd230333bd40..1a5e8d13a1b320fc0ad635eb8d083bd7a095acb9 100644
--- a/src/shared/drivers/piksi/piksi.cpp
+++ b/src/shared/drivers/old_examples/piksi/piksi.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2017 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -70,8 +70,8 @@ static unsigned short crc16piksi(const void *message, unsigned int length)
 
 Piksi::Piksi(const char *serialPath)
 {
-    memset(&data, 0, sizeof(GPSData));
-    memset(&partialData, 0, sizeof(GPSData));
+    memset(&data, 0, sizeof(PiksiGPSData));
+    memset(&partialData, 0, sizeof(PiksiGPSData));
 
     fd = open(serialPath, O_RDWR);
     if (fd < 0)
@@ -99,9 +99,9 @@ Piksi::Piksi(const char *serialPath)
     pthread_cond_init(&cond, NULL);
 }
 
-GPSData Piksi::getGpsData()
+PiksiGPSData Piksi::getGpsData()
 {
-    GPSData result;
+    PiksiGPSData result;
     pthread_mutex_lock(&mutex);
     result = data;
     
@@ -115,9 +115,9 @@ GPSData Piksi::getGpsData()
     return result;
 }
 
-GPSData Piksi::waitForGpsData()
+PiksiGPSData Piksi::waitForGpsData()
 {
-    GPSData result;
+    PiksiGPSData result;
     pthread_mutex_lock(&mutex);
     pthread_cond_wait(&cond, &mutex);
     result = data;
@@ -188,8 +188,9 @@ unsigned int Piksi::lookForMessages(uint8_t *buffer, unsigned int size)
         if (messageSize > size)
             return consumed;  // We don't have the entire message
 
-        uint16_t crc =
-            *reinterpret_cast<uint16_t *>(buffer + messageSize - crcSize);
+        uint16_t crc;
+        memcpy(&crc, buffer + messageSize - crcSize, sizeof(crc));
+
         if (crc16piksi(buffer + 1, messageSize - crcSize - 1) == crc)
         {
             processValidMessage(buffer, messageSize);
diff --git a/src/shared/drivers/piksi/piksi.h b/src/shared/drivers/old_examples/piksi/piksi.h
similarity index 95%
rename from src/shared/drivers/piksi/piksi.h
rename to src/shared/drivers/old_examples/piksi/piksi.h
index 876a49e87f4d4e8863a06a486f11d9984cb04863..93926a82a71151aba6b7503e02b74926ee1bf34c 100644
--- a/src/shared/drivers/piksi/piksi.h
+++ b/src/shared/drivers/old_examples/piksi/piksi.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2017 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -24,7 +24,7 @@
 #define PIKSI_H
 
 #include <pthread.h>
-#include "contiguous_queue.h"
+#include "utils/collections/contiguous_queue.h"
 #include "piksi_data.h"
 
 /**
@@ -52,13 +52,13 @@ public:
      * use the timestamp field of the GPSData struct to know this.
      * \throws runtime_error is no data is available
      */
-    GPSData getGpsData();
+    PiksiGPSData getGpsData();
 
     /**
      * \return the latest GPS data. If the GPS has yet got a fix or has lost
      * the fix, this function will block until the fix is regained
      */
-    GPSData waitForGpsData();
+    PiksiGPSData waitForGpsData();
 
     /**
      * Destructor
@@ -165,7 +165,7 @@ private:
     pthread_t thread;
     pthread_mutex_t mutex;
     pthread_cond_t cond;
-    GPSData data, partialData;
+    PiksiGPSData data, partialData;
     uint32_t gpsTimestamp = 0;
     bool pos              = false;
     bool vel              = false;
diff --git a/src/shared/drivers/piksi/piksi_data.h b/src/shared/drivers/old_examples/piksi/piksi_data.h
similarity index 92%
rename from src/shared/drivers/piksi/piksi_data.h
rename to src/shared/drivers/old_examples/piksi/piksi_data.h
index 4def005a27f975394852448ff1383aa53373a7ba..a619a061b4ee97d614b6e75781da1062a38b6a13 100644
--- a/src/shared/drivers/piksi/piksi_data.h
+++ b/src/shared/drivers/old_examples/piksi/piksi_data.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2017-2019 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -25,7 +25,7 @@
 /**
  * The GPS information
  */
-struct GPSData
+struct PiksiGPSData
 {
     /// timestamp in ms (anakin time, not GPS time). getTick()-timestamp tells
     /// you how "old" the data is.
diff --git a/src/shared/drivers/pwm/pwm.cpp b/src/shared/drivers/pwm/pwm.cpp
index 67cdc26c3d8477cdd9b4e45f1ca84070197daecc..e0ca84caac219940d4454c6f5f05b4d4b13df1cc 100644
--- a/src/shared/drivers/pwm/pwm.cpp
+++ b/src/shared/drivers/pwm/pwm.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/pwm/pwm.h b/src/shared/drivers/pwm/pwm.h
index 9617f99e93a9fc5f0353b865361c9c1c4c678e74..ebc9078a2a9668b42f7ce3deb701093004628555 100644
--- a/src/shared/drivers/pwm/pwm.h
+++ b/src/shared/drivers/pwm/pwm.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/pwm/pwmdata.h b/src/shared/drivers/pwm/pwmdata.h
index 4eeed3a705d34b37e1ca44d0b91b5f6a488248db..c6f5ab777163b64c77946b5f6789d52fe96cf48d 100644
--- a/src/shared/drivers/pwm/pwmdata.h
+++ b/src/shared/drivers/pwm/pwmdata.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/servo/servo.cpp b/src/shared/drivers/servo/servo.cpp
index 6e6638a87de93d0b603fa98ee15827c3de2d79b4..fa605c26f021111b3f8e23bd1ddac360a9763c06 100644
--- a/src/shared/drivers/servo/servo.cpp
+++ b/src/shared/drivers/servo/servo.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -22,10 +21,15 @@
  */
 
 #include "servo.h"
+
 #include <cmath>
+#include <cstring>
 
 // Initialize the pwm with 50 Hz frequency and 65535 levels of duty cycle
-Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535) { memset(&positions, 0, 4); }
+Servo::Servo(PWM::Timer t) : pwm(t, 50, 65535)
+{
+    memset(&positions, 0, 4 * sizeof(float));
+}
 
 Servo::~Servo() {}
 
@@ -68,7 +72,7 @@ void Servo::setMinPulseWidth(float min_pulse)
 void Servo::setMaxPulseWidth(float max_pulse)
 {
     this->max_pulse = fmax(1700.0f, fmin(2500.0f, max_pulse));
-    
+
     updateParameters();
 }
 
diff --git a/src/shared/drivers/servo/servo.h b/src/shared/drivers/servo/servo.h
index 3d7770936e16773364b9edef2f16f67b01e5703b..5c9f10b812926ac733b4f9a8ab2b8875b4d5c288 100644
--- a/src/shared/drivers/servo/servo.h
+++ b/src/shared/drivers/servo/servo.h
@@ -1,7 +1,5 @@
-/**
- *
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..4da39ae5e511997a54e2fcb0944c1eb27bd03742
--- /dev/null
+++ b/src/shared/drivers/spi/SPIBus.h
@@ -0,0 +1,302 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cassert>
+
+#include "SPIBusInterface.h"
+
+
+#ifndef USE_MOCK_PERIPHERALS
+using SPIType = SPI_TypeDef;
+#else
+#include "test/FakeSpiTypedef.h"
+using SPIType = FakeSpiTypedef;
+#endif
+
+/**
+ * @brief Low level driver for communicating on a SPI Bus, provides
+ *        SPIBusInterface.
+ */
+class SPIBus : public SPIBusInterface
+{
+public:
+    /**
+     * @brief Instantiates a new SPIBus
+     *
+     * @param spi Pointer to the SPI peripheral to be used
+     */
+    SPIBus(SPIType* spi) : spi(spi) {}
+    ~SPIBus() {}
+
+    // Delete copy/move contructors/operators
+    SPIBus(const SPIBus&) = delete;
+    SPIBus& operator=(const SPIBus&) = delete;
+
+    SPIBus(SPIBus&&) = delete;
+    SPIBus& operator=(SPIBus&&) = delete;
+
+    /**
+     * @brief Disable bus configuration: calls to configure() will have no
+     * effect after calling this & the SPI peripheral will need to be configured
+     * manually.
+     */
+    void disableBusConfiguration() { config_enabled = false; }
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t byte) override;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    uint8_t read() override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    void read(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    uint8_t transfer(uint8_t data) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    void transfer(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::select()
+     */
+    void select(GpioType& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::deselect()
+     */
+    void deselect(GpioType& cs) override;
+
+    /**
+     * @brief Obtains ownership of the bus, configuring it with the provided
+     * config. Since this implementation is not syncronized, if acquire() is
+     * called on an already locked bus, it will:
+     * - FAIL in DEBUG mode
+     * - Do nothing when NOT in DEBUG mode
+     *
+     * Use SyncedSPIBus if you need to synchronize access to the bus.
+     */
+    void acquire(SPIBusConfig config) override;
+
+protected:
+    /**
+     * Writes a single byte on the SPI bus.
+     *
+     * @param    byte Pointer to the byte to be written
+     */
+    void write(uint8_t* byte);
+
+    /**
+     * Reads a single byte from the SPI bus.
+     *
+     * @param    byte Pointer to the byte where the read data will be stored
+     */
+    void read(uint8_t* byte);
+
+    /**
+     * Full duplex transfer. Writes a single byte on the SPI bus and replaces
+     * its content with the received data
+     *
+     * @param    byte Pointer to the byte to be transfered
+     */
+    void transfer(uint8_t* byte);
+
+    void configure(SPIBusConfig new_config);
+
+    SPIType* spi;
+
+    SPIBusConfig config{};
+    bool config_enabled       = true;
+    bool first_config_applied = false;
+};
+
+// Defined here and not in the .cpp to make them inline
+
+inline void SPIBus::write(uint8_t data) { write(&data); }
+
+inline void SPIBus::write(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; ++i)
+    {
+        write(data + i);
+    }
+}
+
+inline uint8_t SPIBus::read()
+{
+    uint8_t data;
+    read(&data);
+
+    return data;
+}
+
+inline void SPIBus::read(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; ++i)
+    {
+        read(data + i);
+    }
+}
+
+inline uint8_t SPIBus::transfer(uint8_t data)
+{
+    transfer(&data);
+    return data;
+}
+
+inline void SPIBus::transfer(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; ++i)
+    {
+        transfer(data + i);
+    }
+}
+
+inline void SPIBus::select(GpioType& cs)
+{
+    cs.low();
+    if (config.cs_setup_time_us > 0)
+    {
+        delayUs(config.cs_setup_time_us);
+    }
+}
+
+inline void SPIBus::deselect(GpioType& cs)
+{
+    if (config.cs_hold_time_us > 0)
+    {
+        delayUs(config.cs_hold_time_us);
+    }
+    cs.high();
+}
+
+inline void SPIBus::write(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Clear the RX buffer by accessing the DR register
+    (void)spi->DR;
+}
+
+inline void SPIBus::transfer(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
+
+inline void SPIBus::read(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write 0 in the transmit buffer
+    spi->DR = 0;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
+
+inline void SPIBus::acquire(SPIBusConfig new_config)
+{
+    // Assert that the bus is not already acquired.
+    // This bus is not syncronized: fail if someone tries to take ownership when
+    // the bus is already being used. Use SyncedSPIBus if you need to
+    // synchronize access to the bus
+#ifdef DEBUG
+    assert(isBusy() == false);
+#endif
+
+    SPIBusInterface::acquire(new_config);
+
+    configure(new_config);
+}
+
+inline void SPIBus::configure(SPIBusConfig new_config)
+{
+    // Reconfigure the bus only if config enabled. Do not reconfigure if already
+    // in the correct configuration.
+    if (config_enabled && (!first_config_applied || new_config != config))
+    {
+        first_config_applied = true;
+        config               = new_config;
+
+        // Wait until the peripheral is done before changing configuration
+        while ((spi->SR & SPI_SR_TXE) == 0)
+            ;
+        while ((spi->SR & SPI_SR_BSY) > 0)
+            ;
+
+        spi->CR1 = 0;
+
+        // Configure CPOL & CPHA bits
+        spi->CR1 |= static_cast<uint32_t>(config.mode);
+
+        // Configure clock division (BR bits)
+        spi->CR1 |= static_cast<uint32_t>(config.clock_div);
+
+        // Configure LSBFIRST bit
+        spi->CR1 |= static_cast<uint32_t>(config.bit_order);
+
+        spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM  // Use software chip-select
+                    | SPI_CR1_MSTR             // Master mode
+                    | SPI_CR1_SPE;             // Enable SPI
+    }
+}
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPIBusInterface.h b/src/shared/drivers/spi/SPIBusInterface.h
new file mode 100644
index 0000000000000000000000000000000000000000..8ac8beb9cc47484c4198eda71e61321aab3616c1
--- /dev/null
+++ b/src/shared/drivers/spi/SPIBusInterface.h
@@ -0,0 +1,284 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <miosix.h>
+
+#include <cstdint>
+#include <cstdio>
+
+using miosix::delayUs;
+using miosix::GpioPin;
+
+#ifndef USE_MOCK_PERIPHERALS
+using GpioType = GpioPin;
+#else
+#include "utils/testutils/MockGpioPin.h"
+using GpioType = MockGpioPin;
+#endif
+
+/**
+ * @brief SPI Clock divider.
+ * SPI clock frequency will be equal to the SPI peripheral bus clock speed (see
+ * datasheet) divided by the value specified in this enum.
+ *
+ * Eg: DIV_2 --> spi clock freq = f_PCLK / 2
+ *
+ * See register CR1  of the SPI peripheral on the reference manual for further
+ * information.
+ */
+enum class SPIClockDivider : uint8_t
+{
+    DIV2   = 0x00,
+    DIV4   = 0x08,
+    DIV8   = 0x10,
+    DIV16  = 0x18,
+    DIV32  = 0x20,
+    DIV64  = 0x28,
+    DIV128 = 0x30,
+    DIV256 = 0x38,
+};
+
+/**
+ * @brief SPI Mode.
+ * See slave device datasheet for information on which one to use.
+ */
+enum class SPIMode : uint8_t
+{
+    MODE0 = 0,  ///> CPOL = 0, CPHA = 0
+    MODE1 = 1,  ///> CPOL = 0, CPHA = 1
+    MODE2 = 2,  ///> CPOL = 1, CPHA = 0
+    MODE3 = 3   ///> CPOL = 1, CPHA = 1
+};
+
+/**
+ * @brief SPI Bit Order
+ * See register CR1  of the SPI peripheral on the reference manual for further
+ * information.
+ */
+enum class SPIBitOrder : uint8_t
+{
+    MSB_FIRST = 0,
+    LSB_FIRST = 0x80
+};
+
+/**
+ * @brief SPI Bus configuration for a specific slave.
+ * See slave datasheet for information on how to populate this struct
+ */
+struct SPIBusConfig
+{
+    SPIClockDivider clock_div =
+        SPIClockDivider::DIV256;             ///> Peripheral clock division
+    SPIMode mode          = SPIMode::MODE0;  ///> Clock polarity (0 - 1)
+    SPIBitOrder bit_order = SPIBitOrder::MSB_FIRST;  ///> MSB or LSB first
+
+    unsigned int cs_setup_time_us = 0;  ///> How long to wait before starting a
+                                        ///> a trasmission after CS is set (us)
+    unsigned int cs_hold_time_us = 0;   ///> How long to hold cs after the end
+                                        ///> of a trasmission (us)
+
+    // Custom comparison operator
+    bool operator==(const SPIBusConfig& other) const
+    {
+        // Compare member-by-member
+        // clang-format off
+        return  clock_div == other.clock_div 
+             && mode == other.mode
+             && bit_order == other.bit_order
+             && cs_setup_time_us == other.cs_setup_time_us 
+             && cs_setup_time_us == other.cs_hold_time_us;
+        // clang-format on
+    }
+
+    bool operator!=(const SPIBusConfig& other) const
+    {
+        return !(*this == other);
+    }
+};
+
+/**
+ * @brief Interface for low level access of a SPI bus
+ */
+class SPIBusInterface
+{
+public:
+    SPIBusInterface() {}
+
+    virtual ~SPIBusInterface() {}
+
+    // Delete copy/move contructors/operators
+    SPIBusInterface(const SPIBusInterface&) = delete;
+    SPIBusInterface& operator=(const SPIBusInterface&) = delete;
+
+    SPIBusInterface(SPIBusInterface&&) = delete;
+    SPIBusInterface& operator=(SPIBusInterface&&) = delete;
+
+    /**
+     * @brief Writes a single \p byte to the bus.
+     *
+     * @param    byte Byte to write
+     */
+    virtual void write(uint8_t byte) = 0;
+
+    /**
+     * @brief Writes \p data to the bus.
+     *
+     * @param    data Buffer containing data to write
+     * @param    size Number of bytes to write
+     */
+    virtual void write(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Reads a single byte from the bus.
+     * @return Byte read from the bus
+     */
+    virtual uint8_t read() = 0;
+
+    /**
+     * @brief Reads \p size bytes from the SPI bus, putting them in \p data.
+     *
+     * @param    data Buffer to be filled with received data
+     * @param    size Number of bytes to receive
+     */
+    virtual void read(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Full duplex transmission on the SPI bus.
+     * A \p byte is written on the bus and a byte is read and returned
+     *
+     * @param    byte Byte to write
+     * @return Data read from the bus
+     */
+    virtual uint8_t transfer(uint8_t byte) = 0;
+
+    /**
+     * @brief Full duplex transmission on the SPI bus.
+     * \p data is written on the bus and its contents are then replaced with the
+     * received bytes.
+     *
+     * @param    data Buffer containing data to transfer
+     * @param    size Number of bytes to transfer
+     */
+    virtual void transfer(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Selects the slave
+     *
+     * @param    cs Chip select pin for the slave
+     */
+    virtual void select(GpioType& cs) = 0;
+
+    /**
+     * @brief Deselects the slave
+     *
+     * @param    cs Chip select pin for the slave
+     * @return
+     */
+    virtual void deselect(GpioType& cs) = 0;
+
+    /**
+     * @brief Configures the bus with the provided configuration parameters.
+     * Call this before every transaction, after the call to lock() and before
+     * select(..)
+     *
+     * @param    config    Configuration parameters
+     * @return
+     */
+    virtual void acquire(SPIBusConfig config)
+    {
+        (void)config;
+        busy = true;
+    }
+
+    /**
+     * @brief Releases ownership of the bus
+     */
+    virtual void release() { busy = false; }
+
+    /**
+     * @brief Checks wether the bus is currently being used
+     */
+    virtual bool isBusy() { return busy; }
+
+private:
+    bool busy = false;  // For use by SPITransaction
+};
+
+/**
+ * @brief Contains information about a single SPI slave device.
+ */
+struct SPISlave
+{
+    SPIBusInterface& bus;  ///> Bus on which the slave is connected
+
+    SPIBusConfig config;  ///> How the bus should be configured to communicate
+                          ///> with the slave.
+    GpioType cs;          ///> Chip select pin
+
+    SPISlave(SPIBusInterface& bus, GpioType cs, SPIBusConfig config)
+        : bus(bus), config(config), cs(cs)
+    {
+    }
+};
+
+/**
+ * @brief RAII Interface for SPI bus acquisition
+ *
+ */
+class SPIAcquireLock
+{
+public:
+    SPIAcquireLock(SPISlave slave) : SPIAcquireLock(slave.bus, slave.config) {}
+
+    SPIAcquireLock(SPIBusInterface& bus, SPIBusConfig cfg) : bus(bus)
+    {
+        bus.acquire(cfg);
+    }
+
+    ~SPIAcquireLock() { bus.release(); }
+
+private:
+    SPIBusInterface& bus;
+};
+
+/**
+ * @brief RAII Interface for SPI chip selection
+ *
+ */
+class SPISelectLock
+{
+public:
+    SPISelectLock(SPISlave slave) : SPISelectLock(slave.bus, slave.cs) {}
+
+    SPISelectLock(SPIBusInterface& bus, GpioType cs) : bus(bus), cs(cs)
+    {
+        bus.select(cs);
+    }
+
+    ~SPISelectLock() { bus.deselect(cs); }
+
+private:
+    SPIBusInterface& bus;
+    GpioType& cs;
+};
\ No newline at end of file
diff --git a/src/shared/math/Quaternion.h b/src/shared/drivers/spi/SPIDriver.h
similarity index 78%
rename from src/shared/math/Quaternion.h
rename to src/shared/drivers/spi/SPIDriver.h
index 245a4c4b35e63bdcdd5004080629258cd44a93b0..4b26d9f209ef58b7d0a306abe6eacd1e7388c4ed 100644
--- a/src/shared/math/Quaternion.h
+++ b/src/shared/drivers/spi/SPIDriver.h
@@ -1,7 +1,5 @@
-/* Quaternion
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,19 +13,16 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#ifndef QUATERNION_H
-#define QUATERNION_H
 
-// TODO
-class Quaternion
-{
-};
+#pragma once
 
-#endif /* ifndef QUATERNION_H */
+#include "SPIBusInterface.h"
+#include "SPITransaction.h"
+#include "SPIBus.h"
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPITransaction.cpp b/src/shared/drivers/spi/SPITransaction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1ae89f7b73264b8276165a6d8899495fb0f6ee3
--- /dev/null
+++ b/src/shared/drivers/spi/SPITransaction.cpp
@@ -0,0 +1,109 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "SPITransaction.h"
+
+SPITransaction::SPITransaction(SPISlave slave)
+    : SPITransaction(slave.bus, slave.cs, slave.config)
+{
+}
+
+SPITransaction::SPITransaction(SPIBusInterface& bus, GpioType cs,
+                               SPIBusConfig config)
+    : bus(bus), cs(cs)
+{
+    bus.acquire(config);
+}
+
+SPITransaction::~SPITransaction() { bus.release(); }
+
+void SPITransaction::write(uint8_t cmd)
+{
+    bus.select(cs);
+    bus.write(cmd);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t reg, uint8_t val)
+{
+    bus.select(cs);
+    bus.write(reg);
+    bus.write(val);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t reg, uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.write(reg);
+    bus.write(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.write(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::transfer(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.transfer(data, size);
+    bus.deselect(cs);
+}
+
+uint8_t SPITransaction::read(uint8_t reg, bool set_read_bit)
+{
+    if (set_read_bit)
+    {
+        reg = reg | 0x80;
+    }
+
+    bus.select(cs);
+    bus.write(reg);
+    reg = bus.read();
+    bus.deselect(cs);
+    return reg;
+}
+
+void SPITransaction::read(uint8_t reg, uint8_t* data, size_t size,
+                          bool set_read_bit)
+{
+    if (set_read_bit)
+    {
+        reg = reg | 0x80;
+    }
+
+    bus.select(cs);
+    bus.write(reg);
+    bus.read(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::read(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.read(data, size);
+    bus.deselect(cs);
+}
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPITransaction.h b/src/shared/drivers/spi/SPITransaction.h
new file mode 100644
index 0000000000000000000000000000000000000000..698e7191575ba59a4f7188ae908e69d3c53b91d1
--- /dev/null
+++ b/src/shared/drivers/spi/SPITransaction.h
@@ -0,0 +1,162 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "SPIBusInterface.h"
+
+/**
+ * @brief Provides high-level access to the SPI Bus for a single transaction.
+ * To make sure the bus is properly configured for the provided slave, you have
+ * to create a new instance of this class for every transaction, as the bus is
+ * configured upon instantiation.
+ *
+ * @warning DO NOT store an instance of this class for later use, as the bus may
+ * be incorrectly configured by then.
+ *
+ * Example transaction:
+ *
+ * {
+ *     // Transaction begin:
+ *     SPITransaction spi(bus, cs, config); // Configures the bus with the
+ *                                          // provided parameters.
+ *
+ *     spi.write(REG_EX, 0x56); // writes data to REG_EX
+ *     uint8_t reg2 = spi.read(REG_EX_2); // reads from REG_EX_2
+ *
+ *     // ...As many read/writes as you wish...
+ *
+ *     // transaction end. SPITransaction object is destructed and the bus is
+ *     // freed for use by someone else
+ * }
+ */
+class SPITransaction
+{
+public:
+    /**
+     * @brief Instatiates a new SPITransaction, configuring the bus with the
+     * provided parameters
+     *
+     * @param    slave     Slave to communicate with
+     */
+    SPITransaction(SPISlave slave);
+
+    /**
+     * @brief Instatiates a new SPITransaction, configuring the bus with the
+     * provided parameters
+     *
+     * @param    bus       Bus to communicate on
+     * @param    cs        Chip select of the slave to communicate to
+     * @param    config    Configuration of the bus for the selected slave
+     */
+    SPITransaction(SPIBusInterface& bus, GpioType cs, SPIBusConfig config);
+
+    ~SPITransaction();
+
+    // Delete copy/move contructors/operators
+    SPITransaction(const SPITransaction&) = delete;
+    SPITransaction& operator=(const SPITransaction&) = delete;
+
+    SPITransaction(SPITransaction&&) = delete;
+    SPITransaction& operator=(SPITransaction&&) = delete;
+
+    /**
+     * @brief Writes a command \p cmd to the bus
+     *
+     * @param    cmd     Command to write on the bus
+     */
+    void write(uint8_t cmd);
+
+    /**
+     * @brief Writes \p val into the \p reg register
+     *
+     * @param    reg     Slave device register
+     * @param    val     Value to be written in the register
+     */
+    void write(uint8_t reg, uint8_t val);
+
+    /**
+     * @brief Writes \p size bytes into the \p reg register
+     *
+     * @param    reg       Slave device register
+     * @param    data      Data to be written
+     * @param    size      Number of bytes to be written
+     */
+    void write(uint8_t reg, uint8_t* data, size_t size);
+
+    /**
+     * @brief Writes \p bytes on the bus
+     *
+     * @param    data      Bytes to be written
+     * @param    size      Number of bytes to be written
+     */
+    void write(uint8_t* data, size_t size);
+
+    /**
+     * @brief Read the contents of the \p reg register
+     *
+     * @param    reg       Slave device register
+     * @param    set_read_bit Wether to set the read bit to 1 (MSB of reg)
+     *                        (default = true).
+     */
+    uint8_t read(uint8_t reg, bool set_read_bit = true);
+
+    /**
+     * @brief Reads \p size bytes from the \p reg register
+     *
+     * @param    reg    Slave device register
+     * @param    data   Buffer where read bytes will be stored
+     * @param    size   Number of bytes to read
+     * @param    set_read_bit Wether to set the read bit to 1 (MSB of reg)
+     *                        (default = true).
+     */
+    void read(uint8_t reg, uint8_t* data, size_t size,
+              bool set_read_bit = true);
+
+    /**
+     * @brief Reads \p size bytes from the bus
+     *
+     * @param    data   Buffer where read bytes will be stored
+     * @param    size   Number of bytes to read
+     */
+    void read(uint8_t* data, size_t size);
+
+    /**
+     * @brief Full duplex transfer: \p data is written on the bus and its
+     *        contents are replaced with the received bytes.
+     *
+     * @param    data      Buffer containign data to be transfered
+     * @param    size      Number of bytes to be transfer
+     */
+    void transfer(uint8_t* data, size_t size);
+
+    /**
+     * @brief Returns the underlying bus for low level access
+     *
+     * @return  SPIBusInterface associated with this transaction
+     */
+    SPIBusInterface& getBus() { return bus; }
+
+private:
+    SPIBusInterface& bus;
+    GpioType cs;
+};
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SensorSpi.cpp b/src/shared/drivers/spi/SensorSpi.cpp
index 62de6c6fa88d523ea4005af6fbdec6406c167a30..2907c80802567223a0c2577d22b4f409b6c160d4 100644
--- a/src/shared/drivers/spi/SensorSpi.cpp
+++ b/src/shared/drivers/spi/SensorSpi.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/spi/SensorSpi.h b/src/shared/drivers/spi/SensorSpi.h
index 856461ced6380347fc7ef9b13e405c0bb39edac7..04cbbf0a4821491e0ffc485242e3e989e64dd950 100644
--- a/src/shared/drivers/spi/SensorSpi.h
+++ b/src/shared/drivers/spi/SensorSpi.h
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/spi/SyncedSPIBus.h b/src/shared/drivers/spi/SyncedSPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..f13e5f81aee9455f672b2fab1676ad9fc855f813
--- /dev/null
+++ b/src/shared/drivers/spi/SyncedSPIBus.h
@@ -0,0 +1,50 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "SPIBus.h"
+#include <miosix.h>
+
+using miosix::FastMutex;
+
+/**
+ * @brief Extension of SPIBus to sync access to the bus between multiple threads
+ */
+class SyncedSPIBus : public SPIBus
+{
+    virtual void acquire(SPIBusConfig config) override
+    {
+        mutex.lock();
+        SPIBusInterface::acquire(config);
+
+        configure(config);
+    }
+
+    virtual void release() override
+    {
+        SPIBusInterface::release();
+        mutex.unlock();
+    }
+private:
+    FastMutex mutex;
+};
\ No newline at end of file
diff --git a/src/shared/drivers/spi/test/FakeSpiTypedef.h b/src/shared/drivers/spi/test/FakeSpiTypedef.h
new file mode 100644
index 0000000000000000000000000000000000000000..63932a08859c84a9c1c5f0b25d2169e613a04e7e
--- /dev/null
+++ b/src/shared/drivers/spi/test/FakeSpiTypedef.h
@@ -0,0 +1,84 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+#include <miosix.h>
+
+#include <cstdint>
+#include <vector>
+#include "utils/testutils/MockGpioPin.h"
+
+using std::vector;
+
+/**
+ * @brief Mock STM32F4 SPI peripheral: intercepts register value changes to
+ * emulate a real SPI peripheral / slave.
+ */
+struct FakeSpiTypedef
+{
+    uint32_t CR1 = 0;
+    uint32_t CR2 = 0;
+    uint32_t SR = 3;
+
+    struct RegDR
+    {
+        // Intercept uint32_t assignements
+        void operator=(uint32_t DR)
+        {
+            // If slave is selected & bus configured properly
+            if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected &&
+                parent.CR2 == parent.CR2_expected)
+            {
+                out_buf.push_back(DR);
+            }
+        }
+
+        operator uint32_t()
+        {
+            // If slave is selected
+            if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected &&
+                parent.CR2 == parent.CR2_expected)
+            {
+                return in_buf[in_it++];
+            }
+
+            return 0;
+        }
+
+        RegDR(FakeSpiTypedef& parent) : parent(parent) {}
+
+        unsigned int in_it = 0;
+        vector<uint32_t> in_buf;
+        vector<uint32_t> out_buf;
+
+    private:
+        FakeSpiTypedef& parent;
+    };
+
+    uint32_t CR1_expected = 0;
+    uint32_t CR2_expected = 0;
+
+    RegDR DR;
+    MockGpioPin cs;
+
+    FakeSpiTypedef() : DR(*this) {cs.high();}
+};
\ No newline at end of file
diff --git a/src/shared/drivers/spi/test/MockSPIBus.h b/src/shared/drivers/spi/test/MockSPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..196a1346294335ecc03d1d2a530f2b82c83cdcfa
--- /dev/null
+++ b/src/shared/drivers/spi/test/MockSPIBus.h
@@ -0,0 +1,297 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+#include "../SPIBusInterface.h"
+
+using std::vector;
+
+/**
+ * @brief Mock SPI Bus to be used for testing communication to a single slave:
+ * data are read and written to two buffers on the memory that can then be
+ * checked. Operations on the mock bus will not be successfull if the
+ * configuration is not correct or the "slave" is not selected.
+ *
+ * Usage:
+ * 1. Set the expected config (data wont be written / read if the bus current
+ * configuration of the bus is different from the expected one)
+ * 2. Set the data to be read from the bus (in_buf).
+ * 3. Perform operations. write() will write bytes in out_buf, read() will
+ * return data from in_buf.
+ * 4. Check if out_buf contains the expected data. Check if data returned from
+ * read() is as expected from in_buf.
+ * 5. ???
+ * 6. Profit.
+ */
+
+#ifndef USE_MOCK_PERIPHERALS
+#error \
+    "SpiBusInterface must be built using MockGpioPin (-DUSE_MOCK_PERIPHERALS)"
+#endif
+
+#include "utils/testutils/MockGpioPin.h"
+
+using miosix::FastMutex;
+using miosix::Lock;
+class MockSPIBus : public SPIBusInterface
+{
+public:
+    MockSPIBus(SPIBusConfig expected_config) : expected_config(expected_config)
+    {
+    }
+
+    ~MockSPIBus() {}
+
+    // Delete copy/move contructors/operators
+    MockSPIBus(const MockSPIBus&) = delete;
+    MockSPIBus& operator=(const MockSPIBus&) = delete;
+
+    MockSPIBus(MockSPIBus&&) = delete;
+    MockSPIBus& operator=(MockSPIBus&&) = delete;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    virtual void write(uint8_t byte) override;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    virtual void write(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    virtual uint8_t read() override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    void read(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    virtual uint8_t transfer(uint8_t data) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    virtual void transfer(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::select()
+     *
+     */
+    virtual void select(GpioType& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::deselect()
+     *
+     */
+    virtual void deselect(GpioType& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::acquire()
+     */
+    virtual void acquire(SPIBusConfig config) override;
+
+    /**
+     * @brief Wether the chip select is asserted or not
+     */
+    virtual bool isSelected()
+    {
+
+        Lock<FastMutex> l(mutex);
+        return selected;
+    }
+
+    virtual vector<uint8_t> getOutBuf()
+    {
+        Lock<FastMutex> l(mutex);
+        return out_buf;
+    }
+
+    virtual vector<uint8_t> getInBuf()
+    {
+        Lock<FastMutex> l(mutex);
+        return in_buf;
+    }
+
+    virtual void push(uint8_t* data, size_t len);
+
+    virtual void clearBuffers()
+    {
+        out_buf.clear();
+        in_buf.clear();
+        in_buf_pos_cntr = 0;
+    }
+
+protected:
+    FastMutex mutex;
+
+    vector<uint8_t> out_buf;  // Data written on the bus are stored here
+    vector<uint8_t> in_buf;   // Store here data to be read from the bus
+    size_t in_buf_pos_cntr = 0;
+
+    SPIBusConfig expected_config;  // Expected configuration of the bus
+
+    virtual uint8_t _read();
+    virtual void _write(uint8_t);
+
+    virtual void _push(uint8_t* data, size_t len);
+
+    bool canCommunicate();
+
+    SPIBusConfig current_config;
+    bool selected = false;
+};
+
+inline bool MockSPIBus::canCommunicate()
+{
+    bool result = selected && current_config == expected_config;
+    if (!result)
+    {
+        printf("Error, cannot communicato on MockSPIBus: ");
+        if (!selected)
+        {
+            printf("Chip select not asserted\n");
+        }
+        else
+        {
+            printf("Incorrect configuration\n");
+        }
+    }
+    return result;
+}
+
+inline void MockSPIBus::_write(uint8_t byte)
+{
+    if (canCommunicate())
+    {
+        out_buf.push_back(byte);
+    }
+    else
+    {
+        out_buf.push_back(0);
+    }
+}
+
+inline void MockSPIBus::write(uint8_t byte)
+{
+    Lock<FastMutex> l(mutex);
+    _write(byte);
+}
+
+inline void MockSPIBus::write(uint8_t* data, size_t size)
+{
+    Lock<FastMutex> l(mutex);
+
+    for (size_t i = 0; i < size; i++)
+    {
+        _write(data[i]);
+    }
+}
+
+inline uint8_t MockSPIBus::read()
+{
+    Lock<FastMutex> l(mutex);
+    return _read();
+}
+
+inline void MockSPIBus::read(uint8_t* data, size_t size)
+{
+    Lock<FastMutex> l(mutex);
+    for (size_t i = 0; i < size; i++)
+    {
+        *data = _read();
+        data++;
+    }
+}
+
+inline uint8_t MockSPIBus::_read()
+{
+    if (canCommunicate())
+    {
+        if (in_buf_pos_cntr < in_buf.size())
+        {
+            return in_buf[in_buf_pos_cntr++];
+        }
+    }
+    return 0;
+}
+
+inline uint8_t MockSPIBus::transfer(uint8_t data)
+{
+    Lock<FastMutex> l(mutex);
+    _write(data);
+    return _read();
+}
+
+inline void MockSPIBus::transfer(uint8_t* data, size_t size)
+{
+    Lock<FastMutex> l(mutex);
+    for (size_t i = 0; i < size; i++)
+    {
+        _write(data[i]);
+        data[i] = _read();
+    }
+}
+
+inline void MockSPIBus::select(GpioType& cs)
+{
+    Lock<FastMutex> l(mutex);
+    cs.low();
+    selected = true;
+}
+
+inline void MockSPIBus::deselect(GpioType& cs)
+{
+    Lock<FastMutex> l(mutex);
+    cs.high();
+    selected = false;
+}
+
+inline void MockSPIBus::acquire(SPIBusConfig config)
+{
+    Lock<FastMutex> l(mutex);
+
+    SPIBusInterface::acquire(config);
+
+    current_config = config;
+}
+
+inline void MockSPIBus::push(uint8_t* data, size_t len)
+{
+    Lock<FastMutex> l(mutex);
+    _push(data, len);
+}
+
+inline void MockSPIBus::_push(uint8_t* data, size_t len)
+{
+    in_buf.insert(in_buf.end(), data, data + len);
+}
\ No newline at end of file
diff --git a/src/shared/events/Event.h b/src/shared/events/Event.h
index 7f49995bc2357460a1167295d114f278c98dcb59..8c51c5610c799fd52f5b359b45a173c38b9159e4 100644
--- a/src/shared/events/Event.h
+++ b/src/shared/events/Event.h
@@ -1,7 +1,5 @@
-/* Event
- *
- * Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Matteo Michele Piazzolla
+/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
+ * Author: Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/events/EventBroker.cpp b/src/shared/events/EventBroker.cpp
index bb823917b3a1d277fea85ec5dffbde125d7406b3..8aeeef2c93654ea8b671694cb03a3aa817fe4d19 100644
--- a/src/shared/events/EventBroker.cpp
+++ b/src/shared/events/EventBroker.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta, Matteo Michele Piazzolla
+ * Authors: Luca Erbetta, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -21,6 +21,7 @@
  */
 
 #include "EventBroker.h"
+
 #include "Debug.h"
 #include "diagnostic/StackLogger.h"
 
@@ -29,7 +30,7 @@ EventBroker::EventBroker() {}
 void EventBroker::post(const Event& ev, uint8_t topic)
 {
 #ifdef TRACE_EVENTS
-    TRACE("[EventBroker] Event: %d, Topic: %d\n", ev.sig, topic);
+    LOG_DEBUG(logger, "Event: {}, Topic: {}", ev.sig, topic);
 #endif
 
     Lock<FastMutex> lock(mtx_subscribers);
diff --git a/src/shared/events/EventBroker.h b/src/shared/events/EventBroker.h
index cbeb64919151a37f1247aa6f2c191db3fe681d78..9aca11782547ab1c1db702f9f2bf6cc00e646e6a 100644
--- a/src/shared/events/EventBroker.h
+++ b/src/shared/events/EventBroker.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta, Matteo Michele Piazzolla
+ * Authors: Luca Erbetta, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,18 +23,18 @@
 #ifndef SRC_SHARED_EVENTS_EVENT_BROKER_H
 #define SRC_SHARED_EVENTS_EVENT_BROKER_H
 
-#include "ActiveObject.h"
-#include "Singleton.h"
-
-#include "events/Event.h"
-#include "events/FSM.h"
-
+#include <diagnostic/PrintLogger.h>
 #include <miosix.h>
 #include <stdint.h>
 
 #include <map>
 #include <vector>
 
+#include "ActiveObject.h"
+#include "Singleton.h"
+#include "events/Event.h"
+#include "events/FSM.h"
+
 using std::map;
 using std::vector;
 
@@ -183,6 +183,8 @@ private:
     FastMutex mtx_subscribers;
 
     uint16_t eventCounter = 0;
+
+    PrintLogger logger = Logging::getLogger("eventbroker");
 };
 
 #define sEventBroker Singleton<EventBroker>::getInstance()
diff --git a/src/shared/events/EventHandler.h b/src/shared/events/EventHandler.h
index 335ab63cd3b52d4a88ba33ebd95a8598ec7d1f18..1c1fecc3ce0dde7c090896dcb962224865883a61 100644
--- a/src/shared/events/EventHandler.h
+++ b/src/shared/events/EventHandler.h
@@ -1,6 +1,4 @@
-/* Event Handler
- *
- * Copyright (c) 2015-2018 Skyward Experimental Rocketry
+/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
  * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/events/FSM.h b/src/shared/events/FSM.h
index b16ae3f2ac3867d35db31eb93661582370c7fc9a..767538252f156912d54d5beb90f5fac956e3beb8 100644
--- a/src/shared/events/FSM.h
+++ b/src/shared/events/FSM.h
@@ -1,7 +1,5 @@
-/* Finite State Machine
- *
- * Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Matteo Michele Piazzolla, Alain Carlucci, Luca Erbetta
+/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci, Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/events/HSM.h b/src/shared/events/HSM.h
index f4f0c0ab417a34b2f7604c292cb97b26b56eb60d..aeaa771491d5933203272cabda9eebb57b960fac 100644
--- a/src/shared/events/HSM.h
+++ b/src/shared/events/HSM.h
@@ -1,29 +1,28 @@
-/* Hierarchical State Machine
- *
- * Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Matteo Michele Piazzolla, Alain Carlucci, Luca Erbetta
- *
- * This work is derived from https://www.state-machine.com by Miro Samek.
+/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci, Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated docuthisntation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, thisrge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
+ * 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 thisRCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEthisNT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
+
+// This work is derived from https://www.state-machine.com by Miro Samek.
+
 #ifndef SRC_SHARED_EVENTS_HSM_H
 #define SRC_SHARED_EVENTS_HSM_H
 
diff --git a/src/shared/events/utils/EventCounter.h b/src/shared/events/utils/EventCounter.h
index 06771fb47c1e990a4824a4b19c0a5e55a5f37247..ba8ff049a02826fcf8c959d4e9d386d8489ae722 100644
--- a/src/shared/events/utils/EventCounter.h
+++ b/src/shared/events/utils/EventCounter.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/events/utils/EventSniffer.h b/src/shared/events/utils/EventSniffer.h
index 00c0197c39ce2a0f83fb751aa2743b488e935332..5d7aabfa5655eeb0c2705e8d7fc884234a754b66 100644
--- a/src/shared/events/utils/EventSniffer.h
+++ b/src/shared/events/utils/EventSniffer.h
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/kalman/Kalman.h b/src/shared/kalman/Kalman.h
index 587e36f9f067cb61b54249bf7f622d65cc499a27..9bb608ae9f495aca09cac34f2101b4aaea324e70 100644
--- a/src/shared/kalman/Kalman.h
+++ b/src/shared/kalman/Kalman.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Mozzarelli
+ * Author: Luca Mozzarelli
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/kalman/KalmanEigen.h b/src/shared/kalman/KalmanEigen.h
new file mode 100644
index 0000000000000000000000000000000000000000..6441a749e0c878673d1e1b90720c7ed61b12939d
--- /dev/null
+++ b/src/shared/kalman/KalmanEigen.h
@@ -0,0 +1,172 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Alessandro Del Duca, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Dense>
+
+using namespace Eigen;
+
+/**
+ * @brief Class representing a Kalman filter using the Eigen library for
+ *        matrix computations
+ *
+ * WARNING : This class uses templates in order to know the size of each matrix
+ *           at compile-time. This way we avoid Eigen to allocate memory
+ *           dynamically.
+ */
+template <typename t, uint8_t n, uint8_t p>
+class KalmanEigen
+{
+    using MatrixNN = Matrix<t, n, n>;
+    using MatrixPN = Matrix<t, p, n>;
+    using MatrixNP = Matrix<t, n, p>;
+    using MatrixPP = Matrix<t, p, p>;
+    using CVectorN = Matrix<t, n, 1>;
+    using CVectorP = Matrix<t, p, 1>;
+
+public:
+    /**
+     * @brief Configuration struct for the KalmanEigen class.
+     */
+    struct KalmanConfig
+    {
+        MatrixNN F;
+        MatrixPN H;
+        MatrixNN Q;
+        MatrixPP R;
+        MatrixNN P;
+        CVectorN x;
+    };
+
+    /**
+     * @param config configuration object containing all the initialized
+     *               matrices
+     */
+    KalmanEigen(const KalmanConfig& config)
+        : F(config.F), H(config.H), Q(config.Q), R(config.R), P(config.P),
+          S(MatrixPP::Zero(p, p)), K(MatrixNP::Zero(n, p)), x(config.x)
+    {
+        I.setIdentity();
+    }
+
+    /**
+     * @brief Prediction step.
+     */
+    void predict()
+    {
+        P = F * P * F.transpose() + Q;
+        x = F * x;
+    }
+
+    /**
+     * @brief Prediction step.
+     *
+     * @param F_new updated F matrix
+     */
+    void predict(const MatrixNN& F_new)
+    {
+        this->F = F_new;
+        this->predict();
+    }
+
+    /**
+     * @brief Correction step (correct the estimate).
+     *
+     * @param y The measurement vector
+     */
+    bool correct(const CVectorP& y)
+    {
+        S = H * P * H.transpose() + R;
+
+        // here the determinant is computed and
+        // then the inverse recomputes it,
+        // this passage could be optimized
+        if (S.determinant() < 1e-3)
+        {
+            return false;
+        }
+
+        K = P * H.transpose() * S.inverse();
+        P = (I - K * H) * P;
+
+        x = x + K * (y - H * x);
+
+        res = y - H * x;
+
+        return true;
+    }
+
+    /**
+     * @return state vector
+     */
+    const CVectorN getState() { return x; }
+
+    /**
+     * @return output vector
+     */
+    const CVectorP getOutput()
+    {
+        y_hat = H * x;
+        return y_hat;
+    }
+
+    /**
+     * @return residual error vector
+     */
+    const CVectorP getResidual() { return res; }
+
+    /**
+     * @brief Predicts k steps ahead the output
+     */
+    const CVectorP predictOutput(uint32_t k) { return H * predictState(k); }
+
+    /**
+     * @brief Predicts k steps ahead the state
+     */
+    const CVectorN predictState(uint32_t k)
+    {
+        CVectorN x_hat = x;
+
+        for (uint32_t i = 0; i < k; i++)
+        {
+            x_hat = F * x_hat;
+        }
+        return x_hat;
+    }
+
+private:
+    MatrixNN F; /**< State propagation matrix (n x n) */
+    MatrixPN H; /**< Output matrix (p x n) */
+    MatrixNN Q; /**< Model variance matrix (n x n) */
+    MatrixPP R; /**< Measurement variance (p x p) */
+    MatrixNN P; /**< Error covariance matrix (n x n) */
+
+    MatrixPP S;
+    MatrixNP K; /**< kalman gain */
+
+    MatrixNN I; /**< identity matrix (n x n) */
+
+    CVectorN x;     /**< state vector (n x 1) */
+    CVectorP y_hat; /**< output vector (p x 1) */
+    CVectorP res;   /**< residual error vector (p x 1) */
+};
\ No newline at end of file
diff --git a/src/shared/logger/Deserializer.h b/src/shared/logger/Deserializer.h
index aa673fdaa38496cd21644179171d1aae112a75e2..d5b0f4144d164f2ecb4e1e6cb5226a9028278618 100644
--- a/src/shared/logger/Deserializer.h
+++ b/src/shared/logger/Deserializer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -83,7 +83,7 @@ public:
             return false;
         }
 
-        char c_filename[64];
+        char c_filename[128];
         sprintf(c_filename, "%s%s_%s.csv", prefix.c_str(), logFile.c_str(),
                 typeid(T).name());
 
diff --git a/src/shared/logger/LogStats.h b/src/shared/logger/LogStats.h
index 6ebce642d057b33868534c21303416edd2abb8d0..db13a3d2350f2963366b6c247b5428cbf174fa71 100644
--- a/src/shared/logger/LogStats.h
+++ b/src/shared/logger/LogStats.h
@@ -1,29 +1,24 @@
-/***************************************************************************
- *   Copyright (C) 2018 by Terraneo Federico                               *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
 
 #pragma once
 
@@ -49,7 +44,7 @@ public:
 
     static std::string header()
     {
-        return "timestamp,stat_toolarge,stat_dropped,stat_queued,stat_buf_"
+        return "timestamp,logNumber,stat_toolarge,stat_dropped,stat_queued,stat_buf_"
                "filled,stat_buf_written,stat_w_failed,stat_w_time,stat_max_"
                "time,stat_last_error\n";
     }
@@ -60,7 +55,7 @@ public:
      */
     void print(std::ostream& os) const
     {
-        os << timestamp << "," << statTooLargeSamples << ","
+        os << timestamp << "," << logNumber << "," << statTooLargeSamples << ","
            << statDroppedSamples << "," << statQueuedSamples << ","
            << statBufferFilled << "," << statBufferWritten << ","
            << statWriteFailed << "," << statWriteTime << "," << statMaxWriteTime
@@ -69,6 +64,7 @@ public:
 
     long long timestamp;  ///< Timestamp
 
+    bool opened = false;
     int logNumber = 0;
     int statTooLargeSamples =
         0;  ///< Number of dropped samples because too large
diff --git a/src/shared/logger/Logger.cpp b/src/shared/logger/Logger.cpp
index afa1d7656c1076a320c80987a856a17df3062dbb..79e8b1de83dfc56216925e4373594796e2fc999f 100644
--- a/src/shared/logger/Logger.cpp
+++ b/src/shared/logger/Logger.cpp
@@ -1,41 +1,39 @@
-/***************************************************************************
- *   Copyright (C) 2018 by Terraneo Federico                               *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
 
 #include "Logger.h"
+
+#include <errno.h>
 #include <fcntl.h>
 #include <interfaces/atomic_ops.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <tscpp/buffer.h>
+
 #include <stdexcept>
+
 #include "Debug.h"
 #include "diagnostic/SkywardStack.h"
 #include "diagnostic/StackLogger.h"
-#include <errno.h>
 
 using namespace std;
 using namespace miosix;
@@ -74,7 +72,10 @@ int Logger::start()
 
     file = fopen(filename.c_str(), "ab");
     if (file == NULL)
+    {
+        fileNumber = -1;
         throw runtime_error("Error opening log file");
+    }
     setbuf(file, NULL);
 
     // The boring part, start threads one by one and if they fail, undo
@@ -116,8 +117,8 @@ int Logger::start()
         fclose(file);
         throw runtime_error("Error creating stats thread");
     }*/
-    started = true;
-
+    started     = true;
+    s.opened    = true;
     s.logNumber = fileNumber;
     return fileNumber;
 }
@@ -133,6 +134,8 @@ void Logger::stop()
     writeT->join();
     // statsT->join();
     fclose(file);
+
+    s.opened = false;
 }
 
 Logger::Logger()
@@ -163,10 +166,11 @@ LogResult Logger::logImpl(const char* name, const void* data, unsigned int size)
 {
     if (started == false)
     {
+        //TRACE("Logger not started!");
         ++s.statDroppedSamples;
 
         // Signal that we are trying to write to a closed log
-        s.statWriteError = EIO;
+        s.statWriteError = -1;
 
         return LogResult::Ignored;
     }
diff --git a/src/shared/logger/Logger.h b/src/shared/logger/Logger.h
index 0794e0fa6f67f2780226b17ec41612edecce2610..831131e161f081107b01e8fcb5aae2d369bc4382 100644
--- a/src/shared/logger/Logger.h
+++ b/src/shared/logger/Logger.h
@@ -1,41 +1,39 @@
-/***************************************************************************
- *   Copyright (C) 2018 by Terraneo Federico                               *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
 
 #pragma once
 
 #include <miosix.h>
+
 #include <cstdio>
 #include <list>
 #include <queue>
 #include <string>
 #include <type_traits>
+
 #include "LogStats.h"
 
 using std::string;
+
 /**
  * Possible outcomes of Logger::log()
  */
@@ -101,9 +99,9 @@ public:
     }
 
     /**
-    * Returns current log filename
-    * @return
-    */
+     * Returns current log filename
+     * @return
+     */
     string getFileName() { return getFileName(fileNumber); }
 
     LogStats getLogStats() { return s; }
@@ -129,7 +127,10 @@ public:
     template <typename T>
     LogResult log(const T &t)
     {
-        // static_assert(std::is_trivially_copyable<T>::value,"");
+        static_assert(
+            std::is_trivially_copyable<T>::value,
+            "A type T must be trivially copyable in order to be logged!");
+
         return logImpl(typeid(t).name(), &t, sizeof(t));
     }
 
@@ -175,9 +176,9 @@ private:
     }
 
     static const unsigned int filenameMaxRetry =
-        100;                                         ///< Limit on new filename
-    static const unsigned int maxRecordSize = 256;   ///< Limit on logged data
-    static const unsigned int numRecords    = 1024;  ///< Size of record queues
+        100;                                        ///< Limit on new filename
+    static const unsigned int maxRecordSize = 512;  ///< Limit on logged data
+    static const unsigned int numRecords    = 512;  ///< Size of record queues
     static const unsigned int bufferSize = 64 * 1024;  ///< Size of each buffer
     static const unsigned int numBuffers = 8;          ///< Number of buffers
 
diff --git a/src/shared/logger/decoder/logdecoder.cpp b/src/shared/logger/decoder/logdecoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..99896b8a5ee168481080f9ef6700b6c9ad10da7f
--- /dev/null
+++ b/src/shared/logger/decoder/logdecoder.cpp
@@ -0,0 +1,165 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * This is a stub program for the program that will decode the logged data.
+ * You must first define a function
+ *  "void registerTypes(Deserializer& ds);"
+ * 
+ * that registers all the required log data types in the deserializer and then
+ * INCLUDE this cpp file in your source. finally compile and run
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tscpp/stream.h>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace tscpp;
+
+namespace fs = filesystem;
+
+void showHelp(string cmdName)
+{
+    std::cerr << "Usage: " << cmdName
+              << " {-a [logs_diretory] | <log_file_path> | -h}"
+              << "Options:\n"
+              << "\t-h,--help\t\tShow help message\n"
+              << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided directory\n"
+              << std::endl;
+}
+
+vector<fs::path> listLogFiles(fs::path dir)
+{
+    vector<fs::path> out;
+    for (const auto& entry : fs::directory_iterator(dir))
+    {
+        if (entry.exists() && entry.is_regular_file())
+        {
+            if (entry.path().extension() == ".dat")
+            {
+                out.push_back(entry.path());
+            }
+        }
+    }
+    return out;
+}
+
+bool deserialize(fs::path log_path)
+{
+    cout << "Deserializing " << log_path << ".dat...\n";
+
+    // remove extension
+    log_path.replace_extension("");
+    Deserializer d(log_path);
+    registerTypes(d);
+
+    return d.deserialize();
+}
+
+bool deserializeAll(fs::path dir = ".")
+{
+    vector<fs::path> logs = listLogFiles(dir);
+    for (auto log : logs)
+    {
+        if (!deserialize(log.string()))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int main(int argc, char* argv[])
+{
+    if (argc < 2)
+    {
+        showHelp(string(argv[0]));
+        return 1;
+    }
+
+    bool success = false;
+    string arg1  = string(argv[1]);
+    if (arg1 == "-h" || arg1 == "--help")
+    {
+        showHelp(string(argv[0]));
+        return 0;
+    }
+
+    if (arg1 == "-a" || arg1 == "--all")
+    {
+        fs::path dir = ".";
+        if (argc == 3)
+        {
+            string arg2 = string(argv[2]);
+            fs::directory_entry entry(arg2);
+            if (entry.exists() && entry.is_directory())
+            {
+                dir = arg2;
+            }
+            else
+            {
+                cout << "Second argument after '-a' or '--all' must be a "
+                        "directory\n";
+                showHelp(string(argv[0]));
+                return 1;
+            }
+        }
+        cout << "Deserializing all logs...\n";
+        success = deserializeAll(dir);
+    }
+    else if (arg1[0] == '-')
+    {
+        cerr << "Unknown option\n";
+        return 1;
+    }
+    else
+    {
+        fs::directory_entry entry(arg1);
+        if (entry.exists() && entry.is_regular_file())
+        {
+            success = deserialize(arg1);
+        }
+        else
+        {
+            showHelp(string(argv[0]));
+            return 1;
+        }
+    }
+
+    if (success)
+    {
+        cout << "Deserialization completed successfully.\n";
+    }
+    else
+    {
+        cout << "Deserialization ended with errors.\n";
+    }
+}
diff --git a/src/shared/math/Matrix.cpp b/src/shared/math/Matrix.cpp
index 562514337d82597f22466140d2d39e866aab70fe..400ed400f4a0155ada800bf0ef7bfbf966a0ed07 100644
--- a/src/shared/math/Matrix.cpp
+++ b/src/shared/math/Matrix.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/math/Matrix.h b/src/shared/math/Matrix.h
index 893d3eb575bb09ab9799d5ab978f70685473877c..1d819f3dbac5809b92bb2454bc0c2f0df529e8b9 100644
--- a/src/shared/math/Matrix.h
+++ b/src/shared/math/Matrix.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/math/SkyQuaternion.cpp b/src/shared/math/SkyQuaternion.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1b5351903eb371e91fbf2786725aec55756c25f
--- /dev/null
+++ b/src/shared/math/SkyQuaternion.cpp
@@ -0,0 +1,191 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Marco Cella
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "SkyQuaternion.h"
+#include "Constants.h"
+#include "iostream"
+
+using namespace std;
+
+SkyQuaternion::SkyQuaternion() {}
+
+Vector4f SkyQuaternion::eul2quat(Vector3f degeul)  // ZYX rotation
+{
+    float yaw = degeul(0) * DEGREES_TO_RADIANS;
+    float pitch = degeul(1) * DEGREES_TO_RADIANS;
+    float roll = degeul(2) * DEGREES_TO_RADIANS;
+
+    float cyaw = cosf(yaw * 0.5F);
+    float syaw = sinf(yaw * 0.5F);
+    float cpitch = cosf(pitch * 0.5F);
+    float spitch = sinf(pitch * 0.5F);
+    float croll = cosf(roll * 0.5F);
+    float sroll = sinf(roll * 0.5F);
+
+    float qx = cyaw * cpitch * sroll - syaw * spitch * croll;
+    float qy = cyaw * spitch * croll + syaw * cpitch * sroll;
+    float qz = syaw * cpitch * croll - cyaw * spitch * sroll;
+    float qw = cyaw * cpitch * croll + syaw * spitch * sroll;
+
+    Vector4f quat(qx, qy, qz, qw);
+
+    return quat;
+}
+
+Vector3f SkyQuaternion::quat2eul(Vector4f quater)
+{
+    float qx = quater(0);
+    float qy = quater(1);
+    float qz = quater(2);
+    float qw = quater(3);
+
+    float yaw = atan2f(2.0F * (qx * qy + qw * qz),
+                        powf(qw, 2) + powf(qx, 2) - powf(qy, 2) - powf(qz, 2));
+
+    float a    = -2.0F * (qx * qz - qw * qy);
+    if (a > 1.0F)
+    {
+        a = 1.0F;
+    }
+    else if (a < -1.0F)
+    {
+        a = -1.0F;
+    }
+
+    float pitch = asinf(a);
+
+    float roll = atan2f(2.0F * (qy * qz + qw * qx),
+                        powf(qw, 2) - powf(qx, 2) - powf(qy, 2) + powf(qz, 2));
+
+    Vector3f eul(roll, pitch, yaw);
+
+    return eul * 180.0F / PI;
+}
+
+Vector4f SkyQuaternion::rotm2quat(Matrix3f R)
+{
+    float r11 = R(0, 0);
+    float r12 = R(0, 1);
+    float r13 = R(0, 2);
+    float r21 = R(1, 0);
+    float r22 = R(1, 1);
+    float r23 = R(1, 2);
+    float r31 = R(2, 0);
+    float r32 = R(2, 1);
+    float r33 = R(2, 2);
+
+    float qx;
+    float qy;
+    float qz;
+    float qw;
+
+    float tr = r11 + r22 + r33;
+    float r = sqrt(1 + tr);
+
+    if (r32 - r23 > 0)
+    {
+        qx = 0.5 * sqrt(1 + r11 - r22 - r33);
+    }
+    else if (r32 - r23 < 0) 
+    {
+        qx = -0.5 * sqrt(1 + r11 - r22 - r33);
+    }
+    else
+    {
+        qx = 0;
+    }
+
+    if (r13 - r31 > 0)
+    {
+        qy = 0.5 * sqrt(1 - r11 + r22 - r33);
+    }
+    else if (r13 - r31 < 0) 
+    {
+        qy = -0.5 * sqrt(1 - r11 + r22 - r33);
+    }
+    else
+    {
+        qy = 0;
+    }
+
+    if (r21 - r12 > 0)
+    {
+        qz = 0.5 * sqrt(1 - r11 - r22 + r33);
+    }
+    else if (r21 - r12 < 0) 
+    {
+        qz = -0.5 * sqrt(1 - r11 - r22 + r33);
+    }
+    else
+    {
+        qz = 0;
+    }
+
+    qw = 0.5 * r;
+
+    if (qw < 0)
+    {
+        qx = -qx;
+        qy = -qy;
+        qz = -qz;
+        qw = -qw;
+    }
+
+    Vector4f quat(qx, qy, qz, qw);
+
+    return quat / quat.norm();
+}
+
+bool SkyQuaternion::quatnormalize(Vector4f& quat)
+{
+    float den = sqrt(powf(quat(0), 2) + powf(quat(1), 2) + powf(quat(2), 2) +
+                     powf(quat(3), 2));
+    if (den < 1e-8)
+        return false;
+
+    quat = quat / den;
+
+    return true;
+}
+
+Vector4f SkyQuaternion::quatProd(const Vector4f q1, const Vector4f q2)
+{
+    float q1x = q1(0);
+    float q1y = q1(1);
+    float q1z = q1(2);
+    Vector3f qv1(q1x, q1y, q1z);
+    float q1w = q1(3);
+
+    float q2x = q2(0);
+    float q2y = q2(1);
+    float q2z = q2(2);
+    Vector3f qv2(q2x, q2y, q2z);
+    float q2w = q2(3);
+
+    Vector4f quater;
+    quater << q1w * qv2 + q2w * qv1 - qv1.cross(qv2), q1w * q2w - qv1.dot(qv2);
+    float quater_norm = sqrt(quater(0) * quater(0) + quater(1) * quater(1) +
+                             quater(2) * quater(2) + quater(3) * quater(3));
+    quater            = quater / quater_norm;
+
+    return quater;
+}
diff --git a/src/shared/math/SkyQuaternion.h b/src/shared/math/SkyQuaternion.h
new file mode 100644
index 0000000000000000000000000000000000000000..ff8cf3cadeb966beb57907ad77b9996d9d7ce267
--- /dev/null
+++ b/src/shared/math/SkyQuaternion.h
@@ -0,0 +1,83 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Marco Cella
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Dense>
+using namespace Eigen;
+
+/**
+ * @brief Class for managing quaternions.
+ *        Convention used: qx, qy, qz, qw (scalar element as last element)
+ */
+class SkyQuaternion
+{
+
+public:
+    SkyQuaternion();
+
+    /**
+     * @brief Transform a vector of euler angles ZYX to quaternion.
+     *
+     * @param degeul the vector of euler angles to be transformed [deg]
+     *
+     * @return transformed quaternion
+     */
+    Vector4f eul2quat(Vector3f degeul);
+
+    /**
+     * @brief Transform a quaternion to a vector of euler angles.
+     *
+     * @param quat the quaternion to be transformed
+     *
+     * @return transformed vector of euler angles [deg]
+     */
+    Vector3f quat2eul(Vector4f quat);
+
+    /**
+     * @brief Transform a rotation matrix to a quaternion.
+     *
+     * @param R the rotation matrix to be transformed (3x3)
+     *
+     * @return transformed quaternion
+     */
+    Vector4f rotm2quat(Matrix3f R);
+
+    /**
+     * @brief Normalize a quaternion.
+     *
+     * @param quat the quaternion to be normalized
+     *
+     * @return boolean indicating if the operation succeded or not
+     */
+    bool quatnormalize(Vector4f& quat);
+
+    /**
+     * @brief Compute the product of two quaternions.
+     *
+     * @param q1 the first factor
+     * @param q2 the second factor
+     *
+     * @return the resulting quaternions product
+     */
+    Vector4f quatProd(const Vector4f q1, const Vector4f q2);
+};
diff --git a/src/shared/math/Stats.cpp b/src/shared/math/Stats.cpp
index 3b3567a716bd53d048fa16aa723c5de331a22729..926964f884cc7e540dfd337a2975c5fd43805341 100644
--- a/src/shared/math/Stats.cpp
+++ b/src/shared/math/Stats.cpp
@@ -1,6 +1,4 @@
-/* Computes statistics (min, max, mean and variance) on a dataset
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
  * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -42,7 +40,7 @@ ostream& operator<<(ostream& os, const StatsResult& sr)
 
 Stats::Stats()
     : minValue(numeric_limits<float>::max()),
-      maxValue(numeric_limits<float>::min()), mean(0.f), m2(0.f), n(0)
+      maxValue(numeric_limits<float>::lowest()), mean(0.f), m2(0.f), n(0)
 {
 }
 
@@ -64,7 +62,7 @@ void Stats::add(float data)
 void Stats::reset()
 {
     minValue = numeric_limits<float>::max();
-    maxValue = numeric_limits<float>::min();
+    maxValue = numeric_limits<float>::lowest();
     mean     = 0.f;
     m2       = 0.f;
     n        = 0;
diff --git a/src/shared/math/Stats.h b/src/shared/math/Stats.h
index c5fec2d57755a5f33836b59688dd72d9936209ce..4ffcf219541207342fe5e9b12f4a763eaff1b7a3 100644
--- a/src/shared/math/Stats.h
+++ b/src/shared/math/Stats.h
@@ -1,6 +1,4 @@
-/* Computes statistics (min, max, mean and variance) on a dataset
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
  * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/math/Vec3.h b/src/shared/math/Vec3.h
index 93bd6c6e3c59d5cbd78e34c923df218fface3824..155740beb05bf4f40ab115edafc6106fbb0376d0 100644
--- a/src/shared/math/Vec3.h
+++ b/src/shared/math/Vec3.h
@@ -1,7 +1,5 @@
-/* Vector3
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -34,6 +32,13 @@ class Vec3
 public:
     Vec3() { clear(); }
 
+    Vec3(const Vec3& rhs)
+    {
+        x = rhs.x;
+        y = rhs.y;
+        z = rhs.z;
+    }
+
     Vec3(float x, float y, float z)
     {
         this->x = x;
@@ -63,6 +68,12 @@ public:
         return true;
     }
 
+    void operator=(const Vec3& rhs){
+        x = rhs.x;
+        y = rhs.y;
+        z = rhs.z;
+    }
+
     void operator*=(float value)
     {
         x *= value;
@@ -70,6 +81,13 @@ public:
         z *= value;
     }
 
+    void operator/=(float value)
+    {
+        x /= value;
+        y /= value;
+        z /= value;
+    }
+
     void operator+=(const Vec3 &v)
     {
         x += v.getX();
@@ -84,7 +102,9 @@ public:
         z -= v.getZ();
     }
 
-    Vec3 operator*(float v) const { return Vec3(x * v, y * v, z * v); }
+    Vec3 operator*(float t) const { return Vec3(x * t, y * t, z * t); }
+
+    Vec3 operator/(float t) const { return Vec3(x / t, y / t, z / t); }
 
     Vec3 operator+(const Vec3 &v) const
     {
@@ -96,6 +116,10 @@ public:
         return Vec3(x - v.getX(), y - v.getY(), z - v.getZ());
     }
 
+    Vec3 operator-() const {
+        return Vec3(-x, -y, -z);
+    }
+
     float dot(const Vec3 &v) const
     {
         return x * v.getX() + y * v.getY() + z * v.getZ();
diff --git a/src/shared/rls/RLS.h b/src/shared/rls/RLS.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ecb1470bd6bd42f48683b76934728d18723abe4
--- /dev/null
+++ b/src/shared/rls/RLS.h
@@ -0,0 +1,65 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Mozzarelli
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <../libs/simple-template-matrix/matrix.h>
+#include <iostream>
+
+/*!
+ * \class RLS
+ * \brief A performin Recursive least squares identification
+ *
+ * ``` n ```: number of parameters to be estimated
+ * ``` n ```: number of states
+ * ``` p ```: number of outputs
+ *
+ * The system model is:
+ * ``` 
+ * y(t) = phi(t)^T * theta(t)
+ *  ```
+ * being y the output and theta the vector of parameters to be identified.
+ * See test-rls.cpp for an example.
+ */
+template <unsigned n>
+class RLS
+{
+    using CVectorN = MatrixBase<float, n, 1>;
+    using MatrixNN = MatrixBase<float, n, n>;
+private:
+    MatrixNN V;
+    CVectorN theta;
+    float mu;
+public:
+    RLS(const MatrixNN &V_0, const CVectorN &theta_0, float mu) : V(V_0), theta(theta_0), mu(mu){};
+
+
+    ~RLS(){};
+    
+    void update(const CVectorN & y_kp1, const CVectorN & phi)
+    {
+        V = V/mu - (V/mu * phi*transpose(phi) * V/mu) / (1 + transpose(phi)*V/mu* phi)(0,0);
+        theta = theta + V*phi*(y_kp1 - transpose(theta)*phi);
+    };
+
+    CVectorN getEstimate(){ return theta; };
+};
\ No newline at end of file
diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp
index 5d2cbb05f48a2763780950f26e349962ab3f3710..73581d1a8aae5387178cfca612b437e6cb33817e 100644
--- a/src/shared/scheduler/TaskScheduler.cpp
+++ b/src/shared/scheduler/TaskScheduler.cpp
@@ -1,7 +1,5 @@
-/* Event scheduler
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Author: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h
index 38e3a2f080f38a2aef6a33d76db001b18777c49b..4506e86a0a5e27f24058fc134683c03ed444b78d 100644
--- a/src/shared/scheduler/TaskScheduler.h
+++ b/src/shared/scheduler/TaskScheduler.h
@@ -1,7 +1,5 @@
-/* Event scheduler
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Author: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/scheduler/TaskSchedulerData.h b/src/shared/scheduler/TaskSchedulerData.h
index e788c371e5469e9356b532d6f7195bee16deaa2f..a3b9af1839f826d2649a4fe755ac8ed5cbaed135 100644
--- a/src/shared/scheduler/TaskSchedulerData.h
+++ b/src/shared/scheduler/TaskSchedulerData.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Federico Terraneo, Matteo Michele Piazzolla
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Federico Terraneo, Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/BME280/BME280.cpp b/src/shared/sensors/BME280/BME280.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b69ed859914262cc1a818f11f9a60f89430fd903
--- /dev/null
+++ b/src/shared/sensors/BME280/BME280.cpp
@@ -0,0 +1,392 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "BME280.h"
+
+#include "TimestampTimer.h"
+
+const BME280::BME280Config BME280::BME280_DEFAULT_CONFIG = {
+    SKIPPED, 0, 0, SLEEP_MODE, SKIPPED, SKIPPED, 0, FILTER_OFF, STB_TIME_0_5};
+
+const BME280::BME280Config BME280::BME280_CONFIG_ALL_ENABLED = {OVERSAMPLING_1,
+                                                                0,
+                                                                0,
+                                                                NORMAL_MODE,
+                                                                OVERSAMPLING_16,
+                                                                OVERSAMPLING_2,
+                                                                0,
+                                                                FILTER_COEFF_16,
+                                                                STB_TIME_0_5};
+
+const BME280::BME280Config BME280::BME280_CONFIG_TEMP_SINGLE = {
+    SKIPPED,        0, 0,          FORCED_MODE, SKIPPED,
+    OVERSAMPLING_1, 0, FILTER_OFF, STB_TIME_0_5};
+
+BME280::BME280(SPISlave spiSlave_, BME280Config config_)
+    : spiSlave(spiSlave_), config(config_)
+{
+}
+
+bool BME280::init()
+{
+    // Check if already initialized
+    if (initialized)
+    {
+        LOG_ERR(logger, "Already initialized");
+
+        last_error = SensorErrors::ALREADY_INIT;
+
+        return false;
+    }
+
+    // Check WHO AM I
+    if (!checkWhoAmI())
+    {
+        LOG_ERR(logger, "Invalid WHO AM I");
+
+        last_error = SensorErrors::INVALID_WHOAMI;
+
+        return false;
+    }
+
+    loadCompensationParameters();
+
+    // Read once the temperature to compute t_fine
+    setConfiguration(BME280_CONFIG_TEMP_SINGLE);
+    miosix::Thread::sleep(
+        calculateMaxMeasurementTime(BME280_CONFIG_TEMP_SINGLE));
+    readTemperature();
+
+    setConfiguration();
+
+    BME280Config readBackConfig = readConfiguration();
+
+    // Check if the configration on the device matches ours
+    if (config.bytes.ctrl_hum != readBackConfig.bytes.ctrl_hum ||
+        config.bytes.ctrl_meas != readBackConfig.bytes.ctrl_meas ||
+        config.bytes.config != readBackConfig.bytes.config)
+    {
+        LOG_ERR(logger, "Device configuration incorrect, setup failed");
+
+        last_error = SensorErrors::NOT_INIT;
+
+        return false;
+    }
+
+    initialized = true;
+    return true;
+}
+
+void BME280::setHumidityOversampling(Oversampling oversampling)
+{
+    config.bits.osrs_h = oversampling;
+
+    setConfiguration();
+}
+
+void BME280::setSensorMode(Mode mode)
+{
+    config.bits.mode = mode;
+
+    setConfiguration();
+}
+
+void BME280::setPressureOversampling(Oversampling oversampling)
+{
+    config.bits.osrs_p = oversampling;
+
+    setConfiguration();
+}
+
+void BME280::setTemperatureOversampling(Oversampling oversampling)
+{
+    config.bits.osrs_t = oversampling;
+
+    setConfiguration();
+}
+
+void BME280::setFilterCoeff(FilterCoeff filterCoeff)
+{
+    config.bits.filter = filterCoeff;
+
+    setConfiguration();
+}
+
+void BME280::setStandbyTime(StandbyTime standbyTime)
+{
+    config.bits.t_sb = standbyTime;
+
+    setConfiguration();
+}
+
+HumidityData BME280::readHumidity()
+{
+    uint8_t buffer[2];
+    int32_t adc_H = 0;
+
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_HUM_MSB, buffer, 2);
+    }
+
+    adc_H |= ((uint32_t)buffer[0] << 8);
+    adc_H |= buffer[1];
+
+    // Compensate humidity
+    last_sample.humid_timestamp = TimestampTimer::getTimestamp();
+    last_sample.humid =
+        (float)compensateHumidity(adc_H) / 1024;  // Converto to %RH
+
+    return last_sample;
+}
+
+PressureData BME280::readPressure()
+{
+    uint8_t buffer[3];
+    int32_t adc_P = 0;
+
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_PRESS_MSB, buffer, 3);
+    }
+
+    adc_P |= ((uint32_t)buffer[0]) << 12;
+    adc_P |= ((uint32_t)buffer[1]) << 4;
+    adc_P |= (buffer[2] >> 4) & 0x0F;
+
+    // Compensate pressure
+    last_sample.press_timestamp = TimestampTimer::getTimestamp();
+    last_sample.press =
+        (float)compensatePressure(adc_P) / 256;  // Convert to Pa
+
+    return last_sample;
+}
+
+TemperatureData BME280::readTemperature()
+{
+    uint8_t buffer[3];
+    int32_t adc_T = 0;
+
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_TEMP_MSB, buffer, 3);
+    }
+
+    adc_T |= ((uint32_t)buffer[0]) << 12;
+    adc_T |= ((uint32_t)buffer[1]) << 4;
+    adc_T |= (buffer[2] >> 4) & 0x0F;
+
+    // Compensate temperature
+    t_fine                     = computeFineTemperature(adc_T);
+    last_sample.temp_timestamp = TimestampTimer::getTimestamp();
+    last_sample.temp =
+        (float)compensateTemperature(t_fine) / 100;  // Converto to DegC
+
+    return last_sample;
+}
+
+HumidityData BME280::getHumidity() { return last_sample; }
+
+PressureData BME280::getPressure() { return last_sample; }
+
+TemperatureData BME280::getTemerature() { return last_sample; }
+
+unsigned int BME280::calculateMaxMeasurementTime(BME280Config config_)
+{
+    return ceil(1.25 + (2.3 * config_.bits.osrs_t) +
+                (2.3 * config_.bits.osrs_p + 0.575) +
+                (2.3 * config_.bits.osrs_h + 0.575));
+}
+
+unsigned int BME280::getMaxMeasurementTime()
+{
+    return calculateMaxMeasurementTime(config);
+}
+
+bool BME280::selfTest() { return checkWhoAmI(); }
+
+BME280Data BME280::sampleImpl()
+{
+    uint8_t buffer[8];
+    int32_t adc_T = 0;
+    int32_t adc_P = 0;
+    int32_t adc_H = 0;
+    BME280Data data;
+
+    // TODO: implement selective read!
+
+    // Burst read pressure, temperature and humidity
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_PRESS_MSB, buffer, 8);
+    }
+
+    adc_T |= ((uint32_t)buffer[3]) << 12;
+    adc_T |= ((uint32_t)buffer[4]) << 4;
+    adc_T |= (buffer[5] >> 4) & 0x0F;
+
+    adc_P |= ((uint32_t)buffer[0]) << 12;
+    adc_P |= ((uint32_t)buffer[1]) << 4;
+    adc_P |= (buffer[2] >> 4) & 0x0F;
+
+    adc_H |= ((uint32_t)buffer[6] << 8);
+    adc_H |= buffer[7];
+
+    // Compensate temperature
+    t_fine              = computeFineTemperature(adc_T);
+    data.temp_timestamp = TimestampTimer::getTimestamp();
+    data.temp = (float)compensateTemperature(t_fine) / 100;  // Converto to DegC
+
+    // Compensate pressure
+    data.press_timestamp = TimestampTimer::getTimestamp();
+    data.press = (float)compensatePressure(adc_P) / 256;  // Convert to Pa
+
+    // Compensate humidity
+    data.humid_timestamp = TimestampTimer::getTimestamp();
+    data.humid = (float)compensateHumidity(adc_H) / 1024;  // Converto to %RH
+
+    return data;
+}
+
+bool BME280::checkWhoAmI()
+{
+    SPITransaction transaction(spiSlave);
+
+    uint8_t who_am_i_value = transaction.read(REG_ID);
+
+    return who_am_i_value == REG_ID_VAL;
+}
+
+void BME280::setConfiguration() { setConfiguration(config); }
+
+void BME280::setConfiguration(BME280Config config_)
+{
+    SPITransaction transaction(spiSlave);
+
+    transaction.write(REG_CONFIG & 0x7F, config_.bytes.config);
+    transaction.write(REG_CTRL_HUM & 0x7F, config_.bytes.ctrl_hum);
+    transaction.write(REG_CTRL_MEAS & 0x7F, config_.bytes.ctrl_meas);
+}
+
+BME280::BME280Config BME280::readConfiguration()
+{
+    BME280Config tmp;
+    SPITransaction transaction(spiSlave);
+
+    transaction.read(REG_CTRL_HUM, (uint8_t *)&tmp, 4);
+
+    return tmp;
+}
+
+void BME280::loadCompensationParameters()
+{
+    // Read first batch of compensation parameters
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_CALIB_0, (uint8_t *)&compParams, 25);
+    }
+
+    // Reat second batch of compensation parameters
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.read(REG_CALIB_26, (uint8_t *)&compParams.bits.dig_H2, 7);
+    }
+
+    // Adjust unaligned data
+    compParams.bytes_array[29] =
+        (compParams.bytes_array[29] << 4) | (compParams.bytes_array[29] >> 4);
+    compParams.bits.dig_H4 =
+        (compParams.bits.dig_H4 << 4) | (compParams.bits.dig_H4 >> 8);
+}
+
+int32_t BME280::computeFineTemperature(int32_t adc_T)
+{
+    int32_t var1, var2;
+    var1 = ((((adc_T >> 3) - ((int32_t)compParams.bits.dig_T1 << 1))) *
+            ((int32_t)compParams.bits.dig_T2)) >>
+           11;
+    var2 = (((((adc_T >> 4) - ((int32_t)compParams.bits.dig_T1)) *
+              ((adc_T >> 4) - ((int32_t)compParams.bits.dig_T1))) >>
+             12) *
+            ((int32_t)compParams.bits.dig_T3)) >>
+           14;
+    return var1 + var2;
+}
+
+int32_t BME280::compensateTemperature(int32_t t_fine)
+{
+    return (t_fine * 5 + 128) >> 8;
+}
+
+uint32_t BME280::compensatePressure(int32_t adc_P)
+{
+    int64_t var1, var2, p;
+    var1 = ((int64_t)t_fine) - 128000;
+    var2 = var1 * var1 * (int64_t)compParams.bits.dig_P6;
+    var2 = var2 + ((var1 * (int64_t)compParams.bits.dig_P5) << 17);
+    var2 = var2 + (((int64_t)compParams.bits.dig_P4) << 35);
+    var1 = ((var1 * var1 * (int64_t)compParams.bits.dig_P3) >> 8) +
+           ((var1 * ((int64_t)compParams.bits.dig_P2) << 12));
+    var1 =
+        ((((int64_t)1) << 47) + var1) * ((int64_t)compParams.bits.dig_P1) >> 33;
+    if (var1 == 0)
+    {
+        return 0;  // avoid exception caused by division by zero
+    }
+    p    = 1048576 - adc_P;
+    p    = (((p << 31) - var2) * 3125) / var1;
+    var1 = (((int64_t)compParams.bits.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
+    var2 = (((int64_t)compParams.bits.dig_P8) * p) >> 19;
+    p    = ((p + var1 + var2) >> 8) + (((int64_t)compParams.bits.dig_P7) << 4);
+    return (uint32_t)p;
+}
+
+uint32_t BME280::compensateHumidity(int32_t adc_H)
+{
+    int32_t v_x1_u32r;
+
+    v_x1_u32r = (t_fine - ((int32_t)768000));
+    v_x1_u32r = (((((adc_H << 14) - (((int32_t)compParams.bits.dig_H4) << 20) -
+                    (((int32_t)compParams.bits.dig_H5) * v_x1_u32r)) +
+                   ((int32_t)16384)) >>
+                  15) *
+                 (((((((v_x1_u32r * ((int32_t)compParams.bits.dig_H6)) >> 10) *
+                      (((v_x1_u32r * ((int32_t)compParams.bits.dig_H3)) >> 11) +
+                       ((int32_t)32768))) >>
+                     10) +
+                    ((int32_t)2097152)) *
+                       ((int32_t)compParams.bits.dig_H2) +
+                   8192) >>
+                  14));
+    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
+                               ((int32_t)compParams.bits.dig_H1)) >>
+                              4));
+    v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
+    v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
+    return (uint32_t)(v_x1_u32r >> 12);
+}
diff --git a/src/shared/sensors/BME280/BME280.h b/src/shared/sensors/BME280/BME280.h
index 129252df0298bd22626ee4c1ad1f168495208b19..562be6f706e693aecd3ab058fb8ad71e483c993a 100644
--- a/src/shared/sensors/BME280/BME280.h
+++ b/src/shared/sensors/BME280/BME280.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,306 +13,288 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#pragma once
-
-#include <stdio.h>
+#include <diagnostic/PrintLogger.h>
 
 #include "BME280Data.h"
+#include "drivers/spi/SPIDriver.h"
 #include "sensors/Sensor.h"
-#include "Debug.h"
 
-template <class Bus>
-class BME280 : public TemperatureSensor, public PressureSensor
+class BME280 : public Sensor<BME280Data>
 {
 public:
-    BME280() {}
-    virtual ~BME280() {}
+    enum Oversampling
+    {
+        SKIPPED         = 0x0,  ///< Skipped (output set to 0x8000)
+        OVERSAMPLING_1  = 0x1,  ///< Oversampling x1
+        OVERSAMPLING_2  = 0x2,  ///< Oversampling x2
+        OVERSAMPLING_4  = 0x3,  ///< Oversampling x4
+        OVERSAMPLING_8  = 0x4,  ///< Oversampling x8
+        OVERSAMPLING_16 = 0x5,  ///< Oversampling x16
+    };
 
-    std::string printData() { return ""; }
+    enum Mode
+    {
+        SLEEP_MODE  = 0x0,  ///< Sleep mode
+        FORCED_MODE = 0x1,  ///< Forced mode
+        NORMAL_MODE = 0x3   ///< Normal mode
+    };
 
-    bool init() override
+    enum StandbyTime
     {
-        if (!initialized)
-        {
-            Bus::init();
-
-            uint8_t who_am_i = Bus::read(REG_WHO_AM_I);
-            if (who_am_i != ID)
-            {
-                last_error = ERR_NOT_ME;
-                TRACE("BME280 not found: who am i read 0x%02X\n", who_am_i);
-                return false;
-            }
-
-            // T standby = 0.5 ms, Filter: 8x ("& 0x7F" to set bit 7 to 0)
-            Bus::write(REG_CONFIG & 0x7F, 0x0C);
-
-            uint8_t config_val = Bus::read(REG_CONFIG);
-            if (config_val != 0x0C)
-            {
-                TRACE("Wrong value in config register: 0x%02X\n", config_val);
-                return false;
-            }
-
-            // Disable humidity reading
-            Bus::write(REG_CTRL_HUM & 0x7F, 0x00);
-
-            uint8_t hum_val = Bus::read(REG_CTRL_HUM);
-            if (hum_val != 0x00)
-            {
-                TRACE("Wrong value in hum register: 0x%02X\n", hum_val);
-                last_error = ERR_BUS_FAULT;
-                return false;
-            }
-
-            // Pressure oversampling x4, temp oversampling x1, normal mode.
-            Bus::write(REG_CTRL_MEAS & 0x7F, (uint8_t)0x2F);
-
-            uint8_t meas_val = Bus::read(REG_CTRL_MEAS);
-            if (meas_val != 0x2F)
-            {
-                TRACE("Wrong value in meas register: 0x%02X\n", meas_val);
-                last_error = ERR_BUS_FAULT;
-                return false;
-            }
-
-            loadCompensationParams();
-
-            initialized = true;
-            return true;
-        }
-        else
-        {
-            return false;
-        }
-    }
+        STB_TIME_0_5  = 0x0,  ///< 0.5 ms
+        STB_TIME_62_5 = 0x1,  ///< 62.5 ms
+        STB_TIME_125  = 0x2,  ///< 125 ms
+        STB_TIME_250  = 0x3,  ///< 250 ms
+        STB_TIME_500  = 0x4,  ///< 500 ms
+        STB_TIME_1000 = 0x5,  ///< 1000 ms
+        STB_TIME_10   = 0x6,  ///< 10 ms
+        STB_TIME_20   = 0x7   ///< 20 ms
+    };
 
-    bool selfTest() override { return true; }
+    enum FilterCoeff
+    {
+        FILTER_OFF      = 0x0,  ///< Filter off
+        FILTER_COEFF_2  = 0x1,  ///< Filter coefficient = 2
+        FILTER_COEFF_4  = 0x2,  ///< Filter coefficient = 4
+        FILTER_COEFF_8  = 0x3,  ///< Filter coefficient = 8
+        FILTER_COEFF_16 = 0x4   ///< Filter coefficient = 16
+    };
 
-    void printCompensationParams()
+    union BME280Config
     {
-        if (initialized)
+        struct __attribute__((packed)) BME280ConfigBits
         {
-            printf("Compensation parameters: \n");
-
-            printf("dig_T1: 0x%04X\n", comp_params.dig_T1);
-            printf("dig_T2: 0x%04X\n", comp_params.dig_T2);
-            printf("dig_T3: 0x%04X\n", comp_params.dig_T3);
-
-            printf("dig_P1: 0x%04X\n", comp_params.dig_P1);
-            printf("dig_P2: 0x%04X\n", comp_params.dig_P2);
-            printf("dig_P3: 0x%04X\n", comp_params.dig_P3);
-            printf("dig_P4: 0x%04X\n", comp_params.dig_P4);
-            printf("dig_P5: 0x%04X\n", comp_params.dig_P5);
-            printf("dig_P6: 0x%04X\n", comp_params.dig_P6);
-            printf("dig_P7: 0x%04X\n", comp_params.dig_P7);
-            printf("dig_P8: 0x%04X\n", comp_params.dig_P8);
-            printf("dig_P9: 0x%04X\n", comp_params.dig_P9);
-        }
-    }
-
-    bool onSimpleUpdate() override
-    {
-        if (initialized)
+            // ctrl_hum
+            Oversampling osrs_h : 3;  ///< Oversampling of humidity
+            uint8_t : 5;
+
+            // status
+            /**
+             * '1' when the NVM data are being copied to image registers, '0'
+             * when the copying is done
+             */
+            uint8_t im_update : 1;
+            uint8_t : 2;
+            /**
+             * '1' whenever a conversion is running, '0' when the result have
+             * been transferred to the data registers
+             */
+            uint8_t measuring : 1;
+            uint8_t : 4;
+
+            // ctrl_meas
+            Mode mode : 2;            ///< Device modes
+            Oversampling osrs_p : 3;  ///< Oversampling of pressure
+            Oversampling osrs_t : 3;  ///< Oversampling of temperature
+
+            // config
+            uint8_t spi3w_en : 1;  ///< Enables 3-wire SPI interface
+            uint8_t : 1;
+            FilterCoeff filter : 3;  ///< Time constant of the IIR filter
+            StandbyTime t_sb : 3;    ///< Inactive duration in normal mode
+        } bits;
+
+        struct
         {
-            // Burst read temperature and pressure registers
-            uint8_t buf[6];
-            Bus::read(REG_PRESS_MSB, buf, 6);
-
-            uint32_t press = 0;
-            press |= ((uint32_t)buf[0]) << 12;
-            press |= ((uint32_t)buf[1]) << 4;
-            press |= ((uint32_t)buf[2]) >> 4;
+            uint8_t ctrl_hum;   ///< Humidity options
+            uint8_t status;     ///< Device status
+            uint8_t ctrl_meas;  ///< Pressure and temperature options
+            uint8_t config;     ///< Rate, filter and interface options
+        } bytes;
 
-            uint32_t temp = 0;
-            temp |= ((uint32_t)buf[3]) << 12;
-            temp |= ((uint32_t)buf[4]) << 4;
-            temp |= ((uint32_t)buf[5]) >> 4;
+        uint8_t bytes_array[4];
+    };
 
-            data.raw_pressure    = press;
-            data.raw_temperature = temp;
+    union BME280Comp
+    {
+        struct __attribute__((packed))
+        {
+            uint16_t dig_T1;
+            int16_t dig_T2;
+            int16_t dig_T3;
+            uint16_t dig_P1;
+            int16_t dig_P2;
+            int16_t dig_P3;
+            int16_t dig_P4;
+            int16_t dig_P5;
+            int16_t dig_P6;
+            int16_t dig_P7;
+            int16_t dig_P8;
+            int16_t dig_P9;
+            uint8_t dig_H1;
+            int16_t dig_H2;
+            uint8_t dig_H3;
+            int16_t dig_H4 : 12;
+            int16_t dig_H5 : 12;
+            int8_t dig_H6;
+        } bits;
+
+        uint8_t bytes_array[32];
+    };
 
-            // Compensate values
-            int32_t t_fine = getFineTemperature(data.raw_temperature);
+    enum BME280Registers : uint8_t
+    {
+        REG_CALIB_0 = 0x88,
+        // Calibration register 1-25
 
-            data.temperature = compensateTemperature(t_fine) / 100.0f;
-            data.pressure =
-                compensatePressure(data.raw_pressure, t_fine) / 256.0f;
+        REG_ID    = 0xD0,
+        REG_RESET = 0xE0,
 
-            data.timestamp = miosix::getTick();
+        REG_CALIB_26 = 0xE1,
+        // Calibration register 27-41
 
-            mLastPressure = data.pressure;
-            mLastTemp     = data.temperature;
+        REG_CTRL_HUM  = 0xF2,
+        REG_STATUS    = 0xF3,
+        REG_CTRL_MEAS = 0xF4,
+        REG_CONFIG    = 0xF5,
 
-            /*printf(
-                "Raw P:\t%d,\tRaw T:\t%d\nComp P:\t%d,\tComp T:\t%d,\tFine T: "
-                "%d\n",
-                (int)press, (int)temp, (int)(pressure_value / 256),
-                (int)temp_value, (int)t_fine);*/
-            return true;
-        }
-        return false;
-    }
+        REG_PRESS_MSB  = 0xF7,
+        REG_PRESS_LSB  = 0xF8,
+        REG_PRESS_XLSB = 0xF9,
+        REG_TEMP_MSB   = 0xFA,
+        REG_TEMP_LSB   = 0xFB,
+        REG_TEMP_XLSB  = 0xFC,
+        REG_HUM_MSB    = 0xFD,
+        REG_HUM_LSB    = 0xFE,
+    };
 
-    const uint32_t* rawPressureDataPtr() { return &data.raw_pressure; }
+    static constexpr uint8_t REG_ID_VAL = 0x60;  ///< Who am I value
 
-    const uint32_t* rawTempDataPtr() { return &data.raw_temperature; }
+    static const BME280Config
+        BME280_DEFAULT_CONFIG;  ///< Default register values
+    static const BME280Config
+        BME280_CONFIG_ALL_ENABLED;  ///< Datasheet values for indoor navigation
+    static const BME280Config
+        BME280_CONFIG_TEMP_SINGLE;  ///< Temperature enabled in forced mode
 
-    const BME280Data* getDataPtr() { return &data; }
+    BME280(SPISlave spiSlave_,
+           BME280Config config_ = BME280_CONFIG_ALL_ENABLED);
 
-    BME280Data data;
+    /**
+     * @brief Initialize the device with the specified configuration
+     */
+    bool init() override;
 
-private:
-    void loadCompensationParams()
-    {
-        uint8_t buf[24];
-        Bus::read(REG_CALIB_T, buf, 24);
-
-        comp_params.dig_T1 = toUInt16(&buf[0]);
-        comp_params.dig_T2 = toInt16(&buf[2]);
-        comp_params.dig_T3 = toInt16(&buf[4]);
-
-        comp_params.dig_P1 = toUInt16(&buf[6]);
-        comp_params.dig_P2 = toInt16(&buf[8]);
-        comp_params.dig_P3 = toInt16(&buf[10]);
-        comp_params.dig_P4 = toInt16(&buf[12]);
-        comp_params.dig_P5 = toInt16(&buf[14]);
-        comp_params.dig_P6 = toInt16(&buf[16]);
-        comp_params.dig_P7 = toInt16(&buf[18]);
-        comp_params.dig_P8 = toInt16(&buf[20]);
-        comp_params.dig_P9 = toInt16(&buf[22]);
-    }
+    /**
+     * @brief Sets the oversampling for humidity readings, use SKIPPED to
+     * disable humidity sampling
+     */
+    void setHumidityOversampling(Oversampling oversampling);
 
     /**
-     * Returns the temperature in degC.
-     * Refer to the data sheet for a description of this method.
+     * @brief Sets the sensor mode
+     *
+     * Values:
+     * - SLEEP_MODE: No measurements are performed
+     * - FORCED_MODE: A single measurement is performed when this function
+     * writes the configuration, after the measurement the sensor return to
+     * sleep mode
+     * - NORMAL_MODE: Automated cycling between measurements, standby time can
+     * be set using setStandbyTime()
      */
-    int32_t getFineTemperature(uint32_t raw_T)
-    {
-        int32_t adc_T = static_cast<int32_t>(raw_T);
-        int32_t var1, var2;
-        var1 = ((((adc_T / 8) - ((int32_t)comp_params.dig_T1 * 2))) *
-                ((int32_t)comp_params.dig_T2)) >>
-               11;
-        var2 = (((((adc_T / 16) - ((int32_t)comp_params.dig_T1)) *
-                  ((adc_T / 16) - ((int32_t)comp_params.dig_T1))) /
-                 4096) *
-                ((int32_t)comp_params.dig_T3)) /
-               16384;
-
-        return var1 + var2;
-    }
-
-    int32_t compensateTemperature(int32_t fine_temperature)
-    {
-        return (fine_temperature * 5 + 128) / 256;
-    }
+    void setSensorMode(Mode mode);
 
-    uint32_t compensatePressure(uint32_t adc_P, int32_t fine_temperature)
-    {
-        int64_t var1, var2, p;
-        var1 = ((int64_t)fine_temperature) - 128000;
+    /**
+     * @brief Sets the oversampling for pressure readings, use SKIPPED to
+     * disable pressure sampling
+     */
+    void setPressureOversampling(Oversampling oversampling);
 
-        var2 = var1 * var1 * (int64_t)comp_params.dig_P6;
-        var2 = var2 + ((var1 * (int64_t)comp_params.dig_P5) * 131072);
-        var2 = var2 + (((int64_t)comp_params.dig_P4) * 34359738368);
+    /**
+     * @brief Sets the oversampling for temperature readings, use SKIPPED to
+     * disable temperature sampling
+     */
+    void setTemperatureOversampling(Oversampling oversampling);
 
-        var1 = ((var1 * var1 * (int64_t)comp_params.dig_P3) / 256) +
-               ((var1 * ((int64_t)comp_params.dig_P2) * 4096));
+    /**
+     * @brief Sets the coefficient for the IIR filter (applied to temperature
+     * and pressure)
+     */
+    void setFilterCoeff(FilterCoeff filterCoeff);
 
-        var1 = (((int64_t)1) * 140737488355328 + var1) *
-               ((int64_t)comp_params.dig_P1) / 8589934592;
+    /**
+     * @brief Sets the standby time between readings in normal mode
+     */
+    void setStandbyTime(StandbyTime standbyTime);
 
-        if (var1 == 0)
-        {
-            return 0;  // avoid exception caused by division by zero
-        }
+    /**
+     * @brief Reads only the humidity, does not set the configuration
+     */
+    HumidityData readHumidity();
 
-        p = 1048576 - (int32_t)adc_P;
-        p = (((p * 2147483648) - var2) * 3125) / var1;
+    /**
+     * @brief Reads only the pressure, does not set the configuration
+     */
+    PressureData readPressure();
 
-        var1 = (((int64_t)comp_params.dig_P9) * (p / 8192) * (p / 8192)) /
-               33554432;
+    /**
+     * @brief Reads only the temperature, does not set the configuration
+     */
+    TemperatureData readTemperature();
 
-        var2 = (((int64_t)comp_params.dig_P8) * p) / 524288;
+    HumidityData getHumidity();
 
-        p = ((p + var1 + var2) / 256) + (((int64_t)comp_params.dig_P7) * 16);
+    PressureData getPressure();
 
-        // p = ((var3 / 2) * 100) / 128;
-        return (uint32_t)p;
-    }
+    TemperatureData getTemerature();
 
     /**
-     * Converts two bytes into an unsigned int 16
-     * @param buf: buffer containing at least two bytes
+     * @brief Maximum measurement time formula from datasheet page 51
+     *
+     * @return Time in milliseconds
      */
-    uint16_t toUInt16(uint8_t* buf) { return buf[0] | buf[1] << 8; }
+    static unsigned int calculateMaxMeasurementTime(BME280Config config_);
+
+    unsigned int getMaxMeasurementTime();
 
     /**
-     * Converts two bytes into a signed int 16
-     * @param buf: buffer containing at least two bytes
+     * @brief Reads the WHO AM I register
+     *
+     * @return True if everything ok
      */
-    int16_t toInt16(uint8_t* buf)
-    {
-        return static_cast<int16_t>(buf[0] | buf[1] << 8);
-    }
+    bool selfTest() override;
 
-    enum Registers : uint8_t
-    {
-        REG_WHO_AM_I  = 0xD0,
-        REG_RESET     = 0xE0,
-        REG_CTRL_HUM  = 0xF2,
-        REG_STATUS    = 0xF3,
-        REG_CTRL_MEAS = 0xF4,
-        REG_CONFIG    = 0xF5,
+private:
+    BME280Data sampleImpl() override;
 
-        REG_PRESS_MSB  = 0xF7,
-        REG_PRESS_LSB  = 0xF8,
-        REG_PRESS_XLSB = 0xF9,
+    void setConfiguration();
 
-        REG_TEMP_MSB  = 0xFA,
-        REG_TEMP_LSB  = 0xFB,
-        REG_TEMP_XLSB = 0xFC,
+    void setConfiguration(BME280Config config_);
 
-        REG_HUM_MSB = 0xFD,
-        REG_HUM_LSB = 0xFE,
+    BME280Config readConfiguration();
 
-        REG_CALIB_T = 0x88,
-        REG_CALUB_P = 0x8E
-    };
+    void loadCompensationParameters();
+
+    // Compensation algorithm rev.1.1 from Bosh datasheet
+
+    int32_t computeFineTemperature(int32_t adc_T);
+
+    int32_t compensateTemperature(int32_t t_fine);
+
+    uint32_t compensatePressure(int32_t adc_P);
+
+    uint32_t compensateHumidity(int32_t adc_H);
 
     /**
-         * Compensation parameters to convert ADC values to real temperature and
-         * pressure
-         */
-    struct CompensationParams
-    {
-        uint16_t dig_T1;
-        int16_t dig_T2;
-        int16_t dig_T3;
-
-        uint16_t dig_P1;
-        int16_t dig_P2;
-        int16_t dig_P3;
-        int16_t dig_P4;
-        int16_t dig_P5;
-        int16_t dig_P6;
-        int16_t dig_P7;
-        int16_t dig_P8;
-        int16_t dig_P9;
-    };
+     * @brief Check the WHO AM I code from the device.
+     *
+     * @return true if the device is recognized
+     */
+    bool checkWhoAmI();
 
-    bool initialized = false;
+    const SPISlave spiSlave;
+    BME280Config config;
+    BME280Comp compParams;
+    int32_t t_fine;  // Used in compensation algorithm
 
-    const uint8_t ID         = 0x60;
-    const uint8_t RESET_WORD = 0xB6;
+    bool initialized = false;  // Whether the sensor has been initialized
 
-    CompensationParams comp_params;
+    PrintLogger logger = Logging::getLogger("bme280");
 };
diff --git a/src/shared/sensors/BME280/BME280Data.h b/src/shared/sensors/BME280/BME280Data.h
index b0fac260ab49776cae4503258c6c82ca488564a8..22e3eec8c9ad78865fa785c690593a60730dfaa0 100644
--- a/src/shared/sensors/BME280/BME280Data.h
+++ b/src/shared/sensors/BME280/BME280Data.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,36 +13,43 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
-#ifndef SRC_SHARED_SENSORS_BME280DATA_H
-#define SRC_SHARED_SENSORS_BME280DATA_H
 
-#include <ostream>
-#include <string>
+#pragma once
 
-struct BME280Data
+#include "sensors/SensorData.h"
+
+struct BME280Data : public TemperatureData,
+                    public PressureData,
+                    public HumidityData
 {
-    uint32_t timestamp = 0;
-    float pressure = 0;
-    float temperature = 0;
-    uint32_t raw_pressure = 0;
-    uint32_t raw_temperature = 0;
-    
+    BME280Data()
+        : TemperatureData{0, 0.0}, PressureData{0, 0.0}, HumidityData{0, 0.0}
+    {
+    }
+
+    BME280Data(uint64_t timestamp, float temperature, float pressure,
+               float humidity)
+        : TemperatureData{timestamp, temperature},
+          PressureData{timestamp, pressure}, HumidityData{timestamp, humidity}
+
+    {
+    }
+
     static std::string header()
     {
-        return "timestamp,pressure,temperature,raw_pressure,raw_temperature\n";
+        return "temp_timestamp,temp,press_timestamp,press,humid_timestamp,"
+               "humid\n";
     }
 
     void print(std::ostream& os) const
     {
-        os << timestamp << "," << pressure << "," << temperature << ","
-           << raw_pressure << "," << raw_temperature << "\n";
+        os << temp_timestamp << "," << temp << "," << press_timestamp << ","
+           << press << "," << humid_timestamp << "," << humid << "\n";
     }
-};
-
-#endif /* SRC_SHARED_SENSORS_BME280DATA_H */
+};
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160.cpp b/src/shared/sensors/BMX160/BMX160.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3189904e434d79d1a056f76f71fbe2613bcdaea1
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160.cpp
@@ -0,0 +1,968 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "BMX160.h"
+
+BMX160::BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config)
+    : BMX160(bus, cs, config, SPIBusConfig{})
+{
+    spi_slave.config.clock_div = SPIClockDivider::DIV32;
+    old_mag.mag_timestamp      = 0.0f;
+    old_gyr.gyro_timestamp     = 0.0f;
+    old_acc.accel_timestamp    = 0.0f;
+}
+
+BMX160::BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config,
+               SPIBusConfig bus_config)
+    : spi_slave(bus, cs, bus_config), config(config)
+{
+    old_mag.mag_timestamp   = 0.0f;
+    old_gyr.gyro_timestamp  = 0.0f;
+    old_acc.accel_timestamp = 0.0f;
+}
+
+bool BMX160::init()
+{
+#ifdef DEBUG
+    assert(!is_init && "init() should be called once");
+#endif
+
+    if (!checkChipid())
+    {
+        LOG_ERR(logger, "Got bad CHIPID");
+        last_error = SensorErrors::INVALID_WHOAMI;
+        return false;
+    }
+
+    softReset();
+
+    if (!setPowerMode())
+    {
+
+        LOG_ERR(logger, "Not all interfaces are up and running!");
+        last_error = SensorErrors::INIT_FAIL;
+        return false;
+    }
+
+    initAcc();
+    initGyr();
+    initMag();
+
+    initFifo();
+    initInt();
+
+    return is_init = true;
+}
+
+bool BMX160::selfTest()
+{
+#ifdef DEBUG
+    assert(is_init && "init() was not called");
+#endif
+
+    // The device will enter in an unusable state when testing.
+    is_init = false;
+
+    if (!testAcc() || !testGyr() || !testMag())
+    {
+        last_error = SensorErrors::SELF_TEST_FAIL;
+        return false;
+    }
+    else
+    {
+        // Finally reinitialize the device into a known state
+        return init();
+    }
+}
+
+void BMX160::IRQupdateTimestamp(uint64_t ts)
+{
+    // Prevent interrupts while reading fifo
+    if (irq_enabled)
+    {
+        SensorFIFO::IRQupdateTimestamp(ts);
+    }
+}
+
+BMX160Data BMX160::sampleImpl()
+{
+#ifdef DEBUG
+    assert(is_init && "init() was not called");
+#endif
+    // Reset any errors.
+    last_error = SensorErrors::NO_ERRORS;
+
+    // Read temperature
+    if (config.temp_divider != 0 && temp_counter % config.temp_divider == 0)
+        readTemp();
+
+    temp_counter++;
+
+    // Delete old samples
+    last_fifo_level = 0;
+
+    switch (config.fifo_mode)
+    {
+        case BMX160Config::FifoMode::DISABLED:
+            // Just push one sample
+            readData();
+            break;
+
+        case BMX160Config::FifoMode::HEADERLESS:
+            // Read whole FIFO (headerless)
+            readFifo(true);
+            break;
+
+        case BMX160Config::FifoMode::HEADER:
+            // Read whole FIFO (header)
+            readFifo(false);
+            break;
+    }
+
+    if (last_error != SensorErrors::NO_ERRORS || last_fifo_level == 0)
+    {
+        // Something went wrong, return dummy data
+        return BMX160Data{};
+    }
+    else
+    {
+        return last_fifo[last_fifo_level - 1];
+    }
+}
+
+BMX160Temperature BMX160::getTemperature()
+{
+    BMX160Temperature t;
+    t.temp_timestamp = TimestampTimer::getTimestamp();
+    t.temp           = temperature;
+    return t;
+}
+
+BMX160FifoStats BMX160::getFifoStats() { return stats; }
+
+void BMX160::sendCmd(SPITransaction& spi, BMX160Defs::Cmd cmd,
+                     BMX160Defs::PowerMode pmu)
+{
+    spi.write(BMX160Defs::REG_CMD,
+              static_cast<uint8_t>(cmd) | static_cast<uint8_t>(pmu));
+}
+
+void BMX160::pushSample(BMX160Data sample)
+{
+    last_fifo[last_fifo_level++] = sample;
+}
+
+void BMX160::confMag(SPITransaction& spi, uint8_t value)
+{
+    spi.write(BMX160Defs::REG_MAG_IF_0, value);
+    miosix::Thread::sleep(10);
+}
+
+void BMX160::mapMag(SPITransaction& spi, uint8_t reg)
+{
+    spi.write(BMX160Defs::REG_MAG_IF_1, reg);
+    miosix::Thread::sleep(10);
+}
+
+uint8_t BMX160::readMag(SPITransaction& spi, uint8_t reg)
+{
+    mapMag(spi, reg);
+    return spi.read(BMX160Defs::REG_DATA_MAG);
+}
+
+void BMX160::readMag(SPITransaction& spi, uint8_t reg, uint8_t* data,
+                     size_t size)
+{
+    while (size != 0)
+    {
+        int burst = 0;
+        if (size >= 8)
+        {
+            confMag(spi,
+                    BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_8);
+            burst = 8;
+        }
+        else if (size >= 6)
+        {
+            confMag(spi,
+                    BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_6);
+            burst = 6;
+        }
+        else if (size >= 2)
+        {
+            confMag(spi,
+                    BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_2);
+            burst = 2;
+        }
+        else
+        {
+            confMag(spi,
+                    BMX160Defs::MAG_IF_0_MANUAL | BMX160Defs::MAG_IF_0_BURST_1);
+            burst = 1;
+        }
+
+        mapMag(spi, reg);
+        spi.read(BMX160Defs::REG_DATA_MAG, data, burst);
+
+        reg += burst;
+        data += burst;
+        size -= burst;
+    }
+
+    confMag(spi, BMX160Defs::MAG_IF_0_MANUAL);
+}
+
+void BMX160::writeMag(SPITransaction& spi, uint8_t reg, uint8_t value)
+{
+    spi.write(BMX160Defs::REG_MAG_IF_3, value);
+    spi.write(BMX160Defs::REG_MAG_IF_2, reg);
+    miosix::Thread::sleep(10);
+}
+
+bool BMX160::checkChipid()
+{
+    SPITransaction spi(spi_slave);
+    auto chip_id = spi.read(BMX160Defs::REG_CHIPID);
+
+    return chip_id == BMX160Defs::CHIPID;
+}
+
+void BMX160::softReset()
+{
+    SPITransaction spi(spi_slave);
+
+    // Reset the state of the device, just to be sure.
+    sendCmd(spi, BMX160Defs::Cmd::SOFTRESET);
+    miosix::Thread::sleep(10);
+
+    // Dummy read of REG_COMM_TEST to enable SPI
+    spi.read(BMX160Defs::REG_COMM_TEST);
+    miosix::Thread::sleep(10);
+}
+
+bool BMX160::setPowerMode()
+{
+    SPITransaction spi(spi_slave);
+
+    sendCmd(spi, BMX160Defs::Cmd::MAG_IF_SET_PMU_MODE,
+            BMX160Defs::PowerMode::NORMAL);
+    miosix::Thread::sleep(80);
+
+    sendCmd(spi, BMX160Defs::Cmd::GYR_SET_PMU_MODE,
+            BMX160Defs::PowerMode::NORMAL);
+    miosix::Thread::sleep(80);
+
+    sendCmd(spi, BMX160Defs::Cmd::ACC_SET_PMU_MODE,
+            BMX160Defs::PowerMode::NORMAL);
+    miosix::Thread::sleep(80);
+
+    // Check if all sensors are up and running
+    return (spi.read(BMX160Defs::REG_PMU_STATUS) &
+            BMX160Defs::PMU_STATUS_ALL_MASK) ==
+           BMX160Defs::PMU_STATUS_ALL_NORMAL;
+}
+
+void BMX160::initAcc()
+{
+    // Calculate accelerometer sensibility
+    switch (config.acc_range)
+    {
+        case BMX160Config::AccelerometerRange::G_2:
+            acc_sensibility = 1.0f / 16384.0f;
+            break;
+        case BMX160Config::AccelerometerRange::G_4:
+            acc_sensibility = 1.0f / 8192.0f;
+            break;
+        case BMX160Config::AccelerometerRange::G_8:
+            acc_sensibility = 1.0f / 4096.0f;
+            break;
+        case BMX160Config::AccelerometerRange::G_16:
+            acc_sensibility = 1.0f / 2048.0f;
+            break;
+    }
+
+    SPITransaction spi(spi_slave);
+
+    spi.write(BMX160Defs::REG_ACC_CONF,
+              static_cast<uint8_t>(config.acc_odr) |
+                  static_cast<uint8_t>(config.acc_bwp));
+    spi.write(BMX160Defs::REG_ACC_RANGE,
+              static_cast<uint8_t>(config.acc_range));
+}
+
+void BMX160::initGyr()
+{
+    // Calculate gyro sensibility
+    switch (config.gyr_range)
+    {
+        case BMX160Config::GyroscopeRange::DEG_2000:
+            gyr_sensibility = 1.0f / 16.4f;
+            break;
+        case BMX160Config::GyroscopeRange::DEG_1000:
+            gyr_sensibility = 1.0f / 32.8f;
+            break;
+        case BMX160Config::GyroscopeRange::DEG_500:
+            gyr_sensibility = 1.0f / 65.6f;
+            break;
+        case BMX160Config::GyroscopeRange::DEG_250:
+            gyr_sensibility = 1.0f / 131.2f;
+            break;
+        case BMX160Config::GyroscopeRange::DEG_125:
+            gyr_sensibility = 1.0f / 262.4f;
+            break;
+    }
+
+    SPITransaction spi(spi_slave);
+    spi.write(BMX160Defs::REG_GYR_CONF,
+              static_cast<uint8_t>(config.gyr_odr) |
+                  static_cast<uint8_t>(config.gyr_bwp));
+    spi.write(BMX160Defs::REG_GYR_RANGE,
+              static_cast<uint8_t>(config.gyr_range));
+}
+
+void BMX160::initMag()
+{
+    /*
+    Little explanation of this:
+    The magnetometer is not controlled directly,
+    instead we have a secondary controller, BMM150,
+    with its own register accessed with REG_MAG_IF_[1-3]
+    */
+
+    SPITransaction spi(spi_slave);
+
+    // Enable manual configuration mode
+    confMag(spi, BMX160Defs::MAG_IF_0_MANUAL);
+
+    // Put MAG into sleep mode (from suspend mode)
+    writeMag(spi, BMX160Defs::MAG_REG_RESET,
+             BMX160Defs::MAG_RESET_POWER_CONTROL);
+
+    writeMag(spi, BMX160Defs::MAG_REG_REPXY, config.mag_repxy);
+    writeMag(spi, BMX160Defs::MAG_REG_REPZ, config.mag_repz);
+
+    if (config.enable_compensation)
+        boschReadTrim(spi);
+
+    // Magic sequence to init it
+    writeMag(spi, BMX160Defs::MAG_REG_CONTROL, BMX160Defs::MAG_CONTROL_FORCED);
+    mapMag(spi, BMX160Defs::MAG_REG_DATA);
+
+    // Set mag output data rate
+    spi.write(BMX160Defs::REG_MAG_CONF, static_cast<uint8_t>(config.mag_odr));
+    miosix::Thread::sleep(10);
+
+    // Disable manual configuration mode
+    confMag(spi, 0);
+}
+
+void BMX160::initFifo()
+{
+    if (config.fifo_mode != BMX160Config::FifoMode::DISABLED)
+    {
+        SPITransaction spi(spi_slave);
+
+        uint8_t config_byte = BMX160Defs::FIFO_CONFIG_1_ACC_EN |
+                              BMX160Defs::FIFO_CONFIG_1_GYR_EN |
+                              BMX160Defs::FIFO_CONFIG_1_MAG_EN;
+
+        if (config.fifo_mode == BMX160Config::FifoMode::HEADER)
+            config_byte |= BMX160Defs::FIFO_CONFIG_1_HEADER_EN;
+
+        spi.write(BMX160Defs::REG_FIFO_CONFIG_1, config_byte);
+
+        config_byte =
+            (config.fifo_gyr_downs & 3) | ((config.fifo_acc_downs & 3) << 4);
+
+        if (config.fifo_acc_filtered)
+            config_byte |= BMX160Defs::FIFO_DOWNS_ACC_FILT_DATA;
+
+        if (config.fifo_gyr_filtered)
+            config_byte |= BMX160Defs::FIFO_DOWNS_GYR_FILT_DATA;
+
+        spi.write(BMX160Defs::REG_FIFO_DOWNS, config_byte);
+
+        sendCmd(spi, BMX160Defs::Cmd::FIFO_FLUSH);
+    }
+}
+
+void BMX160::initInt()
+{
+    if (config.fifo_int != BMX160Config::FifoInterruptPin::DISABLED)
+    {
+        SPITransaction spi(spi_slave);
+
+        // Select mode between PUSH_PULL or OPEN_DRAIN
+        uint8_t out_ctrl = 0;
+        out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT2_OUT_EN |
+                    BMX160Defs::INT_OUT_CTRL_INT1_OUT_EN;
+
+        if (config.int1_mode == BMX160Config::IntMode::OPEN_DRAIN)
+        {
+            out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT1_OD;
+        }
+        if (config.int2_mode == BMX160Config::IntMode::OPEN_DRAIN)
+        {
+            out_ctrl |= BMX160Defs::INT_OUT_CTRL_INT2_OD;
+        }
+
+        // Enable both interrupt pins, otherwise they'll just float.
+        // We configure both of them as push-pull and active-low
+        spi.write(BMX160Defs::REG_INT_OUT_CTRL, out_ctrl);
+
+        // Set fifo watermark
+        spi.write(BMX160Defs::REG_FIFO_CONFIG_0, config.fifo_watermark);
+
+        // Enable FIFO full interrupt and fifo watermark
+        spi.write(BMX160Defs::REG_INT_EN_1,
+                  BMX160Defs::INT_EN_1_FIFO_FULL |
+                      BMX160Defs::INT_EN_1_FIFO_WATERMARK);
+
+        // Enable interrupt pin map
+        if (config.fifo_int == BMX160Config::FifoInterruptPin::PIN_INT1)
+        {
+            // Configure to use INT1
+            spi.write(BMX160Defs::REG_INT_MAP_1,
+                      BMX160Defs::INT_MAP_1_INT_1_FIFO_FULL |
+                          BMX160Defs::INT_MAP_1_INT_1_FIFO_WATERMARK);
+        }
+        else
+        {
+            // Configure to use INT2
+            spi.write(BMX160Defs::REG_INT_MAP_1,
+                      BMX160Defs::INT_MAP_1_INT_2_FIFO_FULL |
+                          BMX160Defs::INT_MAP_1_INT_2_FIFO_WATERMARK);
+        }
+    }
+}
+
+bool BMX160::testAcc()
+{
+    const uint16_t SELF_TEST_LIMIT = 8192;
+    const uint8_t ACC_CONF_TEST    = 0x2C;
+    const uint8_t ACC_RANGE_TEST   = 0x08;
+
+    SPITransaction spi(spi_slave);
+
+    // The acc will complain otherwise...
+    spi.write(BMX160Defs::REG_ACC_CONF, ACC_CONF_TEST);
+    spi.write(BMX160Defs::REG_ACC_RANGE, ACC_RANGE_TEST);
+
+    // Enable acc self-test + positive force + self-test deflection
+    spi.write(BMX160Defs::REG_SELF_TEST, BMX160Defs::SELF_TEST_ACC_AMP |
+                                             BMX160Defs::SELF_TEST_ACC_SIGN |
+                                             BMX160Defs::SELF_TEST_ACC_ENABLE);
+    miosix::Thread::sleep(50);
+
+    int16_t pos_acc[3];
+    spi.read(BMX160Defs::REG_DATA_ACC, reinterpret_cast<uint8_t*>(pos_acc),
+             sizeof(pos_acc));
+
+    // Enable acc self-test + negative force + self-test deflection
+    spi.write(BMX160Defs::REG_SELF_TEST,
+              BMX160Defs::SELF_TEST_ACC_AMP | BMX160Defs::SELF_TEST_ACC_ENABLE);
+    miosix::Thread::sleep(50);
+
+    int16_t neg_acc[3];
+    spi.read(BMX160Defs::REG_DATA_ACC, reinterpret_cast<uint8_t*>(neg_acc),
+             sizeof(neg_acc));
+
+    if ((neg_acc[0] - pos_acc[0]) < SELF_TEST_LIMIT ||
+        (neg_acc[1] - pos_acc[1]) < SELF_TEST_LIMIT ||
+        (neg_acc[2] - pos_acc[2]) < SELF_TEST_LIMIT)
+    {
+        LOG_ERR(logger, "Accelerometer self-test failed!");
+        LOG_ERR(logger, "pos_acc: {} {} {}", pos_acc[0], pos_acc[1],
+                pos_acc[2]);
+        LOG_ERR(logger, "neg_acc: {} {} {}", neg_acc[0], neg_acc[1],
+                neg_acc[2]);
+
+        return false;
+    }
+
+    // Reset self-test
+    spi.write(BMX160Defs::REG_SELF_TEST, 0);
+    return true;
+}
+
+bool BMX160::testGyr()
+{
+    // Start gyro self-test
+    SPITransaction spi(spi_slave);
+
+    spi.write(BMX160Defs::REG_SELF_TEST, BMX160Defs::SELF_TEST_GYR);
+
+    miosix::Thread::sleep(50);
+
+    // Read back the results
+    if (!(spi.read(BMX160Defs::REG_STATUS) & 2))
+    {
+        LOG_ERR(logger, "Gyroscope self-test failed!");
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+bool BMX160::testMag()
+{
+    SPITransaction spi(spi_slave);
+
+    // Enable manual configuration mode
+    confMag(spi, BMX160Defs::MAG_IF_0_MANUAL);
+
+    // Enable self-test and put magnetometer in sleep
+    writeMag(spi, BMX160Defs::MAG_REG_CONTROL,
+             BMX160Defs::MAG_CONTROL_SELF_TEST | BMX160Defs::MAG_CONTROL_SLEEP);
+    miosix::Thread::sleep(200);
+
+    // Check if it has finished
+    if (readMag(spi, BMX160Defs::MAG_REG_CONTROL) &
+        BMX160Defs::MAG_CONTROL_SELF_TEST)
+    {
+        LOG_ERR(logger, "Magnetometer didn't finish self-test!");
+        return false;
+    }
+
+    // Read back test results
+    int16_t mag[4];
+    readMag(spi, BMX160Defs::MAG_REG_DATA, reinterpret_cast<uint8_t*>(mag),
+            sizeof(mag));
+
+    // Test results are stored in the lower bit of the 3 axis
+    if (!(mag[0] & 1) || !(mag[1] & 1) || !(mag[2] & 1))
+    {
+        LOG_ERR(logger, "Magnetometer self-test failed!");
+        LOG_ERR(logger, "result: %d %d %d %d\n", mag[0], mag[1], mag[2],
+                mag[3]);
+        return false;
+    }
+    else
+    {
+        return true;
+    }
+}
+
+MagnetometerData BMX160::buildMagData(BMX160Defs::MagRaw data,
+                                      uint64_t timestamp)
+{
+    // Strip the lower 3 bits for xy
+    data.x >>= 3;
+    data.y >>= 3;
+    // Strip the lower 1 bit for z
+    data.z >>= 1;
+
+    if (config.enable_compensation)
+    {
+        return MagnetometerData{timestamp,
+                                boschMagCompensateX(data.x, data.rhall),
+                                boschMagCompensateY(data.y, data.rhall),
+                                boschMagCompensateZ(data.z, data.rhall)};
+    }
+    else
+    {
+        return MagnetometerData{timestamp, data.x * BMX160Defs::MAG_SENSIBILITY,
+                                data.y * BMX160Defs::MAG_SENSIBILITY,
+                                data.z * BMX160Defs::MAG_SENSIBILITY};
+    }
+}
+
+AccelerometerData BMX160::buildAccData(BMX160Defs::AccRaw data,
+                                       uint64_t timestamp)
+{
+    return AccelerometerData{timestamp,
+                             data.x * acc_sensibility * EARTH_GRAVITY,
+                             data.y * acc_sensibility * EARTH_GRAVITY,
+                             data.z * acc_sensibility * EARTH_GRAVITY};
+}
+
+GyroscopeData BMX160::buildGyrData(BMX160Defs::GyrRaw data, uint64_t timestamp)
+{
+    if (config.gyr_unit == BMX160Config::GyroscopeMeasureUnit::DEG)
+    {
+        return GyroscopeData{timestamp, data.x * gyr_sensibility,
+                             data.y * gyr_sensibility,
+                             data.z * gyr_sensibility};
+    }
+    else
+    {
+        return GyroscopeData{timestamp,
+                             data.x * gyr_sensibility * DEGREES_TO_RADIANS,
+                             data.y * gyr_sensibility * DEGREES_TO_RADIANS,
+                             data.z * gyr_sensibility * DEGREES_TO_RADIANS};
+    }
+}
+
+const char* BMX160::debugErr(SPITransaction& spi)
+{
+    uint8_t err = spi.read(BMX160Defs::REG_ERR);
+
+    if (err & 1)
+    {
+        return "Chip not operable";
+    }
+    else if (err & 64)
+    {
+        return "Dropped command to register 0x7E";
+    }
+    else
+    {
+        // Mask error code
+        err = (err >> 1) & 0x0F;
+        switch (err)
+        {
+            case 0:
+                return "No error";
+            case 1:
+            case 2:
+                return "Generic error";
+            case 3:
+                return "LPM and interrupt uses pre-filtered data";
+            case 6:
+                return "ODR do not match";
+            case 7:
+                return "LPM uses pre-filtered data";
+            default:
+                return "Reserved error";
+        }
+    }
+}
+
+uint64_t BMX160::odrToTimeOffset(BMX160Config::OutputDataRate odr,
+                                 uint8_t downs)
+{
+    // Adjust ODR for downsampling
+    uint8_t real_odr = static_cast<uint64_t>(odr) - (downs & 3);
+
+    // Hz = 100 / 2^(8-odr)
+    // Sec = 2^(13-odr) / 3200
+    // Micro = (2^(13-odr)) * 10000 / 32;
+
+    return ((1 << (13 - real_odr)) * 10000) >> 5;
+}
+
+void BMX160::readTemp()
+{
+    SPITransaction spi(spi_slave);
+
+    int16_t val = spi.read(BMX160Defs::REG_TEMPERATURE_0) |
+                  (spi.read(BMX160Defs::REG_TEMPERATURE_1) << 8);
+
+    // Correct for sensibility and offset
+    temperature = (val * BMX160Defs::TEMP_SENSIBILITY) + 23.0f;
+}
+
+void BMX160::readData()
+{
+    SPITransaction spi(spi_slave);
+
+    uint8_t buf[20];
+    spi.read(BMX160Defs::REG_DATA, buf, sizeof(buf));
+
+    int idx      = 0;
+    auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx);
+    auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx);
+    auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx);
+
+    // Push a new sample into the fifo
+    pushSample(BMX160Data{
+        buildAccData(acc_raw, last_interrupt_us),
+        buildGyrData(gyr_raw, last_interrupt_us),
+        buildMagData(mag_raw, last_interrupt_us),
+    });
+}
+
+void BMX160::readFifo(bool headerless)
+{
+    irq_enabled = false;
+
+    SPITransaction spi(spi_slave);
+
+    int len = spi.read(BMX160Defs::REG_FIFO_LENGTH_0) |
+              ((spi.read(BMX160Defs::REG_FIFO_LENGTH_1) & 7) << 8);
+
+    if (len == 0)
+    {
+        // The buffer is empty, return early
+        last_error = SensorErrors::NO_NEW_DATA;
+        return;
+    }
+
+    uint8_t buf[FIFO_BUF_SIZE];
+
+#ifdef DEBUG
+    assert(len <= static_cast<int>(sizeof(buf)) && "Buffer overflow!");
+#endif
+
+    // Shift the old timestamps, this allows to use old timestamps with the
+    // current frame.
+    if (old_mag.mag_timestamp != 0)
+        old_mag.mag_timestamp -= dt_interrupt;
+    if (old_gyr.gyro_timestamp != 0)
+        old_gyr.gyro_timestamp -= dt_interrupt;
+    if (old_acc.accel_timestamp != 0)
+        old_acc.accel_timestamp -= dt_interrupt;
+
+    // Calculate time offset
+    uint64_t time_offset = std::min({
+        odrToTimeOffset(config.mag_odr, 0),
+        odrToTimeOffset(config.gyr_odr, config.fifo_gyr_downs),
+        odrToTimeOffset(config.acc_odr, config.fifo_acc_downs),
+    });
+
+    spi.read(BMX160Defs::REG_FIFO_DATA, buf, len);
+    uint64_t timestamp    = 0;
+    uint64_t watermark_ts = 0;
+
+    int idx = 0;
+    while (idx < len && buf[idx] != BMX160Defs::FIFO_STOP_BYTE)
+    {
+
+        if (headerless)
+        {
+            auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx);
+            auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx);
+            auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx);
+
+            old_mag = buildMagData(mag_raw, timestamp);
+            old_gyr = buildGyrData(gyr_raw, timestamp);
+            old_acc = buildAccData(acc_raw, timestamp);
+
+            // Push a new sample into the fifo
+            pushSample(BMX160Data{old_acc, old_gyr, old_mag});
+
+            if (watermark_ts == 0 && idx >= (config.fifo_watermark * 4))
+            {
+                watermark_ts = timestamp;
+            }
+
+            timestamp += time_offset;
+        }
+        else
+        {
+            uint8_t header = buf[idx++];
+
+            if ((header & BMX160Defs::FIFO_HEADER_MODE_MASK) ==
+                BMX160Defs::FIFO_HEADER_MODE_REGULAR)
+            {
+                // This is a regular packet
+
+                // Mask out everything but fh_parm
+                header &= BMX160Defs::FIFO_HEADER_PARM_MASK;
+
+                // This contains magnet data
+                if (header & BMX160Defs::FIFO_HEADER_PARM_MAG_DATA)
+                {
+                    auto mag_raw = parseStruct<BMX160Defs::MagRaw>(buf, idx);
+                    old_mag      = buildMagData(mag_raw, timestamp);
+                }
+
+                // This contains gyro data
+                if (header & BMX160Defs::FIFO_HEADER_PARM_GYR_DATA)
+                {
+                    auto gyr_raw = parseStruct<BMX160Defs::GyrRaw>(buf, idx);
+                    old_gyr      = buildGyrData(gyr_raw, timestamp);
+                }
+
+                // This contains accel data
+                if (header & BMX160Defs::FIFO_HEADER_PARM_ACC_DATA)
+                {
+                    auto acc_raw = parseStruct<BMX160Defs::AccRaw>(buf, idx);
+                    old_acc      = buildAccData(acc_raw, timestamp);
+                }
+
+                // Push a new sample into the fifo
+                pushSample(BMX160Data{old_acc, old_gyr, old_mag});
+
+                if (watermark_ts == 0 && idx >= (config.fifo_watermark * 4))
+                {
+                    watermark_ts = timestamp;
+                }
+
+                timestamp += time_offset;
+            }
+            else if ((header & BMX160Defs::FIFO_HEADER_MODE_MASK) ==
+                     BMX160Defs::FIFO_HEADER_MODE_CONTROL)
+            {
+                // This is a control packet
+
+                // Mask out everything but fh_parm
+                header &= BMX160Defs::FIFO_HEADER_PARM_MASK;
+
+                if (header == BMX160Defs::FIFO_HEADER_PARM_SKIP)
+                {
+                    // Skip frame
+                    idx += 1;
+                }
+                else if (header == BMX160Defs::FIFO_HEADER_PARM_SENSORTIME)
+                {
+                    // Sensortime frame
+                    idx += 3;
+                }
+                else if (header == BMX160Defs::FIFO_HEADER_PARM_CONFIG)
+                {
+                    // FIFO_input_config_frame
+                    idx += 1;
+                }
+            }
+            else
+            {
+                LOG_ERR(logger, "Malformed packet! Aborting fifo transfer...");
+
+                last_error =
+                    static_cast<SensorErrors>(BMX160Errors::INVALID_FIFO_DATA);
+                break;
+            }
+        }
+    }
+
+    // Update fifo statistics
+    stats.timestamp     = TimestampTimer::getTimestamp();
+    stats.watermark_ts  = watermark_ts;
+    stats.fifo_duration = timestamp;
+    stats.interrupt_dt  = dt_interrupt;
+    stats.len           = len;
+
+    // Adjust timestamps
+    for (int i = 0; i < last_fifo_level; i++)
+    {
+        last_fifo[i].accel_timestamp += last_interrupt_us - watermark_ts;
+        last_fifo[i].gyro_timestamp += last_interrupt_us - watermark_ts;
+        last_fifo[i].mag_timestamp += last_interrupt_us - watermark_ts;
+    }
+
+    irq_enabled = true;
+}
+
+template <typename T>
+T BMX160::parseStruct(uint8_t* buf, int& idx)
+{
+    T data;
+    memcpy(&data, buf + idx, sizeof(T));
+    idx += sizeof(T);
+
+    return data;
+}
+
+/**========================================================================
+ * Warning: the following code is extrapolated from the bosch driver
+ * source code, I have no idea of what it does.
+ * https://github.com/BoschSensortec/BMM150-Sensor-API/blob/a20641f216057f0c54de115fe81b57368e119c01/bmm150.c#L1614
+ * ========================================================================
+ */
+
+void BMX160::boschReadTrim(SPITransaction& spi)
+{
+    uint8_t trim_x1y1[2]     = {0};
+    uint8_t trim_xyz_data[4] = {0};
+    uint8_t trim_xy1xy2[10]  = {0};
+
+    readMag(spi, BMX160Defs::MAG_REG_DIG_X1, trim_x1y1, sizeof(trim_x1y1));
+    readMag(spi, BMX160Defs::MAG_REG_DIG_Z4_0, trim_xyz_data,
+            sizeof(trim_xyz_data));
+    readMag(spi, BMX160Defs::MAG_REG_DIG_Z2_0, trim_xy1xy2,
+            sizeof(trim_xy1xy2));
+
+    // Read trim registers
+    trim_data.dig_x1   = trim_x1y1[0];
+    trim_data.dig_y1   = trim_x1y1[1];
+    trim_data.dig_x2   = trim_xyz_data[2];
+    trim_data.dig_y2   = trim_xyz_data[3];
+    trim_data.dig_z1   = trim_xy1xy2[2] | (trim_xy1xy2[3] << 8);
+    trim_data.dig_z2   = trim_xy1xy2[0] | (trim_xy1xy2[1] << 8);
+    trim_data.dig_z3   = trim_xy1xy2[6] | (trim_xy1xy2[7] << 8);
+    trim_data.dig_z4   = trim_xyz_data[0] | (trim_xyz_data[1] << 8);
+    trim_data.dig_xy1  = trim_xy1xy2[9];
+    trim_data.dig_xy2  = trim_xy1xy2[8];
+    trim_data.dig_xyz1 = trim_xy1xy2[4] | ((trim_xy1xy2[5] & 0x7F) << 8);
+}
+
+float BMX160::boschMagCompensateX(int16_t x, uint16_t rhall)
+{
+    /* clang-format off */
+        float retval = 0;
+        float process_comp_x0;
+        float process_comp_x1;
+        float process_comp_x2;
+        float process_comp_x3;
+        float process_comp_x4;
+
+        /* Processing compensation equations */
+        process_comp_x0 = (((float)trim_data.dig_xyz1) * 16384.0f / rhall);
+        retval = (process_comp_x0 - 16384.0f);
+        process_comp_x1 = ((float)trim_data.dig_xy2) * (retval * retval / 268435456.0f);
+        process_comp_x2 = process_comp_x1 + retval * ((float)trim_data.dig_xy1) / 16384.0f;
+        process_comp_x3 = ((float)trim_data.dig_x2) + 160.0f;
+        process_comp_x4 = x * ((process_comp_x2 + 256.0f) * process_comp_x3);
+        retval = ((process_comp_x4 / 8192.0f) + (((float)trim_data.dig_x1) * 8.0f)) / 16.0f;
+
+        return retval;
+    /* clang-format on */
+}
+
+float BMX160::boschMagCompensateY(int16_t y, uint16_t rhall)
+{
+    /* clang-format off */
+        float retval = 0;
+        float process_comp_y0;
+        float process_comp_y1;
+        float process_comp_y2;
+        float process_comp_y3;
+        float process_comp_y4;
+
+        /* Processing compensation equations */
+        process_comp_y0 = ((float)trim_data.dig_xyz1) * 16384.0f / rhall;
+        retval = process_comp_y0 - 16384.0f;
+        process_comp_y1 = ((float)trim_data.dig_xy2) * (retval * retval / 268435456.0f);
+        process_comp_y2 = process_comp_y1 + retval * ((float)trim_data.dig_xy1) / 16384.0f;
+        process_comp_y3 = ((float)trim_data.dig_y2) + 160.0f;
+        process_comp_y4 = y * (((process_comp_y2) + 256.0f) * process_comp_y3);
+        retval = ((process_comp_y4 / 8192.0f) + (((float)trim_data.dig_y1) * 8.0f)) / 16.0f;
+
+        return retval;
+    /* clang-format on */
+}
+
+float BMX160::boschMagCompensateZ(int16_t z, uint16_t rhall)
+{
+    /* clang-format off */
+        float retval = 0;
+        float process_comp_z0;
+        float process_comp_z1;
+        float process_comp_z2;
+        float process_comp_z3;
+        float process_comp_z4;
+        float process_comp_z5;
+
+        process_comp_z0 = ((float)z) - ((float)trim_data.dig_z4);
+        process_comp_z1 = ((float)rhall) - ((float)trim_data.dig_xyz1);
+        process_comp_z2 = (((float)trim_data.dig_z3) * process_comp_z1);
+        process_comp_z3 = ((float)trim_data.dig_z1) * ((float)rhall) / 32768.0f;
+        process_comp_z4 = ((float)trim_data.dig_z2) + process_comp_z3;
+        process_comp_z5 = (process_comp_z0 * 131072.0f) - process_comp_z2;
+        retval = (process_comp_z5 / ((process_comp_z4)*4.0f)) / 16.0f;
+
+        return retval;
+    /* clang-format on */
+}
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160.h b/src/shared/sensors/BMX160/BMX160.h
new file mode 100644
index 0000000000000000000000000000000000000000..9dd62c4b62634496856d26cf944cd1169d3a44b3
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160.h
@@ -0,0 +1,351 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+#include <drivers/spi/SPIDriver.h>
+#include <sensors/Sensor.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+
+#include "BMX160Config.h"
+#include "BMX160Data.h"
+#include "BMX160Defs.h"
+#include "Constants.h"
+#include "TimestampTimer.h"
+
+/**
+ * @brief BMX160 Driver.
+ */
+class BMX160 : public SensorFIFO<BMX160Data, 200>
+{
+public:
+    /**
+     * @brief BMX160 Custom errors.
+     */
+    enum BMX160Errors : uint8_t
+    {
+        INVALID_FIFO_DATA =
+            SensorErrors::END_OF_BASE_ERRORS  ///< The fifo contained invalid
+                                              ///< data.
+    };
+
+    /**
+     * @brief BMX160 Constructor.
+     * @param bus SPI bus
+     * @param cs SPI Chip Select pin
+     * @param config BMX160 configuration
+     */
+    BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config = {});
+
+    /**
+     * @brief BMX160 Constructor.
+     * @param bus SPI bus
+     * @param cs SPI Chip Select pin
+     * @param config BMX160 configuration
+     * @param bus_config SPI bus configuration
+     */
+    BMX160(SPIBusInterface& bus, GpioPin cs, BMX160Config config,
+           SPIBusConfig bus_config);
+
+    /**
+     * @brief Initialize the driver.
+     */
+    bool init() override;
+
+    /**
+     * @brief Perform selftest on the device.
+     */
+    bool selfTest() override;
+
+    /**
+     * @brief Gather data from FIFO/data registers and temperature sensor.
+     */
+    BMX160Data sampleImpl() override;
+
+    /**
+     * @brief Get last read temperature.
+     */
+    BMX160Temperature getTemperature();
+
+    /**
+     * @brief Retrieve last fifo stats.
+     */
+    BMX160FifoStats getFifoStats();
+
+    /**
+     * @brief Sometimes the sensor pulls down the interrupt pin while reading
+     * data. We override this method and update the timestamps only if we are
+     * not still reading the fifo ( @see{irq_enabled} ).
+     */
+    void IRQupdateTimestamp(uint64_t ts) override;
+
+private:
+    /**
+     * @brief Execute CMD.
+     *
+     * @param cmd Command to be executed.
+     * @param pmu Powermode (MUST be set to PowerMode::SUSPEND when not needed).
+     */
+    void sendCmd(SPITransaction& spi, BMX160Defs::Cmd cmd,
+                 BMX160Defs::PowerMode pmu = BMX160Defs::PowerMode::SUSPEND);
+
+    /**
+     * @brief Push a sample into the FIFO.
+     *
+     * @param sample Sample to be pushed.
+     */
+    void pushSample(BMX160Data sample);
+
+    /**
+     * @brief Convenience function to configure magnetometer.
+     *
+     * @param value bmm150 configuration value.
+     */
+    void confMag(SPITransaction& spi, uint8_t value);
+
+    /**
+     * @brief Convenience function to map magnetometer for read.
+     *
+     * @param reg bmm150 register to be mapped in REG_DATA_MAG.
+     */
+    void mapMag(SPITransaction& spi, uint8_t reg);
+
+    /**
+     * @brief Convenience function to read a single byte from magnetometer.
+     *
+     * @param reg Register to be read.
+     * @return Value read from the register.
+     */
+    uint8_t readMag(SPITransaction& spi, uint8_t reg);
+
+    /**
+     * @brief Convenience function to read from magnetometer.
+     *
+     * @param reg Register to be read.
+     * @param[out] data Buffer to fill with the data.
+     * @param size Size of the buffer.
+     */
+    void readMag(SPITransaction& spi, uint8_t reg, uint8_t* data, size_t size);
+
+    /**
+     * @brief Convenience function to write to magnetometer.
+     *
+     * @param reg Register to be written to.
+     * @param value Value to write to the register.
+     */
+    void writeMag(SPITransaction& spi, uint8_t reg, uint8_t value);
+
+    /**
+     * @brief Check for chipid validity.
+     *
+     * @return Returns false on failure.
+     */
+    bool checkChipid();
+
+    /**
+     * @brief Perform a soft-reset.
+     */
+    void softReset();
+
+    /**
+     * @brief Wake interfaces up, should be called after soft-reset.
+     *
+     * @return Returns false on failure.
+     */
+    bool setPowerMode();
+
+    /**
+     * @brief Initialize accelerometer.
+     */
+    void initAcc();
+
+    /**
+     * @brief Initialize gyroscope.
+     */
+    void initGyr();
+
+    /**
+     * @brief Initialize magnetometer.
+     */
+    void initMag();
+
+    /**
+     * @brief Initialize FIFO.
+     */
+    void initFifo();
+
+    /**
+     * @brief Initialize interrupts.
+     */
+    void initInt();
+
+    /**
+     * @brief Self-test accelerometer.
+     *
+     * @return Returns false on failure.
+     */
+    bool testAcc();
+
+    /**
+     * @brief Self-test gyroscope.
+     *
+     * @return Returns false on failure.
+     */
+    bool testGyr();
+
+    /**
+     * @brief Self-test magnetometer.
+     *
+     * @return Returns false on failure.
+     */
+    bool testMag();
+
+    /**
+     * @brief Build magnetometer data.
+     *
+     * @param data Raw input data.
+     * @param timestamp Timestamp associated with the data.
+     */
+    MagnetometerData buildMagData(BMX160Defs::MagRaw data, uint64_t timestamp);
+
+    /**
+     * @brief Build gyroscope data.
+     *
+     * @param data Raw input data.
+     * @param timestamp Timestamp associated with the data.
+     */
+    AccelerometerData buildAccData(BMX160Defs::AccRaw data, uint64_t timestamp);
+
+    /**
+     * @brief Build accelerometer data.
+     *
+     * @param data Raw input data.
+     * @param timestamp Timestamp associated with the data.
+     */
+    GyroscopeData buildGyrData(BMX160Defs::GyrRaw data, uint64_t timestamp);
+
+    /**
+     * @brief Debug function used to print the current error state
+     *
+     * @return String representing the error.
+     */
+    const char* debugErr(SPITransaction& spi);
+
+    /**
+     * @brief Convert Output Data Rate to the time between samples.
+     *
+     * Warning, anything resulting in a frequency over 1Hz will underflow the
+     * calculations!
+     *
+     * @param odr input output data rate of the sensor.
+     * @param downs downsampling of the input.
+     * @return Time between samples.
+     */
+    uint64_t odrToTimeOffset(BMX160Config::OutputDataRate odr, uint8_t downs);
+
+    /**
+     * @brief Read the value of the temperature sensor
+     */
+    void readTemp();
+
+    /**
+     * @brief Read the contents of the DATA register
+     */
+    void readData();
+
+    /**
+     * @brief Read the contents of the fifo into buf.
+     *
+     * @return Returns false on failure.
+     */
+    void readFifo(bool headerless);
+
+    /**
+     * @brief Parses T out of a buffer.
+     *
+     * @param buf Input buffer.
+     * @param[in,out] idx Input buffer read index.
+     */
+    template <typename T>
+    T parseStruct(uint8_t* buf, int& idx);
+
+    /**========================================================================
+     * Warning: the following code is extrapolated from the bosch driver
+     * source code, I have no idea of what it does.
+     * https://github.com/BoschSensortec/BMM150-Sensor-API/blob/a20641f216057f0c54de115fe81b57368e119c01/bmm150.c#L1614
+     * ========================================================================
+     */
+
+    /**
+     * @brief Read and parse bosch trim registers
+     */
+    void boschReadTrim(SPITransaction& spi);
+
+    /**
+     * @brief Bosch black-box algorithm
+     */
+    float boschMagCompensateX(int16_t x, uint16_t rhall);
+
+    /**
+     * @brief Bosch black-box algorithm
+     */
+    float boschMagCompensateY(int16_t y, uint16_t rhall);
+
+    /**
+     * @brief Bosch black-box algorithm
+     */
+    float boschMagCompensateZ(int16_t z, uint16_t rhall);
+
+    float temperature = 0.0f;
+    MagnetometerData old_mag;
+    GyroscopeData old_gyr;
+    AccelerometerData old_acc;
+
+    bool is_init = false;
+    SPISlave spi_slave;
+
+    BMX160Defs::TrimData trim_data;
+    BMX160Config config;
+
+    BMX160FifoStats stats;
+
+    float gyr_sensibility = 0.0f;
+    float acc_sensibility = 0.0f;
+
+    int temp_counter = 0;
+
+    // Sometimes the buffer gets over 1000
+    static constexpr unsigned int FIFO_BUF_SIZE = 1100;
+
+    /**
+     * Sometimes the sensor pulls down the interrupt pin while reading data.
+     * We use this variable to ignore incoming interrupts while reading the
+     * fifo.
+     */
+    bool irq_enabled = true;
+
+    PrintLogger logger = Logging::getLogger("bmx160");
+};
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160Config.h b/src/shared/sensors/BMX160/BMX160Config.h
new file mode 100644
index 0000000000000000000000000000000000000000..743c056a48857d34777d771fb045df7ec8aa35d3
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160Config.h
@@ -0,0 +1,242 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+/**
+ * @brief BMX160 Configuration
+ */
+struct BMX160Config
+{
+    /**
+     * @brief Fifo operationg mode.
+     */
+    enum class FifoMode
+    {
+        DISABLED,    ///< The fifo is completely disabled.
+        HEADERLESS,  ///< Sensors MUST have the same odr.
+        HEADER       ///< Sensors can have different odr.
+    };
+
+    /**
+     * @brief Fifo interrupt mode.
+     *
+     * Uses fifo full/watermark as triggers.
+     * We configure the pin as push-pull and active-low.
+     */
+    enum class FifoInterruptPin
+    {
+
+        DISABLED,  ///< The interrupts are completely disabled.
+        PIN_INT1,  ///< Interrupts are enabled on pin 2.
+        PIN_INT2,  ///< Interrupts are enabled on pin 1.
+    };
+
+    /**
+     * @brief Range of the accelerometer expressed in ±g.
+     */
+    enum class AccelerometerRange
+    {
+        G_2  = 0x3,  ///< Accelaration range ±2g.
+        G_4  = 0x5,  ///< Accelaration range ±4g.
+        G_8  = 0x8,  ///< Accelaration range ±8g.
+        G_16 = 0xC   ///< Accelaration range ±16g.
+    };
+
+    /**
+     * @brief Gyroscope range expressed in °/sec.
+     */
+    enum class GyroscopeRange
+    {
+        DEG_2000 = 0x0,  ///< Gyroscope range 2000°/sec.
+        DEG_1000 = 0x1,  ///< Gyroscope range 1000°/sec.
+        DEG_500  = 0x2,  ///< Gyroscope range 500°/sec.
+        DEG_250  = 0x3,  ///< Gyroscope range 250°/sec.
+        DEG_125  = 0x4   ///< Gyroscope range 125°/sec.
+    };
+
+    /**
+     * @brief Gyroscope measure unit (degrees or radiants).
+     */
+    enum class GyroscopeMeasureUnit
+    {
+        DEG,  ///< Degrees.
+        RAD   ///< Radiants.
+    };
+
+    /**
+     * @brief Output Data Rate expressed in Hz.
+     *
+     * Limits are as follows (wrong values will error the device):
+     * - Accelerometer: 25/2  - 1600 (25/32 - 1600 if undersampling is enabled,
+     * which is not)
+     * - Gyroscope:     25    - 3200
+     * - Magnetometer:  25/32 - 800
+     */
+    enum class OutputDataRate
+    {
+        HZ_0_78125 = 0x01,  ///< Output Data Rate of 25/32Hz = 0.78125.
+        HZ_1_5625  = 0x02,  ///< Output Data Rate of 25/16Hz = 1.5625.
+        HZ_3_125   = 0x03,  ///< Output Data Rate of 25/8Hz = 3.125.
+        HZ_6_25    = 0x04,  ///< Output Data Rate of 25/4Hz = 6.25.
+        HZ_12_5    = 0x05,  ///< Output Data Rate of 25/2Hz = 12.5.
+        HZ_25      = 0x06,  ///< Output Data Rate of 25Hz.
+        HZ_50      = 0x07,  ///< Output Data Rate of 50Hz.
+        HZ_100     = 0x08,  ///< Output Data Rate of 100Hz.
+        HZ_200     = 0x09,  ///< Output Data Rate of 200Hz.
+        HZ_400     = 0x0A,  ///< Output Data Rate of 400Hz.
+        HZ_800     = 0x0B,  ///< Output Data Rate of 800Hz.
+        HZ_1600    = 0x0C,  ///< Output Data Rate of 1600Hz.
+        HZ_3200    = 0x0D,  ///< Output Data Rate of 3200Hz.
+    };
+
+    /**
+     * @brief Bandwidth parameter.
+     *
+     * For more detailed explanation, check the 2.4.1 Data Processing
+     * Accelerometer and 2.4.2 Data Processing Gyroscope chapters of the BMX160
+     * datasheet.
+     */
+    enum class BandwidthParameter
+    {
+        NORMAL = 0x20,  ///< Normal filter operation.
+        OSR2   = 0x10,  ///< Oversampling rate of 2.
+        OSR4   = 0x00,  ///< Oversampling rate of 4.
+    };
+
+    /**
+     * @brief Interrupt pin mode.
+     *
+     * Configuration for interrupt pin behaviour.
+     */
+    enum class IntMode
+    {
+        PUSH_PULL,   //< Push-pull behaviour.
+        OPEN_DRAIN,  //< Open drain behaviour.
+    };
+
+    /**
+     * @brief Fifo watermark to use, in multiples of 4.
+     *
+     * Only values between [0-250] make sense to use.
+     * A really high watermark value (the default) will disable it, falling back
+     * to FIFO full.
+     */
+    uint8_t fifo_watermark = -1;
+
+    /**
+     * @brief Repetitions for the XY axis.
+     *
+     * Repetitions represent how many internal samples are averaged in order to
+     * get the final outputted sample, these presets are the ones recommended
+     * in the BMX160 datasheet, for more informations consult the BMM150
+     * datasheet, chapter 4.2.4 Active mode.
+     *
+     * This are the reccomended presets:
+     * - 0x01, RMS Noise (x/y/z) 1.0/1.0/1.4, Current: 0.17mA (Low power)
+     * - 0x04, RMS Noise (x/y/z) 0.6/0.6/0.6, Current: 0.5mA  (Regular)
+     * (Default)
+     * - 0x07, RMS Noise (x/y/z) 0.5/0.5/0.5, Current: 0.8mA  (Enhanced
+     * regular)
+     * - 0x17, RMS Noise (x/y/z) 0.3/0.3/0.3, Current: 4.9mA  (High accuracy)
+     */
+    uint8_t mag_repxy = 0x04;
+
+    /**
+     * @brief Repetitions for the Z axis.
+     *
+     * Repetitions represent how many internal samples are averaged in order to
+     * get the final outputted sample, these presets are the ones reccomended
+     * in the BMX160 datasheet, for more informations consult the BMM150
+     * datasheet, chapter 4.2.4 Active mode.
+     *
+     * This are the reccomended presets:
+     * - 0x02, RMS Noise (x/y/z) 1.0/1.0/1.4, Current: 0.17mA (Low power)
+     * - 0x0E, RMS Noise (x/y/z) 0.6/0.6/0.6, Current: 0.5mA  (Regular)
+     * (Default)
+     * - 0x1A, RMS Noise (x/y/z) 0.5/0.5/0.5, Current: 0.8mA  (Enhanced
+     * regular)
+     * - 0x52, RMS Noise (x/y/z) 0.3/0.3/0.3, Current: 4.9mA  (High accuracy)
+     */
+    uint8_t mag_repz = 0x0E;
+
+    /**
+     * @brief Enable magnetometer data compensation.
+     *
+     * The magnetomer support compensation, but it's not documented, and the
+     * current implementation is based on the source of the bosch driver.
+     *
+     * The implementation is slow, probably buggy, and only god knows what it
+     * does, it's so bad that I added a switch to disable it, use it wisely!
+     */
+    bool enable_compensation = true;
+
+    /**
+     * @brief Divide the temperature sampling rate.
+     *
+     * This is used to limit the sampling of the temperature, use 0 to disable
+     * it completely.
+     *
+     * Every time you call onSimpleUpdate a value is incremented and only if
+     * the value is a multiple of this parameter a read is performed.
+     */
+    int temp_divider = 0;
+
+    /**
+     * @brief Should the fifo use accelerometer filtered data?
+     */
+    bool fifo_acc_filtered = true;
+
+    /**
+     * @brief Fifo accelerometer downsampling (between 0 and 15).
+     */
+    uint8_t fifo_acc_downs = 0;
+
+    /**
+     * @brief Should the fifo use gyroscope filtered data?
+     */
+    bool fifo_gyr_filtered = true;
+
+    /**
+     * @brief Fifo gyroscope downsampling (between 0 and 15).
+     */
+    uint8_t fifo_gyr_downs = 0;
+
+    FifoMode fifo_mode        = FifoMode::DISABLED;
+    FifoInterruptPin fifo_int = FifoInterruptPin::DISABLED;
+
+    OutputDataRate acc_odr       = OutputDataRate::HZ_100;
+    BandwidthParameter acc_bwp   = BandwidthParameter::NORMAL;
+    AccelerometerRange acc_range = AccelerometerRange::G_2;
+
+    OutputDataRate gyr_odr        = OutputDataRate::HZ_100;
+    BandwidthParameter gyr_bwp    = BandwidthParameter::NORMAL;
+    GyroscopeRange gyr_range      = GyroscopeRange::DEG_2000;
+    GyroscopeMeasureUnit gyr_unit = GyroscopeMeasureUnit::RAD;
+
+    OutputDataRate mag_odr = OutputDataRate::HZ_100;
+
+    IntMode int1_mode = IntMode::PUSH_PULL;
+    IntMode int2_mode = IntMode::PUSH_PULL;
+
+    BMX160Config() {}
+};
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160Data.h b/src/shared/sensors/BMX160/BMX160Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..b5655c6d96ce32a70fea4c0b0424b31806c7398b
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160Data.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct BMX160Data : public AccelerometerData,
+                    public GyroscopeData,
+                    public MagnetometerData
+{
+    BMX160Data()
+        : AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0},
+          MagnetometerData{0, 0.0, 0.0, 0.0}
+    {
+    }
+
+    BMX160Data(AccelerometerData acc, GyroscopeData gyr, MagnetometerData mag)
+        : AccelerometerData(acc), GyroscopeData(gyr), MagnetometerData(mag)
+    {
+    }
+
+    static std::string header()
+    {
+        return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x,"
+               "gyro_y,"
+               "gyro_z,mag_timestamp,mag_x,mag_y,mag_z\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accel_timestamp << "," << accel_x << "," << accel_y << ","
+           << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y
+           << "," << gyro_z << "," << mag_timestamp << "," << mag_x << ","
+           << mag_y << "," << mag_z << "\n";
+    }
+};
+
+struct BMX160Temperature : public TemperatureData
+{
+    static std::string header() { return "temp_timestamp,temperature\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << temp_timestamp << "," << temp << "\n";
+    }
+};
+
+/**
+ * @brief BMX160 fifo statistics.
+ */
+struct BMX160FifoStats
+{
+    uint64_t timestamp;
+
+    uint64_t watermark_ts;   //< Watermark timestamp (from the start of
+                             // the fifo)
+    uint64_t fifo_duration;  //< Total fifo duration
+    uint64_t interrupt_dt;   //< Reported delta time between the previous
+                             // interrupt and the current one.
+    int len;                 //< Fifo length in bytes.
+
+    static std::string header()
+    {
+        return "timestamp,watermark_ts,fifo_duration,interrupt_dt,fifo_len\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << watermark_ts << "," << fifo_duration << ","
+           << interrupt_dt << "," << len << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160Defs.h b/src/shared/sensors/BMX160/BMX160Defs.h
new file mode 100644
index 0000000000000000000000000000000000000000..93679bc8eab214ac4a21eeb7fc22c3d44f990ce4
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160Defs.h
@@ -0,0 +1,375 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+/**
+ * @brief Various BMX160 register/enums definitions.
+ */
+namespace BMX160Defs
+{
+
+/**
+ * @brief Temperature sensor sensibility.
+ */
+const float TEMP_SENSIBILITY = 64.0f / 32768.0f;
+
+/**
+ * @brief Magnetometer fixed sensibility.
+ */
+const float MAG_SENSIBILITY = 0.3f;
+
+/**
+ * @brief BMX160 Chip Id.
+ */
+const uint8_t CHIPID = 0xD8;
+
+/**
+ * @brief Default value for FIFO_CONFIG_1.
+ */
+const uint8_t FIFO_CONFIG_1_DEFAULT = 0x10;
+
+/**
+ * @brief Values for SELF_TEST register.
+ */
+enum SELF_TEST
+{
+    SELF_TEST_GYR     = 0x10,  ///< Starts selftest of the gyroscope.
+    SELF_TEST_ACC_AMP = 0x08,  ///< Select amplitude of the selftest deflection.
+    SELF_TEST_ACC_SIGN   = 0x04,  ///< Select sign of selftest exitation.
+    SELF_TEST_ACC_ENABLE = 0x01   ///< Starts selftest of the accelerometer.
+};
+
+/**
+ * @brief Values for FIFO_CONFIG_1 register.
+ */
+enum FIFO_CONFIG_1
+{
+    FIFO_CONFIG_1_ACC_EN    = 0x80,  ///< Store accelerometer data in fifo.
+    FIFO_CONFIG_1_GYR_EN    = 0x40,  ///< Store gyroscope data in fifo.
+    FIFO_CONFIG_1_MAG_EN    = 0x20,  ///< Store magnetometer data in fifo.
+    FIFO_CONFIG_1_HEADER_EN = 0x10,  ///< Stores an header for each frame.
+};
+
+/**
+ * @brief Values for FIFO_DOWNS register.
+ */
+enum FIFO_DOWNS
+{
+    FIFO_DOWNS_ACC_FILT_DATA = 0x80,
+    FIFO_DOWNS_GYR_FILT_DATA = 0x08,
+};
+
+/**
+ * @brief Values for INT_OUT_CTRL register.
+ */
+enum INT_OUT_CTRL
+{
+    INT_OUT_CTRL_INT2_OUT_EN = 0x80,  ///< Output enable for INT2 pin.
+    INT_OUT_CTRL_INT2_OD     = 0x40,  ///< Open drain enable for INT2 pin.
+    INT_OUT_CTRL_INT1_OUT_EN = 0x08,  ///< Output enable for INT1 pin.
+    INT_OUT_CTRL_INT1_OD     = 0x04,  ///< Open drain enable for INT1 pin.
+};
+
+/**
+ * @brief Values for INT_MAP_1 register.
+ */
+enum INT_MAP_1
+{
+    INT_MAP_1_INT_1_FIFO_WATERMARK = 0x40,
+    INT_MAP_1_INT_1_FIFO_FULL      = 0x20,
+    INT_MAP_1_INT_2_FIFO_WATERMARK = 0x04,
+    INT_MAP_1_INT_2_FIFO_FULL      = 0x02,
+};
+
+/**
+ * @brief Values for INT_EN_1 register.
+ */
+enum INT_EN_1
+{
+    INT_EN_1_FIFO_WATERMARK = 0x40,  ///< Enables interrupt for FIFO watermark.
+    INT_EN_1_FIFO_FULL      = 0x20,  ///< Enables interrupt for FIFO full.
+};
+
+/**
+ * @brief Values for MAG_IF_0 register.
+ */
+enum MAG_IF_0
+{
+    MAG_IF_0_MANUAL  = 0x80,
+    MAG_IF_0_BURST_1 = 0x00,  ///< 1 byte of burst operation.
+    MAG_IF_0_BURST_2 = 0x01,  ///< 2 byte of burst operation.
+    MAG_IF_0_BURST_6 = 0x02,  ///< 6 byte of burst operation.
+    MAG_IF_0_BURST_8 = 0x03,  ///< 8 byte of burst operation.
+};
+
+/**
+ * @brief Mask for PMU_STATUS register (Power Mode Unit).
+ */
+const uint8_t PMU_STATUS_ALL_MASK = 0x03 | 0x0C | 0x30;
+
+/**
+ * @brief Mask for PMU_STATUS register, normal status for all sensors.
+ */
+const uint8_t PMU_STATUS_ALL_NORMAL = 0x01 | 0x04 | 0x10;
+
+/**
+ * @brief This value indicates that the data in the FIFO stops prematurely.
+ */
+const uint8_t FIFO_STOP_BYTE = 128;
+
+/**
+ * @brief Mask for fifo header mode.
+ */
+const uint8_t FIFO_HEADER_MODE_MASK = 0xC0;
+
+/**
+ * @brief Mask for fifo header parm.
+ */
+const uint8_t FIFO_HEADER_PARM_MASK = 0x3C;
+
+/**
+ * @brief Mask for fifo header ext.
+ */
+const uint8_t FIFO_HEADER_EXT_MASK = 0x03;
+
+/**
+ * @brief Values for fifo header byte.
+ */
+enum FIFO_HEADER
+{
+    FIFO_HEADER_MODE_REGULAR = 0x80,
+    FIFO_HEADER_MODE_CONTROL = 0x40,
+
+    FIFO_HEADER_PARM_ACC_DATA = 0x04,
+    FIFO_HEADER_PARM_GYR_DATA = 0x08,
+    FIFO_HEADER_PARM_MAG_DATA = 0x10,
+
+    FIFO_HEADER_PARM_SKIP       = 0x00,
+    FIFO_HEADER_PARM_SENSORTIME = 0x04,
+    FIFO_HEADER_PARM_CONFIG     = 0x08,
+};
+
+/**
+ * @brief Values for mag RESET.
+ */
+enum MAG_RESET
+{
+    MAG_RESET_POWER_CONTROL = 0x01
+};
+
+/**
+ * @brief Values for mag CONTROL.
+ */
+enum MAG_CONTROL
+{
+    MAG_CONTROL_SELF_TEST = 0x01,
+    MAG_CONTROL_NORMAL    = 0x00,
+    MAG_CONTROL_FORCED    = 0x02,
+    MAG_CONTROL_SLEEP     = 0x06,
+};
+
+/**
+ * @brief Raw struct, read directly from device.
+ */
+struct MagRaw
+{
+    int16_t x, y, z;
+    uint16_t rhall;
+};
+
+/**
+ * @brief Raw struct, read directly from device.
+ */
+struct GyrRaw
+{
+    int16_t x, y, z;
+};
+
+/**
+ * @brief Raw struct, read directly from device.
+ */
+struct AccRaw
+{
+    int16_t x, y, z;
+};
+
+/**
+ * @brief Struct holding trim data used for magnetomer compensation.
+ */
+struct TrimData
+{
+    int8_t dig_x1;
+    int8_t dig_y1;
+    int8_t dig_x2;
+    int8_t dig_y2;
+    uint16_t dig_z1;
+    int16_t dig_z2;
+    int16_t dig_z3;
+    int16_t dig_z4;
+    uint8_t dig_xy1;
+    int8_t dig_xy2;
+    uint16_t dig_xyz1;
+};
+
+/**
+ * BMX160 Commands.
+ */
+enum class Cmd
+{
+    START_FOC = 0x03,  ///< Starts Fast Offset Calibration for acc and gyro.
+    ACC_SET_PMU_MODE    = 0x10,  ///< Sets the PMU mode for the accelerometer.
+    GYR_SET_PMU_MODE    = 0x14,  ///< Sets the PMU mode for the gyroscope.
+    MAG_IF_SET_PMU_MODE = 0x18,  ///< Sets the PMU mode for the magnetometer.
+    PROG_NVM            = 0xA0,  ///< Writes the NVM backed registers into NVM.
+    FIFO_FLUSH          = 0xB0,  ///< Clears all data in the fifo.
+    INT_RESET = 0xB1,  ///< Resets interrupt engine, INT_STATUS reg and int pin.
+    SOFTRESET = 0xB6,  ///< Triggers a reset including a reboot.
+    STEP_CNT_CLR = 0xB2,  ///< Triggers a reset of the step counter.
+};
+
+/**
+ * BMX160 power modes.
+ */
+enum class PowerMode
+{
+    SUSPEND       = 0x00,
+    NORMAL        = 0x01,
+    LOW_POWER     = 0x02,
+    FAST_START_UP = 0x03
+};
+
+/**
+ * @brief Internal register definitions.
+ */
+enum Registers
+{
+    REG_CHIPID     = 0x00,
+    REG_ERR        = 0x02,
+    REG_PMU_STATUS = 0x03,
+
+    REG_DATA = 0x04,
+
+    REG_DATA_MAG = 0x04,
+    REG_DATA_GYR = 0x0C,
+    REG_DATA_ACC = 0x12,
+
+    REG_SENSORTIME_0 = 0x18,
+    REG_SENSORTIME_1 = 0x19,
+    REG_SENSORTIME_2 = 0x1A,
+
+    REG_STATUS = 0x1B,
+
+    REG_TEMPERATURE_0 = 0x20,
+    REG_TEMPERATURE_1 = 0x21,
+
+    REG_FIFO_LENGTH_0 = 0x22,
+    REG_FIFO_LENGTH_1 = 0x23,
+
+    REG_FIFO_DATA = 0x24,
+
+    REG_ACC_CONF  = 0x40,
+    REG_ACC_RANGE = 0x41,
+    REG_GYR_CONF  = 0x42,
+    REG_GYR_RANGE = 0x43,
+    REG_MAG_CONF  = 0x44,
+
+    REG_FIFO_DOWNS    = 0x45,
+    REG_FIFO_CONFIG_0 = 0x46,
+    REG_FIFO_CONFIG_1 = 0x47,
+
+    REG_MAG_IF_0 = 0x4C,
+    REG_MAG_IF_1 = 0x4D,
+    REG_MAG_IF_2 = 0x4E,
+    REG_MAG_IF_3 = 0x4F,
+
+    REG_INT_EN_0 = 0x50,
+    REG_INT_EN_1 = 0x51,
+    REG_INT_EN_2 = 0x52,
+
+    REG_INT_OUT_CTRL = 0x53,
+    REG_INT_LATCH    = 0x54,
+
+    REG_INT_MAP_0 = 0x55,
+    REG_INT_MAP_1 = 0x56,
+    REG_INT_MAP_2 = 0x57,
+
+    // INT_DATA_[0-1] not needed
+    // INT_LOWHIGH_[0-5] not needed
+    // INT_MOTION_[0-3] not needed
+    // INT_TAP_[0-1] not needed
+    // INT_ORIENT_[0-1] not needed
+    // INT_FLAT_[0-1] not needed
+
+    REG_FOC_CONF = 0x69,
+    REG_CONF     = 0x6A,
+    REG_IF_CONF  = 0x6B,
+
+    // PMU_TRIGGER not needed
+
+    REG_SELF_TEST = 0x6D,
+
+    // NV_CONF not needed
+    // OFFSET_[0-6] not needed
+    // STEP_CNT_[0-1] not needed
+    // STEP_CONF_[0-1] not needed
+
+    REG_CMD       = 0x7E,
+    REG_COMM_TEST = 0x7F,
+};
+
+/**
+ * @brief Internal magnetometer definitions.
+ */
+enum MagnetometerRegisters
+{
+    MAG_REG_DATA    = 0x42,
+    MAG_REG_RESET   = 0x4B,
+    MAG_REG_CONTROL = 0x4C,
+
+    MAG_REG_REPXY = 0x51,
+    MAG_REG_REPZ  = 0x52,
+
+    // Factory calibrated trim registers. This is all undocumented territory
+    // (the datasheet mentions this as "reserved").
+
+    MAG_REG_DIG_X1     = 0x5D,
+    MAG_REG_DIG_Y1     = 0x5E,
+    MAG_REG_DIG_Z4_0   = 0x62,
+    MAG_REG_DIG_Z4_1   = 0x63,
+    MAG_REG_DIG_X2     = 0x64,
+    MAG_REG_DIG_Y2     = 0x65,
+    MAG_REG_DIG_Z2_0   = 0x68,
+    MAG_REG_DIG_Z2_1   = 0x69,
+    MAG_REG_DIG_Z1_0   = 0x6A,
+    MAG_REG_DIG_Z1_1   = 0x6B,
+    MAG_REG_DIG_XYZ1_0 = 0x6C,
+    MAG_REG_DIG_XYZ1_1 = 0x6D,
+    MAG_REG_DIG_Z3_0   = 0x6E,
+    MAG_REG_DIG_Z3_1   = 0x6F,
+    MAG_REG_DIG_XY2    = 0x70,
+    MAG_REG_DIG_XY1    = 0x71,
+};
+
+}  // namespace BMX160Defs
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160WithCorrection.cpp b/src/shared/sensors/BMX160/BMX160WithCorrection.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6617b728bf290412560f1b02b2e617525c7bc970
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160WithCorrection.cpp
@@ -0,0 +1,324 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, Luca Conterio, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "BMX160WithCorrection.h"
+
+#include <miosix.h>
+
+#include <fstream>
+
+BMX160CorrectionParameters::BMX160CorrectionParameters()
+{
+    accelParams << 0, 0, 0, 0, 0, 0;
+    magnetoParams << 0, 0, 0, 0, 0, 0;
+}
+
+std::string BMX160CorrectionParameters::header()
+{
+    return "accel_p1,accel_p2,accel_p3,accel_q1,accel_q2,accel_q3,mag_p1,"
+           "mag_p2,mag_p3,mag_q1,mag_q2,mag_q3,"
+           "minGyroSamplesForCalibration";
+}
+
+void BMX160CorrectionParameters::read(std::istream& inputStream)
+{
+    // Read accelerometer parameters
+    for (int i = 0; i < 2; i++)
+    {
+        for (int j = 0; j < 3; j++)
+        {
+            inputStream >> accelParams(j, i);
+            inputStream.ignore(1, ',');
+        }
+    }
+
+    // Read magnetometer parameters
+    for (int i = 0; i < 2; i++)
+    {
+        for (int j = 0; j < 3; j++)
+        {
+            inputStream >> magnetoParams(j, i);
+            inputStream.ignore(1, ',');
+        }
+    }
+
+    // Read gyroscope correction samples
+    inputStream >> minGyroSamplesForCalibration;
+}
+
+void BMX160CorrectionParameters::print(std::ostream& outputStream) const
+{
+    // Print accelerometer data
+    outputStream << accelParams(0, 0) << "," << accelParams(1, 0) << ","
+                 << accelParams(2, 0) << "," << accelParams(0, 1) << ","
+                 << accelParams(1, 1) << "," << accelParams(2, 1) << ",";
+
+    // Print magnetometer  data
+    outputStream << magnetoParams(0, 0) << "," << magnetoParams(1, 0) << ","
+                 << magnetoParams(2, 0) << "," << magnetoParams(0, 1) << ","
+                 << magnetoParams(1, 1) << "," << magnetoParams(2, 1) << ",";
+
+    // Print gyroscope correction samples
+    outputStream << minGyroSamplesForCalibration << "\n";
+}
+
+BMX160WithCorrection::BMX160WithCorrection(
+    BMX160* bmx160_, BMX160CorrectionParameters correctionParameters,
+    AxisOrthoOrientation rotation_)
+    : bmx160(bmx160_), minGyroSamplesForCalibration(
+                           correctionParameters.minGyroSamplesForCalibration),
+      rotation(rotation_)
+{
+    accelerometerCorrector << correctionParameters.accelParams;
+    magnetometerCorrector << correctionParameters.magnetoParams;
+}
+
+BMX160WithCorrection::BMX160WithCorrection(
+    BMX160* bmx160_, BMX160CorrectionParameters correctionParameters)
+    : bmx160(bmx160_), minGyroSamplesForCalibration(
+                           correctionParameters.minGyroSamplesForCalibration)
+{
+    accelerometerCorrector << correctionParameters.accelParams;
+    magnetometerCorrector << correctionParameters.magnetoParams;
+}
+
+BMX160WithCorrection::BMX160WithCorrection(BMX160* bmx160_) : bmx160(bmx160_) {}
+
+bool BMX160WithCorrection::init() { return true; }
+
+bool BMX160WithCorrection::selfTest() { return true; }
+
+bool BMX160WithCorrection::calibrate()
+{
+    if (!bmx160)
+    {
+        LOG_ERR(logger, "Driver doesn't point to valid sensor");
+        return false;
+    }
+
+    int samplesCounter = 0;
+    BiasCalibration<GyroscopeData> gyroscopeCalibrator;
+    BMX160Data fifoElement;
+    uint8_t fifoSize;
+    uint64_t gyroTimestamp = 0;
+
+    // Set reference vector
+    gyroscopeCalibrator.setReferenceVector({0, 0, 0});
+
+    // Read the fifo and feed the gyroscope data to the calibrator
+    fifoSize = bmx160->getLastFifoSize();
+    for (uint8_t i = 0; i < fifoSize; i++)
+    {
+        fifoElement = bmx160->getFifoElement(i);
+
+        if (fifoElement.gyro_timestamp > gyroTimestamp)
+        {
+            gyroTimestamp = fifoElement.gyro_timestamp;
+            gyroscopeCalibrator.feed(fifoElement);
+
+            samplesCounter++;
+        }
+    }
+
+    // Continues until the averaged samples are at least the amount specified in
+    // the configuration
+    while (samplesCounter < minGyroSamplesForCalibration)
+    {
+        // Wait for another sample
+        miosix::Thread::sleep(100);
+
+        // Read the fifo and feed the gyroscope data to the calibrator
+        fifoSize = bmx160->getLastFifoSize();
+        for (uint8_t i = 0; i < fifoSize; i++)
+        {
+            fifoElement = bmx160->getFifoElement(i);
+
+            if (fifoElement.gyro_timestamp > gyroTimestamp)
+            {
+                gyroTimestamp = fifoElement.gyro_timestamp;
+                gyroscopeCalibrator.feed(fifoElement);
+
+                samplesCounter++;
+            }
+        }
+    }
+
+    // Compute and save the calibration results
+    {
+        miosix::PauseKernelLock lock;
+        gyroscopeCorrector = gyroscopeCalibrator.computeResult();
+    }
+
+    // Print the calibraton data
+    gyroscopeCorrector >> gyroscopeCorrectionParameters;
+    LOG_INFO(logger, "Gyroscope bias vector from calibration\n");
+    LOG_INFO(logger, "b = [    {: >2.5f}    {: >2.5f}    {: >2.5f}    ]\n\n",
+             gyroscopeCorrectionParameters(0), gyroscopeCorrectionParameters(1),
+             gyroscopeCorrectionParameters(2));
+
+    return true;
+}
+
+BMX160GyroscopeCalibrationBiases BMX160WithCorrection::getGyroscopeBiases()
+{
+    return BMX160GyroscopeCalibrationBiases{gyroscopeCorrectionParameters(0),
+                                            gyroscopeCorrectionParameters(1),
+                                            gyroscopeCorrectionParameters(2)};
+}
+
+BMX160CorrectionParameters
+BMX160WithCorrection::readCorrectionParametersFromFile(const char* fileName)
+{
+    BMX160CorrectionParameters correctionParameters;
+    std::ifstream input(fileName);
+
+    // Ignore header line (csv header)
+    input.ignore(1000, '\n');
+
+    correctionParameters.read(input);
+
+    return correctionParameters;
+}
+
+BMX160WithCorrectionData BMX160WithCorrection::sampleImpl()
+{
+    if (!bmx160)
+    {
+        LOG_ERR(logger, "Driver doesn't point to valid sensor");
+        return BMX160WithCorrectionData{};
+    }
+
+    Vector3f avgAccel{0, 0, 0}, avgMag{0, 0, 0}, avgGyro{0, 0, 0}, vec;
+    uint64_t accelTimestamp = 0, magTimestamp = 0, gyroTimestamp = 0;
+    uint8_t fifoSize, numAccel = 0, numMag = 0, numGyro = 0;
+    BMX160Data fifoElement;
+    BMX160WithCorrectionData result;
+
+    fifoSize = bmx160->getLastFifoSize();
+
+    // Read all data in the fifo
+    for (int i = 0; i < fifoSize; i++)
+    {
+        fifoElement = bmx160->getFifoElement(i);
+
+        // Read acceleration data
+        if (fifoElement.accel_timestamp > accelTimestamp)
+        {
+            static_cast<AccelerometerData>(fifoElement) >> vec;
+            avgAccel += vec;
+
+            accelTimestamp = fifoElement.accel_timestamp;
+            numAccel++;
+        }
+
+        // Read magnetometer data
+        if (fifoElement.mag_timestamp > magTimestamp)
+        {
+            static_cast<MagnetometerData>(fifoElement) >> vec;
+            avgMag += vec;
+
+            magTimestamp = fifoElement.mag_timestamp;
+            numMag++;
+        }
+
+        // Read gyroscope data
+        if (fifoElement.gyro_timestamp > gyroTimestamp)
+        {
+            static_cast<GyroscopeData>(fifoElement) >> vec;
+            avgGyro += vec;
+
+            gyroTimestamp = fifoElement.gyro_timestamp;
+            numGyro++;
+        }
+    }
+
+    // Average the samples
+    if (numAccel == 0)
+        static_cast<AccelerometerData>(bmx160->getLastSample()) >> avgAccel;
+    else
+        avgAccel /= numAccel;
+
+    if (numMag == 0)
+        static_cast<MagnetometerData>(bmx160->getLastSample()) >> avgMag;
+    else
+        avgMag /= numMag;
+
+    if (numGyro == 0)
+        static_cast<GyroscopeData>(bmx160->getLastSample()) >> avgGyro;
+    else
+        avgGyro /= numGyro;
+
+    static_cast<AccelerometerData&>(result) << avgAccel;
+    static_cast<MagnetometerData&>(result) << avgMag;
+    static_cast<GyroscopeData&>(result) << avgGyro;
+
+    // Correct the averaged measurements
+    AccelerometerData acc = accelerometerCorrector.correct(result);
+    result                = acc;
+    MagnetometerData mag  = magnetometerCorrector.correct(result);
+    result                = mag;
+    GyroscopeData gyro;
+    gyro   = gyroscopeCorrector.correct(result);
+    result = gyro;
+
+    // Get the timestamp of the newest value in fifo
+    result.accel_timestamp = fifoElement.accel_timestamp;
+    result.mag_timestamp   = fifoElement.accel_timestamp;
+    result.gyro_timestamp  = fifoElement.accel_timestamp;
+
+    result = rotateAxis(result);
+
+    return result;
+}
+
+BMX160WithCorrectionData BMX160WithCorrection::rotateAxis(
+    BMX160WithCorrectionData data)
+{
+    // Rotate the axes according to the given rotation
+
+    // Accelerometer
+    AccelerometerData accData = data;
+    Vector3f accDataVector;
+    accData >> accDataVector;
+    accDataVector = rotation.getMatrix() * accDataVector;
+    accData << accDataVector;
+
+    // Gyroscope
+    GyroscopeData gyrData = data;
+    Vector3f gyrDataVector;
+    gyrData >> gyrDataVector;
+    gyrDataVector = rotation.getMatrix() * gyrDataVector;
+    gyrData << gyrDataVector;
+
+    // Magnetometer
+    MagnetometerData magData = data;
+    Vector3f magDataVector;
+    magData >> magDataVector;
+    magDataVector = rotation.getMatrix() * magDataVector;
+    magData << magDataVector;
+
+    data = accData;
+    data = gyrData;
+    data = magData;
+
+    return data;
+}
diff --git a/src/shared/sensors/BMX160/BMX160WithCorrection.h b/src/shared/sensors/BMX160/BMX160WithCorrection.h
new file mode 100644
index 0000000000000000000000000000000000000000..47738c3788bc8f4ad8f6840e025af45be1dd8997
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160WithCorrection.h
@@ -0,0 +1,131 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, Luca Conterio, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/calibration/BiasCalibration.h>
+#include <sensors/calibration/SixParameterCalibration.h>
+
+#include "BMX160WithCorrectionData.h"
+
+/**
+ * Holds correction parameters for BMX160.
+ */
+struct BMX160CorrectionParameters
+{
+    Matrix<float, 3, 2> accelParams, magnetoParams;
+    int minGyroSamplesForCalibration = 0;
+
+    BMX160CorrectionParameters();
+
+    static std::string header();
+
+    void read(std::istream& inputStream);
+
+    void print(std::ostream& outputStream) const;
+};
+
+/**
+ * @brief Driver for BMX160 with calibration.
+ *
+ * Gets samples from a BMX160 and applies a specified correction and rotation.
+ * It also calibrates the gyroscope.
+ */
+class BMX160WithCorrection : public Sensor<BMX160WithCorrectionData>
+{
+public:
+    /**
+     * @param bmx160_ already initialized bmx.
+     * @param correctionParameters correction parameter to apply.
+     * @param rotation_ axis rotation.
+     */
+    BMX160WithCorrection(BMX160* bmx160_,
+                         BMX160CorrectionParameters correctionParameters,
+                         AxisOrthoOrientation rotation_);
+
+    /**
+     * Constructor without rotation, no rotation will be applied.
+     *
+     * @param bmx160_ already initialized bmx.
+     * @param correctionParameters correction parameter to apply.
+     */
+    BMX160WithCorrection(BMX160* bmx160_,
+                         BMX160CorrectionParameters correctionParameters);
+
+    /**
+     * @brief Constructor without correction nor rotation, no correciton and
+     * rotation will be applied.
+     *
+     * @param bmx160_ correction parameter to apply.
+     */
+    BMX160WithCorrection(BMX160* bmx160_);
+
+    bool init() override;
+
+    bool selfTest() override;
+
+    /**
+     * @brief Performs the gyroscope calibration.
+     *
+     * The gyroscope calibration consists in averaging some samples to measure
+     * the bias.
+     * This function is intended to run while another thread samples the bmx at
+     * at least 10Hz.
+     */
+    bool calibrate();
+
+    /**
+     * @brief Utility function to read correction parameters from file.
+     */
+    static BMX160CorrectionParameters readCorrectionParametersFromFile(
+        const char* fileName);
+
+    /**
+     * @return Gyroscope calibration biases.
+     */
+    BMX160GyroscopeCalibrationBiases getGyroscopeBiases();
+
+private:
+    BMX160WithCorrectionData sampleImpl() override;
+
+    /**
+     * @brief Rotates data axes as specified.
+     */
+    BMX160WithCorrectionData rotateAxis(BMX160WithCorrectionData data);
+
+    BMX160* bmx160;
+
+    int minGyroSamplesForCalibration = 200;
+
+    AxisOrthoOrientation rotation = {Direction::POSITIVE_X,
+                                     Direction::POSITIVE_Y};
+
+    SixParameterCorrector<AccelerometerData> accelerometerCorrector;
+    SixParameterCorrector<MagnetometerData> magnetometerCorrector;
+    BiasCorrector<GyroscopeData> gyroscopeCorrector{};
+
+    Vector3f gyroscopeCorrectionParameters;
+
+    PrintLogger logger = Logging::getLogger("bmx160withcorrection");
+};
\ No newline at end of file
diff --git a/src/shared/sensors/BMX160/BMX160WithCorrectionData.h b/src/shared/sensors/BMX160/BMX160WithCorrectionData.h
new file mode 100644
index 0000000000000000000000000000000000000000..bda3331742fb2396785850af100f32d6fbef7d6d
--- /dev/null
+++ b/src/shared/sensors/BMX160/BMX160WithCorrectionData.h
@@ -0,0 +1,104 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Luca Conterio, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/BMX160/BMX160Data.h"
+
+struct BMX160WithCorrectionData : public BMX160Data
+{
+    BMX160WithCorrectionData() : BMX160Data() {}
+
+    BMX160WithCorrectionData(const BMX160Data& data)
+        : BMX160WithCorrectionData(data, data, data)
+    {
+    }
+
+    BMX160WithCorrectionData(AccelerometerData acc, GyroscopeData gyr,
+                             MagnetometerData mag)
+        : BMX160Data(acc, gyr, mag)
+    {
+    }
+
+    BMX160WithCorrectionData& operator=(AccelerometerData acc)
+    {
+        accel_x = acc.accel_x;
+        accel_y = acc.accel_y;
+        accel_z = acc.accel_z;
+        return *this;
+    }
+
+    BMX160WithCorrectionData& operator=(GyroscopeData gyr)
+    {
+        gyro_x = gyr.gyro_x;
+        gyro_y = gyr.gyro_y;
+        gyro_z = gyr.gyro_z;
+        return *this;
+    }
+
+    BMX160WithCorrectionData& operator=(MagnetometerData mag)
+    {
+        mag_x = mag.mag_x;
+        mag_y = mag.mag_y;
+        mag_z = mag.mag_z;
+        return *this;
+    }
+
+    static std::string header()
+    {
+        return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x,"
+               "gyro_y,"
+               "gyro_z,mag_timestamp,mag_x,mag_y,mag_z\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accel_timestamp << "," << accel_x << "," << accel_y << ","
+           << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y
+           << "," << gyro_z << "," << mag_timestamp << "," << mag_x << ","
+           << mag_y << "," << mag_z << "\n";
+    }
+};
+
+struct BMX160GyroscopeCalibrationBiases
+{
+    float bx;
+    float by;
+    float bz;
+
+    BMX160GyroscopeCalibrationBiases() {}
+
+    BMX160GyroscopeCalibrationBiases(float x, float y, float z)
+        : bx(x), by(y), bz(z)
+    {
+    }
+
+    static std::string header()
+    {
+        return "bias_x,bias_y,bias_z\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << bx << "," << by << "," << bz << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/L3GD20/L3GD20.h b/src/shared/sensors/L3GD20/L3GD20.h
new file mode 100644
index 0000000000000000000000000000000000000000..2de2da52725ac5feda6e0e589b874cf8ffc5ef23
--- /dev/null
+++ b/src/shared/sensors/L3GD20/L3GD20.h
@@ -0,0 +1,348 @@
+
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+#include <miosix.h>
+
+#include <array>
+
+#include "sensors/Sensor.h"
+#include "L3GD20Data.h"
+#include "drivers/spi/SPIDriver.h"
+#include "math/Vec3.h"
+#include "TimestampTimer.h"
+
+using miosix::GpioPin;
+using std::array;
+
+static constexpr uint32_t L3GD20_FIFO_SIZE = 32;
+
+class L3GD20 : public SensorFIFO<L3GD20Data, L3GD20_FIFO_SIZE>
+{
+public:
+    enum class FullScaleRange
+    {
+        FS_250  = 250,
+        FS_500  = 500,
+        FS_2000 = 2000
+    };
+
+    enum class OutPutDataRate
+    {
+        ODR_95  = 95,
+        ODR_190 = 190,
+        ODR_380 = 380,
+        ODR_760 = 760
+    };
+
+    /**
+     * @brief Creates an instance of an L3GD20 sensor
+     *
+     * @param    bus SPI bus the sensor is connected to
+     * @param    cs Chip Select GPIO
+     * @param    range Full Scale Range (See datasheet)
+     * @param    odr Output Data Rate (See datasheet)
+     * @param    cutoff_freq Low pass filter cutoff frequency (See datasheet)
+     */
+    L3GD20(SPIBusInterface& bus, GpioPin cs,
+           FullScaleRange range = FullScaleRange::FS_250,
+           OutPutDataRate odr   = OutPutDataRate::ODR_95,
+           uint8_t cutoff_freq  = 0x03)
+        : L3GD20(bus, cs, {}, range, odr, cutoff_freq)
+    {
+        // Configure SPI
+        spislave.config.clock_div = SPIClockDivider::DIV32;
+    }
+
+    /**
+     * @brief Creates an instance of an L3GD20 sensor
+     *
+     * @param    bus SPI bus the sensor is connected to
+     * @param    cs Chip Select GPIO
+     * @param    cfg Custom SPI bus configuration
+     * @param    range Full Scale Range (See datasheet)
+     * @param    odr Output Data Rate (See datasheet)
+     * @param    cutoff_freq Low pass filter cutoff selector (See datasheet)
+     */
+    L3GD20(SPIBusInterface& bus, GpioPin cs, SPIBusConfig cfg,
+           FullScaleRange range = FullScaleRange::FS_250,
+           OutPutDataRate odr   = OutPutDataRate::ODR_95,
+           uint8_t cutoff_freq  = 0x03)
+        : spislave(bus, cs, cfg), fs(range), odr(odr), cutoff_freq(cutoff_freq)
+    {
+        switch (fs)
+        {
+            case FullScaleRange::FS_250:
+                sensitivity = SENSITIVITY_250;
+                break;
+            case FullScaleRange::FS_500:
+                sensitivity = SENSITIVITY_500;
+                break;
+            case FullScaleRange::FS_2000:
+                sensitivity = SENSITIVITY_2000;
+                break;
+        }
+    }
+
+    /**
+     * @brief Enables storing samples in a FIFO, must be called before init().
+     *
+     * @param fifo_watermark How many samples in the FIFO when the fifo
+     * watermark input is generated on INT2.
+     */
+    void enableFifo(unsigned int fifo_watermark)
+    {
+        fifo_enabled         = true;
+        this->fifo_watermark = fifo_watermark;
+    }
+
+    bool init()
+    {
+        SPITransaction spi(spislave);
+
+        uint8_t whoami = spi.read(REG_WHO_AM_I);
+
+        if (whoami != WHO_AM_I_VAL)
+        {
+            printf("WAMI: %d\n", whoami);
+            last_error = SensorErrors::INVALID_WHOAMI;
+            return false;
+        }
+
+        switch (fs)
+        {
+            case FullScaleRange::FS_250:
+                spi.write(REG_CTRL4, 0);
+                break;
+            case FullScaleRange::FS_500:
+                spi.write(REG_CTRL4, (uint8_t)(1 << 4));
+                break;
+            case FullScaleRange::FS_2000:
+                spi.write(REG_CTRL4, (uint8_t)(2 << 4));
+                break;
+            default:
+                break;
+        }
+
+        if (fifo_enabled)
+        {
+            // Enable fifo
+            spi.write(REG_CTRL5, 1 << 6);
+
+            // Set watermark level to fifo_watermark samples
+            uint8_t fifo_ctrl = fifo_watermark;
+
+            // Set fifo to STREAM mode
+            fifo_ctrl |= 0x02 << 5;
+
+            spi.write(REG_FIFO_CTRL, fifo_ctrl);
+
+            // Enable FIFO watermark interrupt on INT2
+            spi.write(REG_CTRL3, 0x04);
+        }
+        else
+        {
+            // Enable DRDY interrupt on INT2
+            spi.write(REG_CTRL3, 0x08);
+        }
+
+        // Enter normal mode, enable output
+        uint8_t ctrl1 = 0x0F;
+
+        // Configure cutoff frequency
+        ctrl1 |= (cutoff_freq & 0x03) << 4;
+
+        // Configure ODR
+        switch (odr)
+        {
+            case OutPutDataRate::ODR_95:
+                break;
+            case OutPutDataRate::ODR_190:
+                ctrl1 |= 1 << 6;
+                break;
+            case OutPutDataRate::ODR_380:
+                ctrl1 |= 2 << 6;
+                break;
+            case OutPutDataRate::ODR_760:
+                ctrl1 |= 3 << 6;
+                break;
+        }
+
+        spi.write(REG_CTRL1, ctrl1);
+
+        return true;
+    }
+
+    bool selfTest() { return true; }
+
+    L3GD20Data sampleImpl()
+    {
+        if (!fifo_enabled)  // FIFO not enabled
+        {
+            // Timestamp of the last sample
+            uint64_t last_sample_ts;
+
+            // Read output data registers (X, Y, Z)
+            {
+                SPITransaction spi(spislave);
+                spi.read(REG_OUT_X_L | 0x40, buf, 6);
+            }
+
+            {
+                // Disable interrupts and copy the last sample locally, as a new
+                // interrupt may come just as we are reading it and causing a
+                // race condition.
+                miosix::FastInterruptDisableLock dLock;
+                last_sample_ts = last_interrupt_us;
+            }
+
+            int16_t x = buf[0] | buf[1] << 8;
+            int16_t y = buf[2] | buf[3] << 8;
+            int16_t z = buf[4] | buf[5] << 8;
+            // printf("%02X,%02X,%02X\n", x, y, z);
+
+            Vec3 rads    = toRadiansPerSecond(x, y, z);
+            last_fifo[0] = {last_sample_ts, rads.getX(), rads.getY(),
+                            rads.getZ()};
+        }
+
+        else  // FIFO is enabled
+        {
+            SPITransaction spi(spislave);
+            // Read last fifo level
+            uint8_t fifo_src   = spi.read(REG_FIFO_SRC);
+            uint8_t ovr        = (fifo_src & 0x40) >> 7;  // Overrun bit
+            uint8_t fifo_level = (fifo_src & 0x1F) + ovr;
+
+            // Read fifo
+            spi.read(REG_OUT_X_L | 0x40, buf, fifo_level * 6);
+
+            uint64_t dt = dt_interrupt / last_fifo_level;
+
+            uint8_t duplicates = 0;
+            for (uint8_t i = 0; i < fifo_level; ++i)
+            {
+                bool rmv;
+                // Check for duplicates: there seems to be a bug where the
+                // sensor occasionaly shifts out the same sample two times in a
+                // row: discard one
+                if (i < fifo_level - 1)
+                {
+                    rmv = true;
+                    for (uint8_t j = 0; j < 6; ++j)
+                    {
+                        if (buf[i * 6 + j] != buf[(i + 1) * 6 + j])
+                        {
+                            rmv = false;
+                            break;
+                        }
+                    }
+                }
+                else
+                {
+                    rmv = false;
+                }
+
+                if (rmv)
+                {
+                    // Skip this sample;
+                    ++duplicates;
+                    continue;
+                }
+
+                // Assign a timestamp to each sample in the FIFO based of the
+                // timestamp of the FIFO_WATERMARKth sample and the delta
+                // between the watermark interrupts (evenly distribute the
+                // timestamp between the two times)
+                // Ignoring possible duplicates, the timestamp of the ith sample
+                // in the fifo is:
+                // ts(i) = ts(fifo_watermark) + (i - fifo_watermark)*dt;
+                Vec3 rads =
+                    toRadiansPerSecond(buf[i * 6] | buf[i * 6 + 1] << 8,
+                                       buf[i * 6 + 2] | buf[i * 6 + 3] << 8,
+                                       buf[i * 6 + 4] | buf[i * 6 + 5] << 8);
+
+                last_fifo[i - duplicates] = L3GD20Data{
+                    last_interrupt_us +
+                        ((int)i - (int)fifo_watermark - (int)duplicates) * dt,
+                    rads.getX(), rads.getY(), rads.getZ()};
+            }
+
+            last_fifo_level = fifo_level - duplicates;
+        }
+
+        return last_fifo[last_fifo_level - 1];
+    }
+
+private:
+    Vec3 toRadiansPerSecond(int16_t x, int16_t y, int16_t z)
+    {
+        return Vec3(x * sensitivity, y * sensitivity, z * sensitivity);
+    }
+
+    static constexpr float SENSITIVITY_250  = 0.00875f;
+    static constexpr float SENSITIVITY_500  = 0.0175f;
+    static constexpr float SENSITIVITY_2000 = 0.070f;
+
+    SPISlave spislave;
+
+    FullScaleRange fs   = FullScaleRange::FS_250;
+    OutPutDataRate odr  = OutPutDataRate::ODR_760;
+    uint8_t cutoff_freq = 0x03;
+
+    bool fifo_enabled           = false;
+    unsigned int fifo_watermark = 24;
+
+    float sensitivity = SENSITIVITY_250;
+
+    uint8_t buf[192];
+
+    constexpr static uint8_t WHO_AM_I_VAL = 212;
+
+    enum RegMap
+    {
+        REG_WHO_AM_I = 0x0F,
+
+        REG_CTRL1 = 0x20,
+        REG_CTRL2 = 0x21,
+        REG_CTRL3 = 0x22,
+        REG_CTRL4 = 0x23,
+        REG_CTRL5 = 0x24,
+
+        REG_REFERENCE = 0x25,
+        REG_OUT_TEMP  = 0x26,
+        REG_STATUS    = 0x27,
+
+        REG_OUT_X_L = 0x28,
+        REG_OUT_X_H = 0x29,
+
+        REG_OUT_Y_L = 0x2A,
+        REG_OUT_Y_H = 0x2B,
+
+        REG_OUT_Z_L = 0x2C,
+        REG_OUT_Z_H = 0x2D,
+
+        REG_FIFO_CTRL = 0x2E,
+        REG_FIFO_SRC  = 0x2F
+    };
+};
\ No newline at end of file
diff --git a/src/shared/sensors/L3GD20/L3GD20Data.h b/src/shared/sensors/L3GD20/L3GD20Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..889510556d4f0bcff2dee034905f150377422f65
--- /dev/null
+++ b/src/shared/sensors/L3GD20/L3GD20Data.h
@@ -0,0 +1,49 @@
+
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct L3GD20Data : public GyroscopeData
+{
+    L3GD20Data() : GyroscopeData{0, 0.0, 0.0, 0.0} {}
+
+    L3GD20Data(uint64_t t, float x, float y, float z)
+        : GyroscopeData{t, x, y, z}
+    {
+    }
+
+    L3GD20Data(GyroscopeData gyr) : GyroscopeData(gyr) {}
+
+    static std::string header()
+    {
+        return "gyro_timestamp,gyro_x,gyro_y,gyro_z\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << gyro_timestamp << "," << gyro_x << "," << gyro_y << "," << gyro_z
+           << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/LIS3DSH/LIS3DSH.h b/src/shared/sensors/LIS3DSH/LIS3DSH.h
new file mode 100644
index 0000000000000000000000000000000000000000..517a744b4eac4f43391a482989c4103c7d0b567f
--- /dev/null
+++ b/src/shared/sensors/LIS3DSH/LIS3DSH.h
@@ -0,0 +1,550 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+#include <math.h>
+
+#include "LIS3DSHData.h"
+#include "drivers/spi/SPIDriver.h"
+#include "sensors/Sensor.h"
+
+/**
+ * Driver for stm32f407vg discovery on-board 3-axis
+ * accelerometer + temperature sensor.
+ *
+ * The sensor is connected to SPI1 using the
+ * following GPIOs: PA5 : clock
+ *                  PA6 : miso
+ *                  PA7 : mosi
+ *                  PE3 : chip select
+ */
+class LIS3DSH : public Sensor<LIS3DSHData>
+{
+public:
+    /**
+     *  @brief Constructor.
+     *
+     *  @param bus          the spi bus.
+     *  @param chip_select  the chip_select for the sensor.
+     *  @param _odr         output data rate for the accelerometer.
+     *                      Default value is 100 Hz.
+     *  @param _bdu         BlockDataUpdate value, continuous or non-continuous
+     *                      update mode. Default value is to update after data
+     *                      has been read (BDU=1).
+     *  @param _full_scale  full scale range (from +/-2g up to +/-16g).
+     *                      Default value is +/-2g.
+     */
+    LIS3DSH(SPIBusInterface& bus, GpioPin chip_select,
+            uint8_t _odr        = OutputDataRate::ODR_100_HZ,
+            uint8_t _bdu        = BlockDataUpdate::UPDATE_AFTER_READ_MODE,
+            uint8_t _full_scale = FullScale::FULL_SCALE_2G)
+        : spi_slave(bus, chip_select, {}), odr(_odr), bdu(_bdu),
+          full_scale(_full_scale)
+    {
+        spi_slave.config.clock_div =
+            SPIClockDivider::DIV64;  // used to set the spi baud rate (maximum
+                                     // is 10 Mhz)
+    }
+
+    /**
+     *  @brief Constructor.
+     *
+     *  @param bus          the spi bus.
+     *  @param chip_select  the chip_select for the sensor.
+     *  @param config       the spi bus configurations.
+     *  @param _odr         output data rate for the accelerometer.
+     *                      Default value is 100 Hz.
+     *  @param _bdu         BlockDataUpdate value, continuous or non-continuous
+     *                      update mode. Default value is to update after data
+     *                      has been read (BDU=1).
+     *  @param _full_scale  full scale range (from +/-2g up to +/-16g).
+     *                      Default value is +/-2g.
+     */
+    LIS3DSH(SPIBusInterface& bus, GpioPin chip_select, SPIBusConfig config,
+            uint8_t _odr        = OutputDataRate::ODR_100_HZ,
+            uint8_t _bdu        = BlockDataUpdate::UPDATE_AFTER_READ_MODE,
+            uint8_t _full_scale = FullScale::FULL_SCALE_2G)
+        : spi_slave(bus, chip_select, config), odr(_odr), bdu(_bdu),
+          full_scale(_full_scale)
+    {
+    }
+
+    /**
+     * @brief Initialize the sensor.
+     *
+     * @return boolean value indicating whether the operation succeded or not
+     */
+    bool init() override
+    {
+        // check if the sensor is already initialized
+        if (initialized)
+        {
+            LOG_WARN(logger, "Already initialized");
+            last_error = SensorErrors::ALREADY_INIT;
+            return false;
+        }
+
+        // check if the sensor is working properly
+        if (!checkWhoAmI())
+        {                  // whoami default value
+            return false;  // sensor correctly initialized
+        }
+
+        SPITransaction spi(spi_slave);
+
+        // set the full scale value in CTRL_REG5
+        uint8_t ctrl_reg5_value = (full_scale << 3);
+        spi.write(CTRL_REG5, ctrl_reg5_value);
+
+        // select the correct sensitivity
+        // for the specified full scale range
+        sensitivity = select_sensitivity();
+
+        // set the output data rate and the BDU in CTRL_REG4
+        // the three least significant bits are enable bits for X, Y and Z axis
+        uint8_t ctrl_reg4_value =
+            (odr << 4) | (bdu << 3) | (7 << 0);  // 7 = 111 -> enable the 3 axis
+        spi.write(CTRL_REG4, ctrl_reg4_value);
+
+        initialized = true;
+
+        return true;
+    }
+
+    /**
+     * @brief Check if the sensor is working.
+     *
+     * @return boolean indicating whether the sensor is correctly working or not
+     */
+    bool selfTest() override
+    {
+        // check if the sensor is initialized
+        if (!initialized)
+        {
+            LOG_WARN(logger,
+                     "Unable to perform selftest, sensor not initialized");
+            last_error = SensorErrors::NOT_INIT;
+            return false;
+        }
+
+        const uint8_t num_samples = 5;  // number of samples to be used
+        // vectors for storing samples, both
+        // in self-test and no-self-test modes
+        float X_ST[num_samples]    = {0};
+        float Y_ST[num_samples]    = {0};
+        float Z_ST[num_samples]    = {0};
+        float X_NO_ST[num_samples] = {0};
+        float Y_NO_ST[num_samples] = {0};
+        float Z_NO_ST[num_samples] = {0};
+        // vectors containing avg values for each axis
+        float AVG_ST[3]    = {0};  // one element per axis
+        float AVG_NO_ST[3] = {0};  // one element per axis
+
+        // set output data rate to 50 hz
+        uint8_t ctrl_reg4_value =
+            (OutputDataRate::ODR_100_HZ << 4) |
+            (BlockDataUpdate::UPDATE_AFTER_READ_MODE << 3) | (7 << 0);
+
+        {
+            SPITransaction spi(spi_slave);
+            spi.write(CTRL_REG4, ctrl_reg4_value);
+        }
+
+        // set full scale to default value +/-2g
+        // enable the self-test mode with positive sign
+        uint8_t ctrl_reg5_value = (FullScale::FULL_SCALE_2G << 3) | (1 << 1);
+
+        {
+            SPITransaction spi(spi_slave);
+            spi.write(CTRL_REG5, ctrl_reg5_value);
+        }
+
+        // read samples in self-test positive sign mode
+        for (uint8_t i = 0; i < num_samples; i++)
+        {
+            AccelerometerData accel_data = readAccelData();
+            X_ST[i]                      = accel_data.accel_x;
+            Y_ST[i]                      = accel_data.accel_y;
+            Z_ST[i]                      = accel_data.accel_z;
+            miosix::Thread::sleep(10);
+        }
+        // reset the self-test bits
+        ctrl_reg5_value &= ~(3 << 1);
+        // normal mode with full scale range +/-2g
+        ctrl_reg5_value |= (FULL_SCALE_2G << 3);
+
+        {
+            SPITransaction spi(spi_slave);
+            spi.write(CTRL_REG5, ctrl_reg5_value);
+        }
+
+        // read samples in normal mode
+        for (uint8_t i = 0; i < num_samples; i++)
+        {
+            AccelerometerData accel_data = readAccelData();
+            X_NO_ST[i]                   = accel_data.accel_x;
+            Y_NO_ST[i]                   = accel_data.accel_y;
+            Z_NO_ST[i]                   = accel_data.accel_z;
+            miosix::Thread::sleep(10);
+        }
+        // compute averages vectors:
+        // they contain one element for each axis
+        // (position 0 for x, 1 for y and 2 for z)
+        // AVG_ST    : for self-test samples
+        // AVG_NO_ST : for normal mode samples
+        for (uint8_t i = 0; i < num_samples; i++)
+        {
+            AVG_ST[0] += X_ST[i];
+            AVG_ST[1] += Y_ST[i];
+            AVG_ST[2] += Z_ST[i];
+            AVG_NO_ST[0] += X_NO_ST[i];
+            AVG_NO_ST[1] += Y_NO_ST[i];
+            AVG_NO_ST[2] += Z_NO_ST[i];
+        }
+        for (uint8_t i = 0; i < 3; i++)
+        {
+            AVG_ST[i] /= num_samples;
+            AVG_NO_ST[i] /= num_samples;
+        }
+
+        // Reset registers values with the ones
+        // specified in the constructor:
+        // set the output data rate value in CTRL_REG4
+        ctrl_reg4_value = (odr << 4) | (bdu << 3) | (7 << 0);
+
+        {
+            SPITransaction spi(spi_slave);
+            spi.write(CTRL_REG4, ctrl_reg4_value);
+        }
+        // set the full scale value in CTRL_REG5
+        ctrl_reg5_value = (full_scale << 3);  // normal mode
+
+        {
+            SPITransaction spi(spi_slave);
+            spi.write(CTRL_REG5, ctrl_reg5_value);
+        }
+
+        float delta[3] = {0};
+        for (uint8_t i = 0; i < 3; i++)
+        {
+            delta[i] = fabs(AVG_NO_ST[i] - AVG_ST[i]);
+        }
+
+        LOG_INFO(logger,
+                 "Selftest: delta[x] = {}, delta[y] = {}, delta[z] = {}",
+                 delta[0], delta[1], delta[2]);
+
+        // check that the averages differences
+        // do not exceed maximum tolerance
+        if ((delta[0] >
+             SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) ||
+            (delta[1] >
+             SELF_TEST_DIFF_X_Y + SELF_TEST_DIFF_X_Y * SELF_TEST_TOLERANCE) ||
+            (delta[2] >
+             SELF_TEST_DIFF_Z + SELF_TEST_DIFF_Z * SELF_TEST_TOLERANCE))
+        {
+            LOG_ERR(logger, "Selftest failed");
+            last_error = SensorErrors::SELF_TEST_FAIL;
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @brief Output data rate allowed values (4 bits).
+     */
+    enum OutputDataRate
+    {
+        ODR_POWER_DOWN = 0,  // 0000
+        ODR_3_125_HZ   = 1,  // 0001, 3.125 Hz
+        ODR_6_25_HZ    = 2,  // 0010, 6.25  Hz
+        ODR_12_5_HZ    = 3,  // 0011, 12.5  Hz
+        ODR_25_HZ      = 4,  // 0100
+        ODR_50_HZ      = 5,  // 0101
+        ODR_100_HZ     = 6,  // 0110, default value
+        ODR_400_HZ     = 7,  // 0111
+        ODR_800_HZ     = 8,  // 1000
+        ODR_1600_HZ    = 9   // 1001
+    };
+
+    /**
+     * @brief Full scale range allowed values (3 bits).
+     */
+    enum FullScale
+    {
+        FULL_SCALE_2G  = 0,  // 000, +/- 2g
+        FULL_SCALE_4G  = 1,  // 001, +/- 4g
+        FULL_SCALE_6G  = 2,  // 010, +/- 6g
+        FULL_SCALE_8G  = 3,  // 011, +/- 8g
+        FULL_SCALE_16G = 4,  // 100  +/- 16g
+    };
+
+    /**
+     * @brief Block data update allowed modes (1 bit).
+     */
+    enum BlockDataUpdate
+    {
+        CONTINUOUS_UPDATE_MODE = 0,  // continuous update of accelerometer data
+        UPDATE_AFTER_READ_MODE =
+            1  // values updated only when MSB and LSB are read (recommended)
+    };
+
+private:
+    /**
+     * @brief Read new data from the accelerometer.
+     *        Acceleretions are returned in g.
+     *
+     * @return boolean value indicating whether the operation succeded or not
+     */
+    LIS3DSHData sampleImpl() override
+    {
+        // check if the sensor is initialized
+        if (!initialized)
+        {
+            LOG_WARN(logger, "Unable to sample, sensor not initialized");
+            last_error = SensorErrors::NOT_INIT;
+            return last_sample;
+        }
+
+        AccelerometerData accel_data = readAccelData();
+        TemperatureData temp_data    = readTemperature();
+
+        if (last_error != SensorErrors::NO_ERRORS)
+        {
+            return last_sample;
+        }
+        else
+        {
+            return LIS3DSHData(accel_data, temp_data);
+        }
+    }
+
+    /**
+     * @brief Read accelrometer data.
+     *
+     * @return the read accelrometer sample
+     */
+    AccelerometerData readAccelData()
+    {
+        AccelerometerData accel_data;
+
+        SPITransaction spi(spi_slave);
+
+        // read the sensor's status register
+        uint8_t status = spi.read(STATUS);
+
+        if (status & 0x08)
+        {  // bit 3 of status set to 1 (new data available)
+            if (status & 0x80)
+            {  // bit 7 of status set to 1 (some data overwritten)
+
+                accel_data.accel_timestamp = TimestampTimer::getTimestamp();
+
+                // read acceleration on X
+                int8_t accel_L = spi.read(OUT_X_L);
+                int8_t accel_H = spi.read(OUT_X_H);
+                accel_data.accel_x =
+                    static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
+
+                // read acceleration on Y
+                accel_L = spi.read(OUT_Y_L);
+                accel_H = spi.read(OUT_Y_H);
+                accel_data.accel_y =
+                    static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
+
+                // read acceleration on Z
+                accel_L = spi.read(OUT_Z_L);
+                accel_H = spi.read(OUT_Z_H);
+                accel_data.accel_z =
+                    static_cast<float>(combine(accel_H, accel_L)) * sensitivity;
+
+                last_error = SensorErrors::NO_ERRORS;
+            }
+        }
+        else
+        {
+            last_error = SensorErrors::NO_NEW_DATA;
+        }
+
+        return accel_data;
+    }
+
+    /**
+     * @brief Read temperature data.
+     *
+     * @return the read temperature sample
+     */
+    TemperatureData readTemperature()
+    {
+        SPITransaction spi(spi_slave);
+
+        // the temperature is given as a 8-bits integer (in 2-complement)
+        int8_t t = spi.read(OUT_T);
+
+        return TemperatureData{
+            TimestampTimer::getTimestamp(),
+            t + TEMPERATURE_REF};  // add the 'zero' of the temperature sensor
+    }
+
+    /**
+     * @brief Check that the WHO_AM_I register
+     *        contains the correct value.
+     *
+     * @return boolean value indicating whether the value read
+     *          from the WHO_AM_I register is correct or not
+     */
+    bool checkWhoAmI()
+    {
+        SPITransaction spi(spi_slave);
+
+        // check the WHO_AM_I_REG register
+        uint8_t who_am_i_value = spi.read(WHO_AM_I_REG);
+        if (who_am_i_value == WHO_AM_I_DEFAULT_VALUE)
+        {
+            LOG_DEBUG(logger, "Correct WHO_AM_I value");
+            return true;
+        }
+        else
+        {
+            LOG_ERR(logger, "Wrong WHO_AM_I value, got {} instead of {}",
+                    who_am_i_value, WHO_AM_I_DEFAULT_VALUE);
+            last_error = SensorErrors::INVALID_WHOAMI;
+        }
+
+        return false;
+    }
+
+    /**
+     * @brief Combine low and high bits in a single number.
+     *
+     * @param msb   the most significatn bits
+     * @param lsb   the least significant bits
+     * @return MSB and LSB combined in one value
+     */
+    int16_t combine(uint8_t msb, uint8_t lsb) { return (msb << 8) | lsb; }
+
+    /**
+     * @brief Given the requested full scale range, select the correct
+     * sensitivity value.
+     *
+     * @return the sensitivity value corresponding to the requested full scale
+     * range
+     */
+    float select_sensitivity()
+    {
+        float s;
+        switch (full_scale)
+        {
+            case FULL_SCALE_2G:
+                s = sensitivity_values[FullScale::FULL_SCALE_2G];
+                break;
+            case FULL_SCALE_4G:
+                s = sensitivity_values[FullScale::FULL_SCALE_4G];
+                break;
+            case FULL_SCALE_6G:
+                s = sensitivity_values[FullScale::FULL_SCALE_6G];
+                break;
+            case FULL_SCALE_8G:
+                s = sensitivity_values[FullScale::FULL_SCALE_8G];
+                break;
+            case FULL_SCALE_16G:
+                s = sensitivity_values[FullScale::FULL_SCALE_16G];
+                break;
+            default:
+                LOG_ERR(logger, "Invalid full scale range given, using +/-2g");
+                this->full_scale = FullScale::FULL_SCALE_2G;
+                s                = sensitivity_values[FullScale::FULL_SCALE_2G];
+                break;
+        }
+        return s;
+    }
+
+    /**
+     * @brief Registers' addresses definition.
+     */
+    enum REG
+    {
+
+        // whoami register
+        WHO_AM_I_REG = 0x0F,
+
+        // control registers for the accelerometer
+        CTRL_REG4 =
+            0x20,  // control register to set accelerometer's ODR and BDU
+        CTRL_REG1 = 0x21,  // state Machine 1 interrupt configuration register
+        CTRL_REG2 = 0x22,  // state Machine 2 interrupt configuration register
+        CTRL_REG3 = 0x23,
+        CTRL_REG5 =
+            0x24,  // control register to set the accelerometer full scale
+                   // range, anti-aliansing filter and self-test enable
+        CTRL_REG6 = 0x25,
+
+        // status register
+        STATUS = 0x27,
+
+        // accelerometer output registers
+        // for x, y and z axis
+        // (low and high bits in separate registers)
+        OUT_X_L = 0x28,
+        OUT_X_H = 0x29,
+        OUT_Y_L = 0x2A,
+        OUT_Y_H = 0x2B,
+        OUT_Z_L = 0x2C,
+        OUT_Z_H = 0x2D,
+
+        // temperature output register
+        OUT_T = 0x0C,
+    };
+
+    SPISlave spi_slave;
+
+    bool initialized = false;  // whether the sensor has been initialized or not
+
+    uint8_t odr;         // output data rate, default 100 Hz
+    uint8_t bdu;         // continuous or block after update
+    uint8_t full_scale;  // full scale value, default +/- 2g
+
+    /**
+     * @brief Sensitivity values corresponding to full scale range allowed
+     * values.
+     */
+    const float sensitivity_values[5] = {0.06, 0.12, 0.18, 0.24, 0.73};
+
+    float sensitivity =
+        sensitivity_values[FullScale::FULL_SCALE_2G];  // default sensitivity
+                                                       // value
+
+    const uint8_t WHO_AM_I_DEFAULT_VALUE = 63;  // 00111111
+
+    const float TEMPERATURE_REF = 25.0f;  // temperature sensor 'zero'/reference
+                                          // value (value 0x00 from the sensor
+                                          // corresponds to 25 degrees celsius)
+
+    const float SELF_TEST_DIFF_X_Y  = 140.0f;  // 140 mg
+    const float SELF_TEST_DIFF_Z    = 590.0f;  // 590 mg
+    const float SELF_TEST_TOLERANCE = 0.3f;
+
+    PrintLogger logger = Logging::getLogger("lis3dsh");
+};
diff --git a/src/shared/sensors/LIS3DSH/LIS3DSHData.h b/src/shared/sensors/LIS3DSH/LIS3DSHData.h
new file mode 100644
index 0000000000000000000000000000000000000000..4705c527faf0a46806f5fec32bc9e6324e5acfbc
--- /dev/null
+++ b/src/shared/sensors/LIS3DSH/LIS3DSHData.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct LIS3DSHData : public AccelerometerData, public TemperatureData
+{
+    LIS3DSHData() : AccelerometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0}
+    {
+    }
+
+    LIS3DSHData(uint64_t t, float x, float y, float z, float temp)
+        : AccelerometerData{t, x, y, z},
+          TemperatureData{t, temp}
+    {
+    }
+
+    LIS3DSHData(AccelerometerData acc, TemperatureData temp)
+        : AccelerometerData{acc.accel_timestamp, acc.accel_x, acc.accel_y, acc.accel_z},
+          TemperatureData{temp.temp_timestamp, temp.temp}
+    {
+    }
+
+    static std::string header()
+    {
+        return "accel_timestamp,accel_x,accel_y,accel_z,temp_timestamp,temp\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accel_timestamp << "," << accel_x << "," << accel_y << "," << accel_z
+           << "," << temp_timestamp << "," << temp << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/LIS3MDL/LIS3MDL.h b/src/shared/sensors/LIS3MDL/LIS3MDL.h
new file mode 100644
index 0000000000000000000000000000000000000000..51eb11f843a21922b56cf58d5249b8c7bc857019
--- /dev/null
+++ b/src/shared/sensors/LIS3MDL/LIS3MDL.h
@@ -0,0 +1,647 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef SRC_SHARED_SENSORS_LIS3MDL_LIS3MDL_H
+#define SRC_SHARED_SENSORS_LIS3MDL_LIS3MDL_H
+
+#include <Common.h>
+#include <diagnostic/PrintLogger.h>
+
+#include "LIS3MDLData.h"
+#include "drivers/spi/SPIDriver.h"
+#include "miosix.h"
+#include "sensors/Sensor.h"
+
+/**
+ * Driver for LIS3MDL, a three-axis magnetic sensor.
+ */
+class LIS3MDL : public Sensor<LIS3MDLData>
+{
+public:
+    /**
+     * Constants for Output Data Rate configuration.
+     *
+     * Note: constants values already include
+     * axis operative mode selection and FAST_ODR
+     * options, so they are ready to be put inside
+     * CTRL_REG1 along with TEMP_EN and ST
+     */
+    enum ODR : uint8_t
+    {
+        ODR_0_625_HZ = 0x00,  //!< 0.625 Hz
+        ODR_1_25_HZ  = 0x04,  //!<  1.25 Hz
+        ODR_2_5_HZ   = 0x08,  //!<   2.5 Hz
+        ODR_5_HZ     = 0x0c,  //!<     5 Hz
+        ODR_10_HZ    = 0x10,  //!<    10 Hz
+        ODR_20_HZ    = 0x14,  //!<    20 Hz
+        ODR_40_HZ    = 0x18,  //!<    40 Hz
+        ODR_80_HZ    = 0x1c,  //!<    80 Hz
+
+        ODR_155_HZ  = 0x62,  //!<    155 Hz
+        ODR_300_HZ  = 0x42,  //!<    300 Hz
+        ODR_560_HZ  = 0x22,  //!<    560 Hz
+        ODR_1000_HZ = 0x02,  //!<   1000 Hz
+
+        /**
+         * Constant used by the driver: this bit
+         * is 1 for ODR > 80 Hz, set to 0 otherwise.
+         */
+        FAST_ODR_BIT = 0x02,
+    };
+
+    enum FullScale : uint8_t
+    {
+        FS_4_GAUSS  = 0x00,  //!<  +/- 4  gauss
+        FS_8_GAUSS  = 0x20,  //!<  +/- 8  gauss
+        FS_12_GAUSS = 0x40,  //!<  +/- 12 gauss
+        FS_16_GAUSS = 0x60,  //!<  +/- 16 gauss
+    };
+
+    /**
+     * @brief Operative mode constants
+     *
+     * Operative mode (that is power consumpion) options.
+     * Higher power implies better performance.
+     */
+    enum OperativeMode : uint8_t
+    {
+        OM_LOW_POWER        = 0x0,
+        OM_MEDIUM_POWER     = 0x1,
+        OM_HIGH_POWER       = 0x2,
+        OM_ULTRA_HIGH_POWER = 0x3,
+    };
+
+    /**
+     * @brief Sensor configuration
+     *
+     * This struct contains all the settings the user
+     * is able to modify with the help of the driver.
+     * They are applied in the constructor of LIS3MDL class
+     * and on each call of LIS3MDL::applyConfig()
+     */
+    struct Config
+    {
+        /**
+         * @brief Constructor
+         * Creates an instance with the default settings.
+         */
+        Config() {}
+
+        /**
+         * Full scale setting
+         * @see LIS3MDL::FullScale
+         */
+        FullScale scale = FS_8_GAUSS;
+
+        /**
+         * @brief Data rate configuration
+         *
+         * Default: 40 Hz
+         *
+         * Important: if ODR is set more than 80 Hz,
+         * operative mode of x and y axis will be set
+         * accordingly.
+         *
+         * @see LIS3MDL::ODR
+         */
+        ODR odr = ODR_40_HZ;
+
+        /**
+         * Operative mode for x and y axis.
+         * Note: if ODR is greater than 80 Hz,
+         * this setting will be ignored and actual
+         * operative mode will be set depending of
+         * the chosen frequency.
+         * Default: ultra high performance
+         *
+         * @see LIS3MDL::OperativeMode
+         */
+        OperativeMode xyMode = OM_ULTRA_HIGH_POWER;
+
+        /**
+         * Operative mode for z axis.
+         * Default: ultra high performance
+         *
+         * @see LIS3MDL::OM
+         */
+        OperativeMode zMode = OM_ULTRA_HIGH_POWER;
+
+        /**
+         * Enables temperature sensor.
+         * Default: true
+         */
+        bool enableTemperature = true;
+
+        /**
+         * @brief Sets the value of tempDivider.
+         *
+         * With the given value you can instruct the driver to update
+         * the temperature according to a different rate.
+         * The temperature will be updated only once in `tempDivider` calls
+         * to sampleImpl(), so for example:
+         * 2 -> updated half the times,
+         * 1 -> updated every time.
+         * By default the divider is set to 1
+         *
+         */
+        unsigned temperatureDivider = 1;
+
+        /**
+         * @brief Enables interrupts
+         *
+         * Whether are interrupts enabled respectively
+         * on the x, y and z axis. If it is set to true
+         * on at least one axis, the interrupts will be
+         * generated otherwise, they will be completely
+         * disabled by the driver.
+         *
+         * Default: disabled on all axis.
+         */
+        bool enableInterrupt[3] = {false, false, false};
+
+        /**
+         * Absolute value of the threshold that triggers the interrupt
+         * (expressed in gauss). Default: 0
+         */
+        float threshold = 0;
+
+        /**
+         * @brief BDU setting
+         *
+         * If set to true, the sensor won't update the data
+         * until it is read, if false the sensor data will be
+         * continously updated. Default: false
+         */
+        bool doBlockDataUpdate = false;
+    };
+
+    /**
+     * Constructs the default config for SPI Bus.
+     *
+     * @returns the default SPIBusConfig
+     */
+    static SPIBusConfig getDefaultSPIConfig()
+    {
+        SPIBusConfig config{};
+        config.clock_div = SPIClockDivider::DIV32;
+        return config;
+    }
+
+    /**
+     * @brief The constructor.
+     *
+     * Takes all relevant SPI info and the settings
+     * for the sensor. If no SPIBusConfig is given, it will
+     * be used the default configuration.
+     *
+     * @param bus The SPI interface
+     * @param pin The CS pin
+     * @param spiConfig SPI configuration, optional
+     * @param config Driver configuration, optional
+     *
+     * @see LIS3MDL::Config
+     */
+    LIS3MDL(SPIBusInterface& bus, miosix::GpioPin pin,
+            SPIBusConfig spiConfig = getDefaultSPIConfig(), Config config = {})
+        : mSlave{bus, pin, spiConfig}, mConfig(config), currDiv(0),
+          isInitialized(false)
+    {
+    }
+
+    bool init() override
+    {
+        if (isInitialized)
+        {
+            LOG_ERR(logger, "Attempted to initialized sensor twice but failed");
+            last_error = ALREADY_INIT;
+            return false;
+        }
+
+        {
+            SPITransaction spi(mSlave);
+            uint8_t res = spi.read(WHO_AM_I);
+
+            if (res != WHO_AM_I_VALUE)
+            {
+                LOG_ERR(logger,
+                        "WHO_AM_I value differs from expectation: read 0x{02x} "
+                        "but expected 0x{02x}",
+                        res, WHO_AM_I_VALUE);
+                last_error = INVALID_WHOAMI;
+                return false;
+            }
+        }
+
+        isInitialized = true;
+        return applyConfig(mConfig);
+    }
+
+    /**
+     * @brief Executes self test
+     *
+     * The init() method must have been called before.
+     *
+     * @returns false if the sensor failed the self test, true otherwise.
+     */
+    bool selfTest() override
+    {
+        if (!isInitialized)
+        {
+            LOG_ERR(logger, "Invoked selfTest() but sensor was unitialized");
+            last_error = NOT_INIT;
+            return false;
+        }
+
+        /*
+         * NUM_SAMPLES: number of samples used
+         * to take the average value before tests.
+         * NUM_TESTS: number of actual tests.
+         * SLEEP_TIME: millis between samples/tests
+         */
+        constexpr int NUM_SAMPLES = 5, NUM_TESTS = 5, SLEEP_TIME = 50;
+
+        /*
+         * Absolute value of extra tolerance
+         */
+        constexpr float t = 0.1f;
+
+        /*
+         * Range which delta must be between, one for
+         * axis and expressed as {min, max}. The unit is gauss
+         */
+        constexpr float r[3][2] = {{1.f, 3.f}, {1.f, 3.f}, {0.1f, 1.f}};
+
+        float avg_x = 0.f, avg_y = 0.f, avg_z = 0.f;
+
+        {
+            SPITransaction spi(mSlave);
+            spi.write(CTRL_REG2, FS_12_GAUSS);
+        }
+        updateUnit(FS_12_GAUSS);
+
+        for (int i = 0; i < NUM_SAMPLES; ++i)
+        {
+            miosix::Thread::sleep(SLEEP_TIME);
+
+            LIS3MDLData lastData = sampleImpl();
+            avg_x += lastData.mag_x;
+            avg_y += lastData.mag_y;
+            avg_z += lastData.mag_z;
+        }
+
+        avg_x /= NUM_SAMPLES;
+        avg_y /= NUM_SAMPLES;
+        avg_z /= NUM_SAMPLES;
+
+        /*
+         * Setting up the sensor settings for
+         * proper usage of the self test mode.
+         */
+        {
+            SPITransaction spi(mSlave);
+
+            spi.write(CTRL_REG1, ODR_20_HZ | ENABLE_SELF_TEST |
+                                     (OM_ULTRA_HIGH_POWER << 4));
+            spi.write(CTRL_REG2, FS_12_GAUSS);
+            spi.write(CTRL_REG4, OM_ULTRA_HIGH_POWER << 2);
+        }
+
+        /*
+         * Deltas: absolute difference between
+         * the values measured before and during
+         * selftest
+         */
+        float d[3];
+
+        for (int i = 0; i < NUM_TESTS; ++i)
+        {
+            miosix::Thread::sleep(SLEEP_TIME);
+
+            LIS3MDLData lastData = sampleImpl();
+            d[0]                 = std::abs(lastData.mag_x - avg_x);
+            d[1]                 = std::abs(lastData.mag_y - avg_y);
+            d[2]                 = std::abs(lastData.mag_z - avg_z);
+
+            bool passed = true;
+            for (int j = 0; j < 3; ++j)
+            {
+                if (d[j] < (r[j][0] - t) || d[j] > (r[j][1] + t))
+                    passed = false;
+            }
+
+            if (!passed)
+            {
+                // reset configuration, then return
+                applyConfig(mConfig);
+
+                last_error = SELF_TEST_FAIL;
+                return false;
+            }
+        }
+
+        return applyConfig(mConfig);
+    }
+
+    /**
+     * @brief Overwrites the sensor settings.
+     *
+     * Writes a certain config to the sensor
+     * registers. This method is automatically
+     * called in LIS3MDL::init() using as parameter
+     * the configuration given in the constructor.
+     *
+     * This method checks if the values were actually
+     * written in the sensor's registers and returns false
+     * if at least one of them was not as expected.
+     *
+     * @param config The configuration to be applied
+     * @returns true if the configuration was applied successfully,
+     * false otherwise.
+     */
+    bool applyConfig(Config config)
+    {
+
+        SPITransaction spi(mSlave);
+        uint8_t reg = 0, err = 0;
+
+        mConfig = config;
+        currDiv = 0;
+
+        /*  -- CTRL_REG1 --  */
+        if (config.enableTemperature)
+        {
+            reg = ENABLE_TEMPERATURE;
+        }
+        reg |= config.odr;
+
+        // odr <= 80Hz
+        if (!(config.odr & FAST_ODR_BIT))
+            reg |= config.xyMode << 4;
+        spi.write(CTRL_REG1, reg);
+        err |= spi.read(CTRL_REG1) != reg;
+
+        /* -- CTRL_REG2 -- */
+        reg = config.scale;
+        spi.write(CTRL_REG2, reg);
+        err |= spi.read(CTRL_REG2) != reg;
+
+        /* -- CTRL_REG3 -- */
+        reg = CONTINOUS_CONVERSION;
+        spi.write(CTRL_REG3, reg);
+        err |= spi.read(CTRL_REG3) != reg;
+
+        /* -- CTRL_REG4 -- */
+        reg = config.zMode << 2;
+        spi.write(CTRL_REG4, reg);
+        err |= spi.read(CTRL_REG4) != reg;
+
+        /* -- CTRL_REG5 -- */
+        if (config.doBlockDataUpdate)
+        {
+            reg = ENABLE_BDU;
+        }
+        else
+        {
+            reg = 0;
+        }
+
+        spi.write(CTRL_REG5, reg);
+        err |= spi.read(CTRL_REG5) != reg;
+
+        /* -- INT_CFG -- */
+        if (config.enableInterrupt[0])
+        {
+            reg = ENABLE_INT_X;
+        }
+        else
+        {
+            reg = 0;
+        }
+        if (config.enableInterrupt[1])
+        {
+            reg |= ENABLE_INT_Y;
+        }
+        if (config.enableInterrupt[2])
+        {
+            reg |= ENABLE_INT_Z;
+        }
+
+        // the interrupt of at least one axis is enabled
+        if (reg)
+        {
+            reg |= ENABLE_INT_PIN;
+        }
+
+        reg |= 0x08;
+        spi.write(INT_CFG, reg);
+        err |= spi.read(INT_CFG) != reg;
+
+        /** INT_THS */
+        uint16_t val =
+            static_cast<uint16_t>(std::abs(config.threshold / mUnit));
+        reg = static_cast<uint8_t>(0xff & val);
+        spi.write(INT_THS_L, reg);
+        err |= spi.read(INT_THS_L) != reg;
+
+        reg = static_cast<uint8_t>(val >> 8);
+        reg &=
+            0x7f;  // remove MSB (according to the datasheet, it must be zero)
+        spi.write(INT_THS_H, reg);
+        err |= spi.read(INT_THS_H) != reg;
+
+        /* Set mUnit according to scale */
+        updateUnit(config.scale);
+
+        if (err)
+        {
+            LOG_ERR(logger, "Spi error");
+            last_error = BUS_FAULT;
+            return false;
+        }
+
+        return true;
+    }
+
+private:
+    /**
+     * @brief Reads data from the sensor
+     *
+     * The init method must have been called before.
+     * Output data can be fetched with the methods
+     * compassDataPtr() and tempDataPtr inherited respectevely
+     * from CompassSensor and TemperatureSensor classes.
+     * Important: the temperature will be taken only once in a while
+     * according to the value of `temperatureDivider`
+     *
+     * @returns false if the sensor was unitialized, true otherwise.
+     */
+    LIS3MDLData sampleImpl() override
+    {
+        if (!isInitialized)
+        {
+            LOG_ERR(logger,
+                    "Invoked sampleImpl() but sensor was "
+                    "unitialized");
+            last_error = NOT_INIT;
+            return last_sample;
+        }
+
+        SPITransaction spi(mSlave);
+
+        if (!spi.read(STATUS_REG))
+        {
+            last_error = NO_NEW_DATA;
+            return last_sample;
+        }
+
+        // Reset any error
+        last_error = SensorErrors::NO_ERRORS;
+
+        int16_t val;
+        LIS3MDLData newData{};
+
+        if (mConfig.enableTemperature)
+        {
+            if (currDiv == 0)
+            {
+                val = spi.read(TEMP_OUT_L);
+                val |= spi.read(TEMP_OUT_H) << 8;
+
+                newData.temp_timestamp = TimestampTimer::getTimestamp();
+                newData.temp = static_cast<float>(val) / LSB_PER_CELSIUS +
+                               REFERENCE_TEMPERATURE;
+            }
+            else
+            {
+                // Keep old value
+                newData.temp = last_sample.temp;
+            }
+
+            currDiv = (currDiv + 1) % mConfig.temperatureDivider;
+        }
+
+        newData.mag_timestamp = TimestampTimer::getTimestamp();
+
+        val = spi.read(OUT_X_L);
+        val |= spi.read(OUT_X_H) << 8;
+        newData.mag_x = mUnit * val;
+
+        val = spi.read(OUT_Y_L);
+        val |= spi.read(OUT_Y_H) << 8;
+        newData.mag_y = mUnit * val;
+
+        val = spi.read(OUT_Z_L);
+        val |= spi.read(OUT_Z_H) << 8;
+        newData.mag_z = mUnit * val;
+
+        return newData;
+    }
+
+    SPISlave mSlave;
+    Config mConfig;
+
+    unsigned currDiv;
+    bool isInitialized;
+    float mUnit = 0;
+
+    void updateUnit(FullScale fs)
+    {
+        switch (fs)
+        {
+            case FS_4_GAUSS:
+                mUnit = 1.f / LSB_PER_GAUSS_FS_4;
+                break;
+
+            case FS_8_GAUSS:
+                mUnit = 1.f / LSB_PER_GAUSS_FS_8;
+                break;
+
+            case FS_12_GAUSS:
+                mUnit = 1.f / LSB_PER_GAUSS_FS_12;
+                break;
+
+            case FS_16_GAUSS:
+                mUnit = 1.f / LSB_PER_GAUSS_FS_16;
+                break;
+        }
+    };
+
+    /**
+     * List of addresses of sensor registers
+     */
+    enum Reg : uint8_t
+    {
+        WHO_AM_I = 0x0f,
+
+        CTRL_REG1 = 0x20,
+        CTRL_REG2 = 0x21,
+        CTRL_REG3 = 0x22,
+        CTRL_REG4 = 0x23,
+        CTRL_REG5 = 0x24,
+
+        STATUS_REG = 0x27,
+        OUT_X_L    = 0x28,
+        OUT_X_H    = 0x29,
+        OUT_Y_L    = 0x2a,
+        OUT_Y_H    = 0x2b,
+        OUT_Z_L    = 0x2c,
+        OUT_Z_H    = 0x2d,
+
+        TEMP_OUT_L = 0x2e,
+        TEMP_OUT_H = 0x2f,
+
+        INT_CFG   = 0x30,
+        INT_THS_L = 0x32,
+        INT_THS_H = 0x33,
+    };
+
+    /**
+     * Misc. constants used by the driver. They are not
+     * particularly useful to the user.
+     */
+    enum Constants : unsigned
+    {
+        WHO_AM_I_VALUE = 0x3d,
+
+        CONTINOUS_CONVERSION  = 0x0,
+        REFERENCE_TEMPERATURE = 25,
+        LSB_PER_CELSIUS       = 8,
+
+        LSB_PER_GAUSS_FS_4  = 6842,
+        LSB_PER_GAUSS_FS_8  = 3421,
+        LSB_PER_GAUSS_FS_12 = 2281,
+        LSB_PER_GAUSS_FS_16 = 1711,
+
+        ENABLE_TEMPERATURE = 0x80,
+        ENABLE_SELF_TEST   = 0x01,
+        ENABLE_BDU         = 0x40,
+
+        ENABLE_INT_PIN = 0x01,
+        ENABLE_INT_X   = 0x80,
+        ENABLE_INT_Y   = 0x40,
+        ENABLE_INT_Z   = 0x20,
+    };
+
+    PrintLogger logger = Logging::getLogger("lis3mdl");
+};
+
+#endif
diff --git a/src/shared/sensors/LIS3MDL/LIS3MDLData.h b/src/shared/sensors/LIS3MDL/LIS3MDLData.h
new file mode 100644
index 0000000000000000000000000000000000000000..b791437375abda30b56bbd5ae359301aa00b134f
--- /dev/null
+++ b/src/shared/sensors/LIS3MDL/LIS3MDLData.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct LIS3MDLData : public MagnetometerData, public TemperatureData
+{
+    LIS3MDLData() : MagnetometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0}
+    {
+    }
+
+    LIS3MDLData(uint64_t t, float mx, float my, float mz, float deg)
+        : MagnetometerData{t, mx, my, mz},
+          TemperatureData{t, deg}
+
+    {
+    }
+
+    LIS3MDLData(MagnetometerData mag_data, TemperatureData temp_data)
+        : MagnetometerData(mag_data), TemperatureData(temp_data)
+
+    {
+    }
+
+    static std::string header()
+    {
+        return "mag_timestamp,mag_x,mag_y,mag_z,temp_timestamp,temp\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << mag_timestamp << "," << mag_x << "," << mag_y << "," << mag_z
+           << "," << temp_timestamp << "," << temp << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/LSM6DS3H.h b/src/shared/sensors/LSM6DS3H.h
deleted file mode 100644
index fbc35c321bde7a94201d8d728438d3d7ace4144b..0000000000000000000000000000000000000000
--- a/src/shared/sensors/LSM6DS3H.h
+++ /dev/null
@@ -1,423 +0,0 @@
-/*  LSM6DS3H Driver
- *
- * Copyright (c) 2018-2019 Skyward Experimental Rocketry
- * Authors: Soufiane Machkouk
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-#pragma once
-#include <drivers/BusTemplate.h>
-#include "Common.h"
-#include "Sensor.h"
-#include "miosix.h"
-template <typename BusG>
-class LSM6DS3H : public GyroSensor, public AccelSensor
-{
-public:
-    LSM6DS3H(uint8_t accelFullScale, uint16_t gyroFullScale)
-    {
-        accelFS = accelFullScale;
-        gyroFS  = gyroFullScale;
-    }
-    bool init()
-    {
-        selfTest();
-        BusG::write(RegMap::CTRL5_C, 0x60);  //  Rounding read enabled for both accel and gyro
-        BusG::write(RegMap::CTRL3_C, 0x44 ); // incrementare l'indirizzo ad ogni lettura data LSB @ lower address
-        fifoConfiguration();
-        accelConfiguration();
-        gyroConfiguration();
-        // TODO
-        uint8_t XLSamplesToDiscard=2;   //
-        uint8_t gyroSamplesToDiscard=2; //since we set ODR to 416HZ
-        uint8_t buffer[2]={0, 0};
-        BusG::read(RegMap::FIFO_DATA_OUT_L, buffer, XLSamplesToDiscard);
-        BusG::read(RegMap::FIFO_DATA_OUT_L, buffer, gyroSamplesToDiscard);
-        return true;
-    }
-    void fifoConfiguration()
-    {
-        uint8_t continuousMode = 0x36;  // ODR is set to 416HZ
-        BusG::write(RegMap::FIFO_CTRL_REG5, continuousMode);
-        uint8_t bitToSet= BusG::read(RegMap::FIFO_CTRL_REG2);
-        bitToSet = bitToSet & 0xcf;
-        BusG::write(RegMap::FIFO_CTRL_REG2, bitToSet);
-        uint8_t noDecimation = 0x09;
-        BusG::write(RegMap::FIFO_CTRL_REG3, noDecimation);  // NO decimation for both accel and gyro data
-        bitToSet = 0x00;
-        BusG::write(RegMap::FIFO_CTRL_REG5, bitToSet);
-        uint8_t registerStatus = BusG::read(RegMap::FIFO_STATUS4);
-        bitToSet = 0x03;
-        bitToSet = bitToSet & registerStatus;
-        BusG::write(RegMap::FIFO_STATUS4, bitToSet);
-    }
-    bool selfTest()
-    {
-        uint8_t whoami = BusG::read(RegMap::WHO_AM_I);
-        if (whoami != whoami_value)
-        {
-            last_error = ERR_NOT_ME;
-            return false;
-        }
-        return (accelSelfTest() & gyroSelfTest());
-    }
-    void accelConfiguration()
-    {
-        uint8_t i=0;
-        uint8_t controlAccel = 0x61; 
-        for (i = 0; i<=3; i++)
-        {
-            if ((uint8_t)accelFSMAP[i] == accelFS)
-            {
-                controlAccel = controlAccel | (i << 2);
-                BusG::write(RegMap::CTRL1_XL, controlAccel);  // anti-aliasing filter set to 200Hz
-                                            // ODR to 416HZ
-            }
-        }
-
-        BusG::write(RegMap::CTRL8_XL, 0x00); // no LPF2
-        uint8_t enableXYZ = 0x38;
-        BusG::write(RegMap::CTRL9_XL, enableXYZ);
-    }
-    void gyroConfiguration()
-    {
-        uint8_t i=0;
-        uint8_t controlGyro = 0x60;
-        for(i=0; i<=3; i++)
-        {
-            if ((uint16_t)gyroFSMAP[i] == gyroFS)
-            {
-                controlGyro = controlGyro | (i << 2);
-                BusG::write(RegMap::CTRL2_G, controlGyro);
-            }
-        }
-        uint8_t enableXYZ = 0x39;
-        BusG::write(RegMap::CTRL10_C, enableXYZ);
-        //HPfilter disabled
-    }
-    bool onSimpleUpdate() { return false; }
-    void sampleUpdate()
-    {
-        //gyro data set ---> lettura da 6 byte
-        BusG::read(FIFO_DATA_OUT_L, datiGyro, 6);
-        // accel dataset ---> lettura da 6 byte
-        BusG::read(FIFO_DATA_OUT_L, datiAccel, 6);
-        Data.gyroXSample   = toFloat(datiGyro[0]+datiGyro[1]<<8, gyroFS);
-        Data.gyroYSample   = toFloat(datiGyro[2]+datiGyro[3]<<8, gyroFS);
-        Data.gyroZSample   = toFloat(datiGyro[4]+datiGyro[5]<<8, gyroFS);
-        Data.accelXSample  = toFloat(datiAccel[0]+datiAccel[1]<<8, accelFS);
-        Data.accelYSample  = toFloat(datiAccel[2]+datiAccel[3]<<8, accelFS);
-        Data.accelZSample  = toFloat(datiAccel[4]+datiAccel[5]<<8, accelFS);
-       
-    }
-   
-    bool accelSelfTest(){
-        uint8_t discard;
-        uint8_t c=0;
-        uint8_t rxData[2];
-        float OUTX_NOST[]={0, 0, 0, 0, 0};
-        float outXNoST;
-        float OUTY_NOST[]={0, 0, 0, 0, 0};
-        float outYNoST;
-        float OUTZ_NOST[]={0, 0, 0, 0, 0};
-        float outZNoST;
-        float OUTX_ST[]={0, 0, 0, 0, 0};
-        float outXST;
-        float OUTY_ST[]={0, 0, 0, 0, 0};
-        float outYST;
-        float OUTZ_ST[]={0, 0, 0, 0, 0};
-        float outZST;
-        BusG::write(CTRL1_XL, 0x30);
-        BusG::write(CTRL2_G, 0x00);
-        BusG::write(CTRL3_C, 0x44);
-        BusG::write(CTRL4_C, 0x00);
-        BusG::write(CTRL5_C, 0x00);
-        BusG::write(CTRL6_C, 0x00);
-        BusG::write(CTRL7_G, 0x00);
-        BusG::write(CTRL8_XL, 0x00);
-        BusG::write(CTRL9_XL, 0x38);
-        BusG::write(CTRL10_C, 0x00);
-        miosix::Thread::sleep(200);
-        discard = BusG::read(STATUS_REG);
-        while ((discard & 0x01) == 0 ){
-            discard = BusG::read(STATUS_REG);
-        }
-        BusG::read(OUT_X_L_XL, rxData, 2);
-        BusG::read(OUT_Y_L_XL, rxData, 2);
-        BusG::read(OUT_Z_L_XL, rxData, 2);
-        discard = BusG::read(STATUS_REG);
-        while ( c<=5 ){
-            while ((discard & 0x01) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-            BusG::read(OUT_X_L_XL, rxData, 2);
-            OUTX_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); 
-            BusG::read(OUT_Y_L_XL, rxData, 2);
-            OUTY_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            BusG::read(OUT_Z_L_XL, rxData, 2);
-            OUTZ_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            c++;
-        }
-        outXNoST = (OUTX_NOST[0]+OUTX_NOST[1]+OUTX_NOST[2]+OUTX_NOST[3]+OUTX_NOST[4])/5;
-        outYNoST = (OUTY_NOST[0]+OUTY_NOST[1]+OUTY_NOST[2]+OUTY_NOST[3]+OUTY_NOST[4])/5;
-        outZNoST = (OUTZ_NOST[0]+OUTZ_NOST[1]+OUTZ_NOST[2]+OUTZ_NOST[3]+OUTZ_NOST[4])/5;
-        BusG::write(CTRL5_C, 0x01);
-        miosix::Thread::sleep(200);
-        discard = BusG::read(STATUS_REG);
-        while ((discard & 0x01) == 0 ){
-            discard = BusG::read(STATUS_REG);
-            }
-        BusG::read(OUT_X_L_XL, rxData, 2);
-        BusG::read(OUT_Y_L_XL, rxData, 2);
-        BusG::read(OUT_Z_L_XL, rxData, 2);
-        discard = BusG::read(STATUS_REG);
-        c=0;
-        while ( c<=5 ){
-            while ((discard & 0x01) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-            BusG::read(OUT_X_L_XL, rxData, 2);
-            OUTX_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); 
-            BusG::read(OUT_Y_L_XL, rxData, 2);
-            OUTY_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            BusG::read(OUT_Z_L_XL, rxData, 2);
-            OUTZ_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            c++;
-        }
-        outXST = (OUTX_ST[0]+OUTX_ST[1]+OUTX_ST[2]+OUTX_ST[3]+OUTX_ST[4])/5;
-        outYST = (OUTY_ST[0]+OUTY_ST[1]+OUTY_ST[2]+OUTY_ST[3]+OUTY_ST[4])/5;
-        outZST = (OUTZ_ST[0]+OUTZ_ST[1]+OUTZ_ST[2]+OUTZ_ST[3]+OUTZ_ST[4])/5;
-        float maxSTX = max(OUTX_ST);
-        float maxSTY = max(OUTY_ST);
-        float maxSTZ = max(OUTZ_ST);
-        float minSTX = min(OUTX_ST);
-        float minSTY = min(OUTY_ST);
-        float minSTZ = min(OUTZ_ST);
-        if (((uint8_t)(outXST-outXNoST)<=maxSTX) & ((uint8_t)(outXST-outXNoST)>=minSTX)){
-            if (((uint8_t)(outYST-outYNoST)<=maxSTY) & ((uint8_t)(outYST-outYNoST)>=minSTY)){
-                if (((uint8_t)(outZST-outZNoST)<=maxSTZ) & ((uint8_t)(outZST-outZNoST)>=minSTZ)){
-                    BusG::write(CTRL1_XL, 0x00);
-                    BusG::write(CTRL5_C, 0x00);
-                    return true;
-                }
-            }
-        }
-        BusG::write(CTRL1_XL, 0x00);
-        BusG::write(CTRL5_C, 0x00);
-        return false;
-    }
-    bool gyroSelfTest(){
-        uint8_t discard;
-        uint8_t c=0;
-        uint8_t rxData[2];
-        float OUTX_NOST[]={0, 0, 0, 0, 0};
-        float outXNoST;
-        float OUTY_NOST[]={0, 0, 0, 0, 0};
-        float outYNoST;
-        float OUTZ_NOST[]={0, 0, 0, 0, 0};
-        float outZNoST;
-        float OUTX_ST[]={0, 0, 0, 0, 0};
-        float outXST;
-        float OUTY_ST[]={0, 0, 0, 0, 0};
-        float outYST;
-        float OUTZ_ST[]={0, 0, 0, 0, 0};
-        float outZST;
-        BusG::write(CTRL1_XL, 0x00);
-        BusG::write(CTRL2_G, 0x5C);
-        BusG::write(CTRL3_C, 0x44);
-        BusG::write(CTRL4_C, 0x00);
-        BusG::write(CTRL5_C, 0x00);
-        BusG::write(CTRL6_C, 0x00);
-        BusG::write(CTRL7_G, 0x00);
-        BusG::write(CTRL8_XL, 0x00);
-        BusG::write(CTRL9_XL, 0x00);
-        BusG::write(CTRL10_C, 0x38);
-        miosix::Thread::sleep(800);
-        discard = BusG::read(STATUS_REG);
-         while ((discard & 0x01) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-        BusG::read(OUT_X_L_G, rxData, 2);
-        BusG::read(OUT_Y_L_G, rxData, 2);
-        BusG::read(OUT_Z_L_G, rxData, 2);
-        discard = BusG::read(STATUS_REG);
-        while ( c<=5 ){
-            while ((discard & 0x02) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-            BusG::read(OUT_X_L_G, rxData, 2);
-            OUTX_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); 
-            BusG::read(OUT_Y_L_G, rxData, 2);
-            OUTY_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            BusG::read(OUT_Z_L_G, rxData, 2);
-            OUTZ_NOST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            c++;
-        }
-        outXNoST = (OUTX_NOST[0]+OUTX_NOST[1]+OUTX_NOST[2]+OUTX_NOST[3]+OUTX_NOST[4])/5;
-        outYNoST = (OUTY_NOST[0]+OUTY_NOST[1]+OUTY_NOST[2]+OUTY_NOST[3]+OUTY_NOST[4])/5;
-        outZNoST = (OUTZ_NOST[0]+OUTZ_NOST[1]+OUTZ_NOST[2]+OUTZ_NOST[3]+OUTZ_NOST[4])/5;
-        BusG::write(CTRL5_C, 0x04);
-        miosix::Thread::sleep(60);
-        discard = BusG::read(STATUS_REG);
-        while ((discard & 0x02) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-        BusG::read(OUT_X_L_G, rxData, 2);
-        BusG::read(OUT_Y_L_G, rxData, 2);
-        BusG::read(OUT_Z_L_G, rxData, 2);
-        discard = BusG::read(STATUS_REG);
-        while ( c<=5 ){
-            while ((discard & 0x02) == 0 ){
-                discard = BusG::read(STATUS_REG);
-            }
-            BusG::read(OUT_X_L_G, rxData, 2);
-            OUTX_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2); 
-            BusG::read(OUT_Y_L_G, rxData, 2);
-            OUTY_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            BusG::read(OUT_Z_L_G, rxData, 2);
-            OUTZ_ST[c] = toFloat(rxData[0]+(rxData[1]<<8), 2);
-            c++;
-        }
-        outXST = (OUTX_ST[0]+OUTX_ST[1]+OUTX_ST[2]+OUTX_ST[3]+OUTX_ST[4])/5;
-        outYST = (OUTY_ST[0]+OUTY_ST[1]+OUTY_ST[2]+OUTY_ST[3]+OUTY_ST[4])/5;
-        outZST = (OUTZ_ST[0]+OUTZ_ST[1]+OUTZ_ST[2]+OUTZ_ST[3]+OUTZ_ST[4])/5;
-        float maxSTX = max(OUTX_ST);
-        float maxSTY = max(OUTY_ST);
-        float maxSTZ = max(OUTZ_ST);
-        float minSTX = min(OUTX_ST);
-        float minSTY = min(OUTY_ST);
-        float minSTZ = min(OUTZ_ST);
-        if (((uint8_t)(outXST-outXNoST)<=maxSTX) & ((uint8_t)(outXST-outXNoST)>=minSTX)){
-            if (((uint8_t)(outYST-outYNoST)<=maxSTY) & ((uint8_t)(outYST-outYNoST)>=minSTY)){
-                if (((uint8_t)(outZST-outZNoST)<=maxSTZ) & ((uint8_t)(outZST-outZNoST)>=minSTZ)){
-                    BusG::write(CTRL2_G, 0x00);
-                    BusG::write(CTRL5_C, 0x00);
-                    return true;
-                }
-            }
-        }
-        BusG::write(CTRL2_G, 0x00);
-        BusG::write(CTRL5_C, 0x00);
-        return false;
-
-    }
-    float min (float* var){
-        uint8_t min=var[0];
-        uint8_t i=0;
-        for(i=0; i<=5; i++){
-            if (var[i]<= min){
-                min= var[i];
-            }
-        }
-        return min;
-    }
-    float max (float* var){
-        uint8_t max=var[0];
-        uint8_t i=0;
-        for(i=0; i<=5; i++){
-            if (var[i]>= max){
-                max= var[i];
-            }
-        }
-        return max;
-    }
-    
-    float toFloat(uint16_t var, uint16_t FScale){
-        float out;
-        if(var > 0x7FFF){
-            out= var-0xFFFF;
-        }
-        else {
-            out= var;
-        }
-        out = out*FScale/0x8000;
-        return out;
-    }
-    uint8_t datiGyro[6]  = {0, 0, 0, 0, 0, 0};
-    uint8_t datiAccel[6] = {0, 0, 0, 0, 0, 0};
-
-    struct Samples
-    {
-        float gyroXSample;
-        float gyroYSample;
-        float gyroZSample;
-        float accelXSample;
-        float accelYSample;
-        float accelZSample;
-    };
-    Samples Data;
-
-private:
-    uint8_t accelFS;
-    uint16_t gyroFS;
-    constexpr static uint8_t whoami_value = 0x69;
-
-    uint8_t accelFSMAP[4] = {2, 16, 4, 8};
-    uint16_t gyroFSMAP[4]  = {250, 500, 1000, 2000};
-    enum RegMap {
-        WHO_AM_I = 0x0F,  // default value
-        // FIFO configuration registers
-        FIFO_CTRL_REG1 = 0x06,
-        FIFO_CTRL_REG2 = 0x07,
-        FIFO_CTRL_REG3 = 0x08,
-        FIFO_CTRL_REG4 = 0x09,
-        FIFO_CTRL_REG5 = 0x0A,
-
-        // Accelerometer and gyroscope control registers
-        CTRL1_XL = 0x10,
-        CTRL2_G  = 0x11,
-        CTRL3_C  = 0x12,
-        CTRL4_C  = 0x13,
-        CTRL5_C  = 0x14, 
-        CTRL6_C  = 0x15, 
-        CTRL7_G  = 0x16, 
-        CTRL8_XL = 0x17, 
-        CTRL9_XL = 0x18, 
-        CTRL10_C = 0x19,
-
-        // Accelerometer output registers
-
-        OUT_X_L_XL = 0x28,
-        OUT_X_H_XL = 0x29,
-        OUT_Y_L_XL = 0x2A,
-        OUT_Y_H_XL = 0x2B,
-        OUT_Z_L_XL = 0x2C,
-        OUT_Z_H_XL = 0x2D,
-
-        // gyro data registers
-        OUT_X_L_G = 0x22,
-        OUT_X_H_G = 0x23,
-        OUT_Y_L_G = 0x24,
-        OUT_Y_H_G = 0x25,
-        OUT_Z_L_G = 0x26,
-        OUT_Z_H_G = 0x27,
-
-        // FIFO status registers
-        FIFO_STATUS1 = 0x3A,
-        FIFO_STATUS2 = 0x3B,
-        FIFO_STATUS3 = 0x3C,
-        FIFO_STATUS4 = 0x3D,
-
-        // FIFO data output registers
-        FIFO_DATA_OUT_L = 0x3E,
-        FIFO_DATA_OUT_H = 0x3F,
-        // STATUS register
-        STATUS_REG  = 0X1E
-    };
-};
\ No newline at end of file
diff --git a/src/shared/sensors/MPU9250/MPU9250.cpp b/src/shared/sensors/MPU9250/MPU9250.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6cfa201f36995a4c8f9ca9f02fa3b713fe1558da
--- /dev/null
+++ b/src/shared/sensors/MPU9250/MPU9250.cpp
@@ -0,0 +1,447 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "MPU9250.h"
+
+#include "Constants.h"
+#include "TimestampTimer.h"
+#include "interfaces/endianness.h"
+
+MPU9250::MPU9250(SPISlave spiSlave_, unsigned short samplingRate_,
+                 MPU9250GyroFSR gyroFsr_, MPU9250AccelFSR accelFsr_,
+                 SPIClockDivider highSpeedSpiClockDivider_)
+    : spiSlave(spiSlave_), samplingRate(samplingRate_), gyroFsr(gyroFsr_),
+      accelFsr(accelFsr_), highSpeedSpiClockDivider(highSpeedSpiClockDivider_)
+{
+}
+
+SPIBusConfig getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.clock_div = SPIClockDivider::DIV64;
+    spiConfig.mode      = SPIMode::MODE3;
+    return spiConfig;
+}
+
+bool MPU9250::init()
+{
+    // Check if already initialized
+    if (initialized)
+    {
+        LOG_ERR(logger, "Already initialized\n");
+
+        last_error = SensorErrors::ALREADY_INIT;
+
+        return false;
+    }
+
+    // Reset the device
+    resetDevice();
+    miosix::Thread::sleep(100);
+
+    // Wake up the chip
+    selectAutoClock();
+
+    // Check WHO AM I
+    if (!checkWhoAmI())
+    {
+        LOG_ERR(logger, "Invalid WHO AM I\n");
+
+        last_error = SensorErrors::INVALID_WHOAMI;
+
+        return false;
+    }
+
+    // Setup I2C master interface to communicate with the AK8963
+    enableMpuI2CMasterInterface();
+    setMpuI2CMasterInterfaceClock(I2C_MST_IF_CLK_400);
+
+    // Init the AK8963
+    if (!initAk())
+    {
+        return false;
+    }
+
+    // Set full scale resolution for gyroscope and accelerometer (they are
+    // enabled by default)
+    setGyroFsr(gyroFsr);
+    setAccelFsr(accelFsr);
+
+    // Set the sample rate
+    setSampleRate(samplingRate);
+
+    LOG_DEBUG(logger, "Magnetometer sensitivity adjustment: %d, %d, %d\n",
+              magSensAdjCoeff[0], magSensAdjCoeff[1], magSensAdjCoeff[2]);
+
+    initialized = true;
+    return true;
+}
+
+MPU9250Data MPU9250::sampleImpl()
+{
+    MPU9250RawData rawData;
+    MPU9250Data data;
+    SPIClockDivider clockDivider = spiSlave.config.clock_div;
+
+    // Read the data registers at high speed (up to 20MHz)
+    spiSlave.config.clock_div = highSpeedSpiClockDivider;
+    {
+        SPITransaction transaction(spiSlave);
+        transaction.read(REG_ACCEL_XOUT_H, (uint8_t*)rawData.bytes,
+                         sizeof(MPU9250RawData));
+    }
+    spiSlave.config.clock_div = clockDivider;
+
+    // Save timestamps
+    uint64_t timestamp   = TimestampTimer::getTimestamp();
+    data.accel_timestamp = timestamp;
+    data.temp_timestamp  = timestamp;
+    data.gyro_timestamp  = timestamp;
+    data.mag_timestamp   = timestamp;
+
+    // Save data
+    data.accel_x = normalizeAcceleration(swapBytes16(rawData.bits.accelX));
+    data.accel_y = normalizeAcceleration(swapBytes16(rawData.bits.accelY));
+    data.accel_z = normalizeAcceleration(swapBytes16(rawData.bits.accelZ));
+    data.temp    = normalizeTemperature(swapBytes16(rawData.bits.temp));
+    data.gyro_x  = normalizeGyroscope(swapBytes16(rawData.bits.gyroX));
+    data.gyro_y  = normalizeGyroscope(swapBytes16(rawData.bits.gyroY));
+    data.gyro_z  = normalizeGyroscope(swapBytes16(rawData.bits.gyroZ));
+    data.mag_x   = normalizeMagnetometer(rawData.bits.magX, magSensAdjCoeff[0]);
+    data.mag_y   = normalizeMagnetometer(rawData.bits.magY, magSensAdjCoeff[1]);
+    data.mag_z   = normalizeMagnetometer(rawData.bits.magZ, magSensAdjCoeff[2]);
+
+    return data;
+}
+
+void MPU9250::resetDevice()
+{
+    SPITransaction transaction(spiSlave);
+
+    transaction.write(REG_PWR_MGMT_1, REG_PWR_MGMT_1_BIT_H_RESET);
+}
+
+void MPU9250::selectAutoClock()
+{
+    SPITransaction transaction(spiSlave);
+
+    // Wake up by clearing sleep bit and set clock auto select
+    writeSPIWithDelay(transaction, REG_PWR_MGMT_1, REG_PWR_MGMT_1_CLKSEL_AUTO);
+}
+
+void MPU9250::setGyroFsr(MPU9250GyroFSR fs)
+{
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, REG_GYRO_CONFIG, fs);
+}
+
+void MPU9250::setAccelFsr(MPU9250AccelFSR fs)
+{
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, REG_ACCEL_CONFIG, fs);
+}
+
+void MPU9250::setSampleRate(unsigned short rate)
+{
+    if (rate < 4)
+        rate = 4;
+    else if (rate > 1000)
+        rate = 1000;
+
+    uint8_t data = 1000 / rate - 1;
+
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, REG_SMPLRT_DIV, data);
+
+    // Set the digital low pass filter in order to use the sample rate divider
+    writeSPIWithDelay(transaction, REG_CONFIG, REG_CONFIG_DLPF_CFG_1);
+
+    // We do not need to write Fchoise_b bits becouse they reset to 0 and they
+    // are the inverse of Fchoise, see page 13 of MPU9250 register map
+}
+
+void MPU9250::enableMpuI2CMasterInterface()
+{
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, REG_USER_CTRL, REG_USER_CTRL_I2C_MST_EN);
+}
+
+void MPU9250::setMpuI2CMasterInterfaceClock(MPU9250I2CMasterInterfaceClock clk)
+{
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, REG_I2C_MST_CTRL, clk);
+}
+
+void MPU9250::setI2CMasterSlaveRead(uint8_t addr, uint8_t reg, uint8_t nBytes,
+                                    uint8_t slave)
+{
+    uint8_t regSlvAddr, regSlvReg, regSlvCtrl;
+
+    switch (slave)
+    {
+        case 0:
+            regSlvAddr = REG_I2C_SLV0_ADDR;
+            regSlvReg  = REG_I2C_SLV0_REG;
+            regSlvCtrl = REG_I2C_SLV0_CTRL;
+            break;
+        case 1:
+            regSlvAddr = REG_I2C_SLV1_ADDR;
+            regSlvReg  = REG_I2C_SLV1_REG;
+            regSlvCtrl = REG_I2C_SLV1_CTRL;
+            break;
+        case 2:
+            regSlvAddr = REG_I2C_SLV2_ADDR;
+            regSlvReg  = REG_I2C_SLV2_REG;
+            regSlvCtrl = REG_I2C_SLV2_CTRL;
+            break;
+        case 3:
+            regSlvAddr = REG_I2C_SLV3_ADDR;
+            regSlvReg  = REG_I2C_SLV3_REG;
+            regSlvCtrl = REG_I2C_SLV3_CTRL;
+            break;
+
+        default:
+            LOG_ERR(logger,
+                    "invalid slave parameter in function "
+                    "setI2CMasterSlaveRead");
+            return;
+    }
+
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, regSlvAddr, addr | 0x80);  // Set read bit
+    writeSPIWithDelay(transaction, regSlvReg, reg);
+
+    // Set bytes number and enable the data transfer on each sample
+    writeSPIWithDelay(transaction, regSlvCtrl, REG_I2C_SLV_CTRL_EN | nBytes);
+}
+
+void MPU9250::setI2CMasterSlaveWrite(uint8_t addr, uint8_t reg, uint8_t data,
+                                     uint8_t slave)
+{
+    uint8_t regSlvAddr, regSlvReg, regSlvCtrl, regSlvDo;
+
+    switch (slave)
+    {
+        case 0:
+            regSlvAddr = REG_I2C_SLV0_ADDR;
+            regSlvReg  = REG_I2C_SLV0_REG;
+            regSlvCtrl = REG_I2C_SLV0_CTRL;
+            regSlvDo   = REG_I2C_SLV0_DO;
+            break;
+        case 1:
+            regSlvAddr = REG_I2C_SLV1_ADDR;
+            regSlvReg  = REG_I2C_SLV1_REG;
+            regSlvCtrl = REG_I2C_SLV1_CTRL;
+            regSlvDo   = REG_I2C_SLV1_DO;
+            break;
+        case 2:
+            regSlvAddr = REG_I2C_SLV2_ADDR;
+            regSlvReg  = REG_I2C_SLV2_REG;
+            regSlvCtrl = REG_I2C_SLV2_CTRL;
+            regSlvDo   = REG_I2C_SLV2_DO;
+            break;
+        case 3:
+            regSlvAddr = REG_I2C_SLV3_ADDR;
+            regSlvReg  = REG_I2C_SLV3_REG;
+            regSlvCtrl = REG_I2C_SLV3_CTRL;
+            regSlvDo   = REG_I2C_SLV3_DO;
+            break;
+
+        default:
+            LOG_ERR(logger,
+                    "invalid slave parameter in function "
+                    "setI2CMasterSlaveRead");
+            return;
+    }
+
+    SPITransaction transaction(spiSlave);
+
+    writeSPIWithDelay(transaction, regSlvAddr, addr);
+    writeSPIWithDelay(transaction, regSlvReg, reg);
+    writeSPIWithDelay(transaction, regSlvDo, data);
+
+    // Enable the data transfer on each sample
+    writeSPIWithDelay(transaction, regSlvCtrl, REG_I2C_SLV_CTRL_EN | 0x1);
+}
+
+void MPU9250::disableI2CMasterSlave(uint8_t slave)
+{
+    uint8_t regSlvCtrl;
+
+    switch (slave)
+    {
+        case 0:
+            regSlvCtrl = REG_I2C_SLV0_CTRL;
+            break;
+        case 1:
+            regSlvCtrl = REG_I2C_SLV1_CTRL;
+            break;
+        case 2:
+            regSlvCtrl = REG_I2C_SLV2_CTRL;
+            break;
+        case 3:
+            regSlvCtrl = REG_I2C_SLV3_CTRL;
+            break;
+            // In the control register of slave 4 there is also the
+            // configuration for the slaves sampling rate. If slave 4 is needed
+            // the salmpling rate divider should not be changed case 4:
+            //     regSlvCtrl = REG_I2C_SLV4_CTRL;
+            //     break;
+
+        default:
+            LOG_ERR(logger,
+                    "invalid slave parameter in function "
+                    "setI2CMasterSlaveRead");
+            return;
+    }
+
+    SPITransaction transaction(spiSlave);
+
+    // Enable the data transfer on each sample
+    writeSPIWithDelay(transaction, regSlvCtrl, 0);
+}
+
+uint8_t MPU9250::readFromAk(uint8_t reg)
+{
+    // Set the device address and register
+    setI2CMasterSlaveRead(AK8963_ADDR, reg);
+
+    // Wait for the sample
+    miosix::Thread::sleep(1);
+
+    SPITransaction transaction(spiSlave);
+
+    return transaction.read(REG_EXT_SENS_DATA_00);
+}
+
+void MPU9250::writeToAk(uint8_t reg, uint8_t data)
+{
+    // Set the device address, register and data byte
+    setI2CMasterSlaveWrite(AK8963_ADDR, reg, data);
+    miosix::Thread::sleep(1);
+}
+
+bool MPU9250::initAk()
+{
+    // Set the sample rate to 1KHz in order to communicate faster, but safely,
+    // with the AK (if we use 8KHz the communication does not work properly)
+    setSampleRate(1000);
+
+    // Power down the magnetometer
+    writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE);
+
+    // Wait a bit more, the first slave communication seams to be delayed by 1
+    // sample
+    miosix::Thread::sleep(1);
+
+    // Reset
+    writeToAk(AK8963_REG_CNTL2, AK8963_REG_CNTL2_BIT_SRST);
+
+    // Check AK8963 WHO AM I
+    if (!checkAkWhoAmI())
+    {
+        LOG_ERR(logger, "Invalid AK8963 WHO AM I\n");
+
+        last_error = SensorErrors::INVALID_WHOAMI;
+
+        return false;
+    }
+
+    // Enter rom access mode
+    writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE);
+    writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_FUSE_ROM_ACCESS_MODE);
+
+    // Read magnetometer sensitivity adjustment data (page 53 of register map)
+    magSensAdjCoeff[0] =
+        (((readFromAk(AK8963_REG_ASAX) - 128) + .5) / 128 + 1) * 4912.0f /
+        32760.0f;
+    magSensAdjCoeff[1] =
+        (((readFromAk(AK8963_REG_ASAY) - 128) + .5) / 128 + 1) * 4912.0f /
+        32760.0f;
+    magSensAdjCoeff[2] =
+        (((readFromAk(AK8963_REG_ASAZ) - 128) + .5) / 128 + 1) * 4912.0f /
+        32760.0f;
+
+    // Set continuos measurement mode
+    writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_POWER_DOWN_MODE);
+    writeToAk(AK8963_REG_CNTL1, AK8963_REG_CNTL1_CONT_MES_MODE_2);
+
+    // Set I2C master slave communication on each sample
+    setI2CMasterSlaveRead(AK8963_ADDR, AK8963_REG_ST1, 8);
+
+    return true;
+}
+
+bool MPU9250::checkWhoAmI()
+{
+    SPITransaction transaction(spiSlave);
+
+    uint8_t who_am_i_value = transaction.read(REG_WHO_AM_I);
+
+    return who_am_i_value == REG_WHO_AM_I_VAL;
+}
+
+bool MPU9250::checkAkWhoAmI()
+{
+    uint8_t who_am_i_value = readFromAk(AK8963_REG_WHO_AM_I);
+
+    return who_am_i_value == AK8963_REG_WHO_AM_I_VAL;
+}
+
+void MPU9250::writeSPIWithDelay(SPITransaction& transaction, uint8_t reg,
+                                uint8_t data)
+{
+    transaction.write(reg, data);
+    miosix::delayUs(1);
+    /*transaction.read(reg);
+    miosix::delayUs(1);*/
+}
+
+float MPU9250::normalizeAcceleration(int16_t rawValue)
+{
+    return static_cast<float>(rawValue) / 32768.0f *
+           ACCELERATION_FS_MAP[accelFsr >> 3] * EARTH_GRAVITY;
+}
+
+// Page 33 of register map document
+float MPU9250::normalizeTemperature(int16_t rawValue)
+{
+    return static_cast<float>(rawValue) / 512.0f + 21.0f;
+}
+
+float MPU9250::normalizeGyroscope(int16_t rawValue)
+{
+    return static_cast<float>(rawValue) / 32768.0f *
+           GYROSCOPE_FS_MAP[gyroFsr >> 3] * DEGREES_TO_RADIANS;
+}
+
+float MPU9250::normalizeMagnetometer(int16_t rawValue, float adjustmentCoeff)
+{
+    // Page 50 and 53 of register map document
+    return static_cast<float>(rawValue) * adjustmentCoeff;
+}
diff --git a/src/shared/sensors/MPU9250/MPU9250.h b/src/shared/sensors/MPU9250/MPU9250.h
index 23a2edba773f8d90de53954439bb97d2600bca55..b6647bd5e128360832b3e5b206308fb7e581c0ae 100644
--- a/src/shared/sensors/MPU9250/MPU9250.h
+++ b/src/shared/sensors/MPU9250/MPU9250.h
@@ -1,7 +1,5 @@
-/* MPU9250 Driver
- *
- * Copyright (c) 2016,2019 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Nuno Barcellos(Magnetometer)
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,500 +13,319 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#ifndef MPU9250_H
-#define MPU9250_H
-#include <drivers/BusTemplate.h>
-#include "../Sensor.h"
-#include <miosix.h>
-
-// TODO: fix normalizeTemp() (is /512.0f correct?)
-// TODO: Self-Test
-template <typename Bus>
-class MPU9250 : public GyroSensor,
-                public AccelSensor,
-                public CompassSensor,
-                public TemperatureSensor
-{
+#pragma once
 
-#pragma pack(1)
-    // __extension__ is needed to prevent compiler warnings for anonymous
-    // structs.
-    typedef union {
-        __extension__ struct
-        {
-            int16_t mag[3];
-        };
-        uint8_t raw[6];
-    } akdata_t;
+#include <diagnostic/PrintLogger.h>
 
-    typedef union {
-        __extension__ struct
-        {
-            int16_t accel[3];
-            int16_t temp;
-            int16_t gyro[3];
-        };
-        int16_t buf[8];
-    } mpudata_t;
-#pragma pack()
+#include "MPU9250Data.h"
+#include "drivers/spi/SPIDriver.h"
+#include "sensors/Sensor.h"
 
+/**
+ * @brief Driver class for MPU9250
+ */
+class MPU9250 : public Sensor<MPU9250Data>
+{
 public:
-    MPU9250(uint8_t accelFullScale, uint8_t gyroFullScale)
+    enum MPU9250GyroFSR : uint8_t
     {
-        accelFS         = accelFullScale & 0x03;
-        gyroFS          = gyroFullScale & 0x03;
-        magnetoFSMState = 0;
-        mLastTemp       = 0.0f;
-    }
-
-    ~MPU9250() {}
+        GYRO_FSR_250DPS  = 0x0,
+        GYRO_FSR_500DPS  = 0x08,
+        GYRO_FSR_1000DPS = 0x10,
+        GYRO_FSR_2000DPS = 0x18
+    };
 
-    std::vector<SPIRequest> buildDMARequest() override
-    {
-        // clang-format off
-        std::vector<uint8_t> v = 
-        { 
-            (REG_ACCEL_XOUT_H | 0x80),
-            0,0,0,0,0,0, // accel
-            0,0,         // temp
-            0,0,0,0,0,0, // gyro
-        };
-        // clang-format on
-
-        return {SPIRequest(0, Bus::getCSPin(), v)};
-    }
-
-    /*
-     * Only reads accelerometer, gyro and temp
-     */
-    void onDMAUpdate(const SPIRequest &req) override
+    enum MPU9250AccelFSR : uint8_t
     {
-        const auto &r = req.readResponseFromPeripheral();
-        mpudata_t raw_data;
-
-        memcpy(&raw_data.buf, &(r[1]), sizeof(raw_data));
-        for (int i = 0; i < 3; i++)
-        {
-            raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]);
-            raw_data.gyro[i]  = fromBigEndian16(raw_data.gyro[i]);
-        }
-        raw_data.temp = fromBigEndian16(raw_data.temp);
-
-        mLastAccel.setX((normalizeAccel(raw_data.accel[0])-_axb));
-        mLastAccel.setY((normalizeAccel(raw_data.accel[1])-_ayb));
-        mLastAccel.setZ((normalizeAccel(raw_data.accel[2])-_azb));
-
-        mLastGyro.setX(normalizeGyro(raw_data.gyro[0]));
-        mLastGyro.setY(normalizeGyro(raw_data.gyro[1]));
-        mLastGyro.setZ(normalizeGyro(raw_data.gyro[2]));
-
-        mLastTemp = normalizeTemp(raw_data.temp);
-    }
-
-    bool init() override
-    {       
-        miosix::Thread::sleep(100);
-        uint8_t whoami = Bus::read(REG_WHO_AM_I);
-        TRACE("MPU whoami: expected %x actual %x\n", who_am_i_value_mpu, whoami);
-
-        if (whoami != who_am_i_value_mpu && whoami != 0x70)
-        {
-            last_error = ERR_NOT_ME;
-            return false;
-        }
-
-        if(!initMagneto())
-        {
-            TRACE("MPU9250 Magnetometer init failed\n");
-        }
-
-        // Initialize MPU9250
-        // clang-format off
-        uint8_t init_data[][2] = 
-        {
-            {REG_PWR_MGMT_1,     0x01}, // Clock Source
-            {REG_PWR_MGMT_2,     0x00}, // Enable all sensors
-            {REG_CONFIG,         0x00}, // DLPF_CFG = xxxxx000
-            {REG_SMPLRT_DIV,     0x00}, // Do not divide
-            {REG_GYRO_CONFIG,    (uint8_t) (0x03 | ((gyroFS  & 3) << 3))},
-            {REG_ACCEL_CONFIG,   (uint8_t) (0x00 | ((accelFS & 3) << 3))},
-            {REG_ACCEL_CONFIG2,  0x08}, // FCHOICE = 1, A_DLPF_CFG = 000
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++)
-        {
-            Bus::write(init_data[i][0], init_data[i][1]);
-            miosix::Thread::sleep(10);
-        }
-
-        return true;
-    }
+        ACCEL_FSR_2G  = 0x0,
+        ACCEL_FSR_4G  = 0x08,
+        ACCEL_FSR_8G  = 0x10,
+        ACCEL_FSR_16G = 0x18
+    };
 
-    /* Try to initialize AK8963 for 10 times because it doesn't always works */
-    bool initMagneto()
-    { 
-        for (size_t i = 0; i < 10; i++)
-        {
-            // clang-format off
-            uint8_t init_data[][2] = 
-            {
-                {REG_PWR_MGMT_1,     0x80}, // Reset Device
-                {REG_INT_PIN_CFG,    0x16},
-
-                // I2C (MPU9250 <-> AK8963)
-                {REG_USER_CTRL,      0x20}, // I2C Master mode
-
-                {REG_I2C_MST_CTRL,   0x48}, // I2C configuration multi-master  IIC at 258hz
-                
-                {REG_I2C_SLV0_ADDR,  AK8963_I2C_ADDR}, // Set the I2C slave addres of AK8963 and set for write.
-                {REG_I2C_SLV0_REG,   AK8963_CNTL2}, // I2C slave 0 register address from where to begin data transfer
-                {REG_I2C_SLV0_DO,    0x01}, // Reset AK8963
-                {REG_I2C_SLV0_CTRL,  0x81}, // Enable I2C and set 1 byte
-
-                {REG_I2C_SLV0_REG,   AK8963_CNTL1}, // I2C slave 0 register address from where to begin data transfer
-                {REG_I2C_SLV0_DO,    0x16}, // Register value to 100Hz continuous measurement in 16bit
-                // {REG_I2C_SLV0_DO,    0x12}, // Register value to 8Hz continuous measurement in 16bit
-                {REG_I2C_SLV0_CTRL,  0x81}, //Enable I2C and set 1 byte
-            };
-            // clang-format on
-
-            for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++)
-            {
-                Bus::write(init_data[i][0], init_data[i][1]);
-                miosix::Thread::sleep(10); // It won't work without this delay
-            }
-
-            uint8_t ak_wia = akReadWhoAmI();
-            // printf("AK whoami: expected %x actual %x\n", who_am_i_value_ak, ak_wia);
-            if (ak_wia == who_am_i_value_ak)
-            {
-                magnetoFSMState = 1;
-                return true;
-            }
-        }
-
-        // Reset the device if magnetometer initialization was unsuccessfull.
-        Bus::write(REG_PWR_MGMT_1,0x80); // Reset Device
-        miosix::Thread::sleep(10);
-
-        return false;
-    }
-
-    bool onSimpleUpdate() override
+    enum MPU9250I2CMasterInterfaceClock : uint8_t
     {
-        uint8_t buf[14];
-        mpudata_t raw_data;
-
-        Bus::read(REG_ACCEL_XOUT_H, buf, 14);
-
-        memcpy(&raw_data.buf, buf, 14);
-        for (int i = 0; i < 3; i++)
-        {
-            raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]);
-            raw_data.gyro[i]  = fromBigEndian16(raw_data.gyro[i]);
-        }
-        raw_data.temp = fromBigEndian16(raw_data.temp);
-
-        mLastAccel.setX((normalizeAccel(raw_data.accel[0])-_axb));
-        mLastAccel.setY((normalizeAccel(raw_data.accel[1])-_ayb));
-        mLastAccel.setZ((normalizeAccel(raw_data.accel[2])-_azb));
-
-        mLastGyro.setX(normalizeGyro(raw_data.gyro[0]));
-        mLastGyro.setY(normalizeGyro(raw_data.gyro[1]));
-        mLastGyro.setZ(normalizeGyro(raw_data.gyro[2]));
-
-        mLastTemp = normalizeTemp(raw_data.temp);
-
-        return true;
-    }
+        I2C_MST_IF_CLK_348 = 0x0,
+        I2C_MST_IF_CLK_333 = 0x1,
+        I2C_MST_IF_CLK_320 = 0x2,
+        I2C_MST_IF_CLK_308 = 0x3,
+        I2C_MST_IF_CLK_296 = 0x4,
+        I2C_MST_IF_CLK_286 = 0x5,
+        I2C_MST_IF_CLK_276 = 0x6,
+        I2C_MST_IF_CLK_267 = 0x7,
+        I2C_MST_IF_CLK_258 = 0x8,
+        I2C_MST_IF_CLK_500 = 0x9,
+        I2C_MST_IF_CLK_471 = 0xA,
+        I2C_MST_IF_CLK_444 = 0xB,
+        I2C_MST_IF_CLK_421 = 0xC,
+        I2C_MST_IF_CLK_400 = 0xD,
+        I2C_MST_IF_CLK_381 = 0xE,
+        I2C_MST_IF_CLK_364 = 0xF
+    };
 
-    bool updateMagneto()
+    union MPU9250RawData
     {
-        /* Magnetometer FSM ( Board <-SPI-> MPU9250 <-I2C-> Magneto )
-         *              ______________
-         *   __________/            __v________
-         *  / State 1  \           /  State 2  \
-         * |  ReadI2C  |          |  CopyToMem |
-         * \__________/           \___________/
-         *         ^_______________/
-         *
-         */
-        switch (magnetoFSMState)
+        struct __attribute__((packed)) MPU9250RawDataBits
         {
-            case 1:  // ReadI2C
-                akReadData1();
-                magnetoFSMState = 2;
-                break;
-            case 2:  // CopyToMem
-                akReadData2();
-                magnetoFSMState = 1;
-                break;
-            default:
-                return false;
-        }
-        return true;
-    }
-
-    /* Finds offset bias for the accelerometer*/
-    void calibrateAccel()
-    {
-        accelFS = 0;
-        Bus::write(REG_ACCEL_CONFIG, accelFS);  // setting the accel range to 2G
-        Bus::write(REG_ACCEL_CONFIG2, 0x04);    // setting accel bandwidth to 20Hz
-        Bus::write(REG_SMPLRT_DIV,19);          // setting the sample rate divider to 19
-        
-        // accel bias and scale factor estimation
-        double _axbD = 0;
-        double _aybD = 0;
-        double _azbD = 0;
-
-        for (size_t i = 0; i < _numSamples; i++)
-        {
-            onSimpleUpdate();
-            _axbD += (mLastAccel.getX() + _axb)/((double)_numSamples);
-            _aybD += (mLastAccel.getY() + _ayb)/((double)_numSamples);
-            _azbD += (mLastAccel.getZ() + _azb)/((double)_numSamples);
-            miosix::Thread::sleep(10);
-        }
-
-        _axb = _axbD;
-        _ayb = _aybD;
-        _azb = _azbD + EARTH_GRAVITY;
-    }
-
-    void getAccelCalibParams(float* bias)
-    {
-        bias[0] = _axb;
-        bias[1] = _ayb;
-        bias[2] = _azb;
-    }
-
-    void setAccelCalibParams(float* bias)
-    {
-        _axb = bias[0];
-        _ayb = bias[1];
-        _azb = bias[2];
-    }
-
-    bool selfTest() override
-    {
-        return true;
-    }
+            int16_t accelX;
+            int16_t accelY;
+            int16_t accelZ;
+            int16_t temp;
+            int16_t gyroX;
+            int16_t gyroY;
+            int16_t gyroZ;
+            uint8_t magSt1;
+            int16_t magX;
+            int16_t magY;
+            int16_t magZ;
+        } bits;
+        int8_t bytes[21];
+    };
 
-    // clang-format off
-    enum gyroFullScale
+    enum MPU9250Registers : uint8_t
     {
-        GYRO_FS_250           = 0,
-        GYRO_FS_500           = 1,
-        GYRO_FS_1000          = 2,
-        GYRO_FS_2000          = 3
+        REG_SMPLRT_DIV       = 0x19,
+        REG_CONFIG           = 0x1A,
+        REG_GYRO_CONFIG      = 0x1B,
+        REG_ACCEL_CONFIG     = 0x1C,
+        REG_FIFO_EN          = 0x23,
+        REG_I2C_MST_CTRL     = 0x24,
+        REG_I2C_SLV0_ADDR    = 0x25,
+        REG_I2C_SLV0_REG     = 0x26,
+        REG_I2C_SLV0_CTRL    = 0x27,
+        REG_I2C_SLV1_ADDR    = 0x28,
+        REG_I2C_SLV1_REG     = 0x29,
+        REG_I2C_SLV1_CTRL    = 0x2A,
+        REG_I2C_SLV2_ADDR    = 0x2B,
+        REG_I2C_SLV2_REG     = 0x2C,
+        REG_I2C_SLV2_CTRL    = 0x2D,
+        REG_I2C_SLV3_ADDR    = 0x2E,
+        REG_I2C_SLV3_REG     = 0x2F,
+        REG_I2C_SLV3_CTRL    = 0x30,
+        REG_I2C_SLV4_ADDR    = 0x31,
+        REG_I2C_SLV4_REG     = 0x32,
+        REG_I2C_SLV4_DO      = 0x33,
+        REG_I2C_SLV4_CTRL    = 0x34,
+        REG_INT_PIN_CFG      = 0x37,
+        REG_INT_ENABLE       = 0x38,
+        REG_INT_STATUS       = 0x3A,
+        REG_ACCEL_XOUT_H     = 0x3B,
+        REG_ACCEL_XOUT_L     = 0x3C,
+        REG_ACCEL_YOUT_H     = 0x3D,
+        REG_ACCEL_YOUT_L     = 0x3E,
+        REG_ACCEL_ZOUT_H     = 0x3F,
+        REG_ACCEL_ZOUT_L     = 0x40,
+        REG_TEMP_OUT_H       = 0x41,
+        REG_TEMP_OUT_L       = 0x42,
+        REG_GYRO_XOUT_H      = 0x43,
+        REG_GYRO_XOUT_L      = 0x44,
+        REG_GYRO_YOUT_H      = 0x45,
+        REG_GYRO_YOUT_L      = 0x46,
+        REG_GYRO_ZOUT_H      = 0x47,
+        REG_GYRO_ZOUT_L      = 0x48,
+        REG_EXT_SENS_DATA_00 = 0x49,
+        REG_I2C_SLV0_DO      = 0x63,
+        REG_I2C_SLV1_DO      = 0x64,
+        REG_I2C_SLV2_DO      = 0x65,
+        REG_I2C_SLV3_DO      = 0x66,
+        REG_MST_DELAY_CTRL   = 0x67,
+        REG_USER_CTRL        = 0x6A,
+        REG_PWR_MGMT_1       = 0x6B,
+        REG_PWR_MGMT_2       = 0x6C,
+        REG_WHO_AM_I         = 0x75
     };
 
-    enum accelFullScale
+    enum AK8963Registers : uint8_t
     {
-        ACC_FS_2G             = 0,
-        ACC_FS_4G             = 1,
-        ACC_FS_8G             = 2,
-        ACC_FS_16G            = 3
+        AK8963_REG_WHO_AM_I = 0x0,
+        AK8963_REG_ST1      = 0x2,
+        AK8963_REG_HXL      = 0x3,
+        AK8963_REG_HXH      = 0x4,
+        AK8963_REG_HYL      = 0x5,
+        AK8963_REG_HYH      = 0x6,
+        AK8963_REG_HZL      = 0x7,
+        AK8963_REG_HZH      = 0x8,
+        AK8963_REG_ST2      = 0x9,
+        AK8963_REG_CNTL1    = 0xA,
+        AK8963_REG_CNTL2    = 0xB,
+        AK8963_REG_ASAX     = 0x10,
+        AK8963_REG_ASAY     = 0x11,
+        AK8963_REG_ASAZ     = 0x12
     };
-    // clang-format on
 
-private:
-    constexpr static uint8_t who_am_i_value_mpu = 0x71;
-    constexpr static uint8_t who_am_i_value_ak = 0x48;
-    constexpr static float accelFSMAP[]     = {2.0, 4.0, 8.0, 16.0};
-    constexpr static float gyroFSMAP[]      = {250, 500, 1000, 2000};
-
-    uint8_t gyroFS;
-    uint8_t accelFS;
-    uint8_t magnetoFSMState;
-
-    // accel bias and scale factor estimation
-    size_t _numSamples = 1000;
-    float _axb = 0.0f; // offset bias
-    float _ayb = 0.0f;
-    float _azb = 0.0f;
-
-    inline float normalizeAccel(int16_t val)
-    {
-        return static_cast<float>(val) / 32768.0f * accelFSMAP[accelFS] *
-               EARTH_GRAVITY;
-    }
+    // Registers bits
+    static constexpr uint8_t REG_CONFIG_DLPF_CFG_1      = 0x1;
+    static constexpr uint8_t REG_USER_CTRL_I2C_MST_EN   = 0x20;
+    static constexpr uint8_t REG_I2C_SLV_CTRL_EN        = 0x80;
+    static constexpr uint8_t REG_PWR_MGMT_1_BIT_H_RESET = 0x80;
+    static constexpr uint8_t REG_PWR_MGMT_1_CLKSEL_AUTO = 0x1;
+    static constexpr uint8_t REG_WHO_AM_I_VAL           = 0x71;
+
+    // AK8963 address and bits
+    static constexpr uint8_t AK8963_ADDR                      = 0xC;
+    static constexpr uint8_t AK8963_REG_WHO_AM_I_VAL          = 0x48;
+    static constexpr uint8_t AK8963_REG_CNTL1_POWER_DOWN_MODE = 0x0;
+    static constexpr uint8_t AK8963_REG_CNTL1_SINGLE_MES_MODE = 0x11;  // 16 bit
+    static constexpr uint8_t AK8963_REG_CNTL1_CONT_MES_MODE_1 = 0x12;  // 16 bit
+    static constexpr uint8_t AK8963_REG_CNTL1_CONT_MES_MODE_2 = 0x16;  // 16 bit
+    static constexpr uint8_t AK8963_REG_CNTL1_SELF_TEST_MODE  = 0x8;
+    static constexpr uint8_t AK8963_REG_CNTL1_FUSE_ROM_ACCESS_MODE = 0xF;
+    static constexpr uint8_t AK8963_REG_CNTL1_BIT_16_BIT_OUT       = 0xF;
+    static constexpr uint8_t AK8963_REG_CNTL2_BIT_SRST             = 0x1;
+
+    const float ACCELERATION_FS_MAP[4] = {2.0, 4.0, 8.0, 16.0};
+    const float GYROSCOPE_FS_MAP[4]    = {250, 500, 1000, 2000};
+
+    /**
+     * @brief Instantiates the driver
+     *
+     * @param highSpeedSpiClockDivider_ Clocl diver for 20MHz SPI communication
+     * with the device
+     */
+    MPU9250(SPISlave spiSlave_, unsigned short samplingRate_ = 100,
+            MPU9250GyroFSR gyroFsr_                   = GYRO_FSR_250DPS,
+            MPU9250AccelFSR accelFsr_                 = ACCEL_FSR_2G,
+            SPIClockDivider highSpeedSpiClockDivider_ = SPIClockDivider::DIV4);
+
+    /**
+     * @brief Constructs the default config for SPI Bus.
+     *
+     * @returns the default SPIBusConfig
+     */
+    static SPIBusConfig getDefaultSPIConfig();
 
-    inline float normalizeGyro(int16_t val)
-    {
-        return static_cast<float>(val) / 32768.0f * gyroFSMAP[gyroFS] *
-               DEGREES_TO_RADIANS;
-    }
+    /**
+     * @brief Initialize the device
+     *
+     * The AK8963 is initialized such ad its data are read by the MPU9250 at
+     * each sample cycle, the host only needs to communicate with the MPU9250
+     */
+    bool init() override;
 
-    inline float normalizeMagneto(int16_t val)
-    {
-        // Page 50 @ Register Map document
-        return static_cast<float>(val) / 32760.0f * 4912.0f;
-    }
+    /**
+     * @brief Self test
+     *
+     * @return True if everything ok
+     */
+    bool selfTest() override { return false; };
 
-    inline float normalizeTemp(int16_t val)
-    {
-        // Page 33 @ Register Map Document
-        return static_cast<float>(val) / 512.0f + 21.0f;
-    }
+private:
+    MPU9250Data sampleImpl() override;
 
+    void resetDevice();
 
-    uint8_t akReadWhoAmI()
-    {
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV0_ADDR,    (uint8_t)(AK8963_I2C_ADDR | 0x80)},
-            { REG_I2C_SLV0_REG,     AK8963_WIA},
-            { REG_I2C_SLV0_CTRL,    0x81 }, //Read 1 byte from the magnetometer
-        };
-        // clang-format on
+    void selectAutoClock();
 
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
+    /**
+     * @brief Set the gyro full-scale range.
+     *
+     * @param fsr Desired full-scale range
+     */
+    void setGyroFsr(MPU9250GyroFSR fs);
 
-        miosix::Thread::sleep(2);
+    /**
+     * @brief Set the accel full-scale range.
+     *
+     * @param fsr Desired full-scale range
+     */
+    void setAccelFsr(MPU9250AccelFSR fs);
+
+    /**
+     * @brief Set sampling rate.
+     *
+     * Sampling rate must be between 4Hz and 1kHz.
+     *
+     * @param rate Desired sampling rate (Hz)
+     */
+    void setSampleRate(unsigned short rate);
 
-        uint8_t ret = Bus::read(REG_EXT_SENS_DATA_00|0x80);
-        return ret;
-    }
+    void enableMpuI2CMasterInterface();
 
-    void akReadData()
-    {
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV0_ADDR,    (uint8_t)(AK8963_I2C_ADDR | 0x80)},
-            { REG_I2C_SLV0_REG,     AK8963_HXL},
-            { REG_I2C_SLV0_CTRL,    0x87}, // Read 6 bytes from the magnetometer
-        };
-        // clang-format on
+    void setMpuI2CMasterInterfaceClock(MPU9250I2CMasterInterfaceClock clk);
 
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
+    /**
+     * @brief Setup the I2C slave (0-3) for read operations
+     */
+    void setI2CMasterSlaveRead(uint8_t addr, uint8_t reg, uint8_t nBytes = 1,
+                               uint8_t slave = 0);
 
-        miosix::Thread::sleep(2);
+    /**
+     * @brief Setup the I2C slave (0-3) for write operations
+     */
+    void setI2CMasterSlaveWrite(uint8_t addr, uint8_t reg, uint8_t data,
+                                uint8_t slave = 0);
 
-        akdata_t ak;
+    /**
+     * @brief Disables the I2C slave communication
+     */
+    void disableI2CMasterSlave(uint8_t slave = 0);
 
-        Bus::read(REG_EXT_SENS_DATA_00, ak.raw, sizeof(ak.raw));
+    /**
+     * @brief Read a byte form the AK8963 throug the MPU9250 I2C master
+     * interface.
+     *
+     * Requires that MPU I2C master interface to be enabled.
+     */
+    uint8_t readFromAk(uint8_t reg);
 
-        ak.mag[0] = (ak.raw[1] << 8) | ak.raw[0];
-        ak.mag[1] = (ak.raw[3] << 8) | ak.raw[2];
-        ak.mag[2] = (ak.raw[5] << 8) | ak.raw[4];
+    /**
+     * @brief Writes a byte to the AK8963 throug the MPU9250 I2C master
+     * interface.
+     *
+     * Requires that MPU I2C master interface to be enabled.
+     */
+    void writeToAk(uint8_t reg, uint8_t data);
 
-        mLastCompass.setX(normalizeMagneto(ak.mag[0]));
-        mLastCompass.setY(normalizeMagneto(ak.mag[1]));
-        mLastCompass.setZ(normalizeMagneto(ak.mag[2]));
-    }
+    /**
+     * @brief Initialize the AK8963.
+     *
+     * Requires the MPU I2C master interface to be enabled.
+     */
+    bool initAk();
 
-    // akReadData11 tells to the i2c master to begin reading and.. (akReadData2)
-    void akReadData1()
-    {
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV0_ADDR,    (uint8_t)(AK8963_I2C_ADDR | 0x80)},
-            { REG_I2C_SLV0_REG,     AK8963_HXL},
-            { REG_I2C_SLV0_CTRL,    0x87}, // Read 6 bytes from the magnetometer
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
-    }
-
-    // akReadData2 this one moves the data from spi to the local processor   
-    void akReadData2()
-    {
-        akdata_t ak;
+    /**
+     * @brief Check the WHO AM I code from the device.
+     *
+     * @return true if the device is recognized
+     */
+    bool checkWhoAmI();
 
-        Bus::read(REG_EXT_SENS_DATA_00, ak.raw, sizeof(ak.raw));
+    /**
+     * @brief Check the WHO AM I code for the AK8963.
+     *
+     * @return true if the device is recognized
+     */
+    bool checkAkWhoAmI();
 
-        ak.mag[0] = (ak.raw[1] << 8) | ak.raw[0];
-        ak.mag[1] = (ak.raw[3] << 8) | ak.raw[2];
-        ak.mag[2] = (ak.raw[5] << 8) | ak.raw[4];
+    inline void writeSPIWithDelay(SPITransaction& transaction, uint8_t reg,
+                                  uint8_t data);
 
-        mLastCompass.setX(normalizeMagneto(ak.mag[0]));
-        mLastCompass.setY(normalizeMagneto(ak.mag[1]));
-        mLastCompass.setZ(normalizeMagneto(ak.mag[2]));
-    }
+    inline float normalizeAcceleration(int16_t rawValue);
 
-    // clang-format off
-    enum magnetoMap
-    {
-        AK8963_I2C_ADDR     = 0x0c,
+    inline float normalizeTemperature(int16_t rawValue);
 
-        AK8963_WIA          = 0x00,
-        AK8963_STATUS1      = 0x02,
-        AK8963_HXL          = 0x03,
-        AK8963_CNTL1        = 0x0A,
-        AK8963_CNTL2        = 0x0B,
-    };
+    inline float normalizeGyroscope(int16_t rawValue);
 
-    enum regMap
-    {
-        REG_ST_GYRO         = 0x00,
-        REG_ST_ACCEL        = 0x0D,
-
-        REG_SMPLRT_DIV      = 0x19,
-        REG_CONFIG          = 0x1A,
-        REG_GYRO_CONFIG     = 0x1B,
-        REG_ACCEL_CONFIG    = 0x1C,
-        REG_ACCEL_CONFIG2   = 0x1D,
-
-        REG_I2C_MST_CTRL    = 0x24,
-        REG_I2C_SLV0_ADDR   = 0x25,
-        REG_I2C_SLV0_REG    = 0x26,
-        REG_I2C_SLV0_CTRL   = 0x27,
-        REG_I2C_SLV0_DO     = 0x63,
-
-        REG_I2C_SLV4_ADDR   = 0x31,
-        REG_I2C_SLV4_REG    = 0x32,
-        REG_I2C_SLV4_DO     = 0x33,
-        REG_I2C_SLV4_CTRL   = 0x34,
-        REG_I2C_SLV4_DI     = 0x35,
-        REG_I2C_MST_STATUS  = 0x36,
-
-        REG_ACCEL_XOUT_H    = 0x3b,
-
-        REG_INT_PIN_CFG     = 0x37,
-        REG_INT_ENABLE      = 0x38,
-        REG_INT_STATUS      = 0x3A,
-
-        REG_EXT_SENS_DATA_00= 0x49,
-
-        REG_USER_CTRL       = 0x6A,
-        REG_PWR_MGMT_1      = 0x6B,
-        REG_PWR_MGMT_2      = 0x6C,
-        REG_WHO_AM_I        = 0x75
-    };
-    // clang-format on
+    inline float normalizeMagnetometer(int16_t rawValue, float adjustmentCoeff);
 
-    void readRAWData(mpudata_t &out)
-    {
-        Bus::read(REG_ACCEL_XOUT_H, reinterpret_cast<uint8_t *>(out.buf),
-                  sizeof(out.buf));
+    SPISlave spiSlave;
+
+    const unsigned short samplingRate;
 
-        // BigEndian -> CPUArch (LittleEndian as usual)
-        for (size_t i = 0; i < (sizeof(out.buf) / sizeof(out.buf[0])); i++)
-            out.buf[i] = fromBigEndian16(out.buf[i]);
-    }
-};
+    const MPU9250GyroFSR gyroFsr;
+    const MPU9250AccelFSR accelFsr;
+    float magSensAdjCoeff[3];  // Page 32 on datasheet
 
-template <typename Bus>
-constexpr float MPU9250<Bus>::accelFSMAP[];
+    const SPIClockDivider highSpeedSpiClockDivider;
 
-template <typename Bus>
-constexpr float MPU9250<Bus>::gyroFSMAP[];
+    bool initialized = false;  // Whether the sensor has been initialized
 
-#endif /* ifndef MPU9250 */
+    PrintLogger logger = Logging::getLogger("mpu9250");
+};
\ No newline at end of file
diff --git a/src/shared/sensors/MPU9250/MPU9250Data.h b/src/shared/sensors/MPU9250/MPU9250Data.h
index 7c850c56bbf708da68a4aa17e0bdfcda1e398b3f..4204b0791e656d4b44cde96828890c4af88e4519 100644
--- a/src/shared/sensors/MPU9250/MPU9250Data.h
+++ b/src/shared/sensors/MPU9250/MPU9250Data.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,39 +13,41 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
-#ifndef SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H
-#define SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H
 
-#include <ostream>
-#include "math/Vec3.h"
+#pragma once
 
-struct MPU9250Data
+#include "sensors/SensorData.h"
+
+struct MPU9250Data : public AccelerometerData,
+                     public GyroscopeData,
+                     public MagnetometerData,
+                     public TemperatureData
 {
-    long long timestamp;
-    Vec3 accel;
-    Vec3 gyro;
-    Vec3 compass;
-    float temp;
+    MPU9250Data()
+        : AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0},
+          MagnetometerData{0, 0.0, 0.0, 0.0}, TemperatureData{0, 0.0}
+    {
+    }
 
     static std::string header()
     {
-        return "timestamp,acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z,compass_x,"
-               "compass_y,compass_z\n";
+        return "accel_timestamp,accel_x,accel_y,accel_z,gyro_timestamp,gyro_x,"
+               "gyro_y,gyro_z,mag_timestamp,mag_x,mag_y,mag_z,temp_timestamp,"
+               "temp\n";
     }
 
     void print(std::ostream& os) const
     {
-        os << timestamp << "," << accel.getX() << "," << accel.getY() << ","
-           << accel.getZ() << "," << gyro.getX() << "," << gyro.getY() << ","
-           << gyro.getZ() << "," << compass.getX() << "," << compass.getY()
-           << "," << compass.getZ() << "\n";
+        os << accel_timestamp << "," << accel_x << "," << accel_y << ","
+           << accel_z << "," << gyro_timestamp << "," << gyro_x << "," << gyro_y
+           << "," << gyro_z << "," << mag_timestamp << "," << mag_x << ","
+           << mag_y << "," << mag_z << "," << temp_timestamp << "," << temp
+           << "\n";
     }
-};
-
-#endif /* SRC_SHARED_SENSORS_MPU9250_MPU9250DATA_H */
+};
\ No newline at end of file
diff --git a/src/shared/sensors/MPU9250/MPU9250_magneto.h b/src/shared/sensors/MPU9250/MPU9250_magneto.h
deleted file mode 100644
index 952caa7af3746f28920fdb62dd880914c0e85e1c..0000000000000000000000000000000000000000
--- a/src/shared/sensors/MPU9250/MPU9250_magneto.h
+++ /dev/null
@@ -1,435 +0,0 @@
-/* MAX9250 Driver
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#ifndef MAX9250_H
-#define MAX9250_H
-#include <drivers/BusTemplate.h>
-#include "../Sensor.h"
-
-/* THIS FILE INCLUDES THE DRIVER FOR THE MAGNETOMETER PART, WHICH IS CURRENTLY
- * NOT WORKING. :(
- */
-
-// TODO: fix normalizeTemp() (is /512.0f correct?)
-// TODO: Self-Test
-template <typename Bus>
-class MPU9250 : public GyroSensor,
-                public AccelSensor,
-                public CompassSensor,
-                public TemperatureSensor
-{
-
-#pragma pack(1)
-    typedef union {
-        __extension__ struct
-        {
-            uint8_t status1;
-            int16_t mag[3];
-            uint8_t status2;
-        };
-        uint8_t raw[9];
-    } akdata_t;
-
-    typedef union {
-        __extension__ struct
-        {
-            int16_t accel[3];
-            int16_t temp;
-            int16_t gyro[3];
-        };
-        int16_t buf[8];
-    } mpudata_t;
-#pragma pack()
-
-public:
-    MPU9250(uint8_t accelFullScale, uint8_t gyroFullScale)
-    {
-        accelFS         = accelFullScale & 0x03;
-        gyroFS          = gyroFullScale & 0x03;
-        magnetoFSMState = 0;
-        mLastTemp       = 0.0f;
-    }
-
-    ~MPU9250() {}
-
-    std::vector<SPIRequest> buildDMARequest() override
-    {
-        // clang-format off
-        std::vector<uint8_t> v = 
-        { 
-            (REG_ACCEL_XOUT_H | 0x80),
-            0,0,0,0,0,0, // accel
-            0,0,         // temp
-            0,0,0,0,0,0, // gyro
-        };
-        // clang-format on
-
-        return {SPIRequest(0, Bus::getCSPin(), v)};
-    }
-
-    /*
-     * Only reads accelerometer, gyro and temp
-     */
-    void onDMAUpdate(const SPIRequest &req) override
-    {
-        const auto &r = req.readResponseFromPeripheral();
-        mpudata_t raw_data;
-
-        memcpy(&raw_data.buf, &(r[1]), sizeof(raw_data));
-        for (int i = 0; i < 3; i++)
-        {
-            raw_data.accel[i] = fromBigEndian16(raw_data.accel[i]);
-            raw_data.gyro[i]  = fromBigEndian16(raw_data.gyro[i]);
-        }
-        raw_data.temp = fromBigEndian16(raw_data.temp);
-
-        mLastAccel.setX(normalizeAccel(raw_data.accel[0]));
-        mLastAccel.setY(normalizeAccel(raw_data.accel[1]));
-        mLastAccel.setZ(normalizeAccel(raw_data.accel[2]));
-
-        mLastGyro.setX(normalizeGyro(raw_data.gyro[0]));
-        mLastGyro.setY(normalizeGyro(raw_data.gyro[1]));
-        mLastGyro.setZ(normalizeGyro(raw_data.gyro[2]));
-
-        mLastTemp = normalizeTemp(raw_data.temp);
-    }
-
-    bool init() override
-    {
-        uint8_t whoami = Bus::read(REG_WHO_AM_I);
-        // printf("WHOAMI: %d\n", whoami);
-        if (whoami != who_am_i_value)
-        {
-            last_error = ERR_NOT_ME;
-            return false;
-        }
-
-        // Warning: do not reset this sensor at startup, otherwise
-        // its magnetometer could randomly stop working.
-
-        // clang-format off
-        uint8_t init_data[][2] = 
-        {
-            {REG_PWR_MGMT_1,     0x01},
-            {REG_PWR_MGMT_2,     0x00}, // Enable all sensors
-            {REG_CONFIG,         0x00}, // DLPF_CFG = xxxxx000
-            {REG_SMPLRT_DIV,     0x00}, // Do not divide
-            {REG_GYRO_CONFIG,    (uint8_t) (0x03 | ((gyroFS  & 3) << 3))},
-            {REG_ACCEL_CONFIG,   (uint8_t) (0x00 | ((accelFS & 3) << 3))},
-            {REG_ACCEL_CONFIG2,  0x08}, // FCHOICE = 1, A_DLPF_CFG = 000
-            {REG_INT_PIN_CFG,    0x30},
-            {REG_INT_ENABLE,     0x00}, // No interrupts
-
-            // I2C (MPU9250 <-> AK8963)
-            {REG_USER_CTRL,      0x20}, // Master mode TODO: I2C_IF_DIS???
-            {REG_I2C_MST_CTRL,   0x00}, // I2C @ 348khz(400 seems giving issues)
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++)
-            Bus::write(init_data[i][0], init_data[i][1]);
-
-        //  MAGNETOMETER NOT WORKING  
-        /*
-        uint8_t ak_wia = akReadReg(AK8963_WIA);
-
-        if (ak_wia != 0x48)
-        {
-            last_error = ERR_CANT_TALK_TO_CHILD;  // TODO
-            return false;
-        }
-
-        akWriteReg(AK8963_CNTL2, 0x01);
-        miosix::Thread::sleep(1);
-        akWriteReg(AK8963_CNTL1, 0x16);
-        miosix::Thread::sleep(1);
-
-        */
-        magnetoFSMState = 1;
-        return true;
-    }
-
-    /**
-     * MAGNETOMETER NOT WORKING
-     * Reads external magnetometer.
-
-    bool onSimpleUpdate() override
-    {
-
-        akdata_t ak;
-
-        /* Magnetometer FSM ( Board <-SPI-> MPU9250 <-I2C-> Magneto )
-         *              ______________
-         *   __________/            __v________
-         *  / State 1  \           /  State 2  \
-         * |  ReadI2C  |          |  CopyToMem |
-         * \__________/           \___________/
-         *         ^_______________/
-         *
-         
-
-        switch (magnetoFSMState)
-        {
-            case 1:  // ReadI2C
-                akReadReg_1(AK8963_STATUS1, sizeof(ak.raw));
-                magnetoFSMState = 2;
-                break;
-            case 2:  // CopyToMem
-                akReadReg_2(ak.raw, sizeof(ak.raw));
-
-                mLastCompass.setX(normalizeMagneto(ak.mag[0]));
-                mLastCompass.setY(normalizeMagneto(ak.mag[1]));
-                mLastCompass.setZ(normalizeMagneto(ak.mag[2]));
-                magnetoFSMState = 1;
-                break;
-            default:
-                return false;
-        }
-
-        return true;
-    }
-    */
-
-    bool selfTest() override
-    {
-        uint8_t st_gyro[3], st_accel[3];
-        mpudata_t test, real;
-        uint16_t cfg;  // [ REG_GYRO_CONFIG REG_ACCEL_CONFIG ]
-
-        readRAWData(real);
-        Bus::read(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg),
-                  sizeof(cfg));
-
-        // -- ENABLE SELF TEST --
-        cfg |= 0xe0e0;
-        Bus::write(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg),
-                   sizeof(cfg));
-
-        // -- SELF TEST ROUTINE --
-        Bus::read(REG_ST_GYRO, st_gyro, sizeof(st_gyro));
-        Bus::read(REG_ST_ACCEL, st_accel, sizeof(st_accel));
-        miosix::Thread::sleep(10);
-        readRAWData(test);
-
-        // -- DISABLE SELF TEST --
-        cfg &= ~(0xe0e0);
-        Bus::write(REG_GYRO_CONFIG, reinterpret_cast<uint8_t *>(&cfg),
-                   sizeof(cfg));
-
-        // TODO Self-Test
-
-        return false;
-    }
-
-    // clang-format off
-    enum gyroFullScale
-    {
-        GYRO_FS_250           = 0,
-        GYRO_FS_500           = 1,
-        GYRO_FS_1000          = 2,
-        GYRO_FS_2000          = 3
-    };
-
-    enum accelFullScale
-    {
-        ACC_FS_2G             = 0,
-        ACC_FS_4G             = 1,
-        ACC_FS_8G             = 2,
-        ACC_FS_16G            = 3
-    };
-    // clang-format on
-
-private:
-    constexpr static uint8_t who_am_i_value = 0x71;
-    constexpr static float accelFSMAP[]     = {2.0, 4.0, 8.0, 16.0};
-    constexpr static float gyroFSMAP[]      = {250, 500, 1000, 2000};
-
-    uint8_t gyroFS;
-    uint8_t accelFS;
-    uint8_t magnetoFSMState;
-
-    inline float normalizeAccel(int16_t val)
-    {
-        return static_cast<float>(val) / 32768.0f * accelFSMAP[accelFS] *
-               EARTH_GRAVITY;
-    }
-
-    inline float normalizeGyro(int16_t val)
-    {
-        return static_cast<float>(val) / 32768.0f * gyroFSMAP[gyroFS] *
-               DEGREES_TO_RADIANS;
-    }
-
-    inline float normalizeMagneto(int16_t val)
-    {
-        // Page 50 @ Register Map document
-        return static_cast<float>(val) / 32760.0f * 4912.0f;
-    }
-
-    inline float normalizeTemp(int16_t val)
-    {
-        // Page 33 @ Register Map Document
-        return static_cast<float>(val) / 512.0f + 21.0f;
-    }
-
-    uint8_t akReadReg(uint8_t reg)
-    {
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV4_ADDR,    (uint8_t)(AK8963_I2C_ADDR | 0x80)},
-            { REG_I2C_SLV4_REG,     reg },
-            { REG_I2C_SLV4_CTRL,    0x80 },
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
-
-        int timeout = 20;
-        while (--timeout >= 0)
-        {
-            if (Bus::read(REG_I2C_MST_STATUS) & 0x40)
-                break;
-            miosix::Thread::sleep(1);
-        }
-
-        if (timeout < 0)
-            return -1;
-
-        uint8_t ret = Bus::read(REG_I2C_SLV4_DI);
-        return ret;
-    }
-
-    // This is an optimized version of the common (i2c) burst-read:
-    // _1 tells to the i2c master to begin reading and.. (See _2)
-    void akReadReg_1(uint8_t addr, uint8_t len)
-    {
-        assert(len < 16 && len > 0);
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV0_ADDR,    (uint8_t)(AK8963_I2C_ADDR | 0x80)},
-            { REG_I2C_SLV0_REG,     addr },
-            { REG_I2C_SLV0_CTRL,    (uint8_t)(0x80 + len)},
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
-    }
-
-    // _2 this one moves the data from spi to the local processor
-    void akReadReg_2(uint8_t *buf, uint8_t len)
-    {
-        Bus::read(REG_EXT_SENS_DATA_00, buf, len);
-    }
-
-    void akWriteReg(uint8_t reg, uint8_t data)
-    {
-        // clang-format off
-        uint8_t regs[][2] =
-        {
-            { REG_I2C_SLV4_ADDR,    AK8963_I2C_ADDR},
-            { REG_I2C_SLV4_REG,     reg},
-            { REG_I2C_SLV4_DO,      data},
-            { REG_I2C_SLV4_CTRL,    0x80},
-        };
-        // clang-format on
-
-        for (size_t i = 0; i < sizeof(regs) / sizeof(regs[0]); i++)
-            Bus::write(regs[i][0], regs[i][1]);
-
-        miosix::Thread::sleep(1);
-    }
-
-    // clang-format off
-    enum magnetoMap 
-    {
-        AK8963_I2C_ADDR     = 0x0c,
-
-        AK8963_WIA          = 0x00,
-        AK8963_STATUS1      = 0x02,
-        AK8963_HXL          = 0x03,
-        AK8963_CNTL1        = 0x0a,
-        AK8963_CNTL2        = 0x0b,
-    };
-
-    enum regMap
-    {
-        REG_ST_GYRO         = 0x00,
-        REG_ST_ACCEL        = 0x0D,
-
-        REG_SMPLRT_DIV      = 0x19,
-        REG_CONFIG          = 0x1A,
-        REG_GYRO_CONFIG     = 0x1B,
-        REG_ACCEL_CONFIG    = 0x1C,
-        REG_ACCEL_CONFIG2   = 0x1D,
-
-        REG_I2C_MST_CTRL    = 0x24, 
-        REG_I2C_SLV0_ADDR   = 0x25,
-        REG_I2C_SLV0_REG    = 0x26,
-        REG_I2C_SLV0_CTRL   = 0x27,
-        REG_I2C_SLV0_DO     = 0x63,
-
-        REG_I2C_SLV4_ADDR   = 0x31,
-        REG_I2C_SLV4_REG    = 0x32,
-        REG_I2C_SLV4_DO     = 0x33,
-        REG_I2C_SLV4_CTRL   = 0x34,
-        REG_I2C_SLV4_DI     = 0x35,
-        REG_I2C_MST_STATUS  = 0x36,
-
-        REG_ACCEL_XOUT_H    = 0x3b,
-
-        REG_INT_PIN_CFG     = 0x37,
-        REG_INT_ENABLE      = 0x38,
-        REG_INT_STATUS      = 0x3A,
-
-        REG_EXT_SENS_DATA_00= 0x49,
-
-        REG_USER_CTRL       = 0x6A,
-        REG_PWR_MGMT_1      = 0x6B,
-        REG_PWR_MGMT_2      = 0x6C,
-        REG_WHO_AM_I        = 0x75
-    };
-    // clang-format on
-
-    void readRAWData(mpudata_t &out)
-    {
-        Bus::read(REG_ACCEL_XOUT_H, reinterpret_cast<uint8_t *>(out.buf),
-                  sizeof(out.buf));
-
-        // BigEndian -> CPUArch (LittleEndian as usual)
-        for (size_t i  = 0; i < (sizeof(out.buf) / sizeof(out.buf[0])); i++)
-            out.buf[i] = fromBigEndian16(out.buf[i]);
-    }
-};
-
-template <typename Bus>
-constexpr float MPU9250<Bus>::accelFSMAP[];
-
-template <typename Bus>
-constexpr float MPU9250<Bus>::gyroFSMAP[];
-
-#endif /* ifndef MAX9250_H */
diff --git a/src/shared/sensors/MS5803/MS5803.cpp b/src/shared/sensors/MS5803/MS5803.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b7de74b25648d5254b077683f6402cb9008715d6
--- /dev/null
+++ b/src/shared/sensors/MS5803/MS5803.cpp
@@ -0,0 +1,200 @@
+
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "MS5803.h"
+
+#include "TimestampTimer.h"
+
+MS5803::MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_)
+    : spiSlave(spiSlave_), temperatureDivider(temperatureDivider_)
+{
+}
+
+MS5803::MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_,
+               SPIBusConfig spiConfig_, uint16_t temperatureDivider_)
+    : spiSlave(spiBus_, cs_, spiConfig_),
+      temperatureDivider(temperatureDivider_)
+{
+}
+
+bool MS5803::init()
+{
+    SPITransaction transaction{spiSlave};
+
+    // Read calibration data
+    calibrationData.sens =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_SENS_MASK);
+    calibrationData.off =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_OFF_MASK);
+    calibrationData.tcs =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TCS_MASK);
+    calibrationData.tco =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TCO_MASK);
+    calibrationData.tref =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TREF_MASK);
+    calibrationData.tempsens =
+        readReg(transaction, REG_PROM_READ_MASK | REG_PROM_TEMPSENS_MASK);
+
+    LOG_INFO(logger, "Init: off={}, tcs={}, tco={}, tref={}, tsens={}",
+             calibrationData.off, calibrationData.tcs, calibrationData.tco,
+             calibrationData.tref, calibrationData.tempsens);
+
+    return true;
+}
+
+bool MS5803::selfTest() { return true; }
+
+MS5803Data MS5803::sampleImpl()
+{
+    SPITransaction transaction{spiSlave};
+
+    uint8_t buffer[3];
+
+    switch (deviceState)
+    {
+        case STATE_INIT:
+        {
+            // Begin temperature sampling
+            transaction.write(REG_CONVERT_D2_4096);
+            deviceState = STATE_SAMPLED_TEMP;
+            break;
+        }
+        case STATE_SAMPLED_TEMP:
+        {
+            // Read back the sampled temperature
+            transaction.read(REG_ADC_READ, buffer, 3, false);
+
+            uint32_t tmpRawTemperature = (uint32_t)buffer[2] |
+                                         ((uint32_t)buffer[1] << 8) |
+                                         ((uint32_t)buffer[0] << 16);
+            lastTemperatureTimestamp = TimestampTimer::getTimestamp();
+
+            // Check if the value is valid
+            if (tmpRawTemperature != 0)
+            {
+                rawTemperature = tmpRawTemperature;
+            }
+            else
+            {
+                LOG_ERR(logger, "The read raw temperature isn't valid");
+            }
+
+            // Begin pressure sampling
+            transaction.write(REG_CONVERT_D1_4096);
+            deviceState = STATE_SAMPLED_PRESS;
+            break;
+        }
+        case STATE_SAMPLED_PRESS:
+        {
+            // Read back the sampled pressure
+            transaction.read(REG_ADC_READ, buffer, 3, false);
+
+            uint32_t tmpRawPressure = (uint32_t)buffer[2] |
+                                      ((uint32_t)buffer[1] << 8) |
+                                      ((uint32_t)buffer[0] << 16);
+
+            // Check if the value is valid
+            if (tmpRawPressure != 0)
+            {
+                rawPressure = tmpRawPressure;
+            }
+            else
+            {
+                LOG_ERR(logger, "The read raw pressure isn't valid");
+            }
+
+            last_sample = updateData();
+
+            // Check whether to read the pressure or the temperature
+            tempCounter++;
+            if (tempCounter % temperatureDivider == 0)
+            {
+                // Begin temperature sampling
+                transaction.write(REG_CONVERT_D2_4096);
+                deviceState = STATE_SAMPLED_TEMP;
+            }
+            else
+            {
+                // Begin pressure sampling again
+                transaction.write(REG_CONVERT_D1_4096);
+            }
+            break;
+        }
+        default:
+            break;
+    }
+
+    return last_sample;
+}
+
+MS5803Data MS5803::updateData()
+{
+    // First order compensation
+    int32_t dt   = rawTemperature - (((uint32_t)calibrationData.tref) << 8);
+    int32_t temp = 2000 + (((uint64_t)dt * calibrationData.tempsens) >> 23);
+
+    int64_t offs = ((int64_t)calibrationData.off << 16) +
+                   (((int64_t)calibrationData.tco * dt) >> 7);
+    int64_t sens = ((int64_t)calibrationData.sens << 15) +
+                   (((int64_t)calibrationData.tcs * dt) >> 8);
+
+    int64_t t2 = 0, off2 = 0, sens2 = 0;
+
+    // Second order temperature compensation
+    if (temp < 2000)
+    {
+        t2    = (((int64_t)dt) * dt) >> 31;
+        off2  = 3 * (temp - 2000) * (temp - 2000);
+        sens2 = (7 * (temp - 2000) * (temp - 2000)) >> 3;
+
+        if (temp < -1500)
+        {
+            sens2 = sens2 + 2 * (temp + 1500) * (temp + 1500);
+        }
+    }
+    else if (temp >= 4500)
+    {
+        sens2 = sens2 - (((temp - 4500) * (temp - 4500)) >> 3);
+    }
+
+    temp = temp - t2;
+    offs = offs - off2;
+    sens = sens - sens2;
+
+    float pressure =
+        (((((int64_t)rawPressure) * sens) / 2097152.0) - offs) / 32786.0;
+
+    // Pressure in Pascal
+    float temp_ = temp / 100.0f;
+
+    return MS5803Data(TimestampTimer::getTimestamp(), pressure,
+                      lastTemperatureTimestamp, temp_);
+}
+
+uint16_t MS5803::readReg(SPITransaction& transaction, uint8_t reg)
+{
+    uint8_t rcv[2];
+    transaction.read(reg, rcv, 2);
+    uint16_t data = (rcv[0] << 8) | rcv[1];
+    return data;
+}
\ No newline at end of file
diff --git a/src/shared/sensors/MS5803/MS5803.h b/src/shared/sensors/MS5803/MS5803.h
new file mode 100644
index 0000000000000000000000000000000000000000..66ff70ac84c51dec7c1cd0bfd7de644aed08ce10
--- /dev/null
+++ b/src/shared/sensors/MS5803/MS5803.h
@@ -0,0 +1,112 @@
+/* Copyright (c) 2015-2021 Skyward Experimental Rocketry
+ * Authors: Davide Benini, Matteo Piazzolla, Alain Carlucci, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Debug.h>
+#include <diagnostic/PrintLogger.h>
+#include <drivers/spi/SPIDriver.h>
+#include <sensors/MS5803/MS5803Data.h>
+#include <sensors/Sensor.h>
+
+class MS5803 : public Sensor<MS5803Data>
+{
+public:
+    enum FSMStates
+    {
+        STATE_INIT = 0,
+        STATE_SAMPLED_TEMP,
+        STATE_SAMPLED_PRESS
+    };
+
+    enum MS5803Registers : uint8_t
+    {
+        REG_RESET = 0x1E,
+
+        // Conversion commands for pressure and output data resolution
+        REG_CONVERT_D1_256  = 0x40,  // Conversion time: 0.48-0.54-0.60ms
+        REG_CONVERT_D1_512  = 0x42,  // Conversion time: 0.95-1.06-1.17ms
+        REG_CONVERT_D1_1024 = 0x44,  // Conversion time: 1.88-2.08-2.28ms
+        REG_CONVERT_D1_2048 = 0x46,  // Conversion time: 3.72-4.13-4-54ms
+        REG_CONVERT_D1_4096 = 0x48,  // Conversion time: 7.40-8.22-9.04ms
+
+        // Conversion commands for temperature and output data resolution
+        REG_CONVERT_D2_256  = 0x50,  // Conversion time: 0.48-0.54-0.60ms
+        REG_CONVERT_D2_512  = 0x52,  // Conversion time: 0.95-1.06-1.17ms
+        REG_CONVERT_D2_1024 = 0x54,  // Conversion time: 1.88-2.08-2.28ms
+        REG_CONVERT_D2_2048 = 0x56,  // Conversion time: 3.72-4.13-4-54ms
+        REG_CONVERT_D2_4096 = 0x58,  // Conversion time: 7.40-8.22-9.04ms
+
+        // Read command for adc output
+        REG_ADC_READ = 0x00,
+
+        REG_PROM_READ_MASK     = 0xA0,
+        REG_PROM_SENS_MASK     = 0x02,
+        REG_PROM_OFF_MASK      = 0x04,
+        REG_PROM_TCS_MASK      = 0x06,
+        REG_PROM_TCO_MASK      = 0x08,
+        REG_PROM_TREF_MASK     = 0x0A,
+        REG_PROM_TEMPSENS_MASK = 0x0C,
+    };
+
+    static constexpr uint8_t TIMEOUT = 5;
+
+    MS5803(SPISlave spiSlave_, uint16_t temperatureDivider_ = 1);
+    MS5803(SPIBusInterface& spiBus_, miosix::GpioPin cs_,
+           SPIBusConfig spiConfig_, uint16_t temperatureDivider_ = 1);
+
+    bool init() override;
+
+    bool selfTest() override;
+
+private:
+    /**
+     * Implements a state machines composed of 3 states:
+     * 1. Command pressure sample
+     * 2. Read Pressure sample & command temperature sample
+     * 3. Read temperature sample & command pressure sample
+     *
+     * After the first call to sample() (state 1), the machine
+     * transitions between states 2 and 3: The effective sampling rate is half
+     * the rate at which this function is called.
+     * Example: call sample() at 100 Hz -> Pressure & Temperature sample
+     * Rate = 50 Hz
+     */
+    MS5803Data sampleImpl() override;
+
+    MS5803Data updateData();
+
+    uint16_t readReg(SPITransaction& spi, uint8_t reg);
+
+    const SPISlave spiSlave;
+
+    MS5803CalibrationData calibrationData;
+    uint16_t temperatureDivider;
+
+    uint8_t deviceState               = STATE_INIT;
+    uint32_t rawTemperature           = 0;
+    uint32_t rawPressure              = 0;
+    uint64_t lastTemperatureTimestamp = 0;
+    uint16_t tempCounter              = 0;
+
+    PrintLogger logger = Logging::getLogger("ms5803");
+};
diff --git a/src/shared/sensors/MS5803/MS5803Data.h b/src/shared/sensors/MS5803/MS5803Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d7afe0e146d079624df50d9af1c3874a9dbf757
--- /dev/null
+++ b/src/shared/sensors/MS5803/MS5803Data.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2015-2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+/**
+ * @brief MS5803 calibration data. See page 13 of datasheet for more details.
+ */
+struct MS5803CalibrationData
+{
+    uint16_t sens     = 0;
+    uint16_t off      = 0;
+    uint16_t tcs      = 0;
+    uint16_t tco      = 0;
+    uint16_t tref     = 0;
+    uint16_t tempsens = 0;
+};
+
+struct MS5803Data : public PressureData, TemperatureData
+{
+
+    MS5803Data() : PressureData{0, 0.0}, TemperatureData{0, 0.0} {}
+
+    MS5803Data(uint64_t pressureTimestamp, float pressure,
+               uint64_t temperatureTimestamp, float temperature)
+        : PressureData{pressureTimestamp, pressure}, TemperatureData{
+                                                         temperatureTimestamp,
+                                                         temperature}
+    {
+    }
+
+    static std::string header()
+    {
+        return "press_timestamp,press,temp_timestamp,temp\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "," << temp_timestamp << ","
+           << temp << "\n";
+    }
+};
diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07.h b/src/shared/sensors/MS580301BA07/MS580301BA07.h
deleted file mode 100644
index b52b1474f8b0e74093602ea0b6517bbf3a3d30ac..0000000000000000000000000000000000000000
--- a/src/shared/sensors/MS580301BA07/MS580301BA07.h
+++ /dev/null
@@ -1,244 +0,0 @@
-/* MS580301BA07 Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Davide Benini, Matteo Michele Piazzolla, Alain Carlucci
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#pragma once
-
-#include <drivers/BusTemplate.h>
-#include "../Sensor.h"
-#include "MS580301BA07Data.h"
-#include "Debug.h"
-
-// TODO second order temperature compensation
-template <class Bus>
-class MS580301BA07 : public PressureSensor, public TemperatureSensor
-{
-
-public:
-    /* Class constructor. Reset lastPressure and lastTemperature */
-    MS580301BA07()
-    {
-        memset(&cd, 0, sizeof(calibration_data));
-        mLastTemp     = 0;
-        mLastPressure = 0;
-    }
-
-    ~MS580301BA07() {}
-
-    bool init()
-    {
-        int timeout = 10;
-        do
-        {
-            Bus::read(RESET_DEV);
-
-            miosix::Thread::sleep(3);
-
-            cd.sens = readReg(PROM_READ_MASK | PROM_SENS_MASK);
-            if (cd.sens == 0){
-                miosix::Thread::sleep(1);
-                TRACE("Could not read cd.sens\n");
-            }
-        } while (cd.sens == 0 && --timeout > 0);
-
-        if (timeout <= 0)
-        {
-            last_error = ERR_RESET_TIMEOUT;
-            return false;
-        }
-
-        cd.off      = readReg(PROM_READ_MASK | PROM_OFF_MASK);
-        cd.tcs      = readReg(PROM_READ_MASK | PROM_TCS_MASK);
-        cd.tco      = readReg(PROM_READ_MASK | PROM_TCO_MASK);
-        cd.tref     = readReg(PROM_READ_MASK | PROM_TREF_MASK);
-        cd.tempsens = readReg(PROM_READ_MASK | PROM_TEMPSENS_MASK);
-
-        TRACE("off: %d, tcs: %d, tco: %d, tref: %d, tsens: %d\n", (int)cd.off,
-               (int)cd.tcs, (int)cd.tco, (int)cd.tref, (int)cd.tempsens);
-
-        mStatus = 0;
-
-        return true;
-    }
-
-    bool selfTest() { return false; }
-
-    /**
-     * Implements a state machines composed of 3 states:
-     * 1. Command pressure sample
-     * 2. Read Pressure sample & command temperature sample
-     * 3. Read temperature sample & command pressure sample
-     *
-     * After the first call to onSimpleUpdate() (state 1), the machine
-     * transitions between states 2 and 3: The effectoive sampling rate is half
-     * the rate at which this function is called.
-     * Example: call onSimpleUpdate() at 100 Hz -> Pressure & Temperature sample
-     * Rate = 50 Hz
-     */
-    bool onSimpleUpdate()
-    {
-        uint8_t rcvbuf[3];
-        uint32_t temperature = 0;
-
-        switch (mStatus)
-        {
-            case STATE_INIT:
-                Bus::write(CONVERT_D1_4096);
-                mStatus = STATE_SAMPLED_PRESSURE;
-            case STATE_SAMPLED_PRESSURE:
-                Bus::read_low(ADC_READ, rcvbuf, 3);
-                mInternalPressure = rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) |
-                                    ((uint32_t)rcvbuf[0] << 16);
-
-                Bus::write(CONVERT_D2_4096);  // Begin temperature sampling
-                mStatus = STATE_SAMPLED_TEMPERATURE;
-
-                break;
-            case STATE_SAMPLED_TEMPERATURE:
-                Bus::read_low(ADC_READ, rcvbuf, 3);
-
-                // TODO use swapBytes
-                temperature = (uint32_t)rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) |
-                              ((uint32_t)rcvbuf[0] << 16);
-
-                updateData(mInternalPressure, temperature);
-                Bus::write(CONVERT_D1_4096);  // Begin pressure sampling
-                mStatus = STATE_SAMPLED_PRESSURE;
-            
-                break;
-        }
-
-        return true;
-    }
-
-    MS5803Data getData() { return dataStruct; }
-
-    enum FSM_State
-    {
-        STATE_INIT                = 0,
-        STATE_SAMPLED_PRESSURE    = 1,
-        STATE_SAMPLED_TEMPERATURE = 2
-    };
-
-    uint8_t getState() { return mStatus; }
-
-private:
-    static constexpr uint8_t TIMEOUT = 5;
-    uint8_t mStatus;
-    uint32_t mInternalPressure;
-
-    MS5803Data dataStruct;
-
-    void updateData(uint32_t pressure, uint32_t temperature)
-    {
-        int32_t dt   = temperature - (((uint32_t)cd.tref) << 8);
-        int32_t temp = 2000 + (((uint64_t)dt * cd.tempsens) >> 23);
-
-        int64_t offs = ((int64_t)cd.off << 16) + (((int64_t)cd.tco * dt) >> 7);
-        int64_t sens = ((int64_t)cd.sens << 15) + (((int64_t)cd.tcs * dt) >> 8);
-
-        int64_t t2 = 0, off2 = 0, sens2 = 0;
-
-        // Second order temperature compensation
-        if (temp < 2000)
-        {
-            t2    = (((int64_t)dt) * dt) >> 31;
-            off2  = 3 * (temp - 2000) * (temp - 2000);
-            sens2 = (7 * (temp - 2000) * (temp - 2000)) >> 3;
-
-            if (temp < -1500)
-            {
-                sens2 = sens2 + 2 * (temp + 1500) * (temp + 1500);
-            }
-        }
-        else if (temp >= 4500)
-        {
-            sens2 = sens2 - (((temp - 4500) * (temp - 4500)) >> 3);
-        }
-
-        temp = temp - t2;
-        offs = offs - off2;
-        sens = sens - sens2;
-
-        int64_t ttemp = ((int64_t)pressure) * sens;
-        int32_t pres  = ((ttemp >> 21) - offs) >> 15;
-
-        mLastTemp     = temp / 100.0f;
-        mLastPressure = pres;
-
-        dataStruct.raw_temp  = temperature;
-        dataStruct.temp      = mLastTemp;
-        dataStruct.raw_press = pressure;
-        dataStruct.pressure  = mLastPressure;
-        dataStruct.timestamp = miosix::getTick();
-    }
-
-    typedef struct
-    {
-        uint16_t sens;
-        uint16_t off;
-        uint16_t tcs;
-        uint16_t tco;
-        uint16_t tref;
-        uint16_t tempsens;
-    } calibration_data;
-
-    calibration_data cd;
-
-    uint16_t readReg(uint8_t reg)
-    {
-        uint8_t rcv[2];
-        Bus::read(reg, rcv, 2);
-        uint16_t data = (rcv[0] << 8) | rcv[1];
-        return data;
-    }
-
-    // clang-format off
-    enum eRegisters
-    {
-        RESET_DEV                 = 0x1E,
-
-        CONVERT_D1_256            = 0x40,
-        CONVERT_D1_512            = 0x42,
-        CONVERT_D1_1024           = 0x44,
-        CONVERT_D1_2048           = 0x46,
-        CONVERT_D1_4096           = 0x48,
-
-        CONVERT_D2_256            = 0x50,
-        CONVERT_D2_512            = 0x52,
-        CONVERT_D2_1024           = 0x54,
-        CONVERT_D2_2048           = 0x56,
-        CONVERT_D2_4096           = 0x58,
-
-        ADC_READ                  = 0x00,
-
-        PROM_READ_MASK            = 0xA0,
-        PROM_SENS_MASK            = 0x02,
-        PROM_OFF_MASK             = 0x04,
-        PROM_TCS_MASK             = 0x06,
-        PROM_TCO_MASK             = 0x08,
-        PROM_TREF_MASK            = 0x0A,
-        PROM_TEMPSENS_MASK        = 0x0C,
-    };
-    // clang-format on
-};
diff --git a/src/shared/sensors/Sensor.h b/src/shared/sensors/Sensor.h
index 5ab85199cab91f4c1584d3b808852f323c3549a0..5dedcd0d15527bcdce526e90b8c99f8b559ff321 100644
--- a/src/shared/sensors/Sensor.h
+++ b/src/shared/sensors/Sensor.h
@@ -1,7 +1,5 @@
-/* Sensors Base Classes
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+/* Copyright (c) 2016-2020 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Luca Conterio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,160 +13,140 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#ifndef SENSORS_H
-#define SENSORS_H
-#include <Common.h>
-#include <drivers/spi/SensorSpi.h>
-#include <math/Quaternion.h>
-#include <math/Vec3.h>
-
-/** Sensors class diagram
- *               ________
- *              | Sensor |                      <- Sensor parent
- *      _______/ -------- \________
- * ____/_______   _|__________   __\_______
- *| GyroSensor | | ...Sensor  | | AnySensor|    <- Virtual childs
- * ------------   ------------   ----------
- *    |     _______/       \       /
- *  __|____/_             __\_____/_
- * | ABC1234 |           | LOL12345 |           <- You write these
- *  ---------             ----------
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "SensorData.h"
+
+/**
+ * @brief Check that a given type has a method called `getData()` and that the
+ * return type of this method is a subclass of the expected data type.
  */
+template <class T, class ExpectedDataType>
+struct checkIfProduces
+    : std::is_base_of<ExpectedDataType,
+                      typename std::remove_reference<
+                          decltype(std::declval<T>().getLastSample())>::type>
+{
+};
 
-class Sensor
+/**
+ * @brief Base abstract class for sensor drivers.
+ */
+class AbstractSensor
 {
+protected:
+    SensorErrors last_error = SensorErrors::NO_ERRORS;
+
 public:
-    /** Here the code to initialize this sensor */
-    virtual bool init() { return true; };
-
-    /** Self test code
-     * It should return a boolean:
-     *    True  = sensor ok
-     *    False = sensor ko, write into last_error the error code.
-     * Anyone should be able to call getLastError() and read the error.
-     */
-    virtual bool selfTest() = 0;
+    virtual ~AbstractSensor() {}
 
-    virtual std::vector<SPIRequest> buildDMARequest()
-    {
-        // printf("** SENSOR::buildDMARequest **\n");
-        return std::vector<SPIRequest>();
-    }
+    /**
+     * @brief Initialize the sensor.
+     * @return boolean value indicating whether the operation succeded or not
+     */
+    virtual bool init() = 0;
 
-    virtual void onDMAUpdate(const SPIRequest& req)
-    {
-        (void)req;
-        // printf("** SENSOR::onDMAUpdate **\n");
-    }
+    /**
+     * @brief Check if the sensor is working.
+     * @return boolean indicating whether the sensor is correctly working or not
+     */
+    virtual bool selfTest() = 0;
 
     /**
-     * This method is called once every N msec, read new values and
-     * store them in local variables.
-     *
-     * You should check getLastError() if this function returns false.
+     * @brief Sample the sensor.
      */
-    virtual bool onSimpleUpdate() = 0;
-
-    /** Return last error code */
-    uint8_t getLastError() const { return last_error; }
-
-    /** Errors (for each subsensor) */
-    // clang-format off
-        enum eErrors
-        {
-            ERR_NOT_ME              = 0x01,
-            ERR_RESET_TIMEOUT       = 0x02,
-            ERR_BUS_FAULT           = 0x03, // A bus op has encountered an error
-            ERR_X_SELFTEST_FAIL     = 0x04,
-            ERR_Y_SELFTEST_FAIL     = 0x05,
-            ERR_Z_SELFTEST_FAIL     = 0x06,
-            ERR_ACCEL_SELFTEST      = 0x07,
-            ERR_GYRO_SELFTEST       = 0x08,
-            ERR_CANT_TALK_TO_CHILD  = 0x09, // MPU9250 can't talk to AK8963
-        };
-    // clang-format on
+    virtual void sample() = 0;
 
-protected:
-    uint8_t last_error = 0;
+    /**
+     * @brief Get last error for debugging purposes. Avoid silent fails.
+     * @return the last error recorded by this sensor
+     */
+    SensorErrors getLastError() { return last_error; };
 };
 
-class GyroSensor : public virtual Sensor
+/**
+ * @brief Base sensor class with has to be extended by any sensor driver.
+ *
+ * A sensor driver can define a custom data structure extending any
+ * combination of base sensors data structures, defined in `SensorData.h`.
+ */
+template <typename Data>
+class Sensor : public virtual AbstractSensor
 {
-public:
-    const Vec3* gyroDataPtr() const { return &mLastGyro; }
-
 protected:
-    Vec3 mLastGyro;
-};
+    Data last_sample;
 
-class AccelSensor : public virtual Sensor
-{
-public:
-    const Vec3* accelDataPtr() const { return &mLastAccel; }
-
-protected:
-    Vec3 mLastAccel;
-};
+    /**
+     * @brief Read a data sample from the sensor.
+     *        In case of errors, the method should return the last
+     *        available correct sample.
+     * @return sensor data sample
+     */
+    virtual Data sampleImpl() = 0;
 
-class CompassSensor : public virtual Sensor
-{
 public:
-    const Vec3* compassDataPtr() const { return &mLastCompass; }
+    virtual ~Sensor() {}
 
-protected:
-    Vec3 mLastCompass;
-};
-
-class TemperatureSensor : public virtual Sensor
-{
-public:
-    const float* tempDataPtr() const { return &mLastTemp; }
+    void sample() override { last_sample = sampleImpl(); }
 
-protected:
-    float mLastTemp;
+    /**
+     * @return last available sample from this sensor
+     */
+    virtual const Data& getLastSample() { return last_sample; }
 };
 
-class HumiditySensor : public virtual Sensor
+/**
+ * @brief Interface for sensor that implement a FIFO.
+ */
+template <typename Data, uint32_t FifoSize>
+class SensorFIFO : public Sensor<Data>
 {
-public:
-    const float* humidityDataPtr() const { return &mLastHumidity; }
-
 protected:
-    float mLastHumidity;
-};
+    std::array<Data, FifoSize> last_fifo;
+    uint8_t last_fifo_level = 1; /**< number of samples in last_fifo */
 
-class PressureSensor : public virtual Sensor
-{
-public:
-    const float* pressureDataPtr() const { return &mLastPressure; }
+    uint64_t last_interrupt_us = 0; /**< last interrupt timestamp */
+    uint64_t dt_interrupt = 0; /**< delta between previous interrupt timestamp
+                                  and the last received one */
 
-protected:
-    float mLastPressure;
-};
-
-class AltitudeSensor : public virtual Sensor
-{
 public:
-    const float* altitudeDataPtr() const { return &mLastAltitude; }
+    /**
+     * @return last FIFO sampled from the sensor
+     */
+    const std::array<Data, FifoSize>& getLastFifo() { return last_fifo; }
 
-protected:
-    float mLastAltitude;
-};
+    /**
+     * @param i index of the requested item inside the FIFO
+     *
+     * @return the i-th element of the FIFO
+     */
+    const Data& getFifoElement(uint32_t i) const { return last_fifo[i]; }
 
-class DebugIntSensor : public virtual Sensor
-{
-public:
-    const int* debugIntPtr() const { return &mDebugInt; }
+    /**
+     * @return number of elements in the last FIFO sampled from the sensor
+     */
+    uint8_t getLastFifoSize() const { return last_fifo_level; }
 
-protected:
-    int mDebugInt;
+    /**
+     * @brief Called by the interrupt handling routine: provides the timestamp
+     *        of the last interrupt (if FIFO is disabled) or the last watermark
+     *        interrupt (if FIFO enabled)
+     *
+     * @param ts Timestamp of the lasts interrupt, in microseconds
+     */
+    inline virtual void IRQupdateTimestamp(uint64_t ts)
+    {
+        dt_interrupt      = ts - last_interrupt_us;
+        last_interrupt_us = ts;
+    }
 };
-
-#endif /* ifndef SENSORS_H */
diff --git a/src/shared/sensors/SensorData.h b/src/shared/sensors/SensorData.h
new file mode 100644
index 0000000000000000000000000000000000000000..06ea8ffdc77656261dfdec4f6d8a879835de36ad
--- /dev/null
+++ b/src/shared/sensors/SensorData.h
@@ -0,0 +1,142 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <ostream>
+
+/**
+ * @brief Generic error codes that a sensor can generate.
+ *
+ * Sensors can extend this enum by defining a new set of errors,
+ * starting from END_OF_BASE_ERRORS.
+ */
+enum SensorErrors : uint8_t
+{
+    NO_ERRORS          = 0,
+    INVALID_WHOAMI     = 1,
+    INIT_FAIL          = 2,
+    NOT_INIT           = 3,  // if some method called before init()
+    ALREADY_INIT       = 4,  // if init() called multiple times
+    SELF_TEST_FAIL     = 5,
+    BUS_FAULT          = 6,
+    NO_NEW_DATA        = 7,  // no new data available from the sensor
+    INVALID_FIFO_INDEX = 8,
+    DMA_ERROR          = 9,
+    END_OF_BASE_ERRORS = 10  // used to extend this enum
+};
+
+/**
+ * @brief Structure to handle sensor data timestamp.
+ */
+struct TimestampData
+{
+    uint64_t timestamp;
+};
+
+/**
+ * @brief Structure to handle temperature data.
+ */
+struct TemperatureData
+{
+    uint64_t temp_timestamp;
+    float temp;
+};
+
+/**
+ * @brief Structure to handle pressure data.
+ */
+struct PressureData
+{
+    uint64_t press_timestamp;
+    float press;
+};
+
+/**
+ * @brief Structure to handle humidity data.
+ */
+struct HumidityData
+{
+    uint64_t humid_timestamp;
+    float humid;
+};
+
+/**
+ * @brief Structure to handle accelerometer data.
+ */
+struct AccelerometerData
+{
+    uint64_t accel_timestamp;
+    float accel_x;
+    float accel_y;
+    float accel_z;
+};
+
+/**
+ * @brief Structure to handle gyroscope data.
+ */
+struct GyroscopeData
+{
+    uint64_t gyro_timestamp;
+    float gyro_x;
+    float gyro_y;
+    float gyro_z;
+};
+
+/**
+ * @brief Structure to handle magnetometer data.
+ */
+struct MagnetometerData
+{
+    uint64_t mag_timestamp;
+    float mag_x;
+    float mag_y;
+    float mag_z;
+};
+
+/**
+ * @brief Structure to handle GPS data.
+ */
+struct GPSData
+{
+    uint64_t gps_timestamp;
+    float latitude;         /**< [deg] */
+    float longitude;        /**< [deg] */
+    float height;           /**< [m]   */
+    float velocity_north;   /**< [m/s] */
+    float velocity_east;    /**< [m/s] */
+    float velocity_down;    /**< [m/s] */
+    float speed;            /**< [m/s] */
+    float track;            /**< [deg] */
+    uint8_t num_satellites; /**< [1]   */
+    bool fix;
+};
+
+/**
+ * @brief Structure to handle ADC data.
+ */
+struct ADCData
+{
+    uint64_t adc_timestamp;
+    uint8_t channel_id;
+    float voltage;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/SensorInfo.h b/src/shared/sensors/SensorInfo.h
new file mode 100644
index 0000000000000000000000000000000000000000..0bc2ea6b603573df0018013ab54a940f239d7e3a
--- /dev/null
+++ b/src/shared/sensors/SensorInfo.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+
+/**
+ * @brief Sensors information struct needed by the SensorManager.
+ *
+ * This structure contains the sampling period of a sensor,
+ * the function to be called after the sampling (callback) and
+ * two boolean values indicating if the sensor uses DMA and if
+ * the sensor has to be sampled (is enabled).
+ */
+struct SensorInfo
+{
+    std::string id;
+    uint32_t period;  // Period in ms
+    std::function<void()> callback;
+    bool is_dma;
+    bool is_enabled;
+    bool is_initialized;
+
+    SensorInfo(std::string id, uint32_t period, std::function<void()> callback,
+               bool is_dma, bool is_enabled)
+        : id(id), period(period), callback(callback), is_dma(is_dma),
+          is_enabled(is_enabled), is_initialized(false)
+    {
+    }
+
+    SensorInfo()
+        : id(""), period(0), callback([]() {}), is_dma(false),
+          is_enabled(false), is_initialized(false)
+    {
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/SensorManager.cpp b/src/shared/sensors/SensorManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dcb900d945dfe1b855ee892c61c888229260c38d
--- /dev/null
+++ b/src/shared/sensors/SensorManager.cpp
@@ -0,0 +1,260 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "SensorManager.h"
+
+#include <Debug.h>
+
+SensorManager::SensorManager(const SensorMap_t& sensors_map)
+    : SensorManager(new TaskScheduler(), sensors_map)
+{
+}
+
+SensorManager::SensorManager(TaskScheduler* scheduler,
+                             const SensorMap_t& sensors_map)
+    : scheduler(scheduler)
+{
+    if (!init(sensors_map))
+    {
+        LOG_ERR(logger, "Initialization failed");
+    }
+}
+
+SensorManager::~SensorManager()
+{
+    for (auto s : samplers)
+    {
+        delete s;
+    }
+}
+
+bool SensorManager::start()
+{
+    return scheduler->start() && sensors_init_result;
+}
+
+void SensorManager::stop() { scheduler->stop(); }
+
+void SensorManager::enableSensor(AbstractSensor* sensor)
+{
+    if (samplers_map.find(sensor) != samplers_map.end())
+    {
+        samplers_map[sensor]->toggleSensor(sensor, true);
+    }
+    else
+    {
+        LOG_ERR(logger, "Can't enable sensor {} it does not exist",
+                static_cast<void*>(sensor));
+    }
+}
+
+void SensorManager::disableSensor(AbstractSensor* sensor)
+{
+    if (samplers_map.find(sensor) != samplers_map.end())
+    {
+        samplers_map[sensor]->toggleSensor(sensor, false);
+    }
+    else
+    {
+        LOG_ERR(logger, "Can't disable sensor {}, it does not exist",
+                static_cast<void*>(sensor));
+    }
+}
+
+void SensorManager::enableAllSensors()
+{
+    for (auto s : samplers)
+    {
+        s->enableAllSensors();
+    }
+}
+
+void SensorManager::disableAllSensors()
+{
+    for (auto s : samplers)
+    {
+        s->disableAllSensors();
+    }
+}
+
+const SensorInfo SensorManager::getSensorInfo(AbstractSensor* sensor)
+{
+    if (samplers_map.find(sensor) != samplers_map.end())
+    {
+        return samplers_map[sensor]->getSensorInfo(sensor);
+    }
+
+    LOG_ERR(logger, "Sensor {} not found, can't return SensorInfo",
+            static_cast<void*>(sensor));
+
+    return SensorInfo{};
+}
+
+const vector<TaskStatResult> SensorManager::getSamplersStats()
+{
+    return scheduler->getTaskStats();
+}
+
+bool SensorManager::init(const SensorMap_t& sensors_map)
+{
+    uint8_t current_sampler_id = getFirstTaskID();
+
+    if (current_sampler_id != 0)
+    {
+        LOG_INFO(logger, "Task scheduler not empty: starting from task ID {}",
+                current_sampler_id);
+    }
+
+    for (auto it = sensors_map.begin(); it != sensors_map.end(); it++)
+    {
+        AbstractSensor* sensor = it->first;
+        SensorInfo sensor_info = it->second;
+
+        // avoid adding sensors that fail to be initalized
+        if (!initSensor(sensor))
+        {
+            sensor_info.is_enabled = false;  // disable the failing sensor
+
+            sensors_init_result = false;
+
+            LOG_ERR(
+                logger,
+                "Failed to initialize sensor {} -> Error: {} (period: {} ms)",
+                sensor_info.id.c_str(), sensor->getLastError(),
+                sensor_info.period);
+        }
+        else
+        {
+            sensor_info.is_initialized = true;
+        }
+
+        // add sensor even if not initialized correctly, its is_initialized info
+        // field will be false
+        LOG_DEBUG(logger, "Adding {} -> period: {} ms, enabled = {}",
+                  sensor_info.id.c_str(), sensor_info.period,
+                  sensor_info.is_enabled);
+
+        // check if a sampler with the same sampling period and the same
+        // type exists
+        bool found = false;
+        for (auto s : samplers)
+        {
+            if (sensor_info.period == s->getSamplingPeriod() &&
+                sensor_info.is_dma == s->isDMA())
+            {
+                s->addSensor(sensor, sensor_info);
+                samplers_map[sensor] = s;
+                found                = true;
+            }
+        }
+
+        if (!found)
+        {
+            // a sampler with the required period does not exist yet
+            SensorSampler* new_sampler = createSampler(
+                current_sampler_id, sensor_info.period, sensor_info.is_dma);
+
+            new_sampler->addSensor(sensor, sensor_info);
+
+            samplers.push_back(new_sampler);
+            samplers_map[sensor] = new_sampler;
+
+            if (current_sampler_id == MAX_TASK_ID)
+            {
+                LOG_WARN(logger,
+                         "Max task ID (255) reached in task scheduler, IDs "
+                         "will start again from 0");
+            }
+
+            current_sampler_id++;
+        }
+    }
+
+    initScheduler();
+
+    return sensors_init_result;
+}
+
+bool SensorManager::initSensor(AbstractSensor* sensor)
+{
+    return sensor->init() && sensor->selfTest();
+}
+
+void SensorManager::initScheduler()
+{
+    // sort the vector to have lower period samplers
+    // (higher frequency) inserted before
+    // higher period ones into the TaskScheduler
+    std::sort(samplers.begin(), samplers.end(),
+              [](auto& left, auto& right) {
+                  return left->getSamplingPeriod() < right->getSamplingPeriod();
+              });
+
+    uint64_t start_time = miosix::getTick() + 10;
+
+    // add all the samplers to the scheduler
+    for (auto& s : samplers)
+    {
+        function_t sampler_update_function =
+            bind(&SensorSampler::sampleAndCallback, s);
+
+        scheduler->add(sampler_update_function, s->getSamplingPeriod(),
+                       s->getID(), start_time);
+    }
+}
+
+uint8_t SensorManager::getFirstTaskID()
+{
+    std::vector<TaskStatResult> tasks_stats = scheduler->getTaskStats();
+
+    if (tasks_stats.empty())
+    {
+        return 0;
+    }
+
+    uint8_t max_id = 0;
+    for (auto& stats : tasks_stats)
+    {
+        if (stats.id > max_id)
+        {
+            max_id = stats.id;
+        }
+    }
+
+    return max_id + 1;
+}
+
+SensorSampler* SensorManager::createSampler(uint8_t id, uint32_t period,
+                                            bool is_dma)
+{
+    LOG_DEBUG(logger, "Creating Sampler {} with sampling period {} ms", id,
+              period);
+
+    if (is_dma)
+    {
+        return new DMASensorSampler(id, period);
+    }
+    else
+    {
+        return new SimpleSensorSampler(id, period);
+    }
+}
\ No newline at end of file
diff --git a/src/shared/sensors/SensorManager.h b/src/shared/sensors/SensorManager.h
new file mode 100755
index 0000000000000000000000000000000000000000..63e3207eb1ea464baee56ba69303efb6a23ff8b2
--- /dev/null
+++ b/src/shared/sensors/SensorManager.h
@@ -0,0 +1,181 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+
+#include "SensorInfo.h"
+#include "SensorSampler.h"
+#include "scheduler/TaskScheduler.h"
+// #include "diagnostic/PrintLogger.h"
+
+using namespace std;
+
+/**
+ * @brief The SensorManager class manages all the sensors connected to the
+ * Board.
+ *
+ * Sensors are grouped in various SensorSampler objects by "type" (if they use
+ * DMA or not) and "sampling period". These SensorSampler objects are then
+ * added to the scheduler that manages the timings for the sampling.
+ */
+class SensorManager
+{
+public:
+    using function_t  = function<void()>;
+    using SensorMap_t = map<AbstractSensor*, SensorInfo>;
+
+    /**
+     * @brief Constructor.
+     *
+     * @param sensors_map map containing references to the sensors as keys,
+     *                    and objects of type SensorInfo as values.
+     */
+    SensorManager(const SensorMap_t& sensors_map);
+
+    /**
+     * @brief Constructor taking an external TaskScheduler object.
+     *
+     * @param sensors_map map containing references to the sensors as keys,
+     *                    and objects of type SensorInfo as values.
+     */
+    SensorManager(TaskScheduler* scheduler, const SensorMap_t& sensors_map);
+
+    /**
+     * @brief Destructor.
+     *
+     * Deallocates samplers (through the samplers vector).
+     */
+    ~SensorManager();
+
+    /**
+     * @brief Start the sensor manager.
+     */
+    bool start();
+
+    /**
+     * @brief Stop the sensor manager.
+     */
+    void stop();
+
+    /**
+     * @brief Enable sampling for the passed sensor.
+     *
+     * @param sensor    the sensor to be enabled
+     */
+    void enableSensor(AbstractSensor* sensor);
+
+    /**
+     * @brief Disable sampling for the passed sensor.
+     *
+     * @param sensor    the sensor to be disabled
+     */
+    void disableSensor(AbstractSensor* sensor);
+
+    /**
+     * @brief Enable sampling for all the sensors.
+     */
+    void enableAllSensors();
+
+    /**
+     * @brief Disable sampling for all the sensors.
+     */
+    void disableAllSensors();
+
+    /**
+     * @return the information related to the given sensor
+     */
+    const SensorInfo getSensorInfo(AbstractSensor* sensor);
+
+    /**
+     * @return vector of statistics, one for each sampler, taken from the
+     * scheduler
+     */
+    const vector<TaskStatResult> getSamplersStats();
+
+private:
+    /**
+     * @brief Copy constructor. Deleted.
+     */
+    SensorManager(const SensorManager&) = delete;
+
+    /**
+     * @brief Initializes samplers vector and sensors_map with the given sensors
+     *        map, giving incremental IDs to SensorSampler objects.
+     *        In case a TaskScheduler was passed in the costructor,
+     *        the SensorManager will assign to SensorSamplers incremental IDs
+     *        starting from the maximum among the tasks already existing
+     *        in the TaskScheduler.
+     *
+     * @param sensors_map    map containing sensors and their respective
+     * information for the sampling
+     */
+    bool init(const SensorMap_t& sensors_map);
+
+    /**
+     * @brief Initialize a sensor and run its self-test.
+     *
+     * @param sensor  the sensor to be initialized
+     */
+    bool initSensor(AbstractSensor* sensor);
+
+    /**
+     * @brief Initialize scheduler by adding all the SensorSamplers tasks.
+     */
+    void initScheduler();
+
+    /**
+     * @brief Avoid creating duplicate IDs for tasks in case the scheduler
+     *        is received from outside.
+     *
+     * @return maximum ID among those assigned to tasks in the scheduler.
+     */
+    uint8_t getFirstTaskID();
+
+    /**
+     * @brief Create a sampler object according to the fact that it needs to use
+     * DMA or not.
+     *
+     * @param id        new sampler's identifier
+     * @param period      sampling period of the new sampler
+     * @param is_dma    indicate if the sampler manages sensors that use DMA
+     *
+     * @return pointer to the newly created sampler
+     */
+    SensorSampler* createSampler(uint8_t id, uint32_t period, bool is_dma);
+
+    const uint8_t MAX_TASK_ID = 255; /**< max id for tasks in the scheduler */
+
+    TaskScheduler* scheduler; /**< scheduler to update the samplers at the
+                                correct period */
+
+    vector<SensorSampler*> samplers; /**< vector of all the samplers (unique) */
+
+    map<AbstractSensor*, SensorSampler*>
+        samplers_map; /**< map each sensor to the corresponding sampler */
+
+    bool sensors_init_result =
+        true; /**< true if all the sensors are initialized correctly */
+
+    PrintLogger logger = Logging::getLogger("sensormanager");
+};
diff --git a/src/shared/sensors/SensorSampler.cpp b/src/shared/sensors/SensorSampler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bfa4e892c604efdaa569ef4272f02b62adc4909c
--- /dev/null
+++ b/src/shared/sensors/SensorSampler.cpp
@@ -0,0 +1,140 @@
+/* Copyright (c) 2017-2020 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "SensorSampler.h"
+// #include <diagnostic/PrintLogger.h>
+
+using namespace std;
+
+SensorSampler::SensorSampler(uint8_t id, uint32_t period, bool is_dma)
+    : id(id), period(period), is_dma(is_dma)
+{
+}
+
+SensorSampler::~SensorSampler() { sensors.clear(); }
+
+void SensorSampler::sampleAndCallback()
+{
+    for (auto& s : sensors)
+    {
+        // sample only if that sensor is enabled
+        if (s.second.is_enabled && s.second.is_initialized)
+        {
+            sampleSensor(s.first);
+            s.second.callback();
+        }
+    }
+}
+
+void SensorSampler::toggleSensor(AbstractSensor* sensor, bool is_en)
+{
+    for (auto& s : sensors)
+    {
+        if (s.first == sensor)
+        {
+            s.second.is_enabled = is_en;
+            LOG_DEBUG(logger, "Sampler {}, toggle Sensor {} ---> enabled = {}",
+                      getID(), static_cast<void*>(sensor), s.second.is_enabled);
+            break;
+        }
+    }
+}
+
+void SensorSampler::enableAllSensors()
+{
+    for (auto& s : sensors)
+    {
+        s.second.is_enabled = true;
+    }
+}
+
+void SensorSampler::disableAllSensors()
+{
+    for (auto& s : sensors)
+    {
+        s.second.is_enabled = false;
+    }
+}
+
+bool SensorSampler::isDMA() { return is_dma; }
+
+uint8_t SensorSampler::getID() { return id; }
+
+uint32_t SensorSampler::getSamplingPeriod() { return period; }
+
+unsigned int SensorSampler::getNumSensors() { return sensors.size(); }
+
+const SensorInfo SensorSampler::getSensorInfo(AbstractSensor* sensor)
+{
+    for (auto it = sensors.begin(); it != sensors.end(); ++it)
+    {
+        if (it->first == sensor)
+        {
+            return it->second;
+        }
+    }
+
+    LOG_ERR(logger, "Sampler {}: sensor {} not found", this->id,
+            static_cast<void*>(sensor));
+
+    return SensorInfo{};
+}
+
+// simple sampler
+SimpleSensorSampler::SimpleSensorSampler(uint8_t id, uint32_t period)
+    : SensorSampler(id, period, false)
+{
+}
+
+SimpleSensorSampler::~SimpleSensorSampler() {}
+
+void SimpleSensorSampler::addSensor(AbstractSensor* sensor,
+                                    SensorInfo sensor_info)
+{
+    sensors.push_back(make_pair(sensor, sensor_info));
+}
+
+void SimpleSensorSampler::sampleSensor(AbstractSensor* sensor)
+{
+    sensor->sample();
+}
+
+// DMA sampler
+DMASensorSampler::DMASensorSampler(uint8_t id, uint32_t period)
+    : SensorSampler(id, period, true)
+{
+}
+
+DMASensorSampler::~DMASensorSampler() {}
+
+void DMASensorSampler::addSensor(AbstractSensor* sensor, SensorInfo sensor_info)
+{
+    sensors.push_back(make_pair(sensor, sensor_info));
+}
+
+/*
+    TODO : Handle sensors that use DMA
+*/
+void DMASensorSampler::sampleSensor(AbstractSensor* sensor)
+{
+    sensor->sample();
+}
\ No newline at end of file
diff --git a/src/shared/sensors/SensorSampler.h b/src/shared/sensors/SensorSampler.h
new file mode 100644
index 0000000000000000000000000000000000000000..534ef8945ad0d149b10bd34e63824ad83c796f4b
--- /dev/null
+++ b/src/shared/sensors/SensorSampler.h
@@ -0,0 +1,159 @@
+/* Copyright (c) 2017-2020 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Common.h>
+#include <diagnostic/PrintLogger.h>
+
+#include "Sensor.h"
+#include "SensorInfo.h"
+
+using namespace std;
+
+/**
+ * @brief Virtual sensor sampler class.
+ *
+ * When requested, a SensorSampler samples all the enabled sensors it manages.
+ * After sampling a sensor, the SensorSampler calls the callback associated
+ * with that sensor, where these samples can be processed.
+ */
+class SensorSampler
+{
+public:
+    /**
+     * @param id      sampler identifier
+     * @param period  period at which the sampler performs sensors update
+     * @param is_dma  indicate if the sampler manages DMA sensors or not
+     */
+    SensorSampler(uint8_t id, uint32_t period, bool is_dma);
+
+    virtual ~SensorSampler();
+
+    /**
+     * @brief Add a sensor to the sensors map.
+     *
+     * @param sensor  the sensor to be added
+     */
+    virtual void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) = 0;
+
+    /**
+     * @brief Enabled or disable a sensor.
+     *
+     * @param sensor    the sensor to be toggled (enabled/disabled)
+     * @param is_en     bool value to be set in the sensor info to indicate if
+     *                  the sensor will be enabled or disabled
+     */
+    void toggleSensor(AbstractSensor* sensor, bool is_en);
+
+    /**
+     * @brief Enable sampling for all the sensors.
+     */
+    void enableAllSensors();
+
+    /**
+     * @brief Disable sampling for all the sensors.
+     */
+    void disableAllSensors();
+
+    /**
+     * @brief For each sensor, sample it and call the corresponding callback.
+     */
+    void sampleAndCallback();
+
+    /**
+     * @return  the sampler's type
+     */
+    bool isDMA();
+
+    /**
+     * @return  the sampler's ID
+     */
+    uint8_t getID();
+
+    /**
+     * @return  the sampler's activation period
+     */
+    uint32_t getSamplingPeriod();
+
+    /**
+     * @return  the number of sensors assigned to this sampler
+     */
+    unsigned int getNumSensors();
+
+    /**
+     * @return  the information related to the given sensor
+     */
+    const SensorInfo getSensorInfo(AbstractSensor* sensor);
+
+private:
+    /**
+     * @brief Perform the update of all the sensors in the sampler.
+     */
+    virtual void sampleSensor(AbstractSensor* s) = 0;
+
+    uint8_t id;      /**< sampler id used in the task scheduler */
+    uint32_t period; /**< sampler update/activation period */
+    bool is_dma;     /**< the sampler's type (if it uses DMA or not) */
+
+protected:
+    vector<pair<AbstractSensor*, SensorInfo>> sensors;
+
+    PrintLogger logger = Logging::getLogger("sensorsampler");
+};
+
+/**
+ * @brief Sampler for simple sensors, those that are simply
+ *        sampled by calling the sample() method.
+ */
+class SimpleSensorSampler : public virtual SensorSampler
+{
+public:
+    SimpleSensorSampler(uint8_t id, uint32_t period);
+
+    ~SimpleSensorSampler();
+
+    void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) override;
+
+    void sampleSensor(AbstractSensor* s) override;
+
+private:
+    SimpleSensorSampler(const SimpleSensorSampler&) = delete;
+};
+
+/**
+ * @brief Sampler for sensors that use DMA for updates.
+ */
+class DMASensorSampler : public virtual SensorSampler
+{
+public:
+    DMASensorSampler(uint8_t id, uint32_t period);
+
+    ~DMASensorSampler();
+
+    void addSensor(AbstractSensor* sensor, SensorInfo sensor_info) override;
+
+    void sampleSensor(AbstractSensor* s) override;
+
+private:
+    DMASensorSampler(const DMASensorSampler&) = delete;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/SensorSampling.h b/src/shared/sensors/SensorSampling.h
deleted file mode 100644
index 06bb4b9eae810c7158b9d81da9d4dfbe0eb84398..0000000000000000000000000000000000000000
--- a/src/shared/sensors/SensorSampling.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Copyright (c) 2017 Skyward Experimental Rocketry
- * Author: Alain Carlucci
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-#ifndef SENSOR_SAMPLING_H
-#define SENSOR_SAMPLING_H
-
-#include <Common.h>
-#include <diagnostic/Log.h>
-#include <drivers/spi/SensorSpi.h>
-
-#include "Sensor.h"
-
-class DMASensorSampler
-{
-public:
-    DMASensorSampler() {}
-    ~DMASensorSampler() {}
-
-    void AddSensor(Sensor* sensor)
-    {
-        std::vector<SPIRequest> requests = sensor->buildDMARequest();
-        for (size_t i = 0; i < requests.size(); i++)
-        {
-            mRequests.push_back(requests[i]);
-            mSensors.push_back(sensor);
-        }
-    }
-
-    void Update()
-    {
-        TRACE("Update\n");
-        auto& driver = SPIDriver::instance();
-        bool ret     = driver.transaction(mRequests);
-
-        if (ret)
-            for (size_t i = 0; i < mSensors.size(); i++)
-                mSensors[i]->onDMAUpdate(mRequests[i]);
-        
-        TRACE("Update end\n");
-
-    }
-
-    void UpdateAndCallback(std::function<void()> onSampleUpdateCallback)
-    {
-        Update();
-        TRACE("Update callback\n");
-
-        onSampleUpdateCallback();
-    }
-
-private:
-    std::vector<Sensor*> mSensors;
-    std::vector<SPIRequest> mRequests;
-};
-
-class SimpleSensorSampler
-{
-public:
-    SimpleSensorSampler() {}
-    ~SimpleSensorSampler() {}
-
-    void AddSensor(Sensor* sensor) { mSensors.push_back(sensor); }
-
-    void Update()
-    {
-        for (Sensor* s : mSensors)
-            s->onSimpleUpdate();
-    }
-
-    void UpdateAndCallback(std::function<void()> onSampleUpdateCallback)
-    {
-        Update();
-
-        onSampleUpdateCallback();
-    }
-
-private:
-    std::vector<Sensor*> mSensors;
-};
-
-#endif /* ifndef SENSOR_SAMPLING_H */
diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensor.h b/src/shared/sensors/analog/battery/BatteryVoltageSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..b04bafc73f028a5b266d0969b3f567e8d9cb3ffe
--- /dev/null
+++ b/src/shared/sensors/analog/battery/BatteryVoltageSensor.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <sensors/Sensor.h>
+
+#include <functional>
+
+#include "BatteryVoltageSensorData.h"
+
+/**
+ * @brief Common class for battery voltage sensors
+ *
+ * It needs the conversion ratio, in order to convert the raw voltage to
+ * battery voltage.
+ */
+class BatteryVoltageSensor : public Sensor<BatteryVoltageSensorData>
+{
+public:
+    static constexpr int MOVING_AVAERAGE_N = 20;
+
+    BatteryVoltageSensor(std::function<ADCData()> getADCVoltage_,
+                         float conversionCoeff_)
+        : getADCVoltage(getADCVoltage_), conversionCoeff(conversionCoeff_)
+    {
+        last_sample.bat_voltage = 0;
+    }
+
+    bool init() override { return true; };
+
+    bool selfTest() override { return true; };
+
+    ///< Converts the adc voltage value to battery voltage
+    BatteryVoltageSensorData sampleImpl() override
+    {
+        ADCData adc_data = getADCVoltage();
+
+        if (last_sample.bat_voltage == 0)
+        {
+            last_sample.bat_voltage = adcToBatteryVoltage(adc_data.voltage);
+        }
+
+        BatteryVoltageSensorData bat_data;
+        bat_data.adc_timestamp = adc_data.adc_timestamp;
+        bat_data.channel_id    = adc_data.channel_id;
+        bat_data.voltage       = adc_data.voltage;
+
+        // Moving average
+        bat_data.bat_voltage =
+            last_sample.bat_voltage * MOVING_AVAERAGE_COMP_COEFF;
+        bat_data.bat_voltage +=
+            adcToBatteryVoltage(adc_data.voltage) * MOVING_AVAERAGE_COEFF;
+
+        return bat_data;
+    }
+
+private:
+    ///< Conversion function from adc volts to battery volts
+    float adcToBatteryVoltage(float voltage)
+    {
+        return voltage * conversionCoeff;
+    }
+
+    ///< Function that returns the adc voltage
+    std::function<ADCData()> getADCVoltage;
+
+    float conversionCoeff;
+
+    static constexpr float MOVING_AVAERAGE_COEFF = 1 / (float)MOVING_AVAERAGE_N;
+    static constexpr float MOVING_AVAERAGE_COMP_COEFF =
+        1 - MOVING_AVAERAGE_COEFF;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h b/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c3afba9b1570cf56323c51a20fd6309ee7ea95e
--- /dev/null
+++ b/src/shared/sensors/analog/battery/BatteryVoltageSensorData.h
@@ -0,0 +1,44 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+/**
+ * @brief Structure to handle battery voltage data.
+ */
+struct BatteryVoltageSensorData : public ADCData
+{
+    float bat_voltage;
+
+    static std::string header()
+    {
+        return "adc_timestamp,channel_id,voltage,bat_voltage\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << adc_timestamp << "," << (int)channel_id << "," << voltage << ","
+           << bat_voltage << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/current/CurrentSensor.h b/src/shared/sensors/analog/current/CurrentSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..0c69d5ec214454bb3cdca5f33e61958c167bdb35
--- /dev/null
+++ b/src/shared/sensors/analog/current/CurrentSensor.h
@@ -0,0 +1,85 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <sensors/Sensor.h>
+
+#include <functional>
+
+#include "CurrentSensorData.h"
+
+/**
+ * @brief Common class for current sense sensors
+ *
+ * It needs a transfer function to convert the read voltage into current.
+ */
+class CurrentSensor : public Sensor<CurrentSensorData>
+{
+public:
+    static constexpr int MOVING_AVAERAGE_N = 20;
+
+    CurrentSensor(std::function<ADCData()> getADCVoltage_,
+                  std::function<float(float)> adcToCurrent_)
+        : getADCVoltage(getADCVoltage_), adcToCurrent(adcToCurrent_)
+    {
+        last_sample.current = 0;
+    }
+
+    bool init() override { return true; };
+
+    bool selfTest() override { return true; };
+
+    ///< Converts the voltage value to pressure
+    CurrentSensorData sampleImpl() override
+    {
+        ADCData adc_data = getADCVoltage();
+
+        if (last_sample.current == 0)
+        {
+            last_sample.current = adcToCurrent(adc_data.voltage);
+        }
+
+        CurrentSensorData current_data;
+        current_data.adc_timestamp = adc_data.adc_timestamp;
+        current_data.channel_id    = adc_data.channel_id;
+        current_data.voltage       = adc_data.voltage;
+
+        // Moving average
+        current_data.current = last_sample.current * MOVING_AVAERAGE_COMP_COEFF;
+        current_data.current +=
+            adcToCurrent(adc_data.voltage) * MOVING_AVAERAGE_COEFF;
+
+        return current_data;
+    };
+
+private:
+    ///< Function that returns the adc voltage
+    std::function<ADCData()> getADCVoltage;
+
+    ///< Function that converts adc voltage to current
+    std::function<float(float)> adcToCurrent;
+
+    static constexpr float MOVING_AVAERAGE_COEFF = 1 / (float)MOVING_AVAERAGE_N;
+    static constexpr float MOVING_AVAERAGE_COMP_COEFF =
+        1 - MOVING_AVAERAGE_COEFF;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07Data.h b/src/shared/sensors/analog/current/CurrentSensorData.h
similarity index 69%
rename from src/shared/sensors/MS580301BA07/MS580301BA07Data.h
rename to src/shared/sensors/analog/current/CurrentSensorData.h
index e6c2613243c3d039c1783edb1ef8f528aff987eb..665e75f7afd56892d71ec9babc7ccb74f00ce99b 100644
--- a/src/shared/sensors/MS580301BA07/MS580301BA07Data.h
+++ b/src/shared/sensors/analog/current/CurrentSensorData.h
@@ -1,5 +1,5 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,33 +13,32 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
+
 #pragma once
 
-#include <ostream>
+#include "sensors/SensorData.h"
 
-struct MS5803Data
+/**
+ * @brief Structure to handle current sense data.
+ */
+struct CurrentSensorData : public ADCData
 {
-    long long int timestamp;
-
-    uint32_t raw_press;
-    float pressure;
-    uint32_t raw_temp;
-    float temp;
+    float current;
 
     static std::string header()
     {
-        return "timestamp,raw_temp,raw_press,temp,pressure\n";
+        return "adc_timestamp,channel_id,voltage,current\n";
     }
 
     void print(std::ostream& os) const
     {
-        os << timestamp << "," << raw_temp << "," << raw_press << "," << temp
-           << "," << pressure << "\n";
+        os << adc_timestamp << "," << (int)channel_id << "," << voltage << ","
+           << current << "\n";
     }
-};
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/AnalogPressureSensor.h b/src/shared/sensors/analog/pressure/AnalogPressureSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..15c626681f8537efdbbf51ae29eb4f7a5c136c56
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/AnalogPressureSensor.h
@@ -0,0 +1,91 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <sensors/Sensor.h>
+
+#include <functional>
+
+/**
+ * @brief Common class for all analog pressure sensors
+ *
+ * All analog pressure sensors have a transfer function to convert the read
+ * voltage into pressure and a range within which they operate.
+ */
+template <typename AnalogPressureData>
+class AnalogPressureSensor : public Sensor<AnalogPressureData>
+{
+public:
+    AnalogPressureSensor(std::function<ADCData()> getSensorVoltage_,
+                         const float V_SUPPLY_    = 5.0,
+                         const float maxPressure_ = 0,
+                         const float minPressure_ = 0)
+        : getSensorVoltage(getSensorVoltage_), V_SUPPLY(V_SUPPLY_),
+          maxPressure(maxPressure_), minPressure(minPressure_)
+    {
+    }
+
+    bool init() override { return true; };
+
+    bool selfTest() override { return true; };
+
+    ///< Converts the voltage value to pressure
+    AnalogPressureData sampleImpl() override
+    {
+        AnalogPressureData pressure = AnalogPressureData();
+
+        // Retrieve the voltage
+        ADCData voltage = getSensorVoltage();
+
+        // Save the timestamp
+        pressure.press_timestamp = voltage.adc_timestamp;
+
+        // Convert the value
+        pressure.press = voltageToPressure(voltage.voltage);
+
+        // Check if the pressure is in range
+        if (pressure.press < minPressure)
+        {
+            pressure.press = minPressure;
+        }
+        else if (pressure.press > maxPressure)
+        {
+            pressure.press = maxPressure;
+        }
+
+        return pressure;
+    }
+
+protected:
+    ///< Conversion function from volts to pascals
+    virtual float voltageToPressure(float voltage) = 0;
+
+    ///< Function that returns the sensor voltage
+    std::function<ADCData()> getSensorVoltage;
+
+    const float V_SUPPLY;  ///< Suppply voltage
+
+    const float maxPressure;
+
+    const float minPressure;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h
new file mode 100644
index 0000000000000000000000000000000000000000..83ddd18af9cc004dc529c8bfc14bc205212dde14
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130A.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "../AnalogPressureSensor.h"
+#include "MPXHZ6130AData.h"
+
+/**
+ * @brief Driver for NXP's MPXHZ6130A pressure sensor
+ */
+class MPXHZ6130A final : public AnalogPressureSensor<MPXHZ6130AData>
+{
+public:
+    MPXHZ6130A(std::function<ADCData()> getSensorVoltage_,
+               const float V_SUPPLY_ = 5.0)
+        : AnalogPressureSensor(getSensorVoltage_, V_SUPPLY_, 130000)
+    {
+    }
+
+private:
+    float voltageToPressure(float voltage) override
+    {
+        return (((voltage / V_SUPPLY) + CONST_B) / CONST_A) * 1000;
+    }
+
+    // Constants from datasheet
+    static constexpr float CONST_A = 0.007826;
+    static constexpr float CONST_B = 0.07739;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h
new file mode 100644
index 0000000000000000000000000000000000000000..23add8fca491bae79a026d4529440d3391e35d7a
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/MPXHZ6130A/MPXHZ6130AData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct MPXHZ6130AData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h b/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..c87ec09f3bf4a1cbf59c64ba43620f57649e4259
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/SoftwareDifferentialPressureSensor.h
@@ -0,0 +1,73 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <sensors/Sensor.h>
+
+/**
+ * @brief Class used to simulate a differential pressure sensor in software.
+ *
+ * It takes the pressure measures of two sensors (one for the total pressure
+ * and one for the static one) and returns the difference between the two
+ * pressure values.
+ */
+template <typename FirstPressureData, typename SecondPressureData>
+class SoftwareDifferentialPressureSensor : public Sensor<PressureData>
+{
+    static_assert(
+        checkIfProduces<Sensor<FirstPressureData>, PressureData>::value,
+        "First template argument must be a sensor that produces pressure "
+        "data.");
+    static_assert(
+        checkIfProduces<Sensor<SecondPressureData>, PressureData>::value,
+        "Second template argument must be a sensor that produces pressure "
+        "data.");
+
+public:
+    SoftwareDifferentialPressureSensor(
+        Sensor<FirstPressureData>* first_pressure_sensor,
+        Sensor<SecondPressureData>* second_pressure_sensor)
+        : first_pressure_sensor(first_pressure_sensor),
+          second_pressure_sensor(second_pressure_sensor)
+    {
+    }
+
+    bool init() override { return true; }
+
+    bool selfTest() override { return true; };
+
+    /**
+     * @return the difference between the two pressure values
+     */
+    PressureData sampleImpl() override
+    {
+        float p1 = first_pressure_sensor->getLastSample().press;
+        float p2 = second_pressure_sensor->getLastSample().press;
+
+        return PressureData{miosix::getTick(), p1 - p2};
+    }
+
+private:
+    Sensor<FirstPressureData>* first_pressure_sensor;
+    Sensor<SecondPressureData>* second_pressure_sensor;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h
new file mode 100644
index 0000000000000000000000000000000000000000..34982bfedd05f87f1645c23e70157f1431a65b98
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PA.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "HSCMAND015PAData.h"
+#include "HoneywellPressureSensor.h"
+
+/**
+ * @brief Absolute pressure sensor with a 0-103kPa range (0-15psi)
+ */
+class HSCMAND015PA final : public HoneywellPressureSensor<HSCMAND015PAData>
+{
+public:
+    HSCMAND015PA(std::function<ADCData()> getSensorVoltage_,
+                 const float V_SUPPLY_ = 5.0)
+        : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 103421.3594)
+    {
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h
new file mode 100644
index 0000000000000000000000000000000000000000..92b64579717deb6236ecf4a76ef66bf424ee7d7f
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMAND015PAData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct HSCMAND015PAData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h
new file mode 100644
index 0000000000000000000000000000000000000000..f6caa4972a0a9c966f79f0892cca43563ef12c8a
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PA.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "HSCMRNN030PAData.h"
+#include "HoneywellPressureSensor.h"
+
+/**
+ * @brief Absolute pressure sensor with a 0-206kPa range (0-30psi)
+ */
+class HSCMRNN030PA final : public HoneywellPressureSensor<HSCMRNN030PAData>
+{
+public:
+    HSCMRNN030PA(std::function<ADCData()> getSensorVoltage_,
+                 const float V_SUPPLY_ = 5.0)
+        : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 206842.7188)
+    {
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h
new file mode 100644
index 0000000000000000000000000000000000000000..b75584a788916e3d0eac26c8968eabc12404ba9d
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN030PAData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct HSCMRNN030PAData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ba2ca14840b0b78c85a4491f621568f516ddc1e
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KA.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "HSCMRNN160KAData.h"
+#include "HoneywellPressureSensor.h"
+
+/**
+ * @brief Absolute pressure sensor with a 0-160kPa range
+ */
+class HSCMRNN160KA final : public HoneywellPressureSensor<HSCMRNN160KAData>
+{
+public:
+    HSCMRNN160KA(std::function<ADCData()> getSensorVoltage_,
+                 const float V_SUPPLY_ = 5.0)
+        : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 160000)
+    {
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h
new file mode 100644
index 0000000000000000000000000000000000000000..dbfd5a3bb858d18ca0eb4cd9d5a21c40ee2444e1
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HSCMRNN160KAData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct HSCMRNN160KAData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h b/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e3cfe483b4f125ba7acc2959527598edf5107d5
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/HoneywellPressureSensor.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "../AnalogPressureSensor.h"
+
+/**
+ * @brief Driver for Honeywell's pressure sensors (absolute and differential)
+ *
+ * All this sensors shares the same transfer function which varies only by few
+ * parameters: voltage supply and the sensor pressure range
+ */
+template <typename HoneywellPressureData>
+class HoneywellPressureSensor
+    : public AnalogPressureSensor<HoneywellPressureData>
+{
+public:
+    using AnalogPressureSensor<HoneywellPressureData>::AnalogPressureSensor;
+
+protected:
+    ///< Common transfer function from volts to pascals (from datasheet pag 11)
+    inline float voltageToPressure(float voltage)
+    {
+        float tmp;
+
+        tmp = voltage -
+              0.1 * AnalogPressureSensor<HoneywellPressureData>::V_SUPPLY;
+        tmp *= AnalogPressureSensor<HoneywellPressureData>::maxPressure -
+               AnalogPressureSensor<HoneywellPressureData>::minPressure;
+        tmp /= 0.8 * AnalogPressureSensor<HoneywellPressureData>::V_SUPPLY;
+        tmp += AnalogPressureSensor<HoneywellPressureData>::minPressure;
+
+        return tmp;
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h
new file mode 100644
index 0000000000000000000000000000000000000000..f95ebc7f859511d967874ecbafa8df9e05d6906c
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAA.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "HoneywellPressureSensor.h"
+#include "SSCDANN030PAAData.h"
+
+/**
+ * @brief Absolute pressure sensor with a 0-206kPa range (0-30psi)
+ */
+class SSCDANN030PAA final : public HoneywellPressureSensor<SSCDANN030PAAData>
+{
+public:
+    SSCDANN030PAA(std::function<ADCData()> getSensorVoltage_,
+                  const float V_SUPPLY_ = 5.0)
+        : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 206842.7188)
+    {
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h
new file mode 100644
index 0000000000000000000000000000000000000000..fc31db7beedbae26bf255601ffeccb8917d07eb1
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/SSCDANN030PAAData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct SSCDANN030PAAData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h
new file mode 100644
index 0000000000000000000000000000000000000000..0efc3c37a3dbcead629454ace4f693bf45bce2a5
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDA.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "math/Stats.h"
+
+#include "HoneywellPressureSensor.h"
+#include "SSCDRRN015PDAData.h"
+
+/**
+ * @brief Differential pressure sensor with a ±103kPa range (±15psi)
+ */
+class SSCDRRN015PDA final : public HoneywellPressureSensor<SSCDRRN015PDAData>
+{
+public:
+    SSCDRRN015PDA(std::function<ADCData()> getSensorVoltage_,
+                  const float V_SUPPLY_                 = 5.0,
+                  const unsigned int num_calib_samples_ = 200)
+        : HoneywellPressureSensor(getSensorVoltage_, V_SUPPLY_, 103421.3594,
+                                  -103421.3594),
+          offset(0.0), num_calib_samples(num_calib_samples_)
+    {
+    }
+
+    SSCDRRN015PDAData sampleImpl() override
+    {
+        last_sample = HoneywellPressureSensor<SSCDRRN015PDAData>::sampleImpl();
+
+        if (calibrating)
+        {
+            press_stats.add(last_sample.press);
+
+            if (press_stats.getStats().nSamples >= num_calib_samples)
+            {
+                calibrating = false;
+                offset      = press_stats.getStats().mean;
+
+                TRACE("Differential barometer offset : %.2f \n", offset);
+            }
+        }
+
+        last_sample.press = last_sample.press - offset;
+
+        return last_sample;
+    }
+
+    void calibrate()
+    {
+        press_stats.reset();
+        offset = 0.0f;
+        calibrating = true;
+    }
+
+    bool isCalibrating() { return calibrating; }
+
+private:
+    bool calibrating = false;
+    float offset;
+    Stats press_stats;
+    unsigned int num_calib_samples;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h
new file mode 100644
index 0000000000000000000000000000000000000000..bac2d1a2d1610e51755e8a7ff63933dff364b297
--- /dev/null
+++ b/src/shared/sensors/analog/pressure/honeywell/SSCDRRN015PDAData.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+struct SSCDRRN015PDAData : public PressureData
+{
+    static std::string header()
+    {
+        return "press_timestamp,pressure\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << press_timestamp << "," << press << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/shared/sensors/bme280.h b/src/shared/sensors/bme280.h
deleted file mode 100644
index 96a90b1559e534850ea93c0231ddcb52203185f6..0000000000000000000000000000000000000000
--- a/src/shared/sensors/bme280.h
+++ /dev/null
@@ -1,337 +0,0 @@
-/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- *
- * 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
- * AUTHORS OR COPYRIGHT HOLDERS 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.
- */
-
-#pragma once
-
-#include <stdio.h>
-#include <ostream>
-#include <string>
-#include "Sensor.h"
-// #include "debug.h"
-
-
-struct BME280Data
-{
-    uint32_t timestamp;
-    float pressure, temperature;
-    uint32_t raw_pressure, raw_temperature;
-
-    static std::string header()
-    {
-        return "timestamp,pressure,temperature,raw_pressure,raw_temperature";
-    }
-
-    void print(std::ostream& os)
-    {
-        os << timestamp << "," << pressure << "," << temperature << ","
-           << raw_pressure << "," << raw_temperature;
-    }
-};
-
-template <class Bus>
-class BME280 : public TemperatureSensor, public PressureSensor
-{
-public:
-    BME280() {}
-    virtual ~BME280() {}
-
-    std::string printData() { return ""; }
-
-    bool init() override
-    {
-        if (!initialized)
-        {
-            Bus::init();
-
-            uint8_t who_am_i = Bus::read(REG_WHO_AM_I);
-            if (who_am_i != ID)
-            {
-                last_error = ERR_NOT_ME;
-                printf("BME280 not found: who am i read 0x%02X\n", who_am_i);
-                return false;
-            }
-
-            // T standby = 0.5 ms, Filter: 8x ("& 0x7F" to set bit 7 to 0)
-            Bus::write(REG_CONFIG & 0x7F, 0x0C);
-
-            uint8_t config_val = Bus::read(REG_CONFIG);
-            if (config_val != 0x0C)
-            {
-                // TRACE("Wrong value in config register: 0x%02X\n", config_val);
-                return false;
-            }
-
-            // Disable humidity reading
-            Bus::write(REG_CTRL_HUM & 0x7F, 0x00);
-
-            uint8_t hum_val = Bus::read(REG_CTRL_HUM);
-            if (hum_val != 0x00)
-            {
-                // TRACE("Wrong value in hum register: 0x%02X\n", hum_val);
-                last_error = ERR_BUS_FAULT;
-                return false;
-            }
-
-            // Pressure oversampling x4, temp oversampling x1, normal mode.
-            Bus::write(REG_CTRL_MEAS & 0x7F, (uint8_t)0x2F);
-
-            uint8_t meas_val = Bus::read(REG_CTRL_MEAS);
-            if (meas_val != 0x2F)
-            {
-                // TRACE("Wrong value in meas register: 0x%02X\n", meas_val);
-                last_error = ERR_BUS_FAULT;
-                return false;
-            }
-
-            loadCompensationParams();
-
-            initialized = true;
-            return true;
-        }
-        else
-        {
-            return false;
-        }
-    }
-
-    bool selfTest() override { return true; }
-
-    void printCompensationParams()
-    {
-        if (initialized)
-        {
-            printf("Compensation parameters: \n");
-
-            printf("dig_T1: 0x%04X\n", comp_params.dig_T1);
-            printf("dig_T2: 0x%04X\n", comp_params.dig_T2);
-            printf("dig_T3: 0x%04X\n", comp_params.dig_T3);
-
-            printf("dig_P1: 0x%04X\n", comp_params.dig_P1);
-            printf("dig_P2: 0x%04X\n", comp_params.dig_P2);
-            printf("dig_P3: 0x%04X\n", comp_params.dig_P3);
-            printf("dig_P4: 0x%04X\n", comp_params.dig_P4);
-            printf("dig_P5: 0x%04X\n", comp_params.dig_P5);
-            printf("dig_P6: 0x%04X\n", comp_params.dig_P6);
-            printf("dig_P7: 0x%04X\n", comp_params.dig_P7);
-            printf("dig_P8: 0x%04X\n", comp_params.dig_P8);
-            printf("dig_P9: 0x%04X\n", comp_params.dig_P9);
-        }
-    }
-
-    bool onSimpleUpdate() override
-    {
-        if (initialized)
-        {
-            // Burst read temperature and pressure registers
-            uint8_t buf[6];
-            Bus::read(REG_PRESS_MSB, buf, 6);
-
-            uint32_t press = 0;
-            press |= ((uint32_t)buf[0]) << 12;
-            press |= ((uint32_t)buf[1]) << 4;
-            press |= ((uint32_t)buf[2]) >> 4;
-
-            uint32_t temp = 0;
-            temp |= ((uint32_t)buf[3]) << 12;
-            temp |= ((uint32_t)buf[4]) << 4;
-            temp |= ((uint32_t)buf[5]) >> 4;
-
-            data.raw_pressure    = press;
-            data.raw_temperature = temp;
-
-            // Compensate values
-            int32_t t_fine = getFineTemperature(data.raw_temperature);
-
-            data.temperature = compensateTemperature(t_fine) / 100.0f;
-            data.pressure =
-                compensatePressure(data.raw_pressure, t_fine) / 256.0f;
-
-            data.timestamp = miosix::getTick();
-
-            mLastPressure = data.pressure;
-            mLastTemp     = data.temperature;
-
-            /*printf(
-                "Raw P:\t%d,\tRaw T:\t%d\nComp P:\t%d,\tComp T:\t%d,\tFine T: "
-                "%d\n",
-                (int)press, (int)temp, (int)(pressure_value / 256),
-                (int)temp_value, (int)t_fine);*/
-            return true;
-        }
-        return false;
-    }
-
-    const uint32_t* rawPressureDataPtr() { return &data.raw_pressure; }
-
-    const uint32_t* rawTempDataPtr() { return &data.raw_temperature; }
-
-    const BME280Data* getDataPtr() { return &data; }
-
-    BME280Data data;
-
-private:
-    void loadCompensationParams()
-    {
-        uint8_t buf[24];
-        Bus::read(REG_CALIB_T, buf, 24);
-
-        comp_params.dig_T1 = toUInt16(&buf[0]);
-        comp_params.dig_T2 = toInt16(&buf[2]);
-        comp_params.dig_T3 = toInt16(&buf[4]);
-
-        comp_params.dig_P1 = toUInt16(&buf[6]);
-        comp_params.dig_P2 = toInt16(&buf[8]);
-        comp_params.dig_P3 = toInt16(&buf[10]);
-        comp_params.dig_P4 = toInt16(&buf[12]);
-        comp_params.dig_P5 = toInt16(&buf[14]);
-        comp_params.dig_P6 = toInt16(&buf[16]);
-        comp_params.dig_P7 = toInt16(&buf[18]);
-        comp_params.dig_P8 = toInt16(&buf[20]);
-        comp_params.dig_P9 = toInt16(&buf[22]);
-    }
-
-    /**
-     * Returns the temperature in degC.
-     * Refer to the data sheet for a description of this method.
-     */
-    int32_t getFineTemperature(uint32_t raw_T)
-    {
-        int32_t adc_T = static_cast<int32_t>(raw_T);
-        int32_t var1, var2;
-        var1 = ((((adc_T / 8) - ((int32_t)comp_params.dig_T1 * 2))) *
-                ((int32_t)comp_params.dig_T2)) >>
-               11;
-        var2 = (((((adc_T / 16) - ((int32_t)comp_params.dig_T1)) *
-                  ((adc_T / 16) - ((int32_t)comp_params.dig_T1))) /
-                 4096) *
-                ((int32_t)comp_params.dig_T3)) /
-               16384;
-
-        return var1 + var2;
-    }
-
-    int32_t compensateTemperature(int32_t fine_temperature)
-    {
-        return (fine_temperature * 5 + 128) / 256;
-    }
-
-    uint32_t compensatePressure(uint32_t adc_P, int32_t fine_temperature)
-    {
-        int64_t var1, var2, p;
-        var1 = ((int64_t)fine_temperature) - 128000;
-
-        var2 = var1 * var1 * (int64_t)comp_params.dig_P6;
-        var2 = var2 + ((var1 * (int64_t)comp_params.dig_P5) * 131072);
-        var2 = var2 + (((int64_t)comp_params.dig_P4) * 34359738368);
-
-        var1 = ((var1 * var1 * (int64_t)comp_params.dig_P3) / 256) +
-               ((var1 * ((int64_t)comp_params.dig_P2) * 4096));
-
-        var1 = (((int64_t)1) * 140737488355328 + var1) *
-               ((int64_t)comp_params.dig_P1) / 8589934592;
-
-        if (var1 == 0)
-        {
-            return 0;  // avoid exception caused by division by zero
-        }
-
-        p = 1048576 - (int32_t)adc_P;
-        p = (((p * 2147483648) - var2) * 3125) / var1;
-
-        var1 = (((int64_t)comp_params.dig_P9) * (p / 8192) * (p / 8192)) /
-               33554432;
-
-        var2 = (((int64_t)comp_params.dig_P8) * p) / 524288;
-
-        p = ((p + var1 + var2) / 256) + (((int64_t)comp_params.dig_P7) * 16);
-
-        // p = ((var3 / 2) * 100) / 128;
-        return (uint32_t)p;
-    }
-
-    /**
-     * Converts two bytes into an unsigned int 16
-     * @param buf: buffer containing at least two bytes
-     */
-    uint16_t toUInt16(uint8_t* buf) { return buf[0] | buf[1] << 8; }
-
-    /**
-     * Converts two bytes into a signed int 16
-     * @param buf: buffer containing at least two bytes
-     */
-    int16_t toInt16(uint8_t* buf)
-    {
-        return static_cast<int16_t>(buf[0] | buf[1] << 8);
-    }
-
-    enum Registers : uint8_t
-    {
-        REG_WHO_AM_I  = 0xD0,
-        REG_RESET     = 0xE0,
-        REG_CTRL_HUM  = 0xF2,
-        REG_STATUS    = 0xF3,
-        REG_CTRL_MEAS = 0xF4,
-        REG_CONFIG    = 0xF5,
-
-        REG_PRESS_MSB  = 0xF7,
-        REG_PRESS_LSB  = 0xF8,
-        REG_PRESS_XLSB = 0xF9,
-
-        REG_TEMP_MSB  = 0xFA,
-        REG_TEMP_LSB  = 0xFB,
-        REG_TEMP_XLSB = 0xFC,
-
-        REG_HUM_MSB = 0xFD,
-        REG_HUM_LSB = 0xFE,
-
-        REG_CALIB_T = 0x88,
-        REG_CALUB_P = 0x8E
-    };
-
-    /**
-         * Compensation parameters to convert ADC values to real temperature and
-         * pressure
-         */
-    struct CompensationParams
-    {
-        uint16_t dig_T1;
-        int16_t dig_T2;
-        int16_t dig_T3;
-
-        uint16_t dig_P1;
-        int16_t dig_P2;
-        int16_t dig_P3;
-        int16_t dig_P4;
-        int16_t dig_P5;
-        int16_t dig_P6;
-        int16_t dig_P7;
-        int16_t dig_P8;
-        int16_t dig_P9;
-    };
-
-    bool initialized = false;
-
-    const uint8_t ID         = 0x60;
-    const uint8_t RESET_WORD = 0xB6;
-
-    CompensationParams comp_params;
-};
\ No newline at end of file
diff --git a/src/shared/sensors/calibration/BiasCalibration.h b/src/shared/sensors/calibration/BiasCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..e5d8b204c543d6e33ec96b3e0c747cf0c0389b07
--- /dev/null
+++ b/src/shared/sensors/calibration/BiasCalibration.h
@@ -0,0 +1,110 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Core>
+
+#include "Calibration.h"
+#include "sensors/SensorData.h"
+
+using namespace Eigen;
+
+/**
+ * This is the dumbest type of calibration possible: it stores a 3d vector
+ * (called "bias") that will be added to every measurement.
+ * During the calibration phase it will use a given reference vector (for
+ * example the gravitational acceleration for the accelerometer), and every time
+ * you'll feed the model with a new value, you have to give it the orientation
+ * of the sensor, so it can guess the bias.
+ */
+template <typename T>
+class BiasCorrector : public ValuesCorrector<T>
+{
+
+public:
+    BiasCorrector() : bias(0, 0, 0) {}
+    BiasCorrector(const Vector3f& _bias) : bias(_bias) {}
+
+    void operator>>(Vector3f& rhs) { rhs = bias; }
+
+    void operator<<(const Vector3f& rhs) { bias = rhs; }
+
+    void setIdentity() override { bias = {0, 0, 0}; }
+
+    T correct(const T& data) const override
+    {
+        Vector3f tmp;
+        T out;
+
+        data >> tmp;
+        tmp += bias;
+        out << tmp;
+
+        return out;
+    }
+
+private:
+    Vector3f bias;
+};
+
+template <typename T>
+class BiasCalibration
+    : public AbstractCalibrationModel<T, BiasCorrector<T>, AxisOrientation>
+{
+public:
+    BiasCalibration() : sum(0, 0, 0), ref(0, 0, 0), numSamples(0) {}
+
+    void setReferenceVector(Vector3f vec) { ref = vec; }
+    Vector3f getReferenceVector() { return ref; }
+
+    /**
+     * BiasCalibration accepts an indefinite number of samples,
+     * so feed(...) never returns false.
+     */
+    bool feed(const T& measured, const AxisOrientation& transform) override
+    {
+        Vector3f vec;
+        measured >> vec;
+
+        sum += (transform.getMatrix().transpose() * ref) - vec;
+        numSamples++;
+
+        return true;
+    }
+
+    bool feed(const T& measured)
+    {
+        return feed(measured, AxisOrthoOrientation());
+    }
+
+    BiasCorrector<T> computeResult()
+    {
+        if (numSamples == 0)
+            return {Vector3f{0, 0, 0}};
+        return {sum / numSamples};
+    }
+
+private:
+    Vector3f sum, ref;
+    unsigned numSamples;
+};
diff --git a/src/shared/sensors/calibration/Calibration.h b/src/shared/sensors/calibration/Calibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..a263ab8e0d971af76d04a1fbc8dbf363a9696eff
--- /dev/null
+++ b/src/shared/sensors/calibration/Calibration.h
@@ -0,0 +1,334 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include "SensorDataExtra.h"
+#include "sensors/Sensor.h"
+
+using namespace Eigen;
+
+/**
+ * This class is used to adjust the values given by sensors during the flight.
+ * An object can be obtained only via deserialization or if produced by an
+ * instance of the "CalibrationModel" class.
+ *
+ * T is the type of sensor data the model is applied to (e.g.
+ * GyroscopeData, MagnetometerData). This is needed because certain models could
+ * work slightly differently depending on it
+ *
+ *  Note: derived classes of ValuesCorrector<XX> MUST implement the following
+ *  operators (to make possible to store and load the coefficients):
+ *
+ *  operator << (const XX& t);
+ *  operator >> (XX& t);
+ *
+ *  where XX is a datatype that can fully contain all the coefficients used
+ *  by the function correct(input).
+ *
+ *  Also, an empty constructor must create a neutral instance (identity
+ * transformation).
+ */
+template <typename T>
+class ValuesCorrector
+{
+public:
+    /**
+     * This method will sets the internal coefficients so that the corrected
+     * values are exactly the same of the inputted ones.
+     */
+    virtual void setIdentity() = 0;
+
+    virtual T correct(const T& input) const = 0;
+};
+
+/**
+ * AbstractCalibrationModel represents a "factory" of ValuesCorrector instances,
+ * and it's necessary to create one. You will always use one of its derived
+ * classes, of course.
+ *
+ * Values given to the feed() function are needed for the training of the model.
+ *
+ * T is the sensor data type
+ * C is a class that extends ValuesCorrector<T>
+ */
+template <typename T, typename C, typename... AdditionalFeedParams>
+class AbstractCalibrationModel
+{
+public:
+    /**
+     * Gives to the model a single measurement to store and use to produce the
+     * adapted ValueCorrector instance
+     *
+     * @returns false if the model can't accept the given data (usually because
+     * the internal buffers are full)
+     */
+    virtual bool feed(const T& measurement,
+                      const AdditionalFeedParams&... params) = 0;
+
+    /**
+     * Creates the best ValuesCorrector instance for the given measurements.
+     * Note: you must feed some data to the model before getting the result!
+     */
+    virtual C computeResult() = 0;
+
+    virtual ~AbstractCalibrationModel(){};
+};
+
+/**
+ * This class acts like a Sensor driver but incorporates both a Sensor<T>
+ * instance and a ValuesCorrector. It can be useful to add a calibration step to
+ * alredy existing code that uses the Sensor API.
+ */
+template <typename SensorData>
+class SensorWrapper : public Sensor<SensorData>
+{
+    using S = Sensor<SensorData>;
+    using C = ValuesCorrector<SensorData>;
+
+public:
+    SensorWrapper(S* _sensor, C* _calib) : sensor(_sensor), calib(_calib) {}
+
+    S* getSensor() { return sensor; }
+    void setSensor(S* s) { sensor = s; }
+
+    C* getValuesCorrector() { return calib; }
+    void setValuesCorrector(C* c) { calib = c; }
+
+    void init() override { sensor->init(); }
+
+    bool test() override { return sensor->test(); }
+
+    SensorData sampleImpl() override
+    {
+        return calib->correct(sensor->sampleImpl());
+    }
+
+    SensorErrors getWrappedSensorError() { return sensor->getLastError(); }
+
+private:
+    S* sensor;
+    C* calib;
+};
+
+/**
+ * This enum act like versors towards the chosen axis.
+ *
+ * X, Y and Z always set according to the right hand rule, so that:
+ * X is the index
+ * Y is the second finger
+ * Z is the thumb
+ *
+ */
+enum class Direction : uint8_t
+{
+    POSITIVE_X = 0,
+    NEGATIVE_X,
+    POSITIVE_Y,
+    NEGATIVE_Y,
+    POSITIVE_Z,
+    NEGATIVE_Z,
+};
+
+constexpr const char* humanFriendlyDirection[]{
+    "North", "South", "East", "West", "Down", "Up",
+};
+
+inline Vector3f orientationToVector(Direction val)
+{
+    switch (val)
+    {
+        case Direction::POSITIVE_X:
+            return {1, 0, 0};
+        case Direction::NEGATIVE_X:
+            return {-1, 0, 0};
+        case Direction::POSITIVE_Y:
+            return {0, 1, 0};
+        case Direction::NEGATIVE_Y:
+            return {0, -1, 0};
+        case Direction::POSITIVE_Z:
+            return {0, 0, 1};
+        case Direction::NEGATIVE_Z:
+            return {0, 0, -1};
+        default:
+            /* never happens, added just to shut up the warnings */
+            return {0, 0, 0};
+    }
+}
+
+/**
+ * This struct represents in the most general way any kind of transformation of
+ * the reference frame (axis X, Y and Z).
+ * This data type is intended to simplify the code, so you shouldn't instantiate
+ * this struct directly, but rather use the structures AxisAngleOrientation or
+ * AxisOrthoOrientation that will be automatically corrected to this one, thanks
+ * to the implicit cast in favor of AxisOrientation.
+ *
+ * For example:
+ *
+ * AxisAngleOrientation angles ( PI/2, PI, 0);
+ * AxisOrthoOrientation ortho  ( Direction::NEGATIVE_X,
+ * Direction::POSITIVE_Z );
+ *
+ * // The implicit cast is supported and recommended
+ * AxisOrientation converted1 = angles;
+ * AxisOrientation converted2 = ortho;
+ *
+ * // Now we can use the generated matrix:
+ * Vector3f zeta = convertedX.getMatrix() * Vector3f { 0, 0, 1 }
+ *
+ */
+struct AxisOrientation
+{
+    Matrix3f mat;
+
+    AxisOrientation() : mat(Matrix3f::Identity()) {}
+
+    AxisOrientation(Matrix3f _mat) : mat(_mat) {}
+
+    void setMatrix(Matrix3f _mat) { mat = _mat; }
+
+    Matrix3f getMatrix() const { return mat; }
+};
+
+/**
+ * This struct uses the three angles yaw, pitch and roll to define the
+ * transformation of the reference frame, so according to N.E.D standard we get:
+ *
+ *         X (north)
+ *        /
+ *       /
+ *      .----> Y (east)
+ *      |
+ *      |
+ *      v
+ *     Z (down)
+ *
+ * Where:
+ * Yaw is rotation of Z axis
+ * Pitch is rotation of Y axis
+ * Roll is rotation of X axis
+ */
+struct AxisAngleOrientation
+{
+    float yaw, pitch, roll;
+
+    AxisAngleOrientation() : yaw(0), pitch(0), roll(0) {}
+
+    AxisAngleOrientation(float _yaw, float _pitch, float _roll)
+        : yaw(_yaw), pitch(_pitch), roll(_roll)
+    {
+    }
+
+    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
+
+    Matrix3f getMatrix() const
+    {
+        return (AngleAxisf(yaw, Vector3f{0, 0, 1}) *
+                AngleAxisf(pitch, Vector3f{0, 1, 0}) *
+                AngleAxisf(roll, Vector3f{1, 0, 0}))
+            .toRotationMatrix();
+    }
+};
+
+/**
+ * This struct represents the orientation of the reference frame relative
+ * to X, Y, Z in the start orientation.
+ * If we know the orientation of the X and Y axis, using the right hand rule
+ * we can infer the Z axis.
+ *
+ * For example, if the base reference is:
+ *
+ *        z
+ *        ^
+ *        |
+ *        |
+ *        /----> y
+ *       /
+ *      x
+ *
+ * Then if we set x = NEGATIVE_Y, y = POSITIVE_Z, we get:
+ *
+ *          y   z
+ *          ^   ^
+ *          |  /
+ *          | /
+ *   x <----/
+ *
+ */
+struct AxisOrthoOrientation
+{
+    Direction xAxis, yAxis;
+
+    AxisOrthoOrientation()
+        : xAxis(Direction::POSITIVE_X), yAxis(Direction::POSITIVE_Y)
+    {
+    }
+
+    AxisOrthoOrientation(Direction _xAxis, Direction _yAxis)
+        : xAxis(_xAxis), yAxis(_yAxis)
+    {
+    }
+
+    operator AxisOrientation() const { return AxisOrientation(getMatrix()); }
+
+    Matrix3f getMatrix() const
+    {
+        Vector3f vx, vy, vz;
+
+        vx = orientationToVector(xAxis);
+        vy = orientationToVector(yAxis);
+        vz = vx.cross(vy);
+
+        Matrix3f mat;
+        mat.col(0) << vx;
+        mat.col(1) << vy;
+        mat.col(2) << vz;
+        return mat;
+    }
+};
+
+/**
+ * This struct represents axis orientation relative to a reference system.
+ * Operatively it simply combines two transformations. It is particularly useful
+ * to obtain an AxisOrientation from a reference system generally not N.E.D.
+ */
+struct AxisRelativeOrientation
+{
+    AxisOrientation systemOrientation, orientation;
+
+    AxisRelativeOrientation(const AxisOrientation& _systemOrientation,
+                            const AxisOrientation& _orientation)
+        : systemOrientation(_systemOrientation), orientation(_orientation)
+    {
+    }
+
+    operator AxisOrientation() const { return {getMatrix()}; }
+
+    Matrix3f getMatrix() const
+    {
+        return systemOrientation.getMatrix() * orientation.getMatrix();
+    }
+};
diff --git a/src/shared/sensors/calibration/HardIronCalibration.h b/src/shared/sensors/calibration/HardIronCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..feaa0fdf6cf92e88d249891ba853fa3db791d426
--- /dev/null
+++ b/src/shared/sensors/calibration/HardIronCalibration.h
@@ -0,0 +1,115 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Core>
+
+#include "Calibration.h"
+#include "sensors/SensorData.h"
+
+/*
+ * The Hard-iron calibration removes the bias due to the so named Hard-Iron
+ * distortion of the magnetic field. Unlike bias calibration, 6-parameter
+ * calibration, etc., hard-iron calibration can be applied only to magnetometer
+ * samples.
+ */
+class HardIronCorrector : public ValuesCorrector<MagnetometerData>
+{
+public:
+    HardIronCorrector() : HardIronCorrector({0, 0, 0}) {}
+
+    HardIronCorrector(const Vector3f& _bias) : bias(_bias) {}
+
+    void setIdentity() override { bias = {0, 0, 0}; }
+
+    void operator>>(Vector3f& rhs) { rhs = bias; }
+
+    void operator<<(const Vector3f& rhs) { bias = rhs; }
+
+    MagnetometerData correct(const MagnetometerData& input) const override
+    {
+        MagnetometerData output;
+        Vector3f vec;
+
+        input >> vec;
+        vec += bias;
+        output << vec;
+
+        return output;
+    }
+
+private:
+    Vector3f bias;
+};
+
+template <unsigned MaxSamples>
+class HardIronCalibration
+    : public AbstractCalibrationModel<MagnetometerData, HardIronCorrector>
+{
+public:
+    HardIronCalibration() : samples(), numSamples(0) {}
+
+    bool feed(const MagnetometerData& data) override
+    {
+        if (numSamples >= MaxSamples)
+            return false;
+
+        Vector3f vec;
+        data >> vec;
+
+        samples.block(numSamples, 0, 1, 3) = vec.transpose();
+        samples(numSamples, 3)             = 1;
+        samples(numSamples, 4) =
+            vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2];
+
+        numSamples++;
+        return true;
+    }
+
+    HardIronCorrector computeResult() override
+    {
+        Vector4f sol;
+        Vector3f bias;
+
+        MatrixXf coeffs = samples.block(0, 0, numSamples, 4);
+        VectorXf terms = samples.block(0, 4, numSamples, 1);
+
+        auto colPiv = coeffs.colPivHouseholderQr();
+        sol         = colPiv.solve(terms);
+
+        bias[0] = -sol[0] / 2;
+        bias[1] = -sol[1] / 2;
+        bias[2] = -sol[2] / 2;
+
+        return {bias};
+    }
+
+private:
+    /*
+     * The matrix contains x, y, z measured, a column of 1s and x^2+y^2+z^2
+     * row. Its shape is (N x 5)
+     */
+    Matrix<float, MaxSamples, 5> samples;
+    unsigned numSamples;
+};
+
diff --git a/src/shared/sensors/calibration/SensorDataExtra.cpp b/src/shared/sensors/calibration/SensorDataExtra.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7afc87dc85b3574dac80789ac34229989a64f6a1
--- /dev/null
+++ b/src/shared/sensors/calibration/SensorDataExtra.cpp
@@ -0,0 +1,67 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include "SensorDataExtra.h"
+
+using namespace Eigen;
+
+void operator<<(AccelerometerData& lhs, const Vector3f& rhs)
+{
+    lhs.accel_x = rhs[0];
+    lhs.accel_y = rhs[1];
+    lhs.accel_z = rhs[2];
+}
+
+void operator<<(GyroscopeData& lhs, const Vector3f& rhs)
+{
+    lhs.gyro_x = rhs[0];
+    lhs.gyro_y = rhs[1];
+    lhs.gyro_z = rhs[2];
+}
+
+void operator<<(MagnetometerData& lhs, const Vector3f& rhs)
+{
+    lhs.mag_x = rhs[0];
+    lhs.mag_y = rhs[1];
+    lhs.mag_z = rhs[2];
+}
+
+void operator>>(const AccelerometerData& lhs, Vector3f& rhs)
+{
+    rhs[0] = lhs.accel_x;
+    rhs[1] = lhs.accel_y;
+    rhs[2] = lhs.accel_z;
+}
+
+void operator>>(const GyroscopeData& lhs, Vector3f& rhs)
+{
+    rhs[0] = lhs.gyro_x;
+    rhs[1] = lhs.gyro_y;
+    rhs[2] = lhs.gyro_z;
+}
+
+void operator>>(const MagnetometerData& lhs, Vector3f& rhs)
+{
+    rhs[0] = lhs.mag_x;
+    rhs[1] = lhs.mag_y;
+    rhs[2] = lhs.mag_z;
+}
diff --git a/src/shared/sensors/calibration/SensorDataExtra.h b/src/shared/sensors/calibration/SensorDataExtra.h
new file mode 100644
index 0000000000000000000000000000000000000000..46c76e8a7074244b51364460f235198e46d66606
--- /dev/null
+++ b/src/shared/sensors/calibration/SensorDataExtra.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/* This header file defines operators << and >> from some types
+ * of SensorData.
+ *
+ * This way you can write for example:
+ *
+ * GenericSensorData data;
+ * data << Vector3f{0.f, 0.f, 0.f};
+ *
+ * Vector3f vec;
+ * data >> vec;
+ *
+ * That works if GenericSensorData is any of AccelerometerData, GyroscopeData
+ * and MagnetometerData
+ */
+
+#pragma once
+
+#include <Eigen/Core>
+
+#include "sensors/SensorData.h"
+
+using namespace Eigen;
+
+void operator<<(AccelerometerData& lhs, const Vector3f& rhs);
+
+void operator<<(GyroscopeData& lhs, const Vector3f& rhs);
+
+void operator<<(MagnetometerData& lhs, const Vector3f& rhs);
+
+void operator>>(const AccelerometerData& lhs, Vector3f& rhs);
+
+void operator>>(const GyroscopeData& lhs, Vector3f& rhs);
+
+void operator>>(const MagnetometerData& lhs, Vector3f& rhs);
+
diff --git a/src/shared/sensors/calibration/SixParameterCalibration.h b/src/shared/sensors/calibration/SixParameterCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..c42ab4417db63790bf4fdbdc8b00d1f0c2f61897
--- /dev/null
+++ b/src/shared/sensors/calibration/SixParameterCalibration.h
@@ -0,0 +1,159 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Common.h>
+
+#include <Eigen/Core>
+#include <iostream>
+
+#include "Calibration.h"
+#include "sensors/SensorData.h"
+
+/*
+ * Six-Parameter Calibration uses, for each axis, a coefficient to be multiplied
+ * and a constant to be added, so that is verified the formula:
+ *
+ *    x' = p1*x + q1;
+ *    y' = p2*y + q2;
+ *    z' = p3*z + q3;
+ *
+ * Where (x, y, z) is the input and (x', y', z') the output.
+ */
+template <typename T>
+class SixParameterCorrector : public ValuesCorrector<T>
+{
+public:
+    SixParameterCorrector() : SixParameterCorrector({1, 1, 1}, {0, 0, 0}) {}
+
+    SixParameterCorrector(const Vector3f& _p, const Vector3f& _q) : p(_p), q(_q)
+    {
+    }
+
+    void setIdentity() override
+    {
+        p = {1, 1, 1};
+        q = {0, 0, 0};
+    }
+
+    void operator>>(Matrix<float, 3, 2>& out) const
+    {
+        out.col(0) = p.transpose();
+        out.col(1) = q.transpose();
+    }
+
+    void operator<<(const Matrix<float, 3, 2>& in)
+    {
+        p = in.col(0).transpose();
+        q = in.col(1).transpose();
+    }
+
+    T correct(const T& input) const override
+    {
+        T output;
+        Vector3f vec;
+
+        input >> vec;
+        vec = p.cwiseProduct(vec) + q;
+        output << vec;
+
+        return output;
+    }
+
+private:
+    Vector3f p, q;
+};
+
+template <typename T, unsigned MaxSamples>
+class SixParameterCalibration
+    : public AbstractCalibrationModel<T, SixParameterCorrector<T>,
+                                      AxisOrientation>
+{
+public:
+    SixParameterCalibration() : samples(), ref(1, 0, 0), numSamples(0) {}
+
+    void setReferenceVector(Vector3f vec) { ref = vec; }
+    Vector3f getReferenceVector() { return ref; }
+
+    bool feed(const T& data, const AxisOrientation& transform) override
+    {
+        if (numSamples >= MaxSamples)
+            return false;
+
+        Vector3f measured, expected;
+
+        data >> measured;
+        // TODO: understand why the rotation, although it is needed, it does not
+        // work
+        expected = transform.getMatrix() * ref;
+
+        /*
+         * measered and expected are column vectors, we need to traspose them
+         * to be row vectors
+         */
+        samples.block(numSamples, 0, 1, 3) = measured.transpose();
+        samples.block(numSamples, 3, 1, 3) = expected.transpose();
+
+        numSamples++;
+        return true;
+    }
+
+    SixParameterCorrector<T> computeResult() override
+    {
+        Vector3f p, q;
+
+        for (int i = 0; i < 3; ++i)
+        {
+            MatrixXf coeffs(numSamples, 2);
+            VectorXf terms = samples.block(0, i + 3, numSamples, 1);
+
+            Vector2f solution;
+            coeffs.fill(1);
+            coeffs.block(0, 0, numSamples, 1) =
+                samples.block(0, i, numSamples, 1);
+
+            auto solver = coeffs.colPivHouseholderQr();
+            solution    = solver.solve(terms);
+
+            p[i] = solution[0];
+            q[i] = solution[1];
+        }
+
+        return {p, q};
+    }
+
+private:
+    /*
+     * The matrix contains x, y, z measured and x', y', z' expected for each
+     * row. Its shape is (N x 6)
+     */
+    Matrix<float, MaxSamples, 6> samples;
+    Vector3f ref;
+    unsigned numSamples;
+
+public:
+    /*
+     * Only needed for test-calibration.cpp entry point
+     */
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
diff --git a/src/shared/sensors/calibration/SoftIronCalibration.h b/src/shared/sensors/calibration/SoftIronCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7db084d85d0ca37d70a132fef983a99f85bc2a6
--- /dev/null
+++ b/src/shared/sensors/calibration/SoftIronCalibration.h
@@ -0,0 +1,187 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Common.h>
+
+#include <Eigen/Core>
+#include <Eigen/Eigenvalues>
+
+#include "Calibration.h"
+#include "sensors/SensorData.h"
+
+/*
+ * The Soft-iron calibration removes the measurement error given by both the
+ * Hard and Soft Iron distortion of the magnetic field.
+ */
+class SoftIronCorrector : public ValuesCorrector<MagnetometerData>
+{
+public:
+    SoftIronCorrector() : SoftIronCorrector({1, 1, 1}, {0, 0, 0}) {}
+
+    SoftIronCorrector(const Vector3f& _p, const Vector3f& _q) : p(_p), q(_q) {}
+
+    void setIdentity() override
+    {
+        p = {1, 1, 1};
+        q = {0, 0, 0};
+    }
+
+    void operator>>(Matrix<float, 3, 2>& rhs)
+    {
+        rhs.col(0) = p.transpose();
+        rhs.col(1) = q.transpose();
+    }
+
+    void operator<<(const Matrix<float, 3, 2>& rhs)
+    {
+        p = rhs.col(0).transpose();
+        q = rhs.col(1).transpose();
+    }
+
+    MagnetometerData correct(const MagnetometerData& input) const override
+    {
+        MagnetometerData output;
+        Vector3f vec;
+
+        input >> vec;
+        vec = vec.cwiseProduct(p) + q;
+        output << vec;
+
+        return output;
+    }
+
+private:
+    Vector3f p, q;
+};
+
+template <unsigned MaxSamples>
+class SoftIronCalibration
+    : public AbstractCalibrationModel<MagnetometerData, SoftIronCorrector>
+{
+public:
+    SoftIronCalibration() : samples(), numSamples(0) {}
+
+    bool feed(const MagnetometerData& data) override
+    {
+        if (numSamples >= MaxSamples)
+            return false;
+
+        Vector3f vec;
+        data >> vec;
+
+        for (int i = 0; i < 3; ++i)
+        {
+            samples(numSamples, i)     = vec[i] * vec[i];
+            samples(numSamples, i + 3) = vec[i];
+        }
+        samples(numSamples, 6) = 1;
+
+        numSamples++;
+        return true;
+    }
+
+    SoftIronCorrector computeResult() override
+    {
+        using Mx   = Matrix<float, 7, 7>;
+        using Vec7 = Matrix<float, 7, 1>;
+
+        float minValue, det;
+        int minIdx;
+
+        MatrixXf tmp1, tmp2;
+        Vector3f p, q;
+        Vec7 vec;
+        Mx mat;
+
+        tmp1 = samples.block(0, 0, numSamples, 7);
+        tmp2 = tmp1.transpose();
+
+        // mat = tmp2 * tmp1;
+
+        /*
+         * tmp1: N x 7
+         * tmp2: 7 x N
+         *
+         * Big matrices multiplication: if done with Eigen, the program crashes
+         * (bus fault), so we'll do that with good old for's
+         */
+        for (unsigned i = 0; i < 7; ++i)
+        {
+            for (unsigned j = 0; j < 7; ++j)
+            {
+                mat(i, j) = 0;
+
+                for (unsigned k = 0; k < numSamples; ++k)
+                {
+                    mat(i, j) += tmp2(i, k) * tmp1(k, j);
+                }
+            }
+        }
+
+        SelfAdjointEigenSolver<Mx> solver(mat);
+        auto eigenvalues = solver.eigenvalues();
+
+        minValue = eigenvalues[0];
+        minIdx   = 0;
+
+        for (int i = 1; i < eigenvalues.rows(); ++i)
+        {
+            if (minValue > eigenvalues[i])
+            {
+                minValue = eigenvalues[i];
+                minIdx   = i;
+            }
+        }
+
+        vec = solver.eigenvectors().col(minIdx);
+        det = vec[0] * vec[1] * vec[2];
+
+        if (det < 0)
+        {
+            vec *= -1;
+            det *= -1;
+        }
+
+        p = vec.block(0, 0, 3, 1) / cbrt(det);
+        q = {vec[3] / vec[0] / 2, vec[4] / vec[1] / 2, vec[5] / vec[2] / 2};
+
+        p[0] = sqrt(p[0]);
+        p[1] = sqrt(p[1]);
+        p[2] = sqrt(p[2]);
+
+        q = q.cwiseProduct(p);
+
+        return {p, q};
+    }
+
+    Matrix<float, MaxSamples, 7> getSamples() { return samples; }
+
+private:
+    /*
+     * The matrix contains x, y, z, x^2, y^2, z^2 and a column of 1s
+     * row. Its shape is (N x 7)
+     */
+    Matrix<float, MaxSamples, 7> samples;
+    unsigned numSamples;
+};
diff --git a/src/shared/sensors/calibration/TwelveParameterCalibration.h b/src/shared/sensors/calibration/TwelveParameterCalibration.h
new file mode 100644
index 0000000000000000000000000000000000000000..e8230697cf0abf24bf80cb5396aed6deaec5a361
--- /dev/null
+++ b/src/shared/sensors/calibration/TwelveParameterCalibration.h
@@ -0,0 +1,152 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <Eigen/Core>
+#include <Eigen/Geometry>
+
+#include "Calibration.h"
+
+using namespace Eigen;
+
+/**
+ * This type of correction uses a 3x3 matrix multiplied by the input
+ * data and adds a 3d vector bias, so that:
+ *
+ *   | x' |   | w11 w12 w13 |   | x |   | b1 |
+ *   | y' | = | w21 w22 w23 | . | y | + | b2 |
+ *   | z' |   | w31 w32 w33 |   | z |   | b3 |
+ *
+ * Where (x, y, z) is the input and (x', y', z') the output.
+ */
+template <typename T>
+class TwelveParameterCorrector : public ValuesCorrector<T>
+{
+public:
+    TwelveParameterCorrector()
+        : TwelveParameterCorrector(Matrix3f::Identity(), {0, 0, 0})
+    {
+    }
+
+    TwelveParameterCorrector(const Matrix3f& _w, const Vector3f& _b)
+        : w(_w), b(_b)
+    {
+    }
+
+    TwelveParameterCorrector(const Matrix<float, 3, 4>& mat)
+    {
+        operator<<(mat);
+    }
+
+    void operator<<(const Matrix<float, 3, 4>& rhs)
+    {
+        w = rhs.block<3, 3>(0, 0);
+        b = rhs.col(3).transpose();
+    }
+
+    void operator>>(Matrix<float, 3, 4>& rhs) const
+    {
+        rhs.block<3, 3>(0, 0) = w;
+        rhs.col(3)            = b.transpose();
+    }
+
+    void setIdentity() override
+    {
+        w.setIdentity();
+        b.setZero();
+    }
+
+    T correct(const T& input) const
+    {
+        T output;
+        Vector3f vec;
+
+        input >> vec;
+        vec = w * vec + b;
+        output << vec;
+
+        return output;
+    }
+
+private:
+    Matrix3f w;
+    Vector3f b;
+};
+
+template <typename T, unsigned MaxSamples>
+class TwelveParameterCalibration
+    : public AbstractCalibrationModel<T, TwelveParameterCorrector<T>,
+                                      AxisOrientation>
+{
+public:
+    TwelveParameterCalibration() : samples(), numSamples(0), ref({1, 0, 0})  {}
+
+    void setReferenceVector(Vector3f vec) { ref = vec; }
+    Vector3f getReferenceVector() { return ref; }
+
+    bool feed(const T& data, const AxisOrientation& transform) override
+    {
+        if (numSamples == MaxSamples)
+            return false;
+
+        Vector3f expected, measured;
+        data >> measured;
+        expected = transform.getMatrix().transpose() * ref;
+
+        samples.row(numSamples) << measured.transpose(), 1,
+            expected.transpose();
+        numSamples++;
+
+        return true;
+    }
+
+    TwelveParameterCorrector<T> computeResult() override
+    {
+        Matrix<float, 3, 4> solutions;
+        MatrixXf coeffs = samples.block(0, 0, numSamples, 4);
+        auto qr = coeffs.colPivHouseholderQr();
+
+        for (int i = 0; i < 3; ++i)
+        {
+            VectorXf terms = samples.block(0, 4 + i, numSamples, 1);
+            Vector4f sol     = qr.solve(terms);
+            solutions.col(i) = sol.head<3>();
+            solutions(i, 3)  = sol[3];
+        }
+
+        return {solutions};
+    }
+
+private:
+    /*
+     * The matrix contains x, y, z measured and x', y', z' expected for each
+     * sample. Between (x, y, z) and (x', y', z') there is a column of 1s
+     * Its shape is (N x 7)
+     */
+    Matrix<float, MaxSamples, 7> samples;
+    unsigned numSamples;
+    Vector3f ref;
+
+public:
+    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
diff --git a/src/shared/sensors/ADIS16405/ADIS16405.h b/src/shared/sensors/old_examples/ADIS16405/ADIS16405.h
similarity index 98%
rename from src/shared/sensors/ADIS16405/ADIS16405.h
rename to src/shared/sensors/old_examples/ADIS16405/ADIS16405.h
index 89b0af4ed9aff3ddcd1b8b017425d29138b4917f..48d537b392fd7e3ee1a429ff3968aa093444fd60 100644
--- a/src/shared/sensors/ADIS16405/ADIS16405.h
+++ b/src/shared/sensors/old_examples/ADIS16405/ADIS16405.h
@@ -1,7 +1,5 @@
-/* ADIS16405 Driver
- *
- * Copyright (c) 2018-2019 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+/* Copyright (c) 2018-2019 Skyward Experimental Rocketry
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/ADIS16405/ADIS16405Data.h b/src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h
similarity index 94%
rename from src/shared/sensors/ADIS16405/ADIS16405Data.h
rename to src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h
index 5ce9f6f89b96294ac250f6d78ed4ff09ecbb3d8d..f414b7e06e565854a5dfd9f134303510b27dbb76 100644
--- a/src/shared/sensors/ADIS16405/ADIS16405Data.h
+++ b/src/shared/sensors/old_examples/ADIS16405/ADIS16405Data.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/FXAS21002.h b/src/shared/sensors/old_examples/FXAS21002.h
similarity index 97%
rename from src/shared/sensors/FXAS21002.h
rename to src/shared/sensors/old_examples/FXAS21002.h
index 8c396bbb4692c3db8e8c45eebade045e6e12c7c9..32a90964302eb95e2a1edf525e3412c2fccd7952 100644
--- a/src/shared/sensors/FXAS21002.h
+++ b/src/shared/sensors/old_examples/FXAS21002.h
@@ -1,7 +1,5 @@
-/* FXAS21002 Driver
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci, Matteo Michele Piazzolla, Silvano Seva
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Matteo Piazzolla, Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/LM75B.h b/src/shared/sensors/old_examples/LM75B.h
similarity index 97%
rename from src/shared/sensors/LM75B.h
rename to src/shared/sensors/old_examples/LM75B.h
index 509829c10c8bc8954bce8727614d092b97ec5f59..031eecfc896d48baca9d1419fa7db25891ff6448 100644
--- a/src/shared/sensors/LM75B.h
+++ b/src/shared/sensors/old_examples/LM75B.h
@@ -1,7 +1,5 @@
-/* LM75B Driver
- *
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Alessio Galluccio
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Alessio Galluccio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/LPS331AP.h b/src/shared/sensors/old_examples/LPS331AP.h
similarity index 95%
rename from src/shared/sensors/LPS331AP.h
rename to src/shared/sensors/old_examples/LPS331AP.h
index f7c01e8d3826a73f62ac5f3c811f3752a60f1de8..a7d58338748b5edf2f798896e8e0bab29db93b5b 100644
--- a/src/shared/sensors/LPS331AP.h
+++ b/src/shared/sensors/old_examples/LPS331AP.h
@@ -1,7 +1,5 @@
-/* LPS331AP Driver
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Alain Carlucci
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Author: Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h
new file mode 100644
index 0000000000000000000000000000000000000000..c30d71139d41979497a4dfc0e57ad8dc254b89c2
--- /dev/null
+++ b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3H.h
@@ -0,0 +1,218 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Nuno Barcellos
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef LSM6DS3H_H
+#define LSM6DS3H_H
+
+#include <drivers/BusTemplate.h>
+
+#include "../Sensor.h"
+#include "Common.h"
+#include "miosix.h"
+
+/** ********ATTENTION***********
+ * @warning Once upon a time, this driver was working, but due to hardware
+ * problems we were unable to confirm it and test it properly. If you need this,
+ * you should test it a bit before using it in production code.
+ */
+
+template <typename Bus>
+class LSM6DS3H : public GyroSensor,
+                 public AccelSensor,
+                 public CompassSensor,
+                 public TemperatureSensor
+{
+#pragma pack(1)
+    // __extension__ is needed to prevent compiler warnings for anonymous
+    // structs
+    typedef union {
+        __extension__ struct
+        {
+            int16_t temp;
+            int16_t gyro[3];
+            int16_t accel[3];
+        };
+        int16_t buf[8];
+    } lsmData_t;
+
+#pragma pack()
+
+public:
+    LSM6DS3H(uint8_t accelFullScale, uint16_t gyroFullScale)
+    {
+        accelFS = accelFullScale;
+        gyroFS  = gyroFullScale;
+    }
+
+    bool init() override
+    {
+        // SPI clock frequency up to 10 MHz
+        // The device is compatible with SPI modes 0 and 3
+
+        uint8_t whoami = Bus::read(RegMap::WHO_AM_I);
+        printf("[LSM] expected: %x actual: %x\n", whoami_value, whoami);
+        if (whoami != whoami_value)
+        {
+            last_error = ERR_NOT_ME;
+            return false;
+        }
+
+        // Reset device
+        uint8_t reg = Bus::read(RegMap::CTRL3_C);
+        Bus::write(reg | 0x01);
+
+        miosix::Thread::sleep(100);
+
+        // clang-format off
+        uint8_t init_data[][2] = 
+        {
+            {RegMap::CTRL3_C, 0x44}, // Register address automatically incremented during a multiple 
+                                     // byte access with a serial interface; LSB @ lower address;
+                                     // SPI 4 wire; Output registers not updated until MSB and LSB
+                                     // have been read
+            {RegMap::CTRL1_XL, (uint8_t) (0x70 | (accelFS << 2))}, // Accel ODR to 833 Hz,
+                                                                   // Anti-aliasing filter to 400 hz
+            // {RegMap::CTRL5_C, 0x60}, // Rounding read enabled for both accel and gyro
+            {RegMap::CTRL2_G, (uint8_t) (0x70 | (gyroFS << 2))},   // Gyro ODR to 833 Hz
+        };
+        // clang-format on
+
+        for (size_t i = 0; i < sizeof(init_data) / sizeof(init_data[0]); i++)
+            Bus::write(init_data[i][0], init_data[i][1]);
+
+        return true;
+    }
+
+    bool selfTest() override { return true; }
+
+    bool onSimpleUpdate()
+    {
+        lsmData_t raw_data;
+        uint8_t buf[20];
+
+        // Read temp, gyro, accel
+        Bus::read(RegMap::OUT_TEMP_L, buf, 14);
+        memcpy(&raw_data.buf, buf, 14);
+
+        mLastTemp = normalizeTemp(raw_data.temp);
+
+        mLastGyro.setX(normalizeGyro(raw_data.gyro[0]));
+        mLastGyro.setY(normalizeGyro(raw_data.gyro[1]));
+        mLastGyro.setZ(normalizeGyro(raw_data.gyro[2]));
+
+        mLastAccel.setX(normalizeAccel(raw_data.accel[0]));
+        mLastAccel.setY(normalizeAccel(raw_data.accel[1]));
+        mLastAccel.setZ(normalizeAccel(raw_data.accel[2]));
+
+        return true;
+    }
+
+    // clang-format off
+    enum gyroFullScale
+    {
+        GYRO_FS_250           = 0,
+        GYRO_FS_500           = 1,
+        GYRO_FS_1000          = 2,
+        GYRO_FS_2000          = 3
+    };
+
+    enum accelFullScale
+    {
+        ACC_FS_2G             = 0, // Do not change this sequence
+        ACC_FS_4G             = 2,
+        ACC_FS_8G             = 3,
+        ACC_FS_16G            = 1
+    };
+    // clang-format on
+
+private:
+    constexpr static uint8_t whoami_value = 0x69;
+    constexpr static float accelFSMAP[4]  = {0.061, 0.488, 0.122, 0.244};
+    constexpr static float gyroFSMAP[4]   = {8.75, 17.50, 35.0, 70.0};
+    uint8_t accelFS;
+    uint16_t gyroFS;
+
+    inline float normalizeAccel(int16_t val)
+    {
+        return static_cast<float>(val) / 1000.0f * accelFSMAP[accelFS] *
+               EARTH_GRAVITY;  // [m/ss]
+    }
+
+    inline float normalizeGyro(int16_t val)
+    {
+        return static_cast<float>(val) / 1000.0f * gyroFSMAP[gyroFS] *
+               DEGREES_TO_RADIANS;  // [rad/s]
+    }
+
+    inline float normalizeTemp(int16_t val)
+    {
+        return static_cast<float>(val) / 16.0f + 25.0f;  // [deg C]
+    }
+
+    enum RegMap
+    {
+        WHO_AM_I = 0x0F,  // default value
+
+        // Accelerometer and gyroscope control registers
+        CTRL1_XL = 0x10,
+        CTRL2_G  = 0x11,
+        CTRL3_C  = 0x12,
+        CTRL4_C  = 0x13,
+        CTRL5_C  = 0x14,
+        CTRL6_C  = 0x15,
+        CTRL7_G  = 0x16,
+        CTRL8_XL = 0x17,
+        CTRL9_XL = 0x18,
+        CTRL10_C = 0x19,
+
+        // Temperature output registers
+        OUT_TEMP_L = 0x20,
+        OUT_TEMP_H = 0x21,
+
+        // Gyro data registers
+        OUT_X_L_G = 0x22,
+        OUT_X_H_G = 0x23,
+        OUT_Y_L_G = 0x24,
+        OUT_Y_H_G = 0x25,
+        OUT_Z_L_G = 0x26,
+        OUT_Z_H_G = 0x27,
+
+        // Accelerometer output registers
+        OUT_X_L_XL = 0x28,
+        OUT_X_H_XL = 0x29,
+        OUT_Y_L_XL = 0x2A,
+        OUT_Y_H_XL = 0x2B,
+        OUT_Z_L_XL = 0x2C,
+        OUT_Z_H_XL = 0x2D,
+
+        // STATUS register
+        STATUS_REG = 0X1E
+    };
+};
+
+template <typename Bus>
+constexpr float LSM6DS3H<Bus>::accelFSMAP[];
+
+template <typename Bus>
+constexpr float LSM6DS3H<Bus>::gyroFSMAP[];
+
+#endif /* ifndef LSM6DS3H */
diff --git a/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h
new file mode 100644
index 0000000000000000000000000000000000000000..abd46b5293279a93ce95cc6c2060a86c4dab811e
--- /dev/null
+++ b/src/shared/sensors/old_examples/LSM6DS3H/LSM6DS3HData.h
@@ -0,0 +1,48 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Nuno Barcellos
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+#ifndef SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H
+#define SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H
+
+#include <ostream>
+#include "math/Vec3.h"
+
+struct LSM6DS3HData
+{
+    long long timestamp;
+    Vec3 accel;
+    Vec3 gyro;
+    float temp;
+
+    static std::string header()
+    {
+        return "timestamp,acc_x,acc_y,acc_z,gyro_x,gyro_y,gyro_z\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << accel.getX() << "," << accel.getY() << ","
+           << accel.getZ() << "," << gyro.getX() << "," << gyro.getY() << ","
+           << gyro.getZ() << "\n";
+    }
+};
+
+#endif /* SRC_SHARED_SENSORS_LSM6DS3H_LSM6DS3HDATA_H */
diff --git a/src/shared/sensors/MAX21105.h b/src/shared/sensors/old_examples/MAX21105.h
similarity index 97%
rename from src/shared/sensors/MAX21105.h
rename to src/shared/sensors/old_examples/MAX21105.h
index 3091e9beb56efc820d9b1b082f768ee5fc2a6e6f..21123002afd93f724e3ae439a94ed35955d94980 100644
--- a/src/shared/sensors/MAX21105.h
+++ b/src/shared/sensors/old_examples/MAX21105.h
@@ -1,7 +1,5 @@
-/* MAX21105 Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/MAX31856.h b/src/shared/sensors/old_examples/MAX31856.h
similarity index 97%
rename from src/shared/sensors/MAX31856.h
rename to src/shared/sensors/old_examples/MAX31856.h
index 24f5148844b169092de4c54b715afa0600122e33..49a15ae76356f78657b24055c7e8dd9938828f92 100644
--- a/src/shared/sensors/MAX31856.h
+++ b/src/shared/sensors/old_examples/MAX31856.h
@@ -1,7 +1,5 @@
-/* MAX31856 Driver
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/MPL3115.h b/src/shared/sensors/old_examples/MPL3115.h
similarity index 97%
rename from src/shared/sensors/MPL3115.h
rename to src/shared/sensors/old_examples/MPL3115.h
index acb1cd96219d63280b7ad19f10f43bf8159dcefd..00c545ff481cc63294e46a32ae100859381f9c17 100644
--- a/src/shared/sensors/MPL3115.h
+++ b/src/shared/sensors/old_examples/MPL3115.h
@@ -1,7 +1,5 @@
-/* Freescale MPL3115A barometer driver
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/Si7021.h b/src/shared/sensors/old_examples/Si7021.h
similarity index 96%
rename from src/shared/sensors/Si7021.h
rename to src/shared/sensors/old_examples/Si7021.h
index 01e3caef8284cfdb0fde19c244b47515632722d0..e0b43629a9cde9dc73470727cc3bf3536e3071d7 100644
--- a/src/shared/sensors/Si7021.h
+++ b/src/shared/sensors/old_examples/Si7021.h
@@ -1,7 +1,5 @@
-/* Si7021 Driver
- *
- * Copyright (c) 2016 Skyward Experimental Rocketry
- * Authors: Silvano Seva
+/* Copyright (c) 2016 Skyward Experimental Rocketry
+ * Author: Silvano Seva
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/sensors/iNemo.h b/src/shared/sensors/old_examples/iNemo.h
similarity index 97%
rename from src/shared/sensors/iNemo.h
rename to src/shared/sensors/old_examples/iNemo.h
index 6d02eaed06f8075027efe06dd0cc839af06dff35..dddd4155f491843e2000a7d908212b0031e3b218 100644
--- a/src/shared/sensors/iNemo.h
+++ b/src/shared/sensors/old_examples/iNemo.h
@@ -1,7 +1,5 @@
-/* ST iNEMO LSM9DS0 Driver
- *
- * Copyright (c) 2016-2017 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2016-2017 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/ButtonHandler.h b/src/shared/utils/ButtonHandler.h
index 7e19669afe78d6496b7e8c442b4badde33d05e86..31fca2d6e3451f4232f4413729e5b5758b9e8d3e 100644
--- a/src/shared/utils/ButtonHandler.h
+++ b/src/shared/utils/ButtonHandler.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -22,6 +22,8 @@
 
 #pragma once
 
+#include <diagnostic/PrintLogger.h>
+
 #include <functional>
 
 #include "ActiveObject.h"
@@ -83,20 +85,21 @@ protected:
             {
                 if (pressed_ticks >= VERY_LONG_PRESS_TICKS)
                 {
-                    TRACE("Button pressed (very long) (%d ticks).\n",
-                          pressed_ticks);
+                    LOG_DEBUG(logger, "Button pressed (very long) (%d ticks)",
+                              pressed_ticks);
 
                     callback(btn_id, ButtonPress::VERY_LONG);
                 }
                 else if (pressed_ticks >= LONG_PRESS_TICKS)
                 {
-                    TRACE("Button pressed (long) (%d ticks).\n", pressed_ticks);
+                    LOG_DEBUG(logger, "Button pressed (long) (%d ticks)",
+                              pressed_ticks);
                     callback(btn_id, ButtonPress::LONG);
                 }
                 else
                 {
-                    TRACE("Button pressed (short) (%d ticks).\n",
-                          pressed_ticks);
+                    LOG_DEBUG(logger, "Button pressed (short) (%d ticks)",
+                              pressed_ticks);
                     callback(btn_id, ButtonPress::SHORT);
                 }
 
@@ -116,4 +119,6 @@ private:
     int pressed_ticks = 0;
 
     ButtonCallback callback;
+
+    PrintLogger logger = Logging::getLogger("buttonhandler");
 };
\ No newline at end of file
diff --git a/src/shared/utils/PinObserver.h b/src/shared/utils/PinObserver.h
index e7fc197c7368d53510189ce5883203ad0fbfa51d..aaefb4cd4bccc3ebba70dc7f9c14df6e718e3228 100644
--- a/src/shared/utils/PinObserver.h
+++ b/src/shared/utils/PinObserver.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/aero/AeroUtils.cpp b/src/shared/utils/aero/AeroUtils.cpp
index c035c679744deab813ce53a1d1fb3a326ff92a4b..73bfa22cd84db1651c8c6a25ebe61cd37368f3d8 100644
--- a/src/shared/utils/aero/AeroUtils.cpp
+++ b/src/shared/utils/aero/AeroUtils.cpp
@@ -1,3 +1,26 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
 #include "AeroUtils.h"
 
 namespace aeroutils
@@ -10,6 +33,16 @@ float relAltitude(float pressure, float pressure_ref, float temperature_ref)
     return temperature_ref / a * (1 - powf(pressure / pressure_ref, n_inv));
 }
 
+float relDensity(float pressure, float pressure_ref, float altitude_ref,
+                 float temperature_ref)
+{
+    using namespace constants;
+
+    return pressure /
+           (R * a * altitude_ref +
+            R * temperature_ref * powf(pressure / pressure_ref, n_inv));
+}
+
 float mslPressure(float pressure_ref, float temperature_ref, float altitude_ref)
 {
     using namespace constants;
diff --git a/src/shared/utils/aero/AeroUtils.h b/src/shared/utils/aero/AeroUtils.h
index 6dff5dce00f9878c3453a7188d3ae99132f183e4..90378715ec5a704bf5a38cd4898e86f33db9b52d 100644
--- a/src/shared/utils/aero/AeroUtils.h
+++ b/src/shared/utils/aero/AeroUtils.h
@@ -1,8 +1,5 @@
-/**
- * Collection of functions for common aerospace calculations
- *
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -16,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -64,6 +61,19 @@ constexpr float n_inv = (R * a) / g;
  */
 float relAltitude(float pressure, float pressure_ref, float temperature_ref);
 
+/**
+ * Returns the current air density with respect to a reference density and
+ * temperature, using the Internation Standard Atmosphere model.
+ *
+ * @param pressure Current atmospheric pressure [Pascal]
+ * @param pressure_ref Pressure at reference altitude (must be > 0) [Pascal]
+ * @param altitude_ref Reference altitude [m]
+ * @param temperature_ref Temperature at reference altitude [Kelvin]
+ * @return Current air density  [Kg/m^3]
+ */
+float relDensity(float pressure, float pressure_ref, float altitude_ref,
+                 float temperature_ref);
+
 /**
  * Returns the expected pressure at mean sea level based on temperature and
  * pressure at a reference altitude, using International Standard Atmosphere
diff --git a/src/shared/utils/collections/CircularBuffer.h b/src/shared/utils/collections/CircularBuffer.h
index 49b88bf6de8cb535a05fe79fc96ef8ad594d11af..be08c9b30c612c07481bc068974f9c2332cd4b18 100644
--- a/src/shared/utils/collections/CircularBuffer.h
+++ b/src/shared/utils/collections/CircularBuffer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -47,7 +47,7 @@ public:
     virtual T& put(const T& elem)
     {
         buffer[write_ptr] = elem;
-        T& added = buffer[write_ptr];
+        T& added          = buffer[write_ptr];
 
         if (!empty && write_ptr == read_ptr)
         {
@@ -62,12 +62,15 @@ public:
 
     /**
      * Gets an element from the buffer, without removing it
-     * Index starts at the element returned by get() or pop(): get(0) is
-     * the same as get()
+     * Index starts from the oldest element in the buffer: get(0) returns the
+     * same element as get()
      *
      * @warning Remember to catch the exception!
+     * @throw range_error if index >= count()
+     *
+     * @param i Index of the elemnt to get, starting from the oldest
+     *
      * @return the element
-     * @throws range_error if buffer is empty
      */
     virtual T& get(unsigned int i)
     {
@@ -81,21 +84,19 @@ public:
     }
 
     /**
-     * @brief Returns the last element added in the buffer
+     * @brief Returns the last element added in the buffer.
+     *
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element
-     * @throws range_error if buffer is empty
      */
-    virtual T& last()
-    {
-        return get(count() - 1);
-    }
+    virtual T& last() { return get(count() - 1); }
 
     /**
      * Gets the first element from the buffer, without removing it
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element
-     * @throws range_error if buffer is empty
      */
     virtual T& get()
     {
@@ -109,9 +110,9 @@ public:
 
     /**
      * Pops the first element in the buffer.
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element that has been popped
-     * @throws range_error if buffer is empty
      */
     virtual const T& pop()
     {
@@ -148,7 +149,10 @@ public:
 
     virtual bool isEmpty() const { return empty; }
 
-    virtual bool isFull() const { return count() == Size; }
+    virtual bool isFull() const
+    {
+        return CircularBuffer<T, Size>::count() == Size;
+    }
     /**
      * Returns the maximum number of elements that can be stored in the buffer
      * @return buffer size
diff --git a/src/shared/utils/collections/SyncCircularBuffer.h b/src/shared/utils/collections/SyncCircularBuffer.h
index 1844d78007b40099dd3171550ac1e689329321b4..5d09f360453556425b77141736648bf634154303 100644
--- a/src/shared/utils/collections/SyncCircularBuffer.h
+++ b/src/shared/utils/collections/SyncCircularBuffer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/collections/SyncPacketQueue.h b/src/shared/utils/collections/SyncPacketQueue.h
index c9970c8304c7b94f8523df17cd45f91636cefba5..2c00c03f5dc75e1822252088a59c5cbd8cffbaf1 100644
--- a/src/shared/utils/collections/SyncPacketQueue.h
+++ b/src/shared/utils/collections/SyncPacketQueue.h
@@ -1,6 +1,4 @@
-/* Synchronized Packet Queue
- *
- * Copyright (c) 2019 Skyward Experimental Rocketry
+/* Copyright (c) 2019 Skyward Experimental Rocketry
  * Author: Alvise de'Faveri Tron
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -33,7 +31,6 @@
 
 #include "CircularBuffer.h"
 
-
 // This header can be compiled to run on a PC, for easier testing.
 #ifdef COMPILE_FOR_X86
 #warning The flag COMPILE_FOR_X86 is active! If this is flight code, shame on you
@@ -41,13 +38,13 @@
 #define MIOSIX_ONLY(x)
 #else
 #define MIOSIX_ONLY(x) x
-#include "miosix.h"
 #include "Debug.h"
+#include "miosix.h"
 
 using miosix::ConditionVariable;
-using std::range_error;
 using miosix::FastMutex;
 using miosix::Lock;
+using std::range_error;
 #endif
 
 /*******************************************************************************
@@ -266,16 +263,16 @@ public:
             {
                 // Mark the packet as ready (in the case it wasn't already)
                 last.markAsReady();
-                
-                if(buffer.isFull())
+
+                if (buffer.isFull())
                 {
                     // We have dropped a packet
                     ++dropped;
                 }
                 // Add a new packet and fill that instead
                 Pkt& newpkt = buffer.put(Pkt{});
-                
-                if(!newpkt.tryAppend(msg, msg_len))
+
+                if (!newpkt.tryAppend(msg, msg_len))
                 {
                     TRACE("Packet is too big!\n");
                     return -1;
diff --git a/src/shared/utils/collections/SyncQueue.h b/src/shared/utils/collections/SyncQueue.h
index 234300c1b2a13c8e58ab2b96f54254ba179b940b..1de11bd8c103d433dd737cc7f6a7ccfd4983230b 100644
--- a/src/shared/utils/collections/SyncQueue.h
+++ b/src/shared/utils/collections/SyncQueue.h
@@ -1,7 +1,5 @@
-/* FSM SynchronizedQueue
- *
- * Copyright (c) 2015-2016 Skyward Experimental Rocketry
- * Author: Matteo Michele Piazzolla, Alain Carlucci
+/* Copyright (c) 2015-2016 Skyward Experimental Rocketry
+ * Authors: Matteo Piazzolla, Alain Carlucci
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/drivers/piksi/contiguous_queue.h b/src/shared/utils/collections/contiguous_queue.h
similarity index 64%
rename from src/shared/drivers/piksi/contiguous_queue.h
rename to src/shared/utils/collections/contiguous_queue.h
index 1fa3587f438fd7a8fe56554cc9f3d6957ddc8a13..989beb7ddcaf028e57ce2bd37003e5504a1d4799 100644
--- a/src/shared/drivers/piksi/contiguous_queue.h
+++ b/src/shared/utils/collections/contiguous_queue.h
@@ -1,29 +1,25 @@
-/***************************************************************************
- *   Copyright (C) 2017 by Terraneo Federico                               *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   As a special exception, if other files instantiate templates or use   *
- *   macros or inline functions from this file, or you compile this file   *
- *   and link it with other works to produce a work based on this file,    *
- *   this file does not by itself cause the resulting work to be covered   *
- *   by the GNU General Public License. However the source code for this   *
- *   file must still be made available in accordance with the GNU General  *
- *   Public License. This exception does not invalidate any other reasons  *
- *   why a work based on this file might be covered by the GNU General     *
- *   Public License.                                                       *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
- ***************************************************************************/
+/* Copyright (c) 2017 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 
 #ifndef CONTIGUOUS_QUEUE
 #define CONTIGUOUS_QUEUE
diff --git a/src/shared/utils/gui/GridLayout.h b/src/shared/utils/gui/GridLayout.h
new file mode 100644
index 0000000000000000000000000000000000000000..27ae0c02237eeeedbe5a2aa0e0e2a866ef263980
--- /dev/null
+++ b/src/shared/utils/gui/GridLayout.h
@@ -0,0 +1,208 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/misc_inst.h>
+
+#include <map>
+
+#include "View.h"
+
+/**
+ * @brief Displays childs in a num_rows*num_cols grid
+ */
+class GridLayout : public View
+{
+public:
+    using GridPosition = std::pair<uint8_t, uint8_t>;
+
+    /**
+     * @brief Creates a new GridLayout
+     * 
+     * @param num_rows Number of rows
+     * @param num_cols Number of columns
+     * @param spacing Distance in pixels between each cell
+     */
+    GridLayout(uint8_t num_rows, uint8_t num_cols, short int spacing = 0)
+        : View(), num_rows(num_rows), num_cols(num_cols), spacing(spacing)
+    {
+    }
+
+    virtual ~GridLayout() {}
+
+    void setCell(View* child, unsigned int position)
+    {
+        uint8_t col = position % num_cols;
+        uint8_t row = (position - col) / num_cols;
+
+        setCell(child, row, col);
+    }
+
+    void setCell(View* child, uint8_t row, uint8_t col)
+    {
+        if (row >= num_rows || col >= num_cols)
+        {
+            return;
+        }
+
+        map_childs[GridPosition(row, col)] = child;
+        
+        updateChildBounds();
+        invalidate();
+    }
+
+    View* getCell(uint8_t row, uint8_t col)
+    {
+        GridPosition pos(row, col);
+
+        if (map_childs.count(pos) > 0)
+            return map_childs[pos];
+        else
+            return nullptr;
+    }
+
+    View* getCell(unsigned int position)
+    {
+        uint8_t col = position % num_cols;
+        uint8_t row = position - col / num_rows;
+
+        return getCell(row, col);
+    }
+
+    void clearCell(View* child)
+    {
+        for (auto it = map_childs.begin(); it != map_childs.end(); it++)
+        {
+            if (it->second == child)
+            {
+                map_childs.erase(it);
+                break;
+            }
+        }
+
+        invalidate();
+    }
+
+    /**
+     * @brief Wether to draw the borders of each cell or not
+     * 
+     * @param draw_border True: draw the border
+     * @param color Border color
+     */
+    void setDrawBorder(bool draw_border, mxgui::Color color = mxgui::white)
+    {
+        this->draw_border = draw_border;
+        border_color      = color;
+
+        invalidate();
+    }
+
+    void setBounds(Bounds bounds) override
+    {
+        View::setBounds(bounds);
+
+        updateChildBounds();
+    }
+
+    virtual void draw(mxgui::DrawingContext& context) override
+    {
+        View::draw(context);
+
+        for (uint8_t row = 0; row < num_rows; ++row)
+        {
+            for (uint8_t col = 0; col < num_cols; ++col)
+            {
+                GridPosition pos(row, col);
+
+                bool child_selected = false;
+                if (map_childs.count(pos) > 0)
+                {
+                    map_childs[pos]->draw(context);
+                    child_selected = map_childs[pos]->isSelected();
+                }
+
+                if (draw_border && !child_selected)
+                {
+                    context.drawRectangle(
+                        map_child_bounds[pos].topLeft(),
+                        map_child_bounds[pos].bottomRight(), border_color);
+                }
+            }
+        }   
+    }
+
+    uint8_t getRows() { return num_rows; }
+
+    uint8_t getCols() { return num_cols; }
+
+    std::vector<View*> getChilds() override
+    {
+        std::vector<View*> out;
+        for (auto it = map_childs.begin(); it != map_childs.end(); it++)
+        {
+            out.push_back(it->second);
+        }
+        return out;
+    }
+
+private:
+    void updateChildBounds()
+    {
+        Bounds bounds = getBounds();
+        Bounds child_bounds;
+
+        child_bounds.size.width = std::max(
+            0, (bounds.size.width - (num_cols + 1) * spacing) / num_cols);
+        child_bounds.size.height = std::max(
+            0, (bounds.size.height - (num_rows + 1) * spacing) / num_rows);
+
+        for (uint8_t row = 0; row < num_rows; ++row)
+        {
+            for (uint8_t col = 0; col < num_cols; ++col)
+            {
+                GridPosition pos(row, col);
+
+                child_bounds.pos.x = bounds.pos.x + (col + 1) * spacing +
+                                     col * child_bounds.size.width;
+                child_bounds.pos.y = bounds.pos.y + (row + 1) * spacing +
+                                     row * child_bounds.size.height;
+
+                map_child_bounds[pos] = child_bounds;
+                if (map_childs.count(pos) > 0)
+                {
+                    map_childs[pos]->setBounds(child_bounds);
+                }
+            }
+        }
+    }
+
+    uint8_t num_rows;
+    uint8_t num_cols;
+    short int spacing;
+
+    bool draw_border          = false;
+    mxgui::Color border_color = mxgui::white;
+
+    std::map<GridPosition, View*> map_childs;
+    std::map<GridPosition, Bounds> map_child_bounds;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/ImageView.h b/src/shared/utils/gui/ImageView.h
new file mode 100644
index 0000000000000000000000000000000000000000..4168ba748bb479bffb36ec11789150f4c0eb8eb8
--- /dev/null
+++ b/src/shared/utils/gui/ImageView.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/image.h>
+
+#include "View.h"
+
+/**
+ * @brief Simple view that displays an image
+ */
+class ImageView : public View
+{
+public:
+    ImageView(const mxgui::Image* image) : image(image) {}
+
+    void setImage(mxgui::Image* image)
+    {
+        this->image = image;
+        invalidate();
+    }
+
+    const mxgui::Image* getImage() { return image; }
+
+    void draw(mxgui::DrawingContext& dc) override
+    {
+        if (isInvalidated())
+        {
+            View::draw(dc);
+
+            dc.clippedDrawImage(getBounds().topLeft(), getBounds().topLeft(),
+                                getBounds().bottomRight(), *image);
+        }
+    }
+private:
+    const mxgui::Image* image;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/Misc.h b/src/shared/utils/gui/Misc.h
new file mode 100644
index 0000000000000000000000000000000000000000..62c97bde95bc0858e50d758ca8da2ff11c5c0e78
--- /dev/null
+++ b/src/shared/utils/gui/Misc.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+#include <mxgui/display.h>
+
+/**
+ * @brief Converts a 24bit color to a 16bit color
+ * 
+ * @param rgb 24 bit color
+ * @return Color Closest 16 bit color
+ */
+mxgui::Color color24to16(uint32_t rgb)
+{
+    uint8_t r, g, b;
+    r = ((rgb >> 2) & 0xFF) >> 3;
+    g = ((rgb >> 1) & 0xFF) >> 3;
+    b = (rgb & 0xFF) >> 3;
+    return ((b << 10) | (g << 5) | (r));
+}
\ No newline at end of file
diff --git a/src/shared/utils/gui/NavController.h b/src/shared/utils/gui/NavController.h
new file mode 100644
index 0000000000000000000000000000000000000000..3d097fdc8340bf83d19b53de19d9fc739c8ac55f
--- /dev/null
+++ b/src/shared/utils/gui/NavController.h
@@ -0,0 +1,151 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+
+#include <vector>
+
+#include "Debug.h"
+#include "TextView.h"
+#include "View.h"
+#include "utils/ButtonHandler.h"
+
+/**
+ * @brief UI navigation controller: listens for button clicks and dispatches the
+ * interactions to the view tree.
+ */
+class NavController
+{
+public:
+    NavController() {}
+
+    /**
+     * @brief
+     *
+     * @param root
+     */
+    void updateViewTree(View* root)
+    {
+        if (selected_index < vec_selectable.size())
+        {
+            vec_selectable.at(selected_index)->setSelected(false);
+        }
+
+        vec_selectable.clear();
+        selected_index = 0;
+
+        updateSelectableViews(root);
+
+        if (vec_selectable.size() > 0)
+        {
+            vec_selectable.at(0)->setSelected(true);
+        }
+    }
+
+    void onButtonPress(ButtonPress press)
+    {
+        switch (press)
+        {
+            case ButtonPress::SHORT:
+                if (vec_selectable.size() > 0)
+                {
+                    selectNext();
+                }
+                break;
+            case ButtonPress::DOWN:
+                if (selected_index < vec_selectable.size())
+                {
+                    vec_selectable.at(selected_index)
+                        ->performInteraction(Interaction::BTN_DOWN);
+                }
+                break;
+            case ButtonPress::UP:
+                if (selected_index < vec_selectable.size())
+                {
+                    vec_selectable.at(selected_index)
+                        ->performInteraction(Interaction::BTN_UP);
+                }
+                break;
+            case ButtonPress::LONG:
+                if (selected_index < vec_selectable.size())
+                {
+                    vec_selectable.at(selected_index)
+                        ->performInteraction(Interaction::CLICK);
+                }
+                break;
+            case ButtonPress::VERY_LONG:
+                if (selected_index < vec_selectable.size())
+                {
+                    vec_selectable.at(selected_index)
+                        ->performInteraction(Interaction::LONG_CLICK);
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+private:
+    void selectNext()
+    {
+        // Deselect old drawble
+        if (selected_index < vec_selectable.size())
+        {
+            vec_selectable.at(selected_index)->setSelected(false);
+        }
+
+        if (vec_selectable.size() > 0)
+        {
+            selected_index = (selected_index + 1) % vec_selectable.size();
+
+            vec_selectable.at(selected_index)->setSelected(true);
+
+            TextView* text =
+                dynamic_cast<TextView*>(vec_selectable.at(selected_index));
+
+            if (text)
+            {
+                LOG_DEBUG(logger, "{}", text->getText().c_str());
+            }
+        }
+    }
+
+    void updateSelectableViews(View* root)
+    {
+        std::vector<View*> childs = root->getChilds();
+        for (auto child : childs)
+        {
+            if (child->isSelectable())
+            {
+                vec_selectable.push_back(child);
+            }
+            updateSelectableViews(child);
+        }
+    }
+
+    unsigned int selected_index = 0;
+    std::vector<View*> vec_selectable;
+
+    PrintLogger logger = Logging::getLogger("navcontroller");
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/OptionView.h b/src/shared/utils/gui/OptionView.h
new file mode 100644
index 0000000000000000000000000000000000000000..a10aad93ff1bad27d7b0cf6cdf41d950ef1e5c9c
--- /dev/null
+++ b/src/shared/utils/gui/OptionView.h
@@ -0,0 +1,169 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "GridLayout.h"
+#include "TextView.h"
+
+/**
+ * @brief View used to display an option list, so the user can select one by
+ * clicking on it.
+ */
+class OptionView : public View
+{
+public:
+    using OnOptionChosenListener = std::function<void(unsigned int id)>;
+
+    OptionView(std::string name, std::map<uint8_t, std::string> options,
+               uint8_t default_option = 0, uint8_t num_cols = 4)
+        : tv_title(new TextView(name))
+    {
+        uint8_t num_rows = options.size() / num_cols;
+        if (options.size() % num_cols != 0)
+        {
+            ++num_rows;
+        }
+
+        grid_options = new GridLayout(num_rows, num_cols);
+
+        unsigned int grid_pos = 0;
+        unsigned int i        = 0;
+        for (auto it = options.begin(); it != options.end(); it++)
+        {
+            using namespace std::placeholders;
+
+            TextView* tv_opt = new TextView(it->second);
+            tv_opt->setSelectable(true);
+            tv_opt->addOnInteractionListener(
+                std::bind(&OptionView::onOptionClick, this, _1, _2));
+            tv_opt->setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+
+            map_options[tv_opt] = it->first;
+
+            grid_options->setCell(tv_opt, grid_pos++);
+
+            if (i == default_option)
+            {
+                selectOption(tv_opt);
+            }
+
+            ++i;
+        }
+    }
+
+    virtual ~OptionView()
+    {
+        delete grid_options;
+        delete tv_title;
+        for (auto it = map_options.begin(); it != map_options.end(); it++)
+        {
+            delete it->first;
+        }
+    }
+
+    void setBounds(Bounds bounds) override
+    {
+        View::setBounds(bounds);
+
+        Bounds title_bounds      = getBounds();
+        title_bounds.size.height = tv_title->getFont().getHeight();
+        tv_title->setBounds(title_bounds);
+
+        Bounds grid_bounds = getBounds();
+        grid_bounds.pos.y  = getBounds().pos.y + title_bounds.size.height + 1;
+        grid_bounds.size.height =
+            getBounds().size.height - title_bounds.size.height;
+
+        grid_options->setBounds(grid_bounds);
+    }
+
+    /**
+     * @brief Adds a callback to be called each time an option is selected by
+     * the user
+     */
+    void addOnOptionChosenListener(OnOptionChosenListener listener)
+    {
+        opt_listeners.push_back(listener);
+    }
+
+    std::vector<View*> getChilds() override
+    {
+        std::vector<View*> out;
+        out.push_back(tv_title);
+        out.push_back(grid_options);
+
+        return out;
+    }
+
+    void draw(mxgui::DrawingContext& dc) override
+    {
+        View::draw(dc);
+        tv_title->draw(dc);
+        grid_options->draw(dc);
+    }
+
+private:
+    void selectOption(TextView* opt)
+    {
+        if (selected_option != nullptr)
+        {
+            selected_option->setBackgroundColor(col_bg_normal);
+        }
+
+        selected_option = opt;
+
+        selected_option->setBackgroundColor(col_bg_highlight);
+    }
+
+    void onOptionClick(View* option, Interaction action)
+    {
+        if (action == Interaction::CLICK)
+        {
+            TextView* textview = static_cast<TextView*>(option);
+
+            selectOption(textview);
+
+            for (auto l : opt_listeners)
+            {
+                if (l != nullptr)
+                {
+                    l(map_options[textview]);
+                }
+            }
+        }
+    }
+
+    TextView* tv_title;
+    mxgui::Color col_bg_normal    = mxgui::black;
+    mxgui::Color col_bg_highlight = mxgui::blue;
+
+    GridLayout* grid_options;
+    std::map<TextView*, uint8_t> map_options;
+
+    std::vector<OnOptionChosenListener> opt_listeners;
+
+    TextView* selected_option = nullptr;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/ScreenManager.h b/src/shared/utils/gui/ScreenManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..2738f12cb0abec4e95b76b410d55751a005954d5
--- /dev/null
+++ b/src/shared/utils/gui/ScreenManager.h
@@ -0,0 +1,124 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <miosix.h>
+
+#include <map>
+#include <deque>
+
+#include "NavController.h"
+#include "View.h"
+
+/**
+ * @brief UI Thread: Manages multiple view trees ("Screen") and draws the active
+ * one at the provided refresh rate
+ */
+class ScreenManager : public ActiveObject
+{
+public:
+    ScreenManager(mxgui::DisplayManager& display, unsigned int refresh_rate)
+        : dc(display.getDisplay()), refresh_interval(1000 / refresh_rate)
+    {
+    }
+
+    void showScreen(uint8_t id)
+    {
+        active_screen = id;
+        controller.updateViewTree(screens[id]);
+    }
+
+    uint8_t getScreen() { return active_screen; }
+
+    void addScreen(uint8_t id, View* root)
+    {
+        screens[id] = root;
+
+        root->setBounds({{0, 0}, {dc.getWidth(), dc.getHeight()}});
+
+        if (screens.size() == 1)
+        {
+            showScreen(id);
+        }
+    }
+
+    void onButtonPress(ButtonPress press) { controller.onButtonPress(press); }
+
+    mxgui::DrawingContext& getDrawingContext() { return dc; }
+
+protected:
+    void run() override
+    {
+        uint8_t last_screen = 0;
+        while (!shouldStop())
+        {
+            if (active_screen != last_screen)
+            {
+                last_screen = active_screen;
+                dc.clear(mxgui::black);
+                screens[active_screen]->invalidateTree();
+            }
+
+            long long start = miosix::getTick();
+
+            drawViewTree(screens[active_screen], dc);
+
+            Thread::sleepUntil(start + refresh_interval);
+        }
+    }
+private:
+    /**
+     * @brief Draws the provided view tree on screen
+     * 
+     * @param root Root of the view tree
+     * @param dc Drawing context
+     */
+    void drawViewTree(View* root, mxgui::DrawingContext& dc)
+    {
+        // Avoid recursion
+        std::deque<View*> views_dc;
+        views_dc.push_back(root);
+
+        while(views_dc.size() != 0)
+        {
+            View* view = views_dc.front();
+            views_dc.pop_front();
+
+            view->draw(dc);
+
+            for(View* c : view->getChilds())
+            {
+                views_dc.push_back(c);
+            }
+        }
+    }
+
+    mxgui::DrawingContext dc;
+
+    unsigned int refresh_interval;
+
+    NavController controller;
+    std::map<uint8_t, View*> screens;
+
+    uint8_t active_screen = 0;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/TextView.h b/src/shared/utils/gui/TextView.h
new file mode 100644
index 0000000000000000000000000000000000000000..f487d191c040ea55727db223a0399a8deff8ee8e
--- /dev/null
+++ b/src/shared/utils/gui/TextView.h
@@ -0,0 +1,180 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/misc_inst.h>
+
+#include "View.h"
+
+/**
+ * @brief Simple view to display text on screen.
+ *
+ */
+class TextView : public View
+{
+public:
+    /**
+     * @brief Creates a new TextView
+     *
+     * @param text Text
+     * @param text_color Text color
+     * @param padding Spacing from view bounds in pixels
+     */
+    TextView(std::string text = "", mxgui::Color text_color = mxgui::white,
+             uint8_t padding = 1)
+        : View(), text(text), col_text(text_color), padding(padding)
+    {
+    }
+
+    void setVerticalAlignment(VertAlignment alignment)
+    {
+        vert_align = alignment;
+        invalidate();
+    }
+
+    void setHorizontalAlignment(HorizAlignment alignment)
+    {
+        horiz_align = alignment;
+        invalidate();
+    }
+
+    void setAlignment(HorizAlignment horiz_align, VertAlignment vert_align)
+    {
+        setVerticalAlignment(vert_align);
+        setHorizontalAlignment(horiz_align);
+    }
+
+    void setText(std::string text)
+    {
+        if (text != this->text)
+        {
+            invalidate();
+            this->text = text;
+        }
+    }
+
+    std::string getText() { return text; }
+
+    virtual void setTextColor(mxgui::Color color)
+    {
+        if (color != col_text)
+        {
+            this->col_text = color;
+            invalidate();
+        }
+    }
+
+    mxgui::Color getTextColor() { return col_text; }
+
+    virtual void draw(mxgui::DrawingContext& dc) override
+    {
+        if (isInvalidated())
+        {
+            View::draw(dc);
+
+            Bounds padded_bounds = getBounds();
+            padded_bounds.pos += Position{padding, padding};
+            padded_bounds.size.height -= padding;
+            padded_bounds.size.width -= padding;
+
+            Position top_left = padded_bounds.topLeft();
+            switch (vert_align)
+            {
+                case VertAlignment::BOTTOM:
+                    top_left.y =
+                        padded_bounds.bottomLeft().y - dc.getFont().getHeight();
+                    break;
+                case VertAlignment::CENTER:
+                    top_left.y =
+                        padded_bounds.pos.y +
+                        (padded_bounds.size.height - dc.getFont().getHeight()) /
+                            2;
+                    break;
+                case VertAlignment::TOP:
+                    break;
+            }
+
+            switch (horiz_align)
+            {
+                case HorizAlignment::RIGHT:
+                    top_left.x =
+                        padded_bounds.topRight().x - getTextWidth(text);
+                    break;
+                case HorizAlignment::CENTER:
+                    top_left.x =
+                        padded_bounds.pos.x +
+                        (padded_bounds.size.width - getTextWidth(text)) / 2;
+                    break;
+                case HorizAlignment::LEFT:
+                    break;
+            }
+
+            mxgui::Font old_font = dc.getFont();
+            dc.setFont(font);
+
+            dc.setTextColor(getTextColor(), getBackgroundColor());
+            dc.clippedWrite(top_left, padded_bounds.topLeft(),
+                            padded_bounds.bottomRight(), text);
+
+            dc.setFont(old_font);
+        }
+    }
+
+    void setFont(mxgui::Font font)
+    {
+        this->font = font;
+        invalidate();
+    }
+
+    mxgui::Font getFont() { return font; }
+
+protected:
+    short int getTextWidth(std::string text)
+    {
+        short int w = 0;
+        for (char c : text)
+        {
+            if (c >= font.getStartChar() && c <= font.getEndChar())
+            {
+                if (font.isFixedWidth())
+                {
+                    w += font.getWidth();
+                }
+                else
+                {
+                    w += font.getWidths()[c - font.getStartChar()];
+                }
+            }
+        }
+        return w;
+    }
+
+private:
+    mxgui::Font font           = mxgui::tahoma;
+    VertAlignment vert_align   = VertAlignment::TOP;
+    HorizAlignment horiz_align = HorizAlignment::LEFT;
+    std::string text;
+
+    mxgui::Color col_text = mxgui::white;
+    uint8_t padding;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/VerticalLayout.h b/src/shared/utils/gui/VerticalLayout.h
new file mode 100644
index 0000000000000000000000000000000000000000..d8da0215f74dc25a3ecea15e7e290d53e9ce58ed
--- /dev/null
+++ b/src/shared/utils/gui/VerticalLayout.h
@@ -0,0 +1,123 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include "View.h"
+
+/**
+ * @brief Positions the childs in a vertical grid. The height of each child is
+ * dictated by its weight parameter
+ */
+class VerticalLayout : public View
+{
+public:
+    /**
+     * @brief Creates a new VerticalLayout
+     *
+     * @param spacing Vertical space between each child in pixels
+     */
+    VerticalLayout(short int spacing = 0) : View(), spacing(spacing) {}
+
+    /**
+     * @brief Adds a view to the layout.
+     *
+     * @param view Pointer to the view to be added
+     * @param weight Relative weight of the view to determine its display height
+     */
+    void addView(View* view, float weight)
+    {
+        if (weight < 0)
+            weight = 0;
+        childs.push_back({weight, view});
+        weigth_sum += weight;
+
+        // Recalculated child bounds
+        updateChildBounds();
+
+        // Redraw
+        invalidate();
+    }
+
+    void setBounds(Bounds bounds) override
+    {
+        View::setBounds(bounds);
+
+        updateChildBounds();
+    }
+
+    void draw(mxgui::DrawingContext& dc) override
+    {
+        View::draw(dc);
+
+        for (auto view : childs)
+        {
+            view.drawable->draw(dc);
+        }
+    }
+
+    virtual std::vector<View*> getChilds() override
+    {
+        std::vector<View*> out;
+        out.reserve(childs.size());
+        for (auto v : childs)
+        {
+            out.push_back(v.drawable);
+        }
+        return out;
+    }
+
+private:
+    void updateChildBounds()
+    {
+        if (weigth_sum > 0)
+        {
+            short int aval_height =
+                getBounds().size.height - (childs.size() - 1) * spacing;
+
+            short int y = 0;
+
+            for (auto view : childs)
+            {
+                Bounds view_bounds{{0, y}, {getBounds().size.width, 0}};
+
+                view_bounds.size.height =
+                    aval_height / weigth_sum * view.vert_weight;
+                view_bounds.pos += getBounds().pos;
+
+                view.drawable->setBounds(view_bounds);
+
+                y += view_bounds.size.height + spacing;
+            }
+        }
+    }
+
+    struct Child
+    {
+        float vert_weight;
+        View* drawable;
+    };
+
+    float weigth_sum = 0;
+    short int spacing;
+    std::vector<Child> childs;
+};
\ No newline at end of file
diff --git a/src/shared/utils/gui/View.h b/src/shared/utils/gui/View.h
new file mode 100644
index 0000000000000000000000000000000000000000..230f318f40b72a6dfc32f452c634837990d1fdb3
--- /dev/null
+++ b/src/shared/utils/gui/View.h
@@ -0,0 +1,260 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/display.h>
+#include <mxgui/misc_inst.h>
+
+#include <functional>
+
+#include "Misc.h"
+
+struct Size
+{
+    short int width;
+    short int height;
+};
+
+struct Position
+{
+    short int x;
+    short int y;
+
+    operator mxgui::Point() { return mxgui::Point(x, y); }
+
+    Position operator+(const Position& other)
+    {
+        return {short(x + other.x), short(y + other.y)};
+    }
+
+    Position operator-(const Position& other)
+    {
+        return {short(x - other.x), short(y - other.y)};
+    }
+
+    void operator+=(const Position& other)
+    {
+        x += other.x;
+        y += other.y;
+    }
+
+    void operator-=(const Position& other)
+    {
+        x -= other.x;
+        y -= other.y;
+    }
+
+    void print() { printf("pos: %d %d\n", x, y); }
+};
+
+struct Bounds
+{
+    Position pos;
+    Size size;
+
+    Position topLeft() { return {pos.x, pos.y}; }
+    Position topRight() { return {short(pos.x + size.width - 1), pos.y}; }
+
+    Position bottomRight()
+    {
+        return {short(pos.x + size.width - 1), short(pos.y + size.height - 1)};
+    }
+
+    Position bottomLeft()
+    {
+        return {pos.x, short(pos.y + short(size.height - 1))};
+    }
+
+    void print()
+    {
+        printf("Bounds: p:{%d %d} s:{%d %d}\n", pos.x, pos.y, size.width,
+               size.height);
+    }
+};
+
+enum class VertAlignment
+{
+    TOP,
+    CENTER,
+    BOTTOM
+};
+
+enum class HorizAlignment
+{
+    LEFT,
+    CENTER,
+    RIGHT
+};
+
+enum class Interaction
+{
+    BTN_DOWN,
+    BTN_UP,
+    CLICK,
+    LONG_CLICK
+};
+
+/**
+ * @brief Base class for anything that can be drawn on the screen and interacted
+ * with
+ */
+class View
+{
+public:
+    using OnInteractionListener = std::function<void(View*, Interaction)>;
+
+    View() {}
+    virtual ~View() {}
+
+    /**
+     * @brief Sets the bounds in which the view will be drawn
+     */
+    virtual void setBounds(Bounds bounds)
+    {
+        this->bounds = bounds;
+        invalidate();
+    }
+
+    Bounds getBounds() { return bounds; }
+
+    /**
+     * @brief Signal that what has been previously drawn is now invalid and has
+     * to be redrawn.
+     */
+    void invalidate()
+    {
+        if (!invalidated)
+        {
+            invalidated = true;
+        }
+    }
+
+    /**
+     * @brief Invalidate the view tree which has this view as a root
+     */
+    void invalidateTree()
+    {
+        invalidate();
+
+        std::vector<View*> childs = getChilds();
+        for (auto child : childs)
+        {
+            child->invalidateTree();
+        }
+    }
+
+    /**
+     * @brief Draw the view in its bounds
+     *
+     * @param dc Reference to a drawingcontext
+     */
+    virtual void draw(mxgui::DrawingContext& dc)
+    {
+        if (invalidated)
+        {
+            invalidated = false;
+
+            dc.clear(bounds.topLeft(), bounds.bottomRight(),
+                     getBackgroundColor());
+
+            dc.drawRectangle(bounds.topLeft(), bounds.bottomRight(),
+                             selected ? col_selected : getBackgroundColor());
+        }
+    }
+
+    /**
+     * @brief Returns the list of childs
+     *
+     * @return std::vector<View*>
+     */
+    virtual std::vector<View*> getChilds()
+    {
+        // Default behavious is no childs
+        return {};
+    }
+
+    virtual void setBackgroundColor(mxgui::Color color)
+    {
+        if (color != col_bg)
+        {
+            col_bg = color;
+            invalidate();
+        }
+    }
+
+    virtual mxgui::Color getBackgroundColor() { return col_bg; }
+
+    void setSelectedColor(mxgui::Color color)
+    {
+        col_selected = color;
+        invalidate();
+    }
+
+    void setSelectable(bool selectable) { this->selectable = selectable; }
+
+    bool isSelectable() { return selectable; }
+
+    void setSelected(bool selected)
+    {
+        this->selected = selected;
+        invalidate();
+    }
+
+    bool isSelected() { return selected && selectable; }
+
+    void addOnInteractionListener(OnInteractionListener listener)
+    {
+        listeners.push_back(listener);
+    }
+
+    virtual void performInteraction(Interaction action)
+    {
+        for (auto lst : listeners)
+        {
+            if (lst)
+            {
+                lst(this, action);
+            }
+        }
+    }
+
+protected:
+    bool isInvalidated() { return invalidated; }
+
+private:
+    mxgui::Color col_selected = mxgui::red;
+    mxgui::Color col_bg       = mxgui::black;
+
+    std::vector<OnInteractionListener> listeners;
+
+    bool selected    = false;
+    bool selectable  = false;
+    bool invalidated = true;
+
+    Bounds bounds{{0, 0}, {0, 0}};
+
+    // Drawing big solid chunks (such as backgrounds) takes a lot of time: do
+    // not redraw if not necessary
+    bool was_selected          = false;
+    mxgui::Color last_bg_color = col_bg;
+};
diff --git a/src/shared/utils/testutils/BusTemplateMock.h b/src/shared/utils/testutils/BusTemplateMock.h
index 288d8f3499abe737df60a2a87ff5f5e121a970ed..b8e4d267f1f3127534852a2359065230eefeb1b4 100644
--- a/src/shared/utils/testutils/BusTemplateMock.h
+++ b/src/shared/utils/testutils/BusTemplateMock.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/testutils/MockGpioPin.h b/src/shared/utils/testutils/MockGpioPin.h
new file mode 100644
index 0000000000000000000000000000000000000000..ed8b0a38013097bf90500aaed868f052001531c0
--- /dev/null
+++ b/src/shared/utils/testutils/MockGpioPin.h
@@ -0,0 +1,51 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+#pragma once
+
+#include <miosix.h>
+
+#include <memory>
+
+class MockGpioPin
+{
+public:
+    MockGpioPin()
+        : val(new int(0)),
+          gpio_mode(new miosix::Mode::Mode_(miosix::Mode::OUTPUT))
+    {
+    }
+
+    void mode(miosix::Mode::Mode_ m) { *gpio_mode = m; }
+
+    void high() { *val = 1; }
+
+    void low() { *val = 0; }
+
+    int value() { return *val; }
+
+    // shared_ptr: make all copies of this instance share the same val /
+    // gpio_mode
+    std::shared_ptr<int> val;
+    std::shared_ptr<miosix::Mode::Mode_> gpio_mode;
+};
\ No newline at end of file
diff --git a/src/shared/utils/testutils/TestHelper.cpp b/src/shared/utils/testutils/TestHelper.cpp
index 24bd22bb7f1ba3780b64f1d8a04ae2401a05b0d3..adb843e5e8969e775756c23f205891e7de330b13 100644
--- a/src/shared/utils/testutils/TestHelper.cpp
+++ b/src/shared/utils/testutils/TestHelper.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/testutils/TestHelper.h b/src/shared/utils/testutils/TestHelper.h
index c8c6a652884babbe1e82874ea1f43a803cb1f994..61612fecfaa0fbda5a8abcf134ec2e1110a6184f 100644
--- a/src/shared/utils/testutils/TestHelper.h
+++ b/src/shared/utils/testutils/TestHelper.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/shared/utils/testutils/TestSensor.h b/src/shared/utils/testutils/TestSensor.h
new file mode 100755
index 0000000000000000000000000000000000000000..e1f4dbe997e23f90812bf3d17090f2267bb4ee7a
--- /dev/null
+++ b/src/shared/utils/testutils/TestSensor.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2015-2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cmath>
+
+#include "Common.h"
+#include "sensors/Sensor.h"
+
+using miosix::getTick;
+using miosix::TICK_FREQ;
+
+struct TestData : public TimestampData
+{
+    float value;
+
+    TestData(float v) : TimestampData{static_cast<uint64_t>(getTick())}, value(v) {}
+
+    TestData() : TimestampData{static_cast<uint64_t>(getTick())}, value(0.0) {}
+};
+
+class TestSensor : public Sensor<TestData>
+{
+public:
+    TestSensor() {}
+    ~TestSensor() {}
+
+    bool init() override { return true; }
+
+    bool selfTest() override { return true; }
+
+    TestData sampleImpl() override
+    {
+        TRACE("[TestSensor] sampleImpl() \n");
+        return TestData(10 * sin(PI * static_cast<float>(getTick()) /
+                                    static_cast<float>(TICK_FREQ)));
+    }
+};
diff --git a/src/shared/utils/testutils/ThroughputCalculator.h b/src/shared/utils/testutils/ThroughputCalculator.h
new file mode 100644
index 0000000000000000000000000000000000000000..009ff7e4e13a1ab73bc238388e2822f307934009
--- /dev/null
+++ b/src/shared/utils/testutils/ThroughputCalculator.h
@@ -0,0 +1,175 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <miosix.h>
+
+#include <deque>
+#include <limits>
+
+using miosix::FastMutex;
+using miosix::Lock;
+using std::deque;
+
+struct DataRateResult
+{
+    float data_rate;
+    float packet_loss;
+    float packets_per_second;
+};
+
+/**
+ * Helper class for calculating the throughput of communications. Just
+ * pass the size of a packet to addPacket(...) as soon as the packet is
+ * received.
+ */
+class ThroughputCalculator
+{
+public:
+    /**
+     * @brief Creates a new ThroughputCalculator
+     *
+     * @param expected_pkt_interval Expected packet delivery interval for packet
+     * loss estimation
+     * @param window_duration Duration of the observation window. Longer windows
+     * means more stable values but slow response to abrupt changes
+     */
+    ThroughputCalculator(unsigned int expected_pkt_interval,
+                         unsigned int window_duration = 2000)
+        : expected_pkt_interval(expected_pkt_interval),
+          window_duration(window_duration)
+    {
+    }
+
+    /**
+     * @brief Signal that a new packet has arrived. Call this as soon as the
+     * packet is available
+     *
+     * @param packet_size The size of the packet
+     */
+    void addPacket(size_t packet_size)
+    {
+        Lock<FastMutex> lock(mutex_pkt);
+
+        long long ts = miosix::getTick();
+
+        unsigned int interval = 0;
+        if (packets.size() > 0)
+        {
+            interval = static_cast<unsigned int>(ts - packets.back().timestamp);
+        }
+
+        packets.push_back({ts, packet_size, interval});
+
+        removeOldPackets(ts);
+    }
+
+    /**
+     * @brief Returns the datarate in Bytes/second averaged over the duration of
+     * the window
+     */
+    float getDataRate()
+    {
+        Lock<FastMutex> lock(mutex_pkt);
+
+        long long ts = miosix::getTick();
+        removeOldPackets(ts);
+
+        float sum = 0;
+        for (size_t i = 0; i < packets.size(); i++)
+        {
+            sum += packets[i].size;
+        }
+        return sum / (window_duration / 1000.0f);
+    }
+
+    /**
+     * @brief Returns the packet loss percentage ([0-1]) based on the expected packet
+     * delivery interval 
+     */
+    float getPacketLoss()
+    {
+        Lock<FastMutex> lock(mutex_pkt);
+        long long ts = miosix::getTick();
+        removeOldPackets(ts);
+
+        float avg_interval = std::numeric_limits<float>::infinity();
+        if (packets.size() > 0)
+        {
+            avg_interval = packets[0].interval;
+            for (size_t i = 1; i < packets.size(); i++)
+            {
+                avg_interval += packets[i].interval;
+            }
+
+            avg_interval /= packets.size();
+        }
+
+        return 1 - expected_pkt_interval / avg_interval;
+    }
+
+    /**
+     * @brief Returns the pps value averaged over the duration of the window
+     */
+    float getPacketsPerSecond()
+    {
+        Lock<FastMutex> lock(mutex_pkt);
+        long long ts = miosix::getTick();
+        removeOldPackets(ts);
+
+        return (float)packets.size() / (window_duration / 1000.0f);
+    }
+
+    DataRateResult getResult()
+    {
+        DataRateResult r;
+        r.data_rate          = getDataRate();
+        r.packet_loss        = getPacketLoss();
+        r.packets_per_second = getPacketsPerSecond();
+
+        return r;
+    }
+
+private:
+    struct Packet
+    {
+        long long timestamp;
+        size_t size;
+        unsigned int interval;
+    };
+
+    void removeOldPackets(long long ts)
+    {
+        while (packets.size() > 0 &&
+               packets.front().timestamp < ts - window_duration)
+        {
+            packets.pop_front();
+        }
+    }
+
+    unsigned int expected_pkt_interval;
+    unsigned int window_duration;
+    deque<Packet> packets;
+
+    FastMutex mutex_pkt;
+};
\ No newline at end of file
diff --git a/src/tests/calibration/accelerometer-data.h b/src/tests/calibration/accelerometer-data.h
new file mode 100644
index 0000000000000000000000000000000000000000..7362152b2ce8b540aa605c999bf7c05fcf6679e2
--- /dev/null
+++ b/src/tests/calibration/accelerometer-data.h
@@ -0,0 +1,289 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * This data was obtained from the accelerometer LIS3DSH on March 2021
+ * on the 6 different orientation
+ */
+
+#pragma once
+
+#include "sensors/SensorData.h"
+#include "sensors/calibration/Calibration.h"
+
+namespace accData
+{
+constexpr unsigned nOrientations = 6;
+constexpr unsigned nSamples      = 60;
+
+AxisOrthoOrientation orientations[nOrientations]{
+    {Direction::POSITIVE_X, Direction::POSITIVE_Y},
+    {Direction::POSITIVE_Y, Direction::POSITIVE_Z},
+    {Direction::POSITIVE_Z, Direction::POSITIVE_X},
+    {Direction::POSITIVE_Y, Direction::POSITIVE_X},
+    {Direction::POSITIVE_Z, Direction::POSITIVE_Y},
+    {Direction::POSITIVE_X, Direction::POSITIVE_Z},
+};
+
+AccelerometerData samples[nOrientations][nSamples] = {
+
+    /* --------------------------  1  ------------------------ */
+
+    {
+        {0, -0.009180, 0.013140, 0.998820},
+        {0, -0.009720, 0.007560, 0.991440},
+        {0, -0.011700, 0.011340, 0.990720},
+        {0, -0.020520, 0.027000, 0.990000},
+        {0, -0.009540, 0.010080, 0.990360},
+        {0, -0.008460, 0.005760, 0.995220},
+        {0, -0.016920, 0.019440, 0.995580},
+        {0, -0.015480, 0.007560, 0.992520},
+        {0, -0.009180, 0.003060, 0.994320},
+        {0, -0.010620, 0.007920, 0.994860},
+        {0, -0.011520, 0.011880, 0.998280},
+        {0, -0.011520, 0.001620, 0.998640},
+        {0, 0.003780, 0.030600, 0.992340},
+        {0, -0.008640, 0.003420, 0.992160},
+        {0, -0.012240, 0.014220, 0.995220},
+        {0, -0.008100, 0.007020, 0.997380},
+        {0, -0.006480, 0.009540, 0.993960},
+        {0, -0.017460, 0.005760, 0.992520},
+        {0, -0.000360, -0.005220, 1.000260},
+        {0, -0.019260, 0.010080, 0.996480},
+        {0, -0.011520, 0.008820, 0.995040},
+        {0, -0.014040, 0.013680, 0.991980},
+        {0, -0.009180, 0.000000, 1.002420},
+        {0, -0.004320, 0.000900, 0.997560},
+        {0, -0.018180, 0.010260, 0.999000},
+        {0, -0.003420, 0.002700, 0.997740},
+        {0, -0.015660, 0.015660, 0.996300},
+        {0, -0.007920, 0.003060, 0.996300},
+        {0, -0.017820, 0.013860, 0.993060},
+        {0, -0.023040, 0.018900, 0.995040},
+        {0, -0.004320, 0.001620, 0.998100},
+        {0, -0.008280, 0.006120, 0.995580},
+        {0, -0.012240, 0.008100, 0.996300},
+        {0, -0.004860, 0.008820, 0.996480},
+        {0, -0.005400, 0.005760, 0.999360},
+        {0, -0.008460, 0.007740, 0.997200},
+        {0, -0.011160, 0.010080, 0.996840},
+        {0, -0.006840, 0.005220, 0.994860},
+        {0, -0.012600, 0.008100, 0.994500},
+        {0, -0.002520, -0.006840, 0.994680},
+        {0, -0.016200, 0.010440, 0.997020},
+        {0, -0.009720, 0.008640, 0.992880},
+        {0, -0.017280, 0.012960, 1.000080},
+        {0, -0.010080, 0.005220, 0.995940},
+        {0, -0.012240, 0.010800, 0.999540},
+        {0, -0.012780, 0.012600, 0.996840},
+        {0, -0.012960, 0.012420, 1.001340},
+        {0, -0.011700, 0.015480, 0.998280},
+        {0, -0.009180, 0.007740, 0.995760},
+        {0, -0.008820, 0.020520, 0.995040},
+        {0, -0.014760, 0.012780, 0.996840},
+        {0, -0.009900, 0.009540, 0.991620},
+        {0, -0.014220, 0.011520, 0.995400},
+        {0, -0.003060, 0.011700, 0.992160},
+        {0, -0.015480, 0.006660, 0.995940},
+        {0, -0.017820, 0.005220, 0.998280},
+        {0, -0.014040, 0.009540, 0.995580},
+        {0, -0.012060, 0.006840, 0.999900},
+        {0, -0.007740, 0.004680, 0.994860},
+        {0, -0.012420, 0.011700, 0.991260},
+    },
+
+    /* --------------------------  2  ------------------------ */
+
+    {
+        {0, 0.003780, 1.038960, 0.043020},  {0, 0.011160, 1.036800, 0.026820},
+        {0, 0.005220, 1.038960, 0.028980},  {0, 0.005040, 1.036260, 0.007020},
+        {0, 0.012600, 1.040940, 0.021780},  {0, 0.005400, 1.042920, 0.002700},
+        {0, 0.020520, 1.039140, 0.010440},  {0, 0.007920, 1.041300, -0.001440},
+        {0, 0.006120, 1.036260, 0.012600},  {0, 0.002520, 1.041120, 0.027900},
+        {0, 0.009540, 1.041840, 0.021420},  {0, 0.005580, 1.037520, 0.011520},
+        {0, -0.041940, 1.025100, 0.018720}, {0, 0.005040, 1.040580, 0.022140},
+        {0, 0.010980, 1.036440, 0.014400},  {0, 0.007200, 1.040040, 0.018720},
+        {0, 0.006840, 1.040940, 0.020340},  {0, 0.007380, 1.038960, 0.026640},
+        {0, 0.009000, 1.043820, 0.028980},  {0, 0.006660, 1.039500, 0.027180},
+        {0, 0.009540, 1.040400, 0.020520},  {0, 0.008460, 1.039680, 0.022680},
+        {0, 0.012420, 1.044540, 0.032040},  {0, 0.010800, 1.038600, 0.022680},
+        {0, 0.003960, 1.036980, 0.019440},  {0, 0.007560, 1.039860, 0.017460},
+        {0, 0.007020, 1.042740, 0.011340},  {0, 0.004680, 1.039140, 0.009000},
+        {0, 0.007380, 1.041120, 0.018900},  {0, 0.005580, 1.042380, 0.026820},
+        {0, 0.012240, 1.039140, 0.019800},  {0, 0.010260, 1.038780, 0.004140},
+        {0, 0.007740, 1.041120, 0.016740},  {0, 0.002880, 1.038960, 0.016740},
+        {0, 0.003960, 1.040760, 0.026820},  {0, 0.009000, 1.040580, 0.021600},
+        {0, 0.007920, 1.035720, 0.020880},  {0, 0.004680, 1.041840, 0.021600},
+        {0, 0.004140, 1.036440, 0.010980},  {0, 0.010260, 1.038420, 0.015480},
+        {0, 0.014040, 1.036980, 0.018540},  {0, 0.009900, 1.036620, 0.018000},
+        {0, 0.009000, 1.035900, 0.018540},  {0, 0.009180, 1.038780, 0.023760},
+        {0, 0.011340, 1.041840, 0.019620},  {0, 0.004320, 1.036440, 0.009360},
+        {0, 0.008280, 1.036980, 0.014940},  {0, 0.007020, 1.036620, 0.011340},
+        {0, 0.005940, 1.035000, 0.024300},  {0, 0.005220, 1.041840, 0.023220},
+        {0, 0.009720, 1.037340, 0.032220},  {0, 0.007380, 1.036260, 0.036900},
+        {0, 0.008640, 1.035720, 0.020160},  {0, 0.009000, 1.042200, 0.014760},
+        {0, 0.011880, 1.038960, 0.015840},  {0, 0.004140, 1.033560, 0.016200},
+        {0, 0.011880, 1.040760, 0.007200},  {0, 0.004860, 1.039140, 0.021780},
+        {0, 0.006660, 1.038960, 0.008820},  {0, 0.009360, 1.039680, 0.017100},
+    },
+
+    /* --------------------------  3  ------------------------ */
+
+    {
+        {0, 1.026180, 0.020340, -0.035100}, {0, 1.026900, 0.021240, -0.037080},
+        {0, 1.024920, 0.022140, -0.032580}, {0, 1.024200, 0.013320, -0.039420},
+        {0, 1.022400, 0.018720, -0.033120}, {0, 1.025640, 0.016740, -0.046080},
+        {0, 1.027260, 0.018720, -0.044100}, {0, 1.025100, 0.024300, -0.040680},
+        {0, 1.026000, 0.024480, -0.041940}, {0, 1.025100, 0.021240, -0.049860},
+        {0, 1.023840, 0.018540, -0.044280}, {0, 1.024200, 0.020520, -0.036900},
+        {0, 1.025460, 0.019080, -0.045540}, {0, 1.024920, 0.025200, -0.043020},
+        {0, 1.022040, 0.018000, -0.045540}, {0, 1.025820, 0.018540, -0.045540},
+        {0, 1.024200, 0.014040, -0.043380}, {0, 1.027440, 0.021780, -0.044820},
+        {0, 1.026900, 0.021600, -0.047880}, {0, 1.024020, 0.021420, -0.043740},
+        {0, 1.020600, 0.021240, -0.043920}, {0, 1.023120, 0.017280, -0.055080},
+        {0, 1.023480, 0.019440, -0.058680}, {0, 1.026900, 0.021420, -0.041580},
+        {0, 1.026720, 0.019260, -0.045000}, {0, 1.021500, 0.021240, -0.040500},
+        {0, 1.025280, 0.024300, -0.040680}, {0, 1.029600, 0.016200, -0.047340},
+        {0, 1.024560, 0.020160, -0.024660}, {0, 1.030860, 0.021600, -0.041580},
+        {0, 1.028700, 0.023040, -0.039960}, {0, 1.024560, 0.019080, -0.037980},
+        {0, 1.027800, 0.021240, -0.051480}, {0, 1.029060, 0.022320, -0.061380},
+        {0, 1.024560, 0.021780, -0.053100}, {0, 1.027080, 0.018000, -0.055800},
+        {0, 1.023300, 0.018900, -0.056700}, {0, 1.024380, 0.017280, -0.059220},
+        {0, 1.025640, 0.018540, -0.062820}, {0, 1.029240, 0.022680, -0.046080},
+        {0, 1.023300, 0.019440, -0.061020}, {0, 1.024740, 0.018360, -0.068220},
+        {0, 1.026000, 0.025920, -0.054540}, {0, 1.023120, 0.020160, -0.054900},
+        {0, 1.028160, 0.019980, -0.063000}, {0, 1.026000, 0.017280, -0.054360},
+        {0, 1.024920, 0.022680, -0.050220}, {0, 1.023120, 0.017820, -0.058860},
+        {0, 1.028520, 0.026460, -0.047160}, {0, 1.021860, 0.019080, -0.053640},
+        {0, 1.033380, 0.019080, -0.057780}, {0, 1.023120, 0.020880, -0.050400},
+        {0, 1.022580, 0.020160, -0.058500}, {0, 1.024560, 0.019080, -0.049320},
+        {0, 1.027980, 0.022500, -0.052380}, {0, 1.026180, 0.021060, -0.053100},
+        {0, 1.028340, 0.019980, -0.053280}, {0, 1.025280, 0.018900, -0.054540},
+        {0, 1.023660, 0.021420, -0.039420}, {0, 1.023300, 0.021600, -0.048600},
+    },
+
+    /* --------------------------  4  ------------------------ */
+
+    {
+        {0, 0.009180, 0.021420, -1.006920}, {0, 0.007920, 0.017280, -1.005300},
+        {0, 0.004500, 0.019260, -1.013040}, {0, 0.012600, 0.022500, -1.006920},
+        {0, 0.006480, 0.021060, -1.008180}, {0, 0.004500, 0.012780, -1.006740},
+        {0, 0.005220, 0.015300, -1.013760}, {0, 0.011520, 0.021420, -1.012320},
+        {0, 0.005940, 0.016560, -1.010700}, {0, -0.008280, 0.005220, -1.010700},
+        {0, 0.002880, 0.013140, -1.011600}, {0, 0.010800, 0.019800, -1.010160},
+        {0, 0.010080, 0.021960, -1.005480}, {0, -0.003960, 0.008820, -1.010520},
+        {0, 0.009180, 0.021060, -1.009800}, {0, 0.011160, 0.023400, -1.010880},
+        {0, 0.002700, 0.017100, -1.012860}, {0, 0.005760, 0.021600, -1.009620},
+        {0, 0.011160, 0.016740, -1.011060}, {0, 0.005040, 0.018360, -1.012680},
+        {0, 0.009540, 0.020880, -1.009440}, {0, 0.005220, 0.018900, -1.008900},
+        {0, 0.004680, 0.015840, -1.010340}, {0, 0.010440, 0.023040, -1.014300},
+        {0, 0.012420, 0.021600, -1.007820}, {0, 0.008820, 0.022140, -1.006200},
+        {0, 0.008460, 0.021780, -1.008540}, {0, 0.009540, 0.019980, -1.008720},
+        {0, 0.012600, 0.019980, -1.010880}, {0, 0.010260, 0.019620, -1.011960},
+        {0, 0.009540, 0.018000, -1.007640}, {0, 0.010440, 0.021060, -1.010160},
+        {0, 0.011160, 0.018360, -1.013760}, {0, 0.008460, 0.014940, -1.003320},
+        {0, 0.009720, 0.017820, -1.007280}, {0, 0.013320, 0.022860, -1.005840},
+        {0, 0.004680, 0.016020, -1.007820}, {0, 0.011700, 0.020340, -1.008360},
+        {0, 0.009000, 0.022500, -1.013940}, {0, 0.010080, 0.021420, -1.010880},
+        {0, 0.007020, 0.021240, -1.014300}, {0, 0.010800, 0.018180, -1.010160},
+        {0, 0.010260, 0.018900, -1.010520}, {0, 0.007920, 0.022500, -1.008180},
+        {0, 0.006300, 0.016920, -1.008720}, {0, 0.008460, 0.019620, -1.011780},
+        {0, 0.007200, 0.018720, -1.008900}, {0, 0.008460, 0.020340, -1.009620},
+        {0, 0.014580, 0.023580, -1.010520}, {0, 0.007200, 0.021060, -1.008000},
+        {0, 0.005400, 0.019620, -1.005480}, {0, 0.012780, 0.020520, -1.010520},
+        {0, 0.009000, 0.018000, -1.009080}, {0, 0.011700, 0.024480, -1.008540},
+        {0, 0.008280, 0.015480, -1.007460}, {0, 0.006840, 0.021240, -1.010160},
+        {0, 0.004500, 0.015660, -1.010700}, {0, 0.014040, 0.021960, -1.007280},
+        {0, 0.010260, 0.018180, -1.005840}, {0, 0.007740, 0.017280, -1.006920},
+    },
+
+    /* --------------------------  5  ------------------------ */
+
+    {
+        {0, 1.026540, 0.028800, 0.023580}, {0, 1.027800, 0.022680, 0.016560},
+        {0, 1.029240, 0.021600, 0.015660}, {0, 1.027260, 0.025020, 0.012060},
+        {0, 1.031220, 0.025740, 0.014400}, {0, 1.027980, 0.025020, 0.022140},
+        {0, 1.026360, 0.025020, 0.019980}, {0, 1.026540, 0.024660, 0.018720},
+        {0, 1.028160, 0.024480, 0.010260}, {0, 1.028520, 0.025740, 0.016200},
+        {0, 1.026180, 0.027720, 0.016380}, {0, 1.031940, 0.030780, 0.015120},
+        {0, 1.023660, 0.022680, 0.011880}, {0, 1.024920, 0.023580, 0.016560},
+        {0, 1.027080, 0.025560, 0.012240}, {0, 1.029240, 0.022140, 0.018900},
+        {0, 1.028880, 0.026100, 0.017820}, {0, 1.027260, 0.026100, 0.030240},
+        {0, 1.024020, 0.026460, 0.021600}, {0, 1.029420, 0.023040, 0.015120},
+        {0, 1.026900, 0.019800, 0.018360}, {0, 1.026720, 0.026280, 0.017100},
+        {0, 1.028880, 0.025020, 0.013140}, {0, 1.025640, 0.023220, 0.019440},
+        {0, 1.028160, 0.026460, 0.021240}, {0, 1.026180, 0.028080, 0.020160},
+        {0, 1.031400, 0.034560, 0.020520}, {0, 1.026180, 0.029160, 0.020340},
+        {0, 1.027980, 0.024120, 0.015300}, {0, 1.026180, 0.026100, 0.023580},
+        {0, 1.029240, 0.026820, 0.015840}, {0, 1.026720, 0.026820, 0.025200},
+        {0, 1.023300, 0.025560, 0.022680}, {0, 1.028880, 0.029520, 0.019980},
+        {0, 1.025820, 0.028800, 0.020160}, {0, 1.025640, 0.022680, 0.025740},
+        {0, 1.026720, 0.028800, 0.026100}, {0, 1.025100, 0.026640, 0.018360},
+        {0, 1.022400, 0.023580, 0.020340}, {0, 1.024020, 0.026100, 0.018720},
+        {0, 1.029060, 0.024300, 0.024480}, {0, 1.028520, 0.022860, 0.015480},
+        {0, 1.022940, 0.023220, 0.021780}, {0, 1.026360, 0.025740, 0.020160},
+        {0, 1.026900, 0.024480, 0.023580}, {0, 1.024380, 0.026640, 0.027900},
+        {0, 1.024200, 0.025020, 0.027720}, {0, 1.028700, 0.024300, 0.018000},
+        {0, 1.025100, 0.028800, 0.030060}, {0, 1.024380, 0.027000, 0.018180},
+        {0, 1.026180, 0.027180, 0.020340}, {0, 1.020960, 0.025380, 0.030780},
+        {0, 1.028520, 0.026820, 0.019440}, {0, 1.022040, 0.026640, 0.027720},
+        {0, 1.028700, 0.024660, 0.019080}, {0, 1.028520, 0.030780, 0.020160},
+        {0, 1.025100, 0.028980, 0.021240}, {0, 1.027440, 0.027540, 0.019260},
+        {0, 1.027440, 0.027540, 0.024660}, {0, 1.028700, 0.019980, 0.024660},
+    },
+
+    /* --------------------------  6  ------------------------ */
+
+    {
+        {0, 0.023400, 1.033740, -0.036720}, {0, 0.022860, 1.035180, -0.030780},
+        {0, 0.019260, 1.042200, -0.045180}, {0, 0.022500, 1.039500, -0.037260},
+        {0, 0.025920, 1.035360, -0.033480}, {0, 0.022140, 1.040400, -0.035460},
+        {0, 0.024660, 1.038240, -0.036900}, {0, 0.024120, 1.036440, -0.036540},
+        {0, 0.019260, 1.038240, -0.032040}, {0, 0.021960, 1.038600, -0.044640},
+        {0, 0.021420, 1.038960, -0.037800}, {0, 0.020340, 1.039860, -0.042300},
+        {0, 0.025020, 1.040580, -0.030780}, {0, 0.025920, 1.038780, -0.039240},
+        {0, 0.016560, 1.038960, -0.036000}, {0, 0.027180, 1.037520, -0.037260},
+        {0, 0.022320, 1.036440, -0.039960}, {0, 0.021600, 1.038420, -0.036540},
+        {0, 0.022140, 1.038420, -0.032940}, {0, 0.018720, 1.038960, -0.033300},
+        {0, 0.019620, 1.035180, -0.029340}, {0, 0.033480, 1.029780, -0.025380},
+        {0, 0.017460, 1.035000, -0.037620}, {0, 0.018540, 1.035720, -0.024840},
+        {0, 0.023400, 1.034280, -0.028800}, {0, 0.022320, 1.035360, -0.034560},
+        {0, 0.021960, 1.041840, -0.038880}, {0, 0.023940, 1.033380, -0.034020},
+        {0, 0.023220, 1.036800, -0.035820}, {0, 0.023400, 1.039140, -0.042300},
+        {0, 0.022140, 1.037880, -0.036900}, {0, 0.022320, 1.037880, -0.025380},
+        {0, 0.023760, 1.042920, -0.038520}, {0, 0.023220, 1.043640, -0.033660},
+        {0, 0.021960, 1.035540, -0.036000}, {0, 0.023400, 1.036440, -0.040140},
+        {0, 0.022140, 1.039500, -0.041040}, {0, 0.024120, 1.036620, -0.048600},
+        {0, 0.020160, 1.036800, -0.039960}, {0, 0.023760, 1.041300, -0.055620},
+        {0, 0.023040, 1.041660, -0.042480}, {0, 0.025920, 1.034640, -0.033480},
+        {0, 0.024300, 1.035540, -0.043200}, {0, 0.024300, 1.036260, -0.042660},
+        {0, 0.019980, 1.042380, -0.052920}, {0, 0.022320, 1.036800, -0.037800},
+        {0, 0.024480, 1.036800, -0.040320}, {0, 0.023220, 1.035720, -0.052740},
+        {0, 0.027720, 1.039860, -0.037080}, {0, 0.023400, 1.040220, -0.043740},
+        {0, 0.020340, 1.034460, -0.045360}, {0, 0.018180, 1.037520, -0.036900},
+        {0, 0.024480, 1.034100, -0.021960}, {0, 0.021060, 1.040580, -0.045900},
+        {0, 0.025380, 1.040760, -0.032400}, {0, 0.024120, 1.039320, -0.032580},
+        {0, 0.023400, 1.035720, -0.034740}, {0, 0.024480, 1.039860, -0.047700},
+        {0, 0.028980, 1.032300, -0.032040}, {0, 0.025200, 1.038240, -0.027540},
+    },
+};
+}  // namespace accData
diff --git a/src/tests/calibration/magnetometer-data.h b/src/tests/calibration/magnetometer-data.h
new file mode 100644
index 0000000000000000000000000000000000000000..b841ccfa28071835b392c383329a42463b7262e9
--- /dev/null
+++ b/src/tests/calibration/magnetometer-data.h
@@ -0,0 +1,256 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+// This data is a stripped down version of LOG_6.dat file of the calibration repository.
+
+#pragma once
+
+#include "sensors/SensorData.h"
+
+namespace magnetoData {
+    constexpr unsigned nSamples = 885;
+
+    MagnetometerData samples[nSamples]{
+        { 0, 0.038,0.271,0.015 }, { 0, 0.048,0.252,0.001 }, { 0, 0.256,0.388,-0.008 }, { 0, 0.438,0.500,0.110 },
+        { 0, 0.113,0.529,0.068 }, { 0, -0.086,0.551,0.368 }, { 0, 0.017,0.601,0.503 }, { 0, 0.201,0.571,0.593 },
+        { 0, -0.065,0.429,0.609 }, { 0, -0.141,0.443,0.219 }, { 0, 0.165,0.487,0.027 }, { 0, 0.481,0.135,0.666 },
+        { 0, 0.357,0.021,0.701 }, { 0, 0.272,-0.041,0.689 }, { 0, 0.040,-0.021,0.695 }, { 0, -0.121,0.199,0.120 },
+        { 0, 0.003,0.203,0.020 }, { 0, -0.006,0.226,0.027 }, { 0, -0.191,0.234,0.530 }, { 0, 0.560,0.093,0.568 },
+        { 0, 0.636,0.225,0.213 }, { 0, 0.378,0.597,0.235 }, { 0, 0.257,0.643,0.417 }, { 0, 0.220,0.562,0.601 },
+        { 0, 0.380,0.496,0.608 }, { 0, 0.377,0.451,0.639 }, { 0, 0.236,0.425,0.702 }, { 0, 0.038,0.574,0.189 },
+        { 0, 0.169,0.492,0.050 }, { 0, 0.369,0.419,0.037 }, { 0, 0.569,0.316,0.134 }, { 0, 0.543,-0.010,0.509 },
+        { 0, 0.361,-0.060,0.656 }, { 0, 0.152,-0.045,0.704 }, { 0, -0.126,0.058,0.572 }, { 0, -0.100,0.077,0.614 },
+        { 0, -0.156,0.081,0.519 }, { 0, -0.172,0.164,0.211 }, { 0, -0.111,0.182,0.116 }, { 0, 0.049,0.342,0.018 },
+        { 0, 0.363,0.394,0.020 }, { 0, 0.475,0.356,0.074 }, { 0, 0.574,0.319,0.151 }, { 0, 0.182,0.518,0.051 },
+        { 0, -0.055,0.550,0.214 }, { 0, -0.014,0.588,0.468 }, { 0, 0.117,0.650,0.462 }, { 0, 0.019,0.535,0.094 },
+        { 0, -0.095,0.541,0.197 }, { 0, -0.099,0.557,0.303 }, { 0, -0.184,0.444,0.440 }, { 0, -0.208,0.378,0.402 },
+        { 0, -0.117,0.367,0.143 }, { 0, 0.331,0.400,0.018 }, { 0, 0.498,0.440,0.154 }, { 0, 0.410,0.592,0.347 },
+        { 0, 0.261,0.646,0.285 }, { 0, 0.309,0.629,0.281 }, { 0, 0.334,0.632,0.292 }, { 0, 0.311,0.637,0.307 },
+        { 0, 0.260,0.646,0.267 }, { 0, 0.267,0.621,0.195 }, { 0, 0.325,0.574,0.145 }, { 0, 0.645,0.271,0.285 },
+        { 0, 0.651,0.148,0.433 }, { 0, 0.527,0.043,0.625 }, { 0, 0.231,-0.049,0.710 }, { 0, -0.075,0.027,0.651 },
+        { 0, -0.163,0.127,0.594 }, { 0, -0.092,0.103,0.680 }, { 0, 0.227,-0.063,0.724 }, { 0, 0.632,0.071,0.362 },
+        { 0, 0.606,0.379,0.419 }, { 0, 0.540,0.467,0.455 }, { 0, 0.483,0.503,0.516 }, { 0, 0.405,0.522,0.573 },
+        { 0, 0.403,0.530,0.558 }, { 0, 0.441,0.501,0.563 }, { 0, 0.451,0.451,0.617 }, { 0, 0.597,0.118,0.494 },
+        { 0, 0.599,0.057,0.438 }, { 0, 0.591,0.046,0.377 }, { 0, 0.506,-0.030,0.180 }, { 0, 0.426,0.079,0.038 },
+        { 0, 0.332,0.194,-0.029 }, { 0, 0.259,0.412,-0.012 }, { 0, 0.337,0.304,-0.027 }, { 0, 0.457,-0.050,0.136 },
+        { 0, 0.454,-0.104,0.544 }, { 0, 0.357,-0.003,0.674 }, { 0, 0.317,0.125,0.747 }, { 0, 0.228,0.641,0.476 },
+        { 0, 0.235,0.659,0.338 }, { 0, 0.317,0.619,0.224 }, { 0, 0.320,0.547,0.116 }, { 0, 0.313,0.317,-0.034 },
+        { 0, 0.315,0.276,-0.045 }, { 0, 0.345,0.182,-0.033 }, { 0, 0.373,0.064,0.003 }, { 0, 0.417,-0.068,0.096 },
+        { 0, 0.417,-0.068,0.096 }, { 0, 0.453,-0.122,0.207 }, { 0, 0.488,-0.119,0.246 }, { 0, 0.570,0.006,0.196 },
+        { 0, 0.612,0.113,0.172 }, { 0, 0.641,0.179,0.204 }, { 0, 0.657,0.235,0.266 }, { 0, 0.631,0.336,0.280 },
+        { 0, 0.625,0.362,0.284 }, { 0, 0.617,0.385,0.318 }, { 0, 0.579,0.439,0.358 }, { 0, 0.506,0.523,0.396 },
+        { 0, 0.497,0.507,0.448 }, { 0, 0.523,0.469,0.490 }, { 0, 0.557,0.333,0.562 }, { 0, 0.604,0.246,0.501 },
+        { 0, 0.621,0.242,0.465 }, { 0, 0.632,0.191,0.392 }, { 0, 0.630,0.146,0.332 }, { 0, 0.590,0.081,0.237 },
+        { 0, 0.526,0.038,0.144 }, { 0, 0.490,0.015,0.119 }, { 0, 0.461,0.003,0.107 }, { 0, 0.342,0.050,0.030 },
+        { 0, 0.331,0.062,0.014 }, { 0, 0.301,0.086,-0.008 }, { 0, 0.306,0.083,0.014 }, { 0, 0.564,-0.013,0.331 },
+        { 0, 0.559,-0.012,0.444 }, { 0, 0.487,0.049,0.624 }, { 0, 0.300,0.198,0.755 }, { 0, 0.224,0.350,0.752 },
+        { 0, 0.197,0.405,0.730 }, { 0, 0.103,0.583,0.581 }, { 0, 0.033,0.630,0.428 }, { 0, 0.068,0.578,0.161 },
+        { 0, 0.220,0.437,0.016 }, { 0, 0.238,0.381,-0.017 }, { 0, 0.268,0.278,-0.040 }, { 0, 0.301,0.077,-0.008 },
+        { 0, 0.345,0.013,0.040 }, { 0, 0.371,-0.031,0.070 }, { 0, 0.400,-0.083,0.123 }, { 0, 0.514,-0.082,0.208 },
+        { 0, 0.532,-0.057,0.201 }, { 0, 0.541,-0.040,0.195 }, { 0, 0.600,0.035,0.212 }, { 0, 0.621,0.049,0.236 },
+        { 0, 0.637,0.073,0.270 }, { 0, 0.652,0.114,0.300 }, { 0, 0.659,0.141,0.301 }, { 0, 0.649,0.249,0.336 },
+        { 0, 0.626,0.288,0.441 }, { 0, 0.614,0.246,0.506 }, { 0, 0.600,0.221,0.531 }, { 0, 0.541,0.114,0.604 },
+        { 0, 0.541,0.070,0.587 }, { 0, 0.543,0.032,0.561 }, { 0, 0.519,-0.056,0.507 }, { 0, 0.461,-0.146,0.293 },
+        { 0, 0.435,-0.143,0.197 }, { 0, 0.364,-0.119,0.130 }, { 0, 0.212,-0.056,0.057 }, { 0, 0.150,-0.091,0.101 },
+        { 0, 0.109,-0.151,0.195 }, { 0, 0.018,-0.166,0.400 }, { 0, 0.007,-0.155,0.458 }, { 0, 0.122,-0.132,0.635 },
+        { 0, 0.263,-0.097,0.676 }, { 0, 0.399,-0.050,0.664 }, { 0, 0.463,-0.005,0.651 }, { 0, 0.502,0.190,0.668 },
+        { 0, 0.482,0.359,0.632 }, { 0, 0.468,0.423,0.617 }, { 0, 0.424,0.482,0.599 }, { 0, 0.326,0.574,0.552 },
+        { 0, 0.223,0.638,0.478 }, { 0, 0.183,0.653,0.421 }, { 0, 0.169,0.627,0.219 }, { 0, 0.323,0.519,0.095 },
+        { 0, 0.408,0.482,0.107 }, { 0, 0.589,0.396,0.256 }, { 0, 0.601,0.403,0.317 }, { 0, 0.585,0.426,0.421 },
+        { 0, 0.543,0.472,0.239 }, { 0, 0.488,0.473,0.141 }, { 0, 0.331,0.446,0.010 }, { 0, 0.052,0.241,-0.021 },
+        { 0, 0.008,0.176,0.010 }, { 0, -0.038,0.117,0.046 }, { 0, -0.071,0.033,0.133 }, { 0, -0.088,-0.029,0.193 },
+        { 0, -0.040,-0.016,0.120 }, { 0, 0.156,0.058,-0.006 }, { 0, 0.437,0.191,0.001 }, { 0, 0.578,0.284,0.142 },
+        { 0, 0.621,0.287,0.223 }, { 0, 0.632,0.269,0.265 }, { 0, 0.639,0.241,0.273 }, { 0, 0.626,0.186,0.235 },
+        { 0, 0.611,0.197,0.201 }, { 0, 0.622,0.206,0.212 }, { 0, 0.638,0.228,0.278 }, { 0, 0.629,0.189,0.426 },
+        { 0, 0.605,0.167,0.487 }, { 0, 0.577,0.128,0.530 }, { 0, 0.555,0.094,0.557 }, { 0, 0.458,-0.049,0.568 },
+        { 0, 0.469,-0.121,0.429 }, { 0, 0.444,-0.142,0.377 }, { 0, 0.387,-0.171,0.280 }, { 0, 0.365,-0.112,0.135 },
+        { 0, 0.307,-0.012,0.030 }, { 0, 0.281,0.112,-0.021 }, { 0, 0.279,0.141,-0.032 }, { 0, 0.301,0.155,-0.026 },
+        { 0, 0.505,0.078,0.097 }, { 0, 0.607,0.026,0.305 }, { 0, 0.612,0.015,0.408 }, { 0, 0.322,0.124,0.755 },
+        { 0, 0.269,0.145,0.768 }, { 0, 0.262,0.175,0.779 }, { 0, 0.171,0.239,0.771 }, { 0, -0.073,0.462,0.609 },
+        { 0, -0.116,0.485,0.540 }, { 0, -0.156,0.489,0.335 }, { 0, -0.069,0.460,0.146 }, { 0, 0.091,0.425,0.030 },
+        { 0, 0.149,0.425,0.019 }, { 0, 0.308,0.431,0.029 }, { 0, 0.415,0.430,0.072 }, { 0, 0.537,0.422,0.193 },
+        { 0, 0.575,0.434,0.332 }, { 0, 0.575,0.434,0.332 }, { 0, 0.564,0.449,0.420 }, { 0, 0.538,0.469,0.452 },
+        { 0, 0.527,0.500,0.397 }, { 0, 0.528,0.502,0.350 }, { 0, 0.495,0.510,0.207 }, { 0, 0.263,0.599,0.169 },
+        { 0, 0.251,0.627,0.226 }, { 0, 0.241,0.625,0.244 }, { 0, 0.295,0.610,0.217 }, { 0, 0.222,0.620,0.182 },
+        { 0, 0.168,0.609,0.158 }, { 0, 0.109,0.589,0.148 }, { 0, 0.106,0.594,0.153 }, { 0, 0.148,0.608,0.153 },
+        { 0, 0.129,0.611,0.141 }, { 0, 0.166,0.597,0.120 }, { 0, 0.184,0.588,0.116 }, { 0, 0.345,0.491,0.059 },
+        { 0, 0.376,0.478,0.067 }, { 0, 0.461,0.481,0.151 }, { 0, 0.493,0.490,0.221 }, { 0, 0.470,0.540,0.401 },
+        { 0, 0.454,0.554,0.421 }, { 0, 0.439,0.553,0.445 }, { 0, 0.324,0.603,0.487 }, { 0, 0.242,0.647,0.359 },
+        { 0, 0.242,0.647,0.359 }, { 0, 0.295,0.622,0.239 }, { 0, 0.296,0.621,0.241 }, { 0, 0.281,0.616,0.199 },
+        { 0, 0.292,0.606,0.208 }, { 0, 0.292,0.606,0.208 }, { 0, 0.297,0.563,0.139 }, { 0, 0.342,0.430,0.021 },
+        { 0, 0.418,0.325,-0.002 }, { 0, 0.500,0.240,0.047 }, { 0, 0.525,0.205,0.065 }, { 0, 0.652,0.133,0.310 },
+        { 0, 0.655,0.163,0.364 }, { 0, 0.623,0.223,0.512 }, { 0, 0.556,0.285,0.596 }, { 0, 0.455,0.356,0.674 },
+        { 0, 0.384,0.356,0.716 }, { 0, 0.228,0.384,0.752 }, { 0, 0.148,0.387,0.746 }, { 0, 0.003,0.418,0.696 },
+        { 0, -0.159,0.472,0.490 }, { 0, -0.171,0.488,0.419 }, { 0, -0.165,0.482,0.455 }, { 0, 0.007,0.470,0.679 },
+        { 0, 0.096,0.480,0.709 }, { 0, 0.251,0.473,0.706 }, { 0, 0.251,0.473,0.706 }, { 0, 0.540,0.414,0.529 },
+        { 0, 0.563,0.420,0.485 }, { 0, 0.579,0.440,0.321 }, { 0, 0.493,0.461,0.147 }, { 0, 0.441,0.488,0.122 },
+        { 0, 0.436,0.524,0.173 }, { 0, 0.426,0.548,0.203 }, { 0, 0.402,0.573,0.206 }, { 0, 0.380,0.596,0.253 },
+        { 0, 0.348,0.623,0.317 }, { 0, 0.357,0.620,0.325 }, { 0, 0.342,0.629,0.339 }, { 0, 0.406,0.601,0.366 },
+        { 0, 0.441,0.574,0.367 }, { 0, 0.443,0.570,0.389 }, { 0, 0.456,0.556,0.387 }, { 0, 0.471,0.534,0.450 },
+        { 0, 0.487,0.505,0.482 }, { 0, 0.508,0.476,0.483 }, { 0, 0.557,0.405,0.495 }, { 0, 0.610,0.311,0.448 },
+        { 0, 0.632,0.260,0.428 }, { 0, 0.635,0.150,0.423 }, { 0, 0.635,0.150,0.423 }, { 0, 0.601,0.061,0.482 },
+        { 0, 0.568,0.007,0.484 }, { 0, 0.557,-0.014,0.511 }, { 0, 0.550,-0.031,0.507 }, { 0, 0.555,-0.053,0.480 },
+        { 0, 0.552,-0.055,0.464 }, { 0, 0.560,-0.033,0.448 }, { 0, 0.560,-0.033,0.448 }, { 0, 0.541,-0.033,0.213 },
+        { 0, 0.541,-0.033,0.213 }, { 0, 0.505,-0.007,0.138 }, { 0, 0.443,0.054,0.048 }, { 0, 0.408,0.135,-0.002 },
+        { 0, 0.370,0.168,-0.026 }, { 0, 0.326,0.221,-0.046 }, { 0, 0.312,0.249,-0.047 }, { 0, 0.305,0.326,-0.037 },
+        { 0, 0.279,0.403,-0.015 }, { 0, 0.265,0.444,0.007 }, { 0, 0.256,0.485,0.043 }, { 0, 0.259,0.583,0.140 },
+        { 0, 0.253,0.600,0.160 }, { 0, 0.249,0.617,0.190 }, { 0, 0.247,0.630,0.214 }, { 0, 0.255,0.646,0.274 },
+        { 0, 0.278,0.644,0.274 }, { 0, 0.300,0.634,0.264 }, { 0, 0.320,0.621,0.228 }, { 0, 0.358,0.548,0.143 },
+        { 0, 0.441,0.415,0.077 }, { 0, 0.488,0.345,0.076 }, { 0, 0.544,0.262,0.108 }, { 0, 0.623,0.127,0.255 },
+        { 0, 0.600,0.033,0.440 }, { 0, 0.565,0.025,0.514 }, { 0, 0.524,0.021,0.570 }, { 0, 0.354,0.063,0.722 },
+        { 0, 0.273,0.116,0.748 }, { 0, 0.191,0.182,0.765 }, { 0, 0.191,0.182,0.765 }, { 0, -0.029,0.452,0.639 },
+        { 0, -0.029,0.452,0.639 }, { 0, -0.039,0.513,0.586 }, { 0, -0.036,0.596,0.420 }, { 0, 0.073,0.602,0.191 },
+        { 0, 0.073,0.602,0.191 }, { 0, 0.155,0.539,0.091 }, { 0, 0.206,0.492,0.047 }, { 0, 0.306,0.373,-0.009 },
+        { 0, 0.381,0.243,-0.017 }, { 0, 0.412,0.183,0.002 }, { 0, 0.418,0.137,0.005 }, { 0, 0.431,0.011,0.055 },
+        { 0, 0.438,-0.028,0.079 }, { 0, 0.438,-0.052,0.099 }, { 0, 0.429,-0.069,0.117 }, { 0, 0.430,-0.095,0.123 },
+        { 0, 0.423,-0.080,0.109 }, { 0, 0.416,-0.044,0.075 }, { 0, 0.420,0.041,0.032 }, { 0, 0.429,0.142,-0.004 },
+        { 0, 0.430,0.180,-0.007 }, { 0, 0.416,0.257,-0.006 }, { 0, 0.422,0.279,0.000 }, { 0, 0.424,0.323,0.015 },
+        { 0, 0.416,0.360,0.028 }, { 0, 0.418,0.459,0.084 }, { 0, 0.418,0.459,0.084 }, { 0, 0.425,0.550,0.199 },
+        { 0, 0.421,0.579,0.260 }, { 0, 0.427,0.583,0.326 }, { 0, 0.444,0.574,0.377 }, { 0, 0.502,0.472,0.523 },
+        { 0, 0.525,0.404,0.574 }, { 0, 0.528,0.345,0.610 }, { 0, 0.533,0.253,0.630 }, { 0, 0.530,0.150,0.623 },
+        { 0, 0.544,0.090,0.602 }, { 0, 0.526,-0.006,0.557 }, { 0, 0.522,-0.042,0.520 }, { 0, 0.510,-0.083,0.414 },
+        { 0, 0.472,-0.113,0.282 }, { 0, 0.472,-0.113,0.282 }, { 0, 0.421,-0.104,0.186 }, { 0, 0.398,-0.029,0.072 },
+        { 0, 0.413,0.016,0.046 }, { 0, 0.413,0.016,0.046 }, { 0, 0.420,0.108,0.023 }, { 0, 0.379,0.256,-0.008 },
+        { 0, 0.362,0.327,-0.001 }, { 0, 0.374,0.383,0.036 }, { 0, 0.421,0.456,0.106 }, { 0, 0.450,0.500,0.191 },
+        { 0, 0.452,0.518,0.220 }, { 0, 0.454,0.539,0.274 }, { 0, 0.460,0.547,0.315 }, { 0, 0.459,0.548,0.387 },
+        { 0, 0.434,0.549,0.455 }, { 0, 0.425,0.540,0.492 }, { 0, 0.430,0.516,0.530 }, { 0, 0.354,0.505,0.617 },
+        { 0, 0.354,0.505,0.617 }, { 0, 0.312,0.535,0.610 }, { 0, 0.312,0.535,0.610 }, { 0, 0.267,0.637,0.411 },
+        { 0, 0.265,0.637,0.300 }, { 0, 0.276,0.613,0.216 }, { 0, 0.386,0.456,0.074 }, { 0, 0.446,0.267,0.030 },
+        { 0, 0.460,0.175,0.030 }, { 0, 0.441,-0.006,0.078 }, { 0, 0.408,-0.086,0.129 }, { 0, 0.336,-0.193,0.268 },
+        { 0, 0.238,-0.218,0.374 }, { 0, 0.192,-0.220,0.412 }, { 0, 0.156,-0.200,0.460 }, { 0, 0.099,-0.093,0.645 },
+        { 0, 0.073,-0.041,0.690 }, { 0, 0.057,0.027,0.722 }, { 0, 0.026,0.096,0.738 }, { 0, -0.071,0.355,0.665 },
+        { 0, -0.073,0.435,0.617 }, { 0, -0.059,0.511,0.554 }, { 0, -0.017,0.564,0.498 }, { 0, 0.164,0.642,0.313 },
+        { 0, 0.225,0.631,0.261 }, { 0, 0.348,0.582,0.202 }, { 0, 0.348,0.582,0.202 }, { 0, 0.460,0.518,0.205 },
+        { 0, 0.553,0.441,0.235 }, { 0, 0.589,0.405,0.273 }, { 0, 0.620,0.365,0.312 }, { 0, 0.621,0.320,0.466 },
+        { 0, 0.617,0.290,0.516 }, { 0, 0.585,0.256,0.570 }, { 0, 0.570,0.231,0.618 }, { 0, 0.555,0.213,0.609 },
+        { 0, 0.597,0.210,0.537 }, { 0, 0.637,0.217,0.441 }, { 0, 0.611,0.296,0.195 }, { 0, 0.483,0.359,0.061 },
+        { 0, 0.483,0.359,0.061 }, { 0, 0.300,0.412,-0.004 }, { 0, 0.090,0.419,0.019 }, { 0, 0.010,0.402,0.038 },
+        { 0, -0.093,0.287,0.071 }, { 0, -0.112,0.221,0.091 }, { 0, -0.155,0.079,0.204 }, { 0, -0.161,0.001,0.341 },
+        { 0, -0.149,-0.023,0.409 }, { 0, -0.116,-0.044,0.486 }, { 0, -0.068,-0.043,0.577 }, { 0, 0.057,0.070,0.742 },
+        { 0, 0.078,0.136,0.764 }, { 0, 0.081,0.210,0.770 }, { 0, 0.042,0.366,0.728 }, { 0, 0.024,0.492,0.650 },
+        { 0, 0.014,0.544,0.597 }, { 0, 0.014,0.610,0.470 }, { 0, 0.030,0.629,0.414 }, { 0, 0.059,0.630,0.260 },
+        { 0, 0.162,0.573,0.131 }, { 0, 0.214,0.549,0.110 }, { 0, 0.214,0.549,0.110 }, { 0, 0.376,0.485,0.098 },
+        { 0, 0.436,0.460,0.117 }, { 0, 0.487,0.433,0.135 }, { 0, 0.515,0.408,0.142 }, { 0, 0.581,0.291,0.161 },
+        { 0, 0.599,0.240,0.160 }, { 0, 0.615,0.192,0.180 }, { 0, 0.633,0.098,0.246 }, { 0, 0.619,0.010,0.389 },
+        { 0, 0.619,0.010,0.389 }, { 0, 0.531,-0.096,0.465 }, { 0, 0.509,-0.119,0.454 }, { 0, 0.467,-0.150,0.458 },
+        { 0, 0.442,-0.163,0.461 }, { 0, 0.444,-0.170,0.447 }, { 0, 0.440,-0.175,0.430 }, { 0, 0.388,-0.191,0.488 },
+        { 0, 0.364,-0.187,0.505 }, { 0, 0.350,-0.199,0.502 }, { 0, 0.343,-0.200,0.476 }, { 0, 0.331,-0.210,0.425 },
+        { 0, 0.333,-0.219,0.411 }, { 0, 0.324,-0.225,0.407 }, { 0, 0.351,-0.216,0.342 }, { 0, 0.403,-0.194,0.304 },
+        { 0, 0.419,-0.187,0.289 }, { 0, 0.441,-0.160,0.239 }, { 0, 0.462,-0.151,0.247 }, { 0, 0.510,-0.115,0.245 },
+        { 0, 0.515,-0.115,0.242 }, { 0, 0.519,-0.113,0.256 }, { 0, 0.519,-0.113,0.256 }, { 0, 0.537,-0.118,0.371 },
+        { 0, 0.524,-0.129,0.423 }, { 0, 0.505,-0.136,0.480 }, { 0, 0.474,-0.130,0.527 }, { 0, 0.380,-0.082,0.666 },
+        { 0, 0.336,-0.049,0.704 }, { 0, 0.298,-0.023,0.726 }, { 0, 0.273,0.010,0.747 }, { 0, 0.275,0.033,0.754 },
+        { 0, 0.275,0.033,0.754 }, { 0, 0.249,0.063,0.769 }, { 0, 0.225,0.091,0.781 }, { 0, 0.301,0.221,0.779 },
+        { 0, 0.333,0.275,0.768 }, { 0, 0.340,0.337,0.741 }, { 0, 0.347,0.377,0.721 }, { 0, 0.347,0.512,0.631 },
+        { 0, 0.355,0.539,0.588 }, { 0, 0.355,0.570,0.556 }, { 0, 0.338,0.596,0.527 }, { 0, 0.300,0.649,0.404 },
+        { 0, 0.273,0.655,0.356 }, { 0, 0.257,0.660,0.321 }, { 0, 0.216,0.654,0.260 }, { 0, 0.209,0.645,0.222 },
+        { 0, 0.185,0.629,0.182 }, { 0, 0.093,0.570,0.115 }, { 0, 0.032,0.532,0.104 }, { 0, -0.079,0.483,0.140 },
+        { 0, -0.139,0.436,0.191 }, { 0, -0.155,0.423,0.222 }, { 0, -0.167,0.416,0.250 }, { 0, -0.211,0.379,0.380 },
+        { 0, -0.211,0.379,0.380 }, { 0, -0.207,0.380,0.356 }, { 0, -0.190,0.386,0.280 }, { 0, -0.004,0.443,0.072 },
+        { 0, 0.141,0.493,0.062 }, { 0, 0.247,0.536,0.084 }, { 0, 0.308,0.564,0.125 }, { 0, 0.354,0.605,0.206 },
+        { 0, 0.356,0.622,0.279 }, { 0, 0.327,0.632,0.342 }, { 0, 0.289,0.647,0.360 }, { 0, 0.282,0.649,0.357 },
+        { 0, 0.290,0.631,0.198 }, { 0, 0.188,0.581,0.083 }, { 0, 0.188,0.581,0.083 }, { 0, -0.084,0.553,0.235 },
+        { 0, -0.093,0.568,0.323 }, { 0, -0.093,0.568,0.323 }, { 0, -0.088,0.575,0.376 }, { 0, -0.072,0.572,0.434 },
+        { 0, -0.093,0.570,0.378 }, { 0, -0.093,0.570,0.378 }, { 0, -0.048,0.558,0.191 }, { 0, 0.059,0.573,0.146 },
+        { 0, 0.190,0.588,0.146 }, { 0, 0.307,0.634,0.281 }, { 0, 0.307,0.634,0.281 }, { 0, 0.253,0.654,0.372 },
+        { 0, 0.208,0.656,0.395 }, { 0, 0.151,0.659,0.411 }, { 0, 0.082,0.649,0.438 }, { 0, -0.058,0.596,0.446 },
+        { 0, -0.110,0.562,0.425 }, { 0, -0.133,0.543,0.381 }, { 0, -0.155,0.520,0.326 }, { 0, -0.153,0.464,0.219 },
+        { 0, -0.143,0.442,0.179 }, { 0, -0.142,0.417,0.148 }, { 0, -0.114,0.373,0.098 }, { 0, 0.009,0.267,-0.004 },
+        { 0, 0.062,0.241,-0.029 }, { 0, 0.062,0.233,-0.030 }, { 0, 0.054,0.233,-0.022 }, { 0, 0.056,0.277,-0.016 },
+        { 0, 0.075,0.309,-0.024 }, { 0, 0.110,0.348,-0.015 }, { 0, 0.138,0.377,-0.011 }, { 0, 0.186,0.419,-0.002 },
+        { 0, 0.200,0.425,-0.003 }, { 0, 0.218,0.421,-0.007 }, { 0, 0.215,0.410,-0.021 }, { 0, 0.231,0.409,-0.011 },
+        { 0, 0.253,0.425,0.001 }, { 0, 0.253,0.425,0.001 }, { 0, 0.329,0.437,0.030 }, { 0, 0.376,0.412,0.036 },
+        { 0, 0.408,0.383,0.033 }, { 0, 0.423,0.316,0.024 }, { 0, 0.423,0.316,0.024 }, { 0, 0.355,0.272,-0.025 },
+        { 0, 0.295,0.331,-0.014 }, { 0, 0.280,0.365,-0.016 }, { 0, 0.280,0.365,-0.016 }, { 0, 0.205,0.448,0.020 },
+        { 0, 0.205,0.448,0.020 }, { 0, 0.189,0.452,0.019 }, { 0, 0.181,0.432,0.006 }, { 0, 0.212,0.394,-0.016 },
+        { 0, 0.218,0.386,-0.017 }, { 0, 0.217,0.346,-0.033 }, { 0, 0.242,0.299,-0.048 }, { 0, 0.234,0.260,-0.055 },
+        { 0, 0.221,0.220,-0.047 }, { 0, 0.215,0.193,-0.041 }, { 0, 0.212,0.164,-0.042 }, { 0, 0.249,0.112,-0.028 },
+        { 0, 0.355,0.083,0.012 }, { 0, 0.355,0.083,0.012 }, { 0, 0.418,0.056,0.044 }, { 0, 0.513,0.026,0.123 },
+        { 0, 0.541,0.008,0.180 }, { 0, 0.566,-0.013,0.251 }, { 0, 0.582,-0.023,0.342 }, { 0, 0.513,-0.051,0.528 },
+        { 0, 0.460,-0.067,0.573 }, { 0, 0.406,-0.071,0.615 }, { 0, 0.352,-0.072,0.642 }, { 0, 0.201,-0.030,0.693 },
+        { 0, 0.142,0.004,0.717 }, { 0, 0.088,0.045,0.721 }, { 0, 0.028,0.091,0.720 }, { 0, -0.036,0.153,0.717 },
+        { 0, -0.052,0.172,0.711 }, { 0, -0.057,0.180,0.717 }, { 0, -0.028,0.177,0.735 }, { 0, -0.088,0.177,0.675 },
+        { 0, -0.129,0.177,0.641 }, { 0, -0.178,0.200,0.571 }, { 0, -0.203,0.237,0.501 }, { 0, -0.225,0.270,0.407 },
+        { 0, -0.225,0.270,0.407 }, { 0, -0.214,0.296,0.306 }, { 0, -0.214,0.296,0.306 }, { 0, -0.181,0.328,0.216 },
+        { 0, -0.123,0.355,0.156 }, { 0, -0.123,0.355,0.156 }, { 0, -0.081,0.394,0.117 }, { 0, -0.047,0.422,0.100 },
+        { 0, -0.006,0.440,0.083 }, { 0, -0.006,0.440,0.083 }, { 0, -0.006,0.440,0.083 }, { 0, 0.031,0.454,0.066 },
+        { 0, 0.063,0.455,0.049 }, { 0, 0.063,0.455,0.049 }, { 0, 0.091,0.467,0.041 }, { 0, 0.108,0.470,0.045 },
+        { 0, 0.108,0.470,0.045 }, { 0, 0.126,0.472,0.041 }, { 0, 0.126,0.472,0.041 }, { 0, 0.154,0.477,0.043 },
+        { 0, 0.154,0.477,0.043 }, { 0, 0.166,0.476,0.030 }, { 0, 0.165,0.458,0.029 }, { 0, 0.165,0.458,0.029 },
+        { 0, 0.141,0.451,0.029 }, { 0, 0.141,0.451,0.029 }, { 0, 0.108,0.458,0.031 }, { 0, 0.089,0.463,0.038 },
+        { 0, 0.089,0.463,0.038 }, { 0, 0.061,0.477,0.056 }, { 0, 0.061,0.477,0.056 }, { 0, 0.035,0.508,0.094 },
+        { 0, 0.025,0.537,0.126 }, { 0, 0.025,0.537,0.126 }, { 0, 0.020,0.570,0.159 }, { 0, 0.030,0.582,0.184 },
+        { 0, 0.030,0.582,0.184 }, { 0, 0.038,0.592,0.195 }, { 0, 0.038,0.592,0.195 }, { 0, 0.051,0.588,0.182 },
+        { 0, 0.051,0.588,0.182 }, { 0, 0.044,0.586,0.175 }, { 0, 0.044,0.586,0.175 }, { 0, 0.039,0.577,0.170 },
+        { 0, 0.039,0.578,0.158 }, { 0, 0.039,0.578,0.158 }, { 0, 0.034,0.561,0.140 }, { 0, 0.046,0.540,0.106 },
+        { 0, 0.046,0.540,0.106 }, { 0, 0.046,0.540,0.106 }, { 0, 0.067,0.528,0.087 }, { 0, 0.076,0.502,0.059 },
+        { 0, 0.076,0.502,0.059 }, { 0, 0.088,0.482,0.036 }, { 0, 0.088,0.482,0.036 }, { 0, 0.117,0.441,0.016 },
+        { 0, 0.189,0.401,-0.009 }, { 0, 0.189,0.401,-0.009 }, { 0, 0.281,0.357,-0.028 }, { 0, 0.397,0.331,0.010 },
+        { 0, 0.397,0.331,0.010 }, { 0, 0.504,0.293,0.076 }, { 0, 0.504,0.293,0.076 }, { 0, 0.592,0.267,0.173 },
+        { 0, 0.637,0.244,0.291 }, { 0, 0.637,0.244,0.291 }, { 0, 0.635,0.237,0.402 }, { 0, 0.612,0.247,0.485 },
+        { 0, 0.612,0.247,0.485 }, { 0, 0.573,0.266,0.562 }, { 0, 0.573,0.266,0.562 }, { 0, 0.497,0.289,0.645 },
+        { 0, 0.382,0.306,0.729 }, { 0, 0.382,0.306,0.729 }, { 0, 0.382,0.306,0.729 }, { 0, 0.135,0.303,0.770 },
+        { 0, 0.135,0.303,0.770 }, { 0, 0.135,0.303,0.770 }, { 0, 0.048,0.300,0.755 }, { 0, -0.020,0.306,0.728 },
+        { 0, -0.053,0.310,0.715 }, { 0, -0.053,0.310,0.715 }, { 0, -0.053,0.310,0.715 }, { 0, -0.089,0.305,0.682 },
+        { 0, -0.134,0.308,0.631 }, { 0, -0.168,0.318,0.574 }, { 0, -0.168,0.318,0.574 }, { 0, -0.194,0.324,0.528 },
+        { 0, -0.207,0.327,0.473 }, { 0, -0.207,0.327,0.473 }, { 0, -0.207,0.327,0.473 }, { 0, -0.206,0.330,0.434 },
+        { 0, -0.205,0.341,0.403 }, { 0, -0.205,0.341,0.403 }, { 0, -0.205,0.358,0.363 }, { 0, -0.202,0.360,0.338 },
+        { 0, -0.202,0.360,0.338 }, { 0, -0.197,0.363,0.299 }, { 0, -0.197,0.363,0.299 }, { 0, -0.190,0.360,0.253 },
+        { 0, -0.190,0.360,0.253 }, { 0, -0.168,0.347,0.202 }, { 0, -0.144,0.338,0.154 }, { 0, -0.103,0.327,0.109 },
+        { 0, -0.103,0.327,0.109 }, { 0, -0.068,0.308,0.066 }, { 0, -0.068,0.308,0.066 }, { 0, -0.032,0.301,0.040 },
+        { 0, 0.006,0.269,0.017 }, { 0, 0.006,0.269,0.017 }, { 0, 0.039,0.234,0.002 }, { 0, 0.074,0.194,-0.017 },
+        { 0, 0.074,0.194,-0.017 }, { 0, 0.074,0.194,-0.017 }, { 0, 0.087,0.148,-0.010 }, { 0, 0.094,0.105,-0.005 },
+        { 0, 0.110,0.053,0.008 }, { 0, 0.110,0.053,0.008 }, { 0, 0.130,0.014,0.022 }, { 0, 0.160,-0.030,0.039 },
+        { 0, 0.160,-0.030,0.039 }, { 0, 0.180,-0.067,0.057 }, { 0, 0.180,-0.067,0.057 }, { 0, 0.197,-0.096,0.083 },
+        { 0, 0.206,-0.116,0.107 }, { 0, 0.206,-0.116,0.107 }, { 0, 0.206,-0.116,0.107 }, { 0, 0.259,-0.136,0.131 },
+        { 0, 0.259,-0.136,0.131 }, { 0, 0.259,-0.136,0.131 }, { 0, 0.285,-0.143,0.130 }, { 0, 0.323,-0.148,0.148 },
+        { 0, 0.339,-0.161,0.163 }, { 0, 0.339,-0.161,0.163 }, { 0, 0.350,-0.177,0.186 }, { 0, 0.350,-0.177,0.186 },
+        { 0, 0.347,-0.201,0.225 }, { 0, 0.332,-0.216,0.251 }, { 0, 0.332,-0.216,0.251 }, { 0, 0.320,-0.223,0.284 },
+        { 0, 0.310,-0.216,0.310 }, { 0, 0.310,-0.216,0.310 }, { 0, 0.295,-0.220,0.337 }, { 0, 0.295,-0.220,0.337 },
+        { 0, 0.291,-0.231,0.348 }, { 0, 0.307,-0.235,0.352 }, { 0, 0.307,-0.235,0.352 }, { 0, 0.308,-0.253,0.363 },
+        { 0, 0.291,-0.250,0.363 }, { 0, 0.291,-0.250,0.363 }, { 0, 0.269,-0.251,0.369 }, { 0, 0.269,-0.251,0.369 },
+        { 0, 0.228,-0.237,0.362 }, { 0, 0.201,-0.238,0.367 }, { 0, 0.201,-0.238,0.367 }, { 0, 0.183,-0.235,0.343 },
+        { 0, 0.161,-0.219,0.313 }, { 0, 0.161,-0.219,0.313 }, { 0, 0.149,-0.208,0.290 }, { 0, 0.151,-0.205,0.273 },
+        { 0, 0.151,-0.205,0.273 }, { 0, 0.160,-0.213,0.275 }, { 0, 0.143,-0.220,0.311 }, { 0, 0.143,-0.220,0.311 },
+        { 0, 0.143,-0.220,0.311 }, { 0, 0.128,-0.222,0.363 }, { 0, 0.128,-0.222,0.363 }, { 0, 0.116,-0.214,0.430 },
+        { 0, 0.116,-0.214,0.430 }, { 0, 0.116,-0.214,0.430 }, { 0, 0.089,-0.188,0.503 }, { 0, 0.089,-0.188,0.503 },
+        { 0, 0.057,-0.158,0.560 }, { 0, 0.057,-0.158,0.560 }, { 0, 0.057,-0.158,0.560 }, { 0, 0.023,-0.115,0.611 },
+        { 0, 0.023,-0.115,0.611 }, { 0, 0.023,-0.115,0.611 }, { 0, 0.002,-0.066,0.649 }, { 0, 0.002,-0.066,0.649 },
+        { 0, -0.009,-0.017,0.672 }, { 0, -0.009,-0.017,0.672 }, { 0, -0.009,-0.017,0.672 }, { 0, -0.016,0.046,0.708 },
+        { 0, -0.016,0.046,0.708 }, { 0, -0.006,0.120,0.737 }, { 0, -0.006,0.120,0.737 }, { 0, 0.019,0.193,0.760 },
+        { 0, 0.019,0.193,0.760 }, { 0, 0.019,0.193,0.760 }, { 0, 0.019,0.193,0.760 }, { 0, 0.046,0.268,0.766 },
+        { 0, 0.046,0.268,0.766 }, { 0, 0.058,0.334,0.748 }, { 0, 0.058,0.334,0.748 }, { 0, 0.034,0.380,0.726 },
+        { 0, 0.034,0.380,0.726 }, { 0, 0.034,0.380,0.726 }, { 0, 0.001,0.411,0.685 }, { 0, 0.001,0.411,0.685 },
+        { 0, -0.047,0.435,0.657 }, { 0, -0.047,0.435,0.657 }, { 0, -0.047,0.435,0.657 }, { 0, -0.073,0.450,0.612 },
+        { 0, -0.073,0.450,0.612 }, { 0, -0.073,0.450,0.612 }, { 0, -0.101,0.459,0.579 }, { 0, -0.101,0.459,0.579 },
+        { 0, -0.129,0.464,0.529 }, { 0, -0.129,0.464,0.529 }, { 0, -0.129,0.464,0.529 }, { 0, -0.155,0.461,0.475 },
+        { 0, -0.155,0.461,0.475 }, { 0, -0.171,0.459,0.424 }, { 0, -0.171,0.459,0.424 }, { 0, -0.171,0.459,0.424 },
+        { 0, -0.187,0.449,0.347 }, { 0, -0.187,0.449,0.347 }, { 0, -0.178,0.438,0.289 }, { 0, -0.178,0.438,0.289 },
+        { 0, -0.151,0.418,0.218 }, { 0, -0.151,0.418,0.218 }, { 0, -0.151,0.418,0.218 }, { 0, -0.112,0.383,0.131 },
+        { 0, -0.112,0.383,0.131 }, { 0, -0.112,0.383,0.131 }, { 0, -0.044,0.351,0.057 }, { 0, -0.044,0.351,0.057 },
+        { 0, 0.034,0.304,0.012 }, { 0, 0.034,0.304,0.012 }, { 0, 0.034,0.304,0.012 }, { 0, 0.119,0.270,-0.016 },
+        { 0, 0.119,0.270,-0.016 }, { 0, 0.200,0.235,-0.032 }, { 0, 0.200,0.235,-0.032 }, { 0, 0.264,0.217,-0.025 },
+        { 0, 0.264,0.217,-0.025 }, { 0, 0.264,0.217,-0.025 }, { 0, 0.264,0.217,-0.025 }, { 0, 0.308,0.186,-0.018 },
+        { 0, 0.308,0.186,-0.018 }, { 0, 0.349,0.171,-0.012 }, { 0, 0.349,0.171,-0.012 }, { 0, 0.361,0.165,0.001 },
+        { 0, 0.361,0.165,0.001 } 
+    };
+}
diff --git a/src/tests/calibration/test-calibration-benchmark.cpp b/src/tests/calibration/test-calibration-benchmark.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f739ae7e7515d71a8d9dcbc21239b63b96575d93
--- /dev/null
+++ b/src/tests/calibration/test-calibration-benchmark.cpp
@@ -0,0 +1,113 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*  ACCELEROMETER calibration: please set the chosen one to 1  */
+#define BIAS_CALIBRATION_LOAD_TEST 1
+#define SIX_PARAMETER_CALIBRATION_LOAD_TEST 0
+#define TWELVE_PARAMETER_CALIBRATION_LOAD_TEST 0
+
+/* Expressed in Hertz: valid values: 1 <= frequency <= 1000 */
+#define SAMPLE_FREQUENCY_LOAD_TEST 1000
+
+#include <Common.h>
+#include <drivers/spi/SPIDriver.h>
+#include <diagnostic/CpuMeter.h>
+#include <miosix.h>
+
+#include "sensors/LIS3DSH/LIS3DSH.h"
+#include "sensors/calibration/BiasCalibration.h"
+#include "sensors/calibration/HardIronCalibration.h"
+#include "sensors/calibration/SixParameterCalibration.h"
+#include "sensors/calibration/SoftIronCalibration.h"
+#include "sensors/calibration/TwelveParameterCalibration.h"
+
+using namespace miosix;
+
+/* using volatile so that the compiler won't optimize out the variable */
+volatile AccelerometerData testData;
+
+int main()
+{
+#if BIAS_CALIBRATION_LOAD_TEST
+    BiasCorrector<AccelerometerData> corrector;
+    TRACE("Using BIAS calibration model.\n");
+#endif
+
+#if SIX_PARAMETER_CALIBRATION_LOAD_TEST
+    SixParameterCorrector<AccelerometerData> corrector;
+    TRACE("Using SIX-PARAMETER calibration model.\n");
+#endif
+
+#if TWELVE_PARAMETER_CALIBRATION_LOAD_TEST
+    TwelveParameterCorrector<AccelerometerData> corrector;
+    TRACE("Using TWELVE-PARAMETER calibration model.\n");
+#endif
+
+    SPIBus bus(SPI1);
+
+    GpioPin spi_sck(GPIOA_BASE, 5);
+    GpioPin spi_miso(GPIOA_BASE, 6);
+    GpioPin spi_mosi(GPIOA_BASE, 7);
+    GpioPin cs(GPIOE_BASE, 3);
+
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;  // Enable SPI1 bus
+
+        spi_sck.mode(miosix::Mode::ALTERNATE);
+        spi_sck.alternateFunction(5);
+        spi_miso.mode(miosix::Mode::ALTERNATE);
+        spi_miso.alternateFunction(5);
+        spi_mosi.mode(miosix::Mode::ALTERNATE);
+        spi_mosi.alternateFunction(5);
+
+        cs.mode(miosix::Mode::OUTPUT);
+    }
+    cs.high();
+
+    LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE,
+                   sensor.FULL_SCALE_4G);
+    sensor.init();
+
+    constexpr unsigned sleepMillis = 1000 / SAMPLE_FREQUENCY_LOAD_TEST;
+    unsigned elapsed = 0;
+     
+    while(1){
+        Thread::sleep(sleepMillis);
+
+        elapsed += sleepMillis;
+        sensor.sample();
+
+        AccelerometerData tmp = corrector.correct(sensor.getLastSample());
+
+        testData.accel_x = tmp.accel_x;
+        testData.accel_y = tmp.accel_y;
+        testData.accel_z = tmp.accel_z;
+
+        if(elapsed > 500){
+            elapsed = 0;
+            TRACE("Average CPU usage: %f %%\n", averageCpuUtilization());
+        }
+    }
+}
+
diff --git a/src/tests/calibration/test-calibration-stats.cpp b/src/tests/calibration/test-calibration-stats.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b083edfbeee7973686148699024481c6faa3c20d
--- /dev/null
+++ b/src/tests/calibration/test-calibration-stats.cpp
@@ -0,0 +1,312 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#define SENSOR_LIS3DSH_STATS_TEST 1
+
+#include <Common.h>
+#include <drivers/spi/SPIDriver.h>
+#include <math/Stats.h>
+#include <miosix.h>
+
+#if SENSOR_LIS3DSH_STATS_TEST
+#include "sensors/LIS3DSH/LIS3DSH.h"
+#endif
+
+#include "sensors/calibration/Calibration.h"
+
+using namespace miosix;
+
+/* using volatile so that the compiler won't optimize out the variable */
+volatile AccelerometerData testData;
+
+/*
+ * For future readers: new lines are inserted so that each line does no exceed
+ * 57 characters (tab counts as 8 + 49 chars).
+ */
+const char* helpInfo =
+    "Here are all available commands:\n\n"
+    "  ?\n\tDisplay this information.\n\n"
+    "  olist\n\tList which of the orientations are still to sample.\n\n"
+    "  oshow\n\tDescribe the current orientation.\n\n"
+    "  onext\n\tGo to the next orientation of the list (marking\n\tthe current "
+    "one as sampled.\n\n"
+    "  oset [idx]\n\tManually sets the current orientation to the "
+    "[idx]\n\tone of the list given by 'olist'.\n\n"
+    "  take [n]\n\tTake [n] samples from the sensor and use them "
+    "to\n\tcalculate the descriptive values such as min, max,"
+    "\n\tvariance, etc. Note: old values will be used unless\n\t'discard' "
+    "command is called.\n\n"
+    "  setsleep [millis]\n\tSet the sleep time between samples to "
+    "[millis].\n\tThe default sleep time value is 0.\n\n"
+    "  discard\n\tReset all stats about the current orientation.\n\n"
+    "  min\n\tShow the minimum value measured on each axis.\n\n"
+    "  max\n\tShow the maximum value.\n\n"
+    "  mean\n\tShow the mean.\n\n"
+    "  stddev\n\tShow the standard deviation.\n\n"
+    "  samples\n\tShow how many samples are used for the calculation\n\t"
+    "of the descriptive values (for current orientation\n\tonly).\n\n";
+
+int main()
+{
+    SPIBus bus(SPI1);
+
+    GpioPin spi_sck(GPIOA_BASE, 5);
+    GpioPin spi_miso(GPIOA_BASE, 6);
+    GpioPin spi_mosi(GPIOA_BASE, 7);
+    GpioPin cs(GPIOE_BASE, 3);
+
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;  // Enable SPI1 bus
+
+        spi_sck.mode(miosix::Mode::ALTERNATE);
+        spi_sck.alternateFunction(5);
+        spi_miso.mode(miosix::Mode::ALTERNATE);
+        spi_miso.alternateFunction(5);
+        spi_mosi.mode(miosix::Mode::ALTERNATE);
+        spi_mosi.alternateFunction(5);
+
+        cs.mode(miosix::Mode::OUTPUT);
+    }
+    cs.high();
+
+    LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE,
+                   sensor.FULL_SCALE_4G);
+    sensor.init();
+
+    const AxisOrthoOrientation orientations[] = {
+        {Direction::POSITIVE_X, Direction::POSITIVE_Y},
+        {Direction::POSITIVE_Y, Direction::POSITIVE_Z},
+        {Direction::POSITIVE_Z, Direction::POSITIVE_X},
+        {Direction::POSITIVE_Y, Direction::POSITIVE_X},
+        {Direction::POSITIVE_Z, Direction::POSITIVE_Y},
+        {Direction::POSITIVE_X, Direction::POSITIVE_Z},
+    };
+
+    int selected = 0, sleepTime = 0;
+    char input[30];
+
+    Stats xAxis[6], yAxis[6], zAxis[6];
+
+    printf(
+        "Welcome to the sensor stats tool.\n"
+        "Commands are given via serial input. Type ? to see accepted "
+        "commands\n");
+
+    while (1)
+    {
+        printf("> ");
+        fgets(input, 30, stdin);
+
+        if (!strncmp("?", input, 1))
+        {
+            printf("%s", helpInfo);
+        }
+        else if (!strncmp("olist", input, 5))
+        {
+            printf("Here are all the recognized orientations:\n");
+
+            for (int i = 0; i < 6; i++)
+            {
+                const char* xLabel =
+                    humanFriendlyDirection[(uint8_t)orientations[i].xAxis];
+                const char* yLabel =
+                    humanFriendlyDirection[(uint8_t)orientations[i].yAxis];
+
+                printf(" %d. X towards %s, Y towards %s (%d samples)", i,
+                       xLabel, yLabel, xAxis[i].getStats().nSamples);
+
+                if (selected == i)
+                    printf(" [selected]");
+                printf("\n");
+            }
+        }
+        else if (!strncmp("oshow", input, 5))
+        {
+        iLoveSpaghettiCode:
+            const char* xLabel =
+                humanFriendlyDirection[(uint8_t)orientations[selected].xAxis];
+            const char* yLabel =
+                humanFriendlyDirection[(uint8_t)orientations[selected].yAxis];
+            printf("Current orientation is: X towards %s, Y towards %s\n",
+                   xLabel, yLabel);
+
+            printf("You took %d samples for each axis on this orientation.\n",
+                   xAxis[selected].getStats().nSamples);
+        }
+        else if (!strncmp("onext", input, 5))
+        {
+            selected++;
+
+            printf("Switching to orientation n. %d\n", selected);
+            goto iLoveSpaghettiCode;
+        }
+        else if (!strncmp("oset", input, 4))
+        {
+            int idx;
+            if (!sscanf(input, "oset %d", &idx) || idx < 0 || idx > 6)
+            {
+                printf("oset: invalid argument. %d \n", idx);
+            }
+            else
+            {
+                selected = idx;
+
+                printf("Switching to orientation n. %d\n", selected);
+                goto iLoveSpaghettiCode;
+            }
+        }
+        else if (!strncmp("take", input, 4))
+        {
+            int n;
+            if (!sscanf(input, "take %d", &n) || n <= 0)
+            {
+                printf("take: invalid argument.\n");
+            }
+            else
+            {
+                for (int i = 0; i < n; ++i)
+                {
+                    sensor.sample();
+                    auto data = sensor.getLastSample();
+
+                    xAxis[selected].add(data.accel_x);
+                    yAxis[selected].add(data.accel_y);
+                    zAxis[selected].add(data.accel_z);
+
+                    printf("Added sample: %f, %f, %f\n", data.accel_x,
+                           data.accel_y, data.accel_z);
+
+                    if (sleepTime > 0)
+                    {
+                        Thread::sleep(sleepTime);
+                    }
+                }
+            }
+        }
+        else if (!strncmp("setsleep", input, 8))
+        {
+            int newSleep;
+            if (!sscanf(input, "setsleep %d", &newSleep) || newSleep < 0)
+            {
+                printf("setsleep: invalid argument.\n");
+            }
+            else
+            {
+                sleepTime = newSleep;
+                printf("Set new sleep time to %d milliseconds.\n", newSleep);
+            }
+        }
+        else if (!strncmp("discard", input, 7))
+        {
+            xAxis[selected].reset();
+            yAxis[selected].reset();
+            zAxis[selected].reset();
+
+            printf("Data discarded.\n");
+        }
+        else if (!strncmp("min", input, 3))
+        {
+            StatsResult xRes = xAxis[selected].getStats(),
+                        yRes = yAxis[selected].getStats(),
+                        zRes = zAxis[selected].getStats();
+
+            if (xRes.nSamples == 0)
+            {
+                printf(
+                    "Error: not enough samples. Please invoke command "
+                    "'take'.\n");
+            }
+            else
+            {
+                printf("\t%f, %f, %f\n", xRes.minValue, yRes.minValue,
+                       zRes.minValue);
+            }
+        }
+        else if (!strncmp("max", input, 3))
+        {
+            StatsResult xRes = xAxis[selected].getStats(),
+                        yRes = yAxis[selected].getStats(),
+                        zRes = zAxis[selected].getStats();
+
+            if (xRes.nSamples == 0)
+            {
+                printf(
+                    "Error: not enough samples. Please invoke command "
+                    "'take'.\n");
+            }
+            else
+            {
+                printf("\t%f, %f, %f\n",
+                       xRes.maxValue, yRes.maxValue, zRes.maxValue);
+            }
+        }
+        else if (!strncmp("mean", input, 3))
+        {
+            StatsResult xRes = xAxis[selected].getStats(),
+                        yRes = yAxis[selected].getStats(),
+                        zRes = zAxis[selected].getStats();
+
+            if (xRes.nSamples == 0)
+            {
+                printf(
+                    "Error: not enough samples. Please invoke command "
+                    "'take'.\n");
+            }
+            else
+            {
+                printf("\t%f, %f, %f\n",
+                       xRes.mean, yRes.mean, zRes.mean);
+            }
+        }
+        else if (!strncmp("stddev", input, 3))
+        {
+            StatsResult xRes = xAxis[selected].getStats(),
+                        yRes = yAxis[selected].getStats(),
+                        zRes = zAxis[selected].getStats();
+
+            if (xRes.nSamples == 0)
+            {
+                printf(
+                    "Error: not enough samples. Please invoke command "
+                    "'take'.\n");
+            }
+            else
+            {
+                printf(
+                    "\t%f, %f, %f\n",
+                    xRes.stdev, yRes.stdev, zRes.stdev);
+            }
+        }
+        else if (!strncmp("samples", input, 7))
+        {
+            printf("Taken %d samples on the current orientation.\n",
+                   xAxis[selected].getStats().nSamples);
+        }
+        else
+        {
+            printf("Unrecognized command. Type ? for help.\n");
+        }
+    }
+}
+
diff --git a/src/tests/calibration/test-calibration.cpp b/src/tests/calibration/test-calibration.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e7a8da549ee0c2c48809e211128e70d7011e4b4c
--- /dev/null
+++ b/src/tests/calibration/test-calibration.cpp
@@ -0,0 +1,267 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*  Please choose one between accelerometer or magnetometer */
+#define TEST_ACCELEROMETER_DATA 1
+#define TEST_MAGNETOMETER_DATA 0
+
+/*  ACCELEROMETER calibration: please set the chosen one to 1  */
+#define BIAS_CALIBRATION_TEST 0
+#define SIX_PARAMETER_CALIBRATION_TEST 0
+#define TWELVE_PARAMETER_CALIBRATION_TEST 1
+
+/*  MAGNETOMETER calibration: please set the chosen one to 1 */
+#define HARD_IRON_CALIBRATION_TEST 1
+#define SOFT_IRON_CALIBRATION_TEST 0
+
+#include <Common.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+
+#include "sensors/LIS3DSH/LIS3DSH.h"
+#include "sensors/calibration/BiasCalibration.h"
+#include "sensors/calibration/HardIronCalibration.h"
+#include "sensors/calibration/SixParameterCalibration.h"
+#include "sensors/calibration/SoftIronCalibration.h"
+#include "sensors/calibration/TwelveParameterCalibration.h"
+
+#if TEST_ACCELEROMETER_DATA
+#include "accelerometer-data.h"
+#elif TEST_MAGNETOMETER_DATA
+#include "magnetometer-data.h"
+#endif
+
+using namespace miosix;
+
+int main()
+{
+#if TEST_ACCELEROMETER_DATA
+
+    constexpr unsigned NumSamples = accData::nOrientations * accData::nSamples;
+
+#if BIAS_CALIBRATION_TEST
+    auto* model = new BiasCalibration<AccelerometerData>;
+
+    TRACE("Using BIAS calibration model.\n");
+#endif
+
+#if SIX_PARAMETER_CALIBRATION_TEST
+    auto* model = new SixParameterCalibration<AccelerometerData, NumSamples>;
+    model->setReferenceVector({0, 0, 1});
+
+    TRACE("Using SIX-PARAMETER calibration model.\n");
+#endif
+
+#if TWELVE_PARAMETER_CALIBRATION_TEST
+    auto* model = new TwelveParameterCalibration<AccelerometerData, NumSamples>;
+    model->setReferenceVector({0, 0, 1});
+
+    TRACE("Using TWELVE-PARAMETER calibration model.\n");
+#endif
+
+    TRACE("Feeding accelerometer data to the calibration model ... \n");
+
+    for (unsigned i = 0; i < accData::nOrientations; i++)
+        for (unsigned j = 0; j < accData::nSamples; j++)
+            model->feed(accData::samples[i][j], accData::orientations[i]);
+
+    TRACE("Computing the result ... \n");
+
+    auto corrector = model->computeResult();
+
+    TRACE("Now testing with the same data: \n");
+
+    float err0 = 0.f, err1 = 0.f;
+    Vector3f vec0 = {0, 0, 0}, vec1 = {0, 0, 0};
+
+    for (unsigned i = 0; i < accData::nOrientations; i++)
+    {
+        TRACE(
+            "  --- Orientation n. %d: X axis oriented toward %s, Y axis "
+            "oriented toward %s\n",
+            i,
+            humanFriendlyDirection[static_cast<uint8_t>(
+                accData::orientations[i].xAxis)],
+            humanFriendlyDirection[static_cast<uint8_t>(
+                accData::orientations[i].yAxis)]);
+
+        for (unsigned j = 0; j < accData::nSamples; j++)
+        {
+            AccelerometerData out;
+            Vector3f exact, before, after, d0, d1;
+
+            exact = accData::orientations[i].getMatrix().transpose() *
+                    Vector3f{0, 0, 1};
+            out = corrector.correct(accData::samples[i][j]);
+            out >> after;
+            accData::samples[i][j] >> before;
+
+            /* Every five samples, print one */
+            if (j % 5 == 0)
+            {
+                TRACE("%f %f %f -> %f %f %f\n", before[0], before[1], before[2],
+                      after[0], after[1], after[2]);
+            }
+
+            d0 = exact - before;
+            d1 = exact - after;
+
+            err0 += d0.norm();
+            err1 += d1.norm();
+
+            vec0 += Vector3f{abs(d0[0]), abs(d0[1]), abs(d0[2])};
+            vec1 += Vector3f{abs(d1[0]), abs(d1[1]), abs(d1[2])};
+        }
+    }
+
+    const unsigned n = accData::nOrientations * accData::nSamples;
+
+    err0 /= n;
+    err1 /= n;
+    vec0 /= n;
+    vec1 /= n;
+
+    TRACE(
+        "Average norm of displacement vector on accelerometer data: %.3f "
+        "(before it was %.3f)\n",
+        err1, err0);
+    TRACE("Here is the average error per axis (in percentage):\n");
+    TRACE("Before calibration:\t\tx: %3.3f%%\ty: %3.3f%%\tz: %3.3f%%\n",
+          vec0[0] * 100, vec0[1] * 100, vec0[2] * 100);
+    TRACE("After calibration:\t\tx: %3.3f%%\ty: %3.3f%%\tz: %3.3f%%\n",
+          vec1[0] * 100, vec1[1] * 100, vec1[2] * 100);
+
+    TRACE("The parameters are:\n\n");
+
+#if BIAS_CALIBRATION_TEST
+    Vector3f bias;
+    corrector >> bias;
+
+    TRACE("b: the bias vector\n");
+    TRACE("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", bias[0], bias[1],
+          bias[2]);
+#endif
+
+#if SIX_PARAMETER_CALIBRATION_TEST
+    Matrix<float, 3, 2> m;
+    corrector >> m;
+
+    TRACE("b: the bias vector\n");
+    TRACE("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", m(0, 1), m(1, 1),
+          m(2, 1));
+
+    TRACE("M: the matrix to be multiplied to the input vector\n");
+
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(0, 0), 0.f, 0.f);
+    TRACE("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, m(1, 0), 0.f);
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f, m(2, 0));
+#endif
+
+#if TWELVE_PARAMETER_CALIBRATION_TEST
+    Matrix<float, 3, 4> m;
+    corrector >> m;
+
+    TRACE("b: the bias vector\n");
+    TRACE("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", m(0, 1), m(1, 1),
+          m(2, 1));
+
+    TRACE("M: the matrix to be multiplied to the input vector\n");
+
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(0, 0), m(0, 1),
+          m(0, 2));
+    TRACE("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", m(1, 0), m(1, 1),
+          m(1, 2));
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(2, 0), m(2, 1),
+          m(2, 2));
+#endif
+
+#endif /* #if TEST_ACCELEROMETER_DATA */
+
+#if TEST_MAGNETOMETER_DATA
+
+    constexpr unsigned NumSamples = magnetoData::nSamples;
+
+#if HARD_IRON_CALIBRATION_TEST
+    auto* model = new HardIronCalibration<NumSamples>;
+
+    TRACE("Using Hard-iron correction model.\n");
+#endif
+
+#if SOFT_IRON_CALIBRATION_TEST
+    auto* model = new SoftIronCalibration<NumSamples>;
+
+    TRACE("Using Soft-iron correction model.\n");
+#endif
+
+    TRACE("Feeding magnetometer data to the calibration model ... \n");
+
+    for (unsigned i = 0; i < magnetoData::nSamples; i++)
+        model->feed(magnetoData::samples[i]);
+
+    TRACE("Computing the result ... \n");
+
+    auto corrector = model->computeResult();
+
+    TRACE("Done.\n");
+
+    /* Prints only 1/10 of samples */
+    for (unsigned i = 0; i < magnetoData::nSamples; i += 10)
+    {
+        MagnetometerData out;
+        Vector3f before, after;
+
+        out = corrector.correct(magnetoData::samples[i]);
+        out >> after;
+        magnetoData::samples[i] >> before;
+
+        TRACE("%f %f %f -> %f %f %f\n", before[0], before[1], before[2],
+              after[0], after[1], after[2]);
+    }
+
+    TRACE("The parameters are:\n\n");
+
+#if HARD_IRON_CALIBRATION_TEST
+    Vector3f bias;
+    corrector >> bias;
+
+    TRACE("v: the hard iron correction\n");
+    TRACE("v = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", bias[0], bias[1],
+          bias[2]);
+#endif
+
+#if SOFT_IRON_CALIBRATION_TEST
+    Matrix<float, 3, 2> m;
+    corrector >> m;
+
+    TRACE("v: the hard iron correction\n");
+    TRACE("v = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n", m(0, 1), m(1, 1),
+          m(2, 1));
+
+    TRACE("W: the soft iron correction\n");
+
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", m(0, 0), 0.f, 0.f);
+    TRACE("W = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, m(1, 0), 0.f);
+    TRACE("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f, m(2, 0));
+#endif
+
+#endif /* #if TEST_MAGNETOMETER_DATA */
+}
diff --git a/src/tests/catch/catch-tests-entry.cpp b/src/tests/catch/catch-tests-entry.cpp
index b3d785223af626e93de1ccb1fbbaf31a70032f0d..119c654e0942973cc8de1a9eca7d6ea3f33b3692 100644
--- a/src/tests/catch/catch-tests-entry.cpp
+++ b/src/tests/catch/catch-tests-entry.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/examples/example-test-factorial.cpp b/src/tests/catch/examples/example-test-factorial.cpp
index c2b2a0a015d50061fd3fb9c71ad89f2d5c345c1a..2e0ed3d531130c1b7080c38864030d97f6ba3636 100644
--- a/src/tests/catch/examples/example-test-factorial.cpp
+++ b/src/tests/catch/examples/example-test-factorial.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/examples/example-test-fsm.cpp b/src/tests/catch/examples/example-test-fsm.cpp
index 5a9559c029d7c0fc5deedebf88792a7159fcff65..7c480923cb82561e393f72c554c9cd256756484b 100644
--- a/src/tests/catch/examples/example-test-fsm.cpp
+++ b/src/tests/catch/examples/example-test-fsm.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/examples/example-test-fsm.h b/src/tests/catch/examples/example-test-fsm.h
index f8c7aada32e09492a65603181a7efcbb19c4bcb0..6a2b84c14cece5a75ad277b10f6bff22c61314f6 100644
--- a/src/tests/catch/examples/example-test-fsm.h
+++ b/src/tests/catch/examples/example-test-fsm.h
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/spidriver/test-spidriver.cpp b/src/tests/catch/spidriver/test-spidriver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0bcc3975f80f5bd47006326a310c370535f20e42
--- /dev/null
+++ b/src/tests/catch/spidriver/test-spidriver.cpp
@@ -0,0 +1,547 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "../catch-tests-entry.cpp"
+#endif
+
+#ifndef USE_MOCK_PERIPHERALS
+#error "This test requires SpiBusInterface built with MockGpioPin (-DUSE_MOCK_PERIPHERALS)"
+#endif
+
+#include <utils/testutils/catch.hpp>
+
+#include "drivers/spi/SPIBus.h"
+#include "drivers/spi/SPIDriver.h"
+#include "drivers/spi/test/FakeSpiTypedef.h"
+#include "drivers/spi/test/MockSPIBus.h"
+
+template <typename T1, typename T2>
+bool bufcmp(T1* buf1, T2* buf2, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        if (*buf1 != *buf2)
+            return false;
+
+        buf1++;
+        buf2++;
+    }
+    return true;
+}
+
+TEST_CASE("SPIBus - Bus Configuration")
+{
+    FakeSpiTypedef spi;
+
+    SPIBus bus{&spi};
+
+    REQUIRE(spi.CR1 == 0);
+
+    SECTION("Configure & check CR1")
+    {
+        SPIBusConfig config;
+        REQUIRE(spi.CR1 == 0);
+
+        SECTION("Mode")
+        {
+            config.mode           = SPIMode::MODE0;
+            uint32_t expected_CR1 = 0x0344;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.mode  = SPIMode::MODE1;
+            expected_CR1 = 0x0345;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.mode  = SPIMode::MODE2;
+            expected_CR1 = 0x0346;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.mode  = SPIMode::MODE3;
+            expected_CR1 = 0x0347;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+        }
+
+        SECTION("Clock Divider")
+        {
+            config.clock_div      = SPIClockDivider::DIV2;
+            uint32_t expected_CR1 = 0x0344;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV4;
+            expected_CR1     = 0x034C;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV8;
+            expected_CR1     = 0x0354;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV16;
+            expected_CR1     = 0x035C;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV32;
+            expected_CR1     = 0x0364;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV64;
+            expected_CR1     = 0x036C;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV128;
+            expected_CR1     = 0x0374;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.clock_div = SPIClockDivider::DIV256;
+            expected_CR1     = 0x037C;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+        }
+
+        SECTION("Bit order")
+        {
+            config.bit_order      = SPIBitOrder::MSB_FIRST;
+            uint32_t expected_CR1 = 0x0344;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+
+            config.bit_order = SPIBitOrder::LSB_FIRST;
+            expected_CR1     = 0x03C4;
+            bus.acquire(config);
+            REQUIRE(spi.CR1 == expected_CR1);
+            bus.release();
+        }
+    }
+
+    SECTION("Disable configuration")
+    {
+        SPIBusConfig config;
+        config.clock_div = SPIClockDivider::DIV16;
+
+        config.mode      = SPIMode::MODE3;
+        config.bit_order = SPIBitOrder::LSB_FIRST;
+
+        bus.disableBusConfiguration();
+        bus.acquire(config);
+        REQUIRE(spi.CR1 == 0);
+        bus.release();
+    }
+}
+
+TEST_CASE("SPIBus - Chip select")
+{
+    FakeSpiTypedef spi;
+
+    SPIBus bus{&spi};
+
+    REQUIRE(spi.cs.value() == 1);
+
+    bus.select(spi.cs);
+    REQUIRE(spi.cs.value() == 0);
+
+    bus.deselect(spi.cs);
+    REQUIRE(spi.cs.value() == 1);
+}
+
+TEST_CASE("SPIBus - One byte operations")
+{
+    FakeSpiTypedef spi;
+
+    spi.DR.in_buf    = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    spi.CR1_expected = 0x03DF;
+
+    SPIBus bus{&spi};
+
+    SPIBusConfig config;
+    config.clock_div = SPIClockDivider::DIV16;
+
+    config.mode      = SPIMode::MODE3;
+    config.bit_order = SPIBitOrder::LSB_FIRST;
+
+    bus.acquire(config);
+    bus.select(spi.cs);
+
+    SECTION("Write")
+    {
+        bus.write(1);
+        REQUIRE(spi.DR.out_buf.back() == 1);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+
+        bus.write(2);
+        REQUIRE(spi.DR.out_buf.back() == 2);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+    }
+    SECTION("Read")
+    {
+        REQUIRE(bus.read() == spi.DR.in_buf[0]);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+        REQUIRE(spi.DR.out_buf.back() == 0);
+
+        REQUIRE(bus.read() == spi.DR.in_buf[1]);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+        REQUIRE(spi.DR.out_buf.back() == 0);
+    }
+
+    SECTION("Transfer")
+    {
+        REQUIRE(bus.transfer(55) == spi.DR.in_buf[0]);
+        REQUIRE(spi.DR.out_buf.back() == 55);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+
+        REQUIRE(bus.transfer(255) == spi.DR.in_buf[1]);
+        REQUIRE(spi.DR.out_buf.back() == 255);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+    }
+
+    bus.deselect(spi.cs);
+    bus.release();
+}
+
+TEST_CASE("SPIBus - Multi byte operations")
+{
+    FakeSpiTypedef spi;
+
+    spi.DR.in_buf    = {100, 101, 102, 103, 104, 105, 106, 107, 108};
+    spi.CR1_expected = 0x03DF;
+
+    SPIBus bus{&spi};
+
+    SPIBusConfig config;
+    config.clock_div = SPIClockDivider::DIV16;
+
+    config.mode      = SPIMode::MODE3;
+    config.bit_order = SPIBitOrder::LSB_FIRST;
+
+    bus.acquire(config);
+    bus.select(spi.cs);
+
+    // 2 identical buffers
+    uint8_t buf[]  = {5, 4, 3, 2, 1};
+    uint8_t bufc[] = {5, 4, 3, 2, 1};
+
+    SECTION("Write")
+    {
+        bus.write(buf, 0);
+        REQUIRE(spi.DR.out_buf.size() == 0);
+
+        bus.write(buf, 1);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+        REQUIRE(spi.DR.out_buf.back() == bufc[0]);
+
+        bus.write(buf, 4);
+        REQUIRE(spi.DR.out_buf.size() == 5);
+        REQUIRE(bufcmp(bufc, spi.DR.out_buf.data() + 1, 4));
+    }
+
+    SECTION("Read")
+    {
+        bus.read(buf, 0);
+        // Nothing read
+        REQUIRE(bufcmp(bufc, buf, 5));
+
+        bus.read(buf, 1);
+        REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 1));
+        // No overflows
+        REQUIRE(bufcmp(bufc + 1, buf + 1, 4));
+
+        bus.read(buf, 4);
+        REQUIRE(bufcmp(buf, spi.DR.in_buf.data() + 1, 4));
+        // No overflows
+        REQUIRE(bufcmp(bufc + 4, buf + 4, 1));
+    }
+
+    SECTION("Transfer")
+    {
+        bus.transfer(buf, 0);
+        // Nothing read
+        REQUIRE(bufcmp(bufc, buf, 4));
+        // Nothing written
+        REQUIRE(spi.DR.out_buf.size() == 0);
+
+        bus.transfer(buf, 1);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+        REQUIRE(bufcmp(bufc, spi.DR.out_buf.data(), 1));
+        REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 1));
+        // No overflows
+        REQUIRE(bufcmp(bufc + 1, buf + 1, 4));
+
+        bus.transfer(buf + 1, 3);
+        REQUIRE(spi.DR.out_buf.size() == 4);
+        REQUIRE(bufcmp(bufc + 1, spi.DR.out_buf.data() + 1, 3));
+        REQUIRE(bufcmp(buf + 1, spi.DR.in_buf.data() + 1, 3));
+        // No overflows
+        REQUIRE(bufcmp(bufc + 4, buf + 4, 1));
+    }
+
+    bus.deselect(spi.cs);
+    bus.release();
+}
+
+TEST_CASE("SPITransaction - writes")
+{
+    SPIBusConfig config1{};
+
+    config1.mode      = SPIMode::MODE1;
+    config1.clock_div = SPIClockDivider::DIV32;
+
+    MockSPIBus bus(config1);
+    MockGpioPin cs;
+
+    SECTION("Transaction")
+    {
+        SPITransaction spi(bus, cs, config1);
+
+        REQUIRE(bus.getOutBuf().size() == 0);
+
+        SECTION("cmd write")
+        {
+            spi.write(9);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 1);
+            REQUIRE(bus.getOutBuf().back() == 9);
+        }
+
+        SECTION("1 byte reg write")
+        {
+            spi.write(10, 77);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 2);
+            REQUIRE(bus.getOutBuf()[0] == 10);
+            REQUIRE(bus.getOutBuf()[1] == 77);
+        }
+
+        SECTION("multi byte reg write")
+        {
+            uint8_t buf[] = {1, 2, 3, 4, 5, 6};
+
+            SECTION("0 size write")
+            {
+                spi.write(10, buf, 0);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.getOutBuf().size() == 1);
+                REQUIRE(bus.getOutBuf()[0] == 10);
+            }
+
+            SECTION("2 writes")
+            {
+                spi.write(10, buf, 4);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.getOutBuf().size() == 5);
+
+                REQUIRE(bus.getOutBuf()[0] == 10);
+                REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 1, 4));
+
+                spi.write(99, buf, 6);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.getOutBuf().size() == 12);
+
+                REQUIRE(bus.getOutBuf()[5] == 99);
+                REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 6, 6));
+            }
+        }
+
+        SECTION("raw write")
+        {
+            uint8_t buf[] = {1, 2, 3, 4, 5, 6};
+
+            spi.write(buf, 0);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 0);
+
+            spi.write(buf, 4);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 4);
+
+            REQUIRE(bufcmp(buf, bus.getOutBuf().data(), 4));
+
+            spi.write(buf, 6);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 10);
+
+            REQUIRE(bufcmp(buf, bus.getOutBuf().data() + 4, 6));
+        }
+    }
+}
+
+TEST_CASE("SPITransaction - reads")
+{
+    SPIBusConfig config1;
+
+    config1.mode      = SPIMode::MODE1;
+    config1.clock_div = SPIClockDivider::DIV32;
+
+    MockSPIBus bus(config1);
+    MockGpioPin cs;
+
+    uint8_t in_data[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};
+    bus.push(in_data, 10);
+
+    SECTION("Transaction")
+    {
+        SPISlave slave(bus, cs, config1);
+        SPITransaction spi(slave);
+
+        REQUIRE(bus.getOutBuf().size() == 0);
+
+        SECTION("1 byte reg read")
+        {
+
+            REQUIRE(spi.read(0x05) == in_data[0]);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 1);
+            REQUIRE(bus.getOutBuf().back() == 0x85);
+
+            REQUIRE(spi.read(0x05, true) == in_data[1]);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 2);
+            REQUIRE(bus.getOutBuf().back() == 0x85);
+
+            REQUIRE(spi.read(0x05, false) == in_data[2]);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.getOutBuf().size() == 3);
+            REQUIRE(bus.getOutBuf().back() == 0x05);
+        }
+
+        SECTION("multi byte reg read")
+        {
+            const int buf_size = 7;
+            uint8_t buf[]      = {1, 2, 3, 4, 5, 6, 7};
+            uint8_t cmp[]      = {1, 2, 3, 4, 5, 6, 7};
+
+            spi.read(0x05, buf, 0);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 1);
+            REQUIRE(bus.getOutBuf().back() == 0x85);
+            REQUIRE(bufcmp(buf, cmp, buf_size));
+
+            spi.read(0x05, buf, 3);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 2);
+            REQUIRE(bus.getOutBuf().back() == 0x85);
+            REQUIRE(bufcmp(buf, &in_data[0], 3));
+            REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3));
+
+            spi.read(0x05, buf, 3, true);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 3);
+            REQUIRE(bus.getOutBuf().back() == 0x85);
+            REQUIRE(bufcmp(buf, &in_data[3], 3));
+            REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3));
+
+            spi.read(0x05, buf, 4, false);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 4);
+            REQUIRE(bus.getOutBuf().back() == 0x05);
+            REQUIRE(bufcmp(buf,&in_data[6], 4));
+            REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4));
+        }
+
+        SECTION("multi byte raw read")
+        {
+            const int buf_size = 7;
+            uint8_t buf[]      = {1, 2, 3, 4, 5, 6, 7};
+            uint8_t cmp[]      = {1, 2, 3, 4, 5, 6, 7};
+
+            spi.read(buf, 0);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 0);
+            REQUIRE(bufcmp(buf, cmp, buf_size));
+
+            spi.read(buf, 3);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.getOutBuf().size() == 0);
+            REQUIRE(bufcmp(buf, &in_data[0], 3));
+            REQUIRE(bufcmp(buf + 3, cmp + 3, buf_size - 3));
+        }
+    }
+}
+
+TEST_CASE("SPITransaction - transfer")
+{
+    SPIBusConfig config1;
+
+    config1.mode      = SPIMode::MODE1;
+    config1.clock_div = SPIClockDivider::DIV32;
+
+    MockSPIBus bus(config1);
+    MockGpioPin cs;
+
+    uint8_t data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    
+    bus.push(data, 10);
+
+    SECTION("Transaction")
+    {
+        SPISlave slave(bus, cs, config1);
+        SPITransaction spi(slave);
+
+        const int buf_size = 7;
+        uint8_t buf[]      = {1, 2, 3, 4, 5, 6, 7};
+        uint8_t cmp[]      = {1, 2, 3, 4, 5, 6, 7};
+
+        spi.transfer(buf, 0);
+        REQUIRE_FALSE(bus.isSelected());
+        REQUIRE(bus.getOutBuf().size() == 0);
+        REQUIRE(bufcmp(buf, cmp, buf_size));
+
+        spi.transfer(buf, 4);
+        REQUIRE_FALSE(bus.isSelected());
+        REQUIRE(bus.getOutBuf().size() == 4);
+        REQUIRE(bufcmp(buf, &data[0], 4));
+        REQUIRE(bufcmp(cmp, bus.getOutBuf().data(), 4));
+        REQUIRE(bufcmp(buf + 4, cmp + 4, buf_size - 4));
+    }
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-aero.cpp b/src/tests/catch/test-aero.cpp
index 440bd0c444ad45c0a4b01c475afc80ce97d0ca4e..b9014c3a4bcf46a17c2e06bccacbc14e3e2ca494 100644
--- a/src/tests/catch/test-aero.cpp
+++ b/src/tests/catch/test-aero.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -80,6 +79,22 @@ TEST_CASE("[AeroUtils] relAltitude")
             Approx(-100).epsilon(0.0001));
 }
 
+TEST_CASE("[AeroUtils] relDensity")
+{
+    using namespace aeroutils;
+
+    REQUIRE(relDensity(101325, 101325, 0, 288.15) ==
+            Approx(1.225).epsilon(0.0001));
+    REQUIRE(relDensity(100129.438691069, 101325, 0, 288.15) ==
+            Approx(1.21328277727309).epsilon(0.0001));
+    REQUIRE(relDensity(89874.5715517214, 101325, 0, 288.15) ==
+            Approx(1.11164259066989).epsilon(0.0001));
+    REQUIRE(relDensity(70108.5471843675, 101325, 0, 288.15) ==
+            Approx(0.909122116038058).epsilon(0.0001));
+    REQUIRE_FALSE(relDensity(101325, 101325, 0, 288.15) ==
+            Approx(1.226).epsilon(0.0001));
+}
+
 TEST_CASE("[AeroUtils] verticalSpeed")
 {
     using namespace aeroutils;
diff --git a/src/tests/catch/test-buttonhandler.cpp b/src/tests/catch/test-buttonhandler.cpp
index f9df51d0f4fa7201e6ed273eb62fd837ac2fbf22..6be1aa02c5186a31a533946667ee92995c72eee6 100644
--- a/src/tests/catch/test-buttonhandler.cpp
+++ b/src/tests/catch/test-buttonhandler.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/test-circularbuffer.cpp b/src/tests/catch/test-circularbuffer.cpp
index c4c1b63d732d63e74a1d9b744331ec9ba9025365..403d74cb5b54fe4e04d218c175f4f726223251f6 100644
--- a/src/tests/catch/test-circularbuffer.cpp
+++ b/src/tests/catch/test-circularbuffer.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/test-eventbroker.cpp b/src/tests/catch/test-eventbroker.cpp
index 8ae055781de48bc6a1250d1da559e3d800cc676c..1e9ff378d9ef20d48ebf911fc6b25696b10dc669 100644
--- a/src/tests/catch/test-eventbroker.cpp
+++ b/src/tests/catch/test-eventbroker.cpp
@@ -1,12 +1,11 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
- * Permission is herbrokery 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
+ * 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
@@ -14,13 +13,14 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
+
 #ifdef STANDALONE_CATCH1_TEST
 #include "catch-tests-entry.cpp"
 #endif
diff --git a/src/tests/catch/test-hardwaretimer.cpp b/src/tests/catch/test-hardwaretimer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cc817a8784f277f8e9cc0e1718a606a718e09686
--- /dev/null
+++ b/src/tests/catch/test-hardwaretimer.cpp
@@ -0,0 +1,126 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "catch-tests-entry.cpp"
+#endif
+
+#include <drivers/HardwareTimer.h>
+#include <miosix.h>
+
+#include <utils/testutils/catch.hpp>
+
+using namespace miosix;
+
+class TimerTestFixture
+{
+public:
+    TimerTestFixture()
+        : timer32(TIM5, TimerUtils::getPrescalerInputFrequency(
+                            TimerUtils::InputClock::APB1)),
+          timer16(TIM10, TimerUtils::getPrescalerInputFrequency(
+                             TimerUtils::InputClock::APB2))
+    {
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR |= RCC_APB2ENR_TIM10EN;
+    }
+
+    ~TimerTestFixture()
+    {
+        timer16.stop();
+        timer32.stop();
+
+        RCC->APB1ENR &= ~RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR &= ~RCC_APB2ENR_TIM10EN;
+    }
+
+    HardwareTimer<uint32_t> timer32;
+    HardwareTimer<uint16_t> timer16;
+};
+
+TEST_CASE_METHOD(TimerTestFixture, "Test basic functionality")
+{
+    timer32.setPrescaler(63);
+    timer16.setPrescaler(127);
+
+    REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001));
+    REQUIRE(timer16.getMaxDuration() == Approx(0.049).margin(0.001));
+
+    REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001));
+    REQUIRE(timer16.getResolution() == Approx(0.761).margin(0.001));
+
+    REQUIRE(timer32.tick() == 0);
+    REQUIRE(timer16.tick() == 0);
+
+    REQUIRE(timer32.start() == 0);
+    REQUIRE(timer16.start() == 0);
+
+    Thread::sleep(10);
+
+    uint32_t tick32 = timer32.tick();
+    uint32_t tick16 = timer16.tick();
+
+    REQUIRE(timer32.toMilliSeconds(tick32) == Approx(10).margin(1));
+    REQUIRE(timer16.toMilliSeconds(tick16) == Approx(10).margin(1));
+
+    Thread::sleep(30);
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick() - tick32) ==
+            Approx(30).margin(1));
+    REQUIRE(timer16.toMilliSeconds(timer16.tick() - tick16) ==
+            Approx(30).margin(1));
+
+    tick32 = timer32.stop();
+    tick16 = timer16.stop();
+
+    Thread::sleep(20);
+
+    REQUIRE(timer32.tick() == tick32);
+    REQUIRE(timer16.tick() == tick16);
+}
+
+TEST_CASE_METHOD(TimerTestFixture, "Test long term precision")
+{
+    timer32.setPrescaler(63);
+    timer16.setPrescaler(65535);  // Max prescaler
+
+    REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001));
+    REQUIRE(timer16.getMaxDuration() == Approx(25.565).margin(0.001));
+
+    REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001));
+    REQUIRE(timer16.getResolution() == Approx(390.095).margin(0.001));
+
+    REQUIRE(timer32.start() == 0);
+    REQUIRE(timer16.start() == 0);
+
+    Thread::sleep(24000);
+
+    uint32_t tick32 = timer32.tick();
+    uint32_t tick16 = timer16.tick();
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(24000).margin(1));
+    REQUIRE(timer16.toMilliSeconds(timer16.tick()) == Approx(24000).margin(1));
+
+    Thread::sleep(36000);
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(60000).margin(1));
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-kalman-eigen.cpp b/src/tests/catch/test-kalman-eigen.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62074f3e32f2ea7357abaf4e779a77af41acfe19
--- /dev/null
+++ b/src/tests/catch/test-kalman-eigen.cpp
@@ -0,0 +1,175 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Luca Mozzarelli
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "catch-tests-entry.cpp"
+#endif
+
+#define EIGEN_RUNTIME_NO_MALLOC
+
+#include <src/tests/kalman/test-kalman-data.h>
+
+#include <iostream>
+#include <utils/testutils/catch.hpp>
+
+#include "kalman/KalmanEigen.h"
+
+using namespace Eigen;
+
+static const uint8_t STATES_DIM  = 3;
+static const uint8_t OUTPUTS_DIM = 1;
+
+static const Matrix<float, STATES_DIM, STATES_DIM> F =
+    (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 1, 0.2,
+     0.02, 0, 1, 0.2, 0, 0, 1)
+        .finished();
+// Output matrix
+static const Matrix<float, OUTPUTS_DIM, STATES_DIM> H{1, 0, 0};
+
+// Initial error covariance matrix
+static const Matrix<float, STATES_DIM, STATES_DIM> P =
+    (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 0.1, 0, 0,
+     0, 0.1, 0, 0, 0, 0.1)
+        .finished();
+// Model variance matrix
+static const Matrix<float, STATES_DIM, STATES_DIM> Q =
+    (Matrix<float, STATES_DIM, STATES_DIM>(STATES_DIM, STATES_DIM) << 0.01, 0,
+     0, 0, 0.01, 0, 0, 0, 0.01)
+        .finished();
+// Measurement variance
+static const Matrix<float, OUTPUTS_DIM, OUTPUTS_DIM> R{10};
+// State vector
+static const Matrix<float, STATES_DIM, 1> x0(INPUT[0], 0.0, 0.0);
+
+static const KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig
+getKalmanConfig()
+{
+    KalmanEigen<float, STATES_DIM, OUTPUTS_DIM>::KalmanConfig config;
+    config.F = F;
+    config.H = H;
+    config.Q = Q;
+    config.R = R;
+    config.P = P;
+    config.x = x0;
+
+    return config;
+}
+
+TEST_CASE("Update test")
+{
+    KalmanEigen<float, STATES_DIM, OUTPUTS_DIM> filter(getKalmanConfig());
+
+    Matrix<float, OUTPUTS_DIM, 1> y{};
+    float T;
+    float last_time = TIME[0];
+
+    for (unsigned i = 1; i < 101; i++)
+    {
+        // printf("i = %d \n", i);
+
+        y(0, 0) = INPUT[i];
+        T       = TIME[i] - last_time;
+
+        Matrix<float, STATES_DIM, STATES_DIM> F_new;
+        F_new << F;
+        F_new(0, 1) = T;
+        F_new(0, 2) = 0.5 * T * T;
+        F_new(1, 2) = T;
+
+        filter.predict(F_new);
+
+        if (!filter.correct(y))
+        {
+            FAIL("Correction failed at iteration : %d \n", i);
+        }
+
+        if (filter.getState()(0, 0) != Approx(STATE_1[i]).epsilon(0.01))
+        {
+            FAIL("FAILED X(0,0) " << filter.getState()(0, 0)
+                                  << " != " << STATE_1[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+        if (filter.getState()(1, 0) != Approx(STATE_2[i]).epsilon(0.01))
+        {
+            FAIL("FAILED X(1,0) " << filter.getState()(1, 0)
+                                  << " != " << STATE_2[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+        if (filter.getState()(2, 0) != Approx(STATE_3[i]).epsilon(0.01))
+        {
+            FAIL("FAILED X(2,0) " << filter.getState()(2, 0)
+                                  << " != " << STATE_3[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+
+        auto predictedState = filter.predictState(5);
+        if (predictedState(0, 0) != Approx(PRED_STATE_1[i]).epsilon(0.01))
+        {
+            FAIL("FAILED PREDICTED X(0,0) " << predictedState(0, 0)
+                                            << " != " << PRED_STATE_1[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+        if (predictedState(1, 0) != Approx(PRED_STATE_2[i]).epsilon(0.01))
+        {
+            FAIL("FAILED PREDICTED X(1,0) " << predictedState(1, 0)
+                                            << " != " << PRED_STATE_2[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+        if (predictedState(2, 0) != Approx(PRED_STATE_3[i]).epsilon(0.01))
+        {
+            FAIL("FAILED PREDICTED X(2,0) " << predictedState(2, 0)
+                                            << " != " << PRED_STATE_3[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+
+        auto predictedOutput = filter.predictOutput(5);
+        if (predictedOutput(0, 0) != Approx(PRED_STATE_1[i]).epsilon(0.01))
+        {
+            FAIL("FAILED PREDICTED Y " << predictedState(0, 0)
+                                       << " != " << PRED_STATE_1[i]);
+        }
+        else
+        {
+            SUCCEED();
+        }
+
+        last_time = TIME[i];
+    }
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-kalman.cpp b/src/tests/catch/test-kalman.cpp
index e5819da1ebcf7a5d2253099fa12e685cef202746..340fc96a7d34b1fd1f3858146a10aa4285e91326 100644
--- a/src/tests/catch/test-kalman.cpp
+++ b/src/tests/catch/test-kalman.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Mozzarelli
+ * Author: Luca Mozzarelli
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -26,10 +26,10 @@
 
 #include <utils/testutils/catch.hpp>
 #include <iostream>
-#include <src/tests/kalman/test-kalman-data.h>
-#include "kalman/Kalman.h"
 
+#include <src/tests/kalman/test-kalman-data.h>
 
+#include "kalman/Kalman.h"
 
 static MatrixBase<float, 3, 3>
         A{1, 0.2, 0.02, 0, 1, 0.2, 0, 0, 1};
diff --git a/src/tests/catch/test-matrix.cpp b/src/tests/catch/test-matrix.cpp
index 32ee05424d6897230642bc8afe077bdc30bb82a7..82d5c7f480a111ceb936297b9d6ede9f2fa74502 100644
--- a/src/tests/catch/test-matrix.cpp
+++ b/src/tests/catch/test-matrix.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Mozzarelli
+ * Author: Luca Mozzarelli
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -34,6 +34,8 @@ TEST_CASE("Multiply test")
     MatrixBase<float, 3, 1> B{1.0f, 2.0f, 3.7f};
     MatrixBase<float, 3, 1> C{};
 
+    C = A * B;
+
     REQUIRE( C(0,0) == Approx( 8.1f)  );
     REQUIRE( C(1,0) == Approx(-7.7f)  );
     REQUIRE( C(2,0) == Approx(-10.3f) );
@@ -46,6 +48,8 @@ TEST_CASE("Sum test")
 
     MatrixBase<float, 3, 3> C{};
 
+    C = A + B;
+
     REQUIRE( C(0,0) == Approx(2.0f ));
     REQUIRE( C(0,1) == Approx(0.0f ));
     REQUIRE( C(0,2) == Approx(6.0f ));
@@ -64,6 +68,8 @@ TEST_CASE("Subtract test")
 
     MatrixBase<float, 3, 3> C{};
 
+    C = A - B;
+    
     REQUIRE( C(0,0) == Approx(0.0f  ));
     REQUIRE( C(0,1) == Approx(-4.0f ));
     REQUIRE( C(0,2) == Approx(0.0f  ));
diff --git a/src/tests/catch/test-packetqueue.cpp b/src/tests/catch/test-packetqueue.cpp
index 58e6bc12145070d0c6cfc177276686592d99de87..01be7038568b90b88d0278f094d81d6488e7fe57 100644
--- a/src/tests/catch/test-packetqueue.cpp
+++ b/src/tests/catch/test-packetqueue.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/test-sensormanager-catch.cpp b/src/tests/catch/test-sensormanager-catch.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ec4cec8f378ef38b79c5d8f550953222d50895fc
--- /dev/null
+++ b/src/tests/catch/test-sensormanager-catch.cpp
@@ -0,0 +1,306 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "catch-tests-entry.cpp"
+#endif
+
+#include <iostream>
+
+#include "utils/testutils/TestSensor.h"
+#include "utils/testutils/catch.hpp"
+
+#define private public
+#define protected public
+
+#include "sensors/SensorInfo.h"
+#include "sensors/SensorManager.h"
+
+static const uint8_t FIRST_TASK_ID = 7;  // used to test IDs assignment to tasks
+
+class FailingSensor : public Sensor<TestData>
+{
+    bool init() { return true; }
+
+    bool selfTest() { return false; }  // always fail self-test
+
+    TestData sampleImpl() { return TestData{}; }
+};
+
+class SensorManagerFixture
+{
+public:
+    SensorManagerFixture()
+    {
+        scheduler = new TaskScheduler();
+        scheduler->add([]() { std::cout << "Task Callback!" << endl; },
+                       2000,  // inserst a test function in the scheduler
+                       FIRST_TASK_ID);
+
+        sensor_manager = new SensorManager(scheduler, {{&s1, s1_info},
+                                                       {&s2, s2_info},
+                                                       {&s3, s3_info},
+                                                       {&s4, s4_info},
+                                                       {&s5, s5_info}});
+
+        sampler1 = sensor_manager->samplers_map[&s1];
+        sampler2 = sensor_manager->samplers_map[&s2];
+        sampler3 = sensor_manager->samplers_map[&s3];
+        sampler4 = sensor_manager->samplers_map[&s4];
+        sampler5 = sensor_manager->samplers_map[&s5];
+    }
+
+    ~SensorManagerFixture()
+    {
+        sensor_manager->stop();
+
+        delete sensor_manager;
+    }
+
+private:
+    TaskScheduler* scheduler;
+
+    SensorManager* sensor_manager;
+
+    SensorSampler* sampler1;
+    SensorSampler* sampler2;
+    SensorSampler* sampler3;
+    SensorSampler* sampler4;
+    SensorSampler* sampler5;
+
+    TestSensor s1;
+    SensorInfo s1_info{
+        /*ID=*/"s1",
+        /*Period=*/1000,
+        /*Callback=*/[]() { std::cout << "Callback 1!" << endl; },
+        /*DMA=*/false,
+        /*Enabled=*/true};
+
+    TestSensor s2;
+    SensorInfo s2_info{
+        /*ID=*/"s2",
+        /*Period=*/1000,
+        /*Callback=*/[]() { std::cout << "Callback 2!" << endl; },
+        /*DMA=*/false,
+        /*Enabled=*/false};
+
+    TestSensor s3;
+    SensorInfo s3_info{
+        /*ID=*/"s3",
+        /*Period=*/500,
+        /*Callback=*/[]() { std::cout << "Callback 3!" << endl; },
+        /*DMA=*/false,
+        /*Enabled=*/true};
+
+    // same period as s1 and s2 but uses DMA
+    TestSensor s4;
+    SensorInfo s4_info{
+        /*ID=*/"s4",
+        /*Period=*/1000,
+        /*Callback=*/[]() { std::cout << "Callback 4!" << endl; },
+        /*DMA=*/true,
+        /*Enabled=*/true};
+
+    // always failing self-test
+    FailingSensor s5;
+    SensorInfo s5_info{
+        /*ID=*/"s5",
+        /*Period=*/2000,
+        /*Callback=*/[]() { std::cout << "Callback 5!" << endl; },
+        /*DMA=*/false,
+        /*Enabled=*/true};
+};
+
+bool operator==(const SensorInfo& lhs, const SensorInfo& rhs)
+{
+    return lhs.id == rhs.id && lhs.period == rhs.period &&
+           lhs.callback.target_type() == rhs.callback.target_type() &&
+           lhs.callback.target<void()>() == rhs.callback.target<void()>() &&
+           lhs.is_dma == rhs.is_dma && lhs.is_enabled == rhs.is_enabled;
+}
+
+bool operator==(const SensorSampler& lhs, const SensorSampler& rhs)
+{
+    return lhs.id == rhs.id && lhs.period == rhs.period &&
+           lhs.is_dma == rhs.is_dma && lhs.sensors.size() == rhs.sensors.size();
+}
+
+TEST_CASE_METHOD(SensorManagerFixture,
+                 "Samplers IDs should incrementally start from FIRST_TASK_ID")
+{
+    sensor_manager->start();
+
+    std::vector<TaskStatResult> tasks_stats = scheduler->getTaskStats();
+
+    std::cout << "Tasks number : " << tasks_stats.size() << endl;
+
+    // Sampler with lower period are inserted in the TaskScheduler
+    // before higher period ones
+    // =>
+    //    Task id 8  : sampler at 1000 ms (1 Hz), not DMA
+    //    Task id 9  : sampler at 500 ms  (2 Hz), not DMA ---> first to be added
+    //                                                         to the scheduler
+    //    Task id 10 : sampler at 1000 ms (1 Hz), with DMA
+    REQUIRE(tasks_stats[0].id == FIRST_TASK_ID);
+    REQUIRE(tasks_stats[1].id == static_cast<uint8_t>(FIRST_TASK_ID + 2));
+    REQUIRE(tasks_stats[2].id == static_cast<uint8_t>(FIRST_TASK_ID + 1));
+    REQUIRE(tasks_stats[3].id == static_cast<uint8_t>(FIRST_TASK_ID + 3));
+    REQUIRE(tasks_stats[4].id == static_cast<uint8_t>(FIRST_TASK_ID + 4));
+}
+
+TEST_CASE_METHOD(SensorManagerFixture,
+                 "Sensors are correctly added to the samplers")
+{
+    // check that 3 samplers exist (1 hz, 2 hz, 1 hz with DMA and 0.5 Hz)
+    REQUIRE(sensor_manager->samplers.size() == 4);
+
+    // samplers are sorted by period, in decreasing order!
+
+    // check that s1, s2 and s3 are assigned to correct samplers
+    REQUIRE(sampler1 == sensor_manager->samplers[1]);
+    REQUIRE(*sampler1 == *(sensor_manager->samplers[1]));
+    // s1 and s2 are assigned to same sampler
+    REQUIRE(sampler1 == sampler2);
+    REQUIRE(*sampler1 == *sampler2);
+    // s3 assigned to another sampler
+    REQUIRE(sampler3 == sensor_manager->samplers[0]);
+    REQUIRE(*sampler3 == *(sensor_manager->samplers[0]));
+    // s4 assigned to another sampler
+    REQUIRE(sampler4 == sensor_manager->samplers[2]);
+    REQUIRE(*sampler4 == *(sensor_manager->samplers[2]));
+    // s5 assigned to the last sampler
+    REQUIRE(sampler5 == sensor_manager->samplers[3]);
+    REQUIRE(*sampler5 == *(sensor_manager->samplers[3]));
+}
+
+TEST_CASE_METHOD(SensorManagerFixture,
+                 "Sensors are correctly coupled with their info and failing sensors are automatically disabled")
+{
+    SensorInfo info1 = sampler1->getSensorInfo(&s1);
+    SensorInfo info2 = sampler2->getSensorInfo(&s2);
+    SensorInfo info3 = sampler3->getSensorInfo(&s3);
+    SensorInfo info4 = sampler4->getSensorInfo(&s4);
+    SensorInfo info5 = sampler5->getSensorInfo(&s5);
+
+    // correctly initialized sensors
+    REQUIRE(s1_info == info1);
+    REQUIRE(s2_info == info2);
+    REQUIRE(s3_info == info3);
+    REQUIRE(s4_info == info4);
+
+    REQUIRE(!(s5_info == info5)); // it fails, so is_enabled is set to false instead of true
+    REQUIRE(s5_info.id == info5.id);
+    REQUIRE(s5_info.period == info5.period);
+    REQUIRE(s5_info.is_dma == info5.is_dma);
+    REQUIRE(info5.is_enabled == false); // disabled even if it was created as enabled
+    REQUIRE(info5.is_initialized == false); // always fails the initialization
+}
+
+TEST_CASE_METHOD(SensorManagerFixture,
+                 "Samplers have the correct number of sensors")
+{
+    // check that sampler at 1000 ms (1 Hz) has 2 sensors
+    // sampler at 500 ms (2 Hz) has 1 sensor
+    // sampler at 1000 ms (1 Hz) with DMA has 1 sensor
+    for (auto s : sensor_manager->samplers)
+    {
+        if (s->getSamplingPeriod() == 1000 && s->isDMA() == false)
+        {
+            REQUIRE(s->getNumSensors() == 2);
+        }
+        else if (s->getSamplingPeriod() == 500)
+        {
+            REQUIRE(s->getNumSensors() == 1);
+        }
+        else if (s->getSamplingPeriod() == 1000 && s->isDMA() == true)
+        {
+            REQUIRE(s->getNumSensors() == 1);
+        }
+        else if (s->getSamplingPeriod() == 2000 && s->isDMA() == false)
+        {
+            REQUIRE(s->getNumSensors() == 1);
+        }
+        else
+        {
+            FAIL(
+                "Can't exist a sampler with period different from 500, 1000 or "
+                "2000 ms");  // no sampler with a different period exist
+        }
+    }
+}
+
+TEST_CASE_METHOD(SensorManagerFixture, "Enable/disable sensors at runtime")
+{
+    sensor_manager->start();
+
+    sensor_manager->enableSensor(&s2);
+    sensor_manager->disableSensor(&s4);
+
+    REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == false);
+
+    sensor_manager->disableSensor(&s2);
+    sensor_manager->enableSensor(&s4);
+
+    REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == false);
+    REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true);
+}
+
+TEST_CASE_METHOD(SensorManagerFixture, "Enable/disable all sensors at runtime")
+{
+    sensor_manager->start();
+
+    sensor_manager->enableAllSensors();
+
+    REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == true);
+
+    sensor_manager->disableAllSensors();
+
+    REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == false);
+    REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == false);
+    REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == false);
+    REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == false);
+    REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == false);
+
+    sensor_manager->enableAllSensors();
+
+    REQUIRE(sensor_manager->getSensorInfo(&s1).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s2).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s3).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s4).is_enabled == true);
+    REQUIRE(sensor_manager->getSensorInfo(&s5).is_enabled == true);
+}
+
+TEST_CASE_METHOD(SensorManagerFixture,
+                 "Try to get info about a non-existing sensor")
+{
+    TestSensor invalid_sensor;
+    SensorInfo invalid_info = sensor_manager->getSensorInfo(&invalid_sensor);
+    REQUIRE(invalid_info == SensorInfo{});
+
+    SensorInfo valid_info = sensor_manager->getSensorInfo(&s2);
+    REQUIRE(valid_info == s2_info);
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-xbee.cpp b/src/tests/catch/test-xbee.cpp
index 887e0523be452250cbcbed34ac755c0f88bcf1f8..64f7f0c00c8ec37669fb42e25efb90759166d7a6 100644
--- a/src/tests/catch/test-xbee.cpp
+++ b/src/tests/catch/test-xbee.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/xbee/MockXbeeSPIBus.h b/src/tests/catch/xbee/MockXbeeSPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..47de217724c2b686b75c526e02465d188667303f
--- /dev/null
+++ b/src/tests/catch/xbee/MockXbeeSPIBus.h
@@ -0,0 +1,171 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <deque>
+#include <functional>
+#include <memory>
+
+#include "drivers/Xbee/APIFrameParser.h"
+#include "drivers/Xbee/APIFrames.h"
+#include "drivers/spi/test/MockSPIBus.h"
+#include "utils/testutils/MockGpioPin.h"
+
+using std::deque;
+using std::function;
+using std::unique_ptr;
+
+class MockXbeeSPIBus : public MockSPIBus
+{
+public:
+    MockXbeeSPIBus(SPIBusConfig expected_config, MockGpioPin& attn)
+        : MockSPIBus(expected_config), attn(attn)
+    {
+    }
+
+    void registerCallback(function<void(MockGpioPin&)> callback)
+    {
+        this->callback = callback;
+    }
+
+    void removeCallback() { this->callback = nullptr; }
+
+    void pushApiFrame(Xbee::APIFrame& api)
+    {
+        unique_ptr<uint8_t> bytes(new uint8_t[Xbee::MAX_API_FRAME_SIZE]);
+
+        size_t len = api.toBytes(bytes.get());
+        push(bytes.get(), len);
+    }
+
+    deque<Xbee::APIFrame> getParsedFrames()
+    {
+        Lock<FastMutex> l(mutex);
+        return parsed_frames;
+    }
+
+    /**
+     * @brief Wether to generate a tx_status responde upon receiving a tx
+     * request
+     *
+     */
+    void setRespondWithTxStatus(bool respond, uint8_t delivery_status = 0)
+    {
+        tx_status_delivery_status = delivery_status;
+        respond_with_tx_status    = respond;
+    }
+
+protected:
+    void _push(uint8_t* data, size_t len) override
+    {
+        MockSPIBus::_push(data, len);
+
+        assertATTN();
+    }
+
+    uint8_t _read() override
+    {
+        uint8_t r = MockSPIBus::_read();
+
+        if (in_buf_pos_cntr == in_buf.size())
+        {
+            resetATTN();
+        }
+
+        return r;
+    }
+
+    void _write(uint8_t byte) override
+    {
+        MockSPIBus::_write(byte);
+
+        Xbee::APIFrameParser::ParseResult res =
+            parser.parse(byte, &parsing_frame);
+
+        if (res == Xbee::APIFrameParser::ParseResult::SUCCESS)
+        {
+            parsed_frames.push_back(parsing_frame);
+
+            if (parsing_frame.frame_type == Xbee::FTYPE_TX_REQUEST &&
+                respond_with_tx_status)
+            {
+                Xbee::TXRequestFrame* tx_req =
+                    parsing_frame.toFrameType<Xbee::TXRequestFrame>();
+                Xbee::TXStatusFrame tx_stat;
+
+                tx_stat.setFrameID(tx_req->getFrameID());
+                tx_stat.setDeliveryStatus(tx_status_delivery_status);
+                tx_stat.setDiscoveryStatus(0);
+                tx_stat.setTransmitRetryCount(0);
+                tx_stat.calcChecksum();
+                
+                _pushApiFrame(tx_stat);
+            }
+        }
+        else if (res == Xbee::APIFrameParser::ParseResult::FAIL)
+        {
+            printf("[MockXbeeSPI] Failed parsing frame written to SPI bus\n");
+        }
+    }
+
+private:
+    void _pushApiFrame(Xbee::APIFrame& api)
+    {
+        uint8_t* bytes = new uint8_t[Xbee::MAX_API_FRAME_SIZE];
+        size_t len     = api.toBytes(bytes);
+        _push(bytes, len);
+
+        delete[] bytes;
+    }
+
+    void assertATTN()
+    {
+        if (attn.value() != 0)
+        {
+            attn.low();
+            if (callback)
+                callback(attn);
+        }
+    }
+
+    void resetATTN()
+    {
+        if (attn.value() == 0)
+        {
+            attn.high();
+            if (callback)
+                callback(attn);
+        }
+    }
+
+    deque<Xbee::APIFrame> parsed_frames;
+
+    Xbee::APIFrameParser parser;
+    Xbee::APIFrame parsing_frame;
+
+    function<void(MockGpioPin&)> callback;
+    MockGpioPin attn;
+
+    bool respond_with_tx_status       = true;
+    uint8_t tx_status_delivery_status = 0;
+};
\ No newline at end of file
diff --git a/src/tests/catch/xbee/test-xbee-driver.cpp b/src/tests/catch/xbee/test-xbee-driver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a1ddb7c78d2392220e0ab596e3352a08ef77b86
--- /dev/null
+++ b/src/tests/catch/xbee/test-xbee-driver.cpp
@@ -0,0 +1,414 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "../catch-tests-entry.cpp"
+#endif
+#include <ctime>
+#include <future>
+#include <memory>
+
+#include "MockXbeeSPIBus.h"
+#include "drivers/Xbee/Xbee.h"
+#include "drivers/spi/SPIDriver.h"
+#include "utils/testutils/catch.hpp"
+
+using namespace Xbee;
+using std::async;
+using std::unique_ptr;
+
+using uint8_ptr = unique_ptr<uint8_t>;
+
+/**
+ * @brief Generates a sequence of incremental bytes
+ */
+uint8_ptr incrementalBytes(size_t length, uint8_t start_from = 0)
+{
+    uint8_ptr bytes(new uint8_t[length]);
+
+    for (size_t i = 0; i < length; i++)
+    {
+        bytes.get()[i] = (start_from + i) % 256;
+    }
+
+    return bytes;
+}
+
+class XbeeWrapper
+{
+public:
+    XbeeWrapper(unsigned int tx_timeout)
+        : bus(new MockXbeeSPIBus(xbee_cfg, attn)),
+          xbee(new Xbee::Xbee(*bus.get(), xbee_cfg, cs, attn, rst, tx_timeout))
+    {
+        attn.high();
+    }
+
+    MockGpioPin cs;
+    MockGpioPin attn;
+
+    MockGpioPin rst;
+
+    SPIBusConfig xbee_cfg{};
+
+    unique_ptr<MockXbeeSPIBus> bus;
+    unique_ptr<Xbee::Xbee> xbee;
+};
+
+TEST_CASE("[Xbee] Test xbee.send(...) by itself")
+{
+    uint8_ptr pkt      = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1);
+    uint8_ptr pkt_orig = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1);
+
+    XbeeWrapper wrap(DEFAULT_TX_TIMEOUT);
+
+    SECTION("Middle payload length")
+    {
+        REQUIRE(wrap.xbee->send(pkt.get(), 128));
+        REQUIRE(wrap.bus->getParsedFrames().size() > 0);
+
+        REQUIRE(wrap.bus->getParsedFrames().front().frame_type ==
+                Xbee::FTYPE_TX_REQUEST);
+
+        TXRequestFrame tx_req = *wrap.bus->getParsedFrames()
+                                     .front()
+                                     .toFrameType<Xbee::TXRequestFrame>();
+
+        REQUIRE(tx_req.getRFDataLength() == 128);
+        REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(), 128) == 0);
+    }
+
+    SECTION("Max payload length")
+    {
+        REQUIRE(wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH));
+        REQUIRE(wrap.bus->getParsedFrames().size() > 0);
+
+        REQUIRE(wrap.bus->getParsedFrames().front().frame_type ==
+                Xbee::FTYPE_TX_REQUEST);
+
+        TXRequestFrame tx_req = *wrap.bus->getParsedFrames()
+                                     .front()
+                                     .toFrameType<Xbee::TXRequestFrame>();
+
+        REQUIRE(tx_req.getRFDataLength() == Xbee::MAX_PACKET_PAYLOAD_LENGTH);
+        REQUIRE(memcmp(tx_req.getRFDataPointer(), pkt_orig.get(),
+                       Xbee::MAX_PACKET_PAYLOAD_LENGTH) == 0);
+    }
+
+    SECTION("Oversize payload")
+    {
+        REQUIRE_FALSE(
+            wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH + 1));
+        REQUIRE(wrap.bus->getParsedFrames().size() == 0);
+    }
+
+    SECTION("No payload")
+    {
+        REQUIRE_FALSE(wrap.xbee->send(pkt.get(), 0));
+        REQUIRE(wrap.bus->getParsedFrames().size() == 0);
+    }
+
+    SECTION("Send error")
+    {
+        wrap.bus->setRespondWithTxStatus(true, DELS_NO_SPECTRUM_AVAILABLE);
+
+        REQUIRE_FALSE(
+            wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH));
+    }
+
+    SECTION("TX status timeout")
+    {
+        wrap.bus->setRespondWithTxStatus(false);
+
+        long long start = miosix::getTick();
+
+        REQUIRE_FALSE(
+            wrap.xbee->send(pkt.get(), Xbee::MAX_PACKET_PAYLOAD_LENGTH));
+
+        // Should not have returned until the timeout has expired
+        REQUIRE(miosix::getTick() >= start + DEFAULT_TX_TIMEOUT);
+    }
+}
+
+TEST_CASE("[Xbee] Test xbee.receive(...) by itself")
+{
+    uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]);
+
+    uint8_ptr pkt = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH);
+
+    XbeeWrapper wrap(DEFAULT_TX_TIMEOUT);
+
+    RXPacketFrame rx;
+    rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH);
+    memcpy(rx.getRXDataPointer(), pkt.get(), MAX_PACKET_PAYLOAD_LENGTH);
+    rx.setReceiveOptions(RO_POINT_MULTIPOINT);
+    rx.setSourceAddress(0x1122334455667788);
+
+    SECTION("RX buffer bigger than RX packet payload")
+    {
+        rx.setRXDataLength(50);
+        rx.calcChecksum();
+
+        wrap.bus->pushApiFrame(rx);
+
+        REQUIRE(wrap.xbee->receive(rx_buf.get(), MAX_PACKET_PAYLOAD_LENGTH) ==
+                50);
+
+        REQUIRE(memcmp(rx_buf.get(), pkt.get(), 50) == 0);
+    }
+
+    SECTION("RX buffer smaller than RX packet payload")
+    {
+        rx.setRXDataLength(130);
+        rx.calcChecksum();
+
+        wrap.bus->pushApiFrame(rx);
+
+        REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 50);
+        REQUIRE(wrap.xbee->receive(rx_buf.get() + 50, 50) == 50);
+        REQUIRE(wrap.xbee->receive(rx_buf.get() + 100, 50) == 30);
+
+        REQUIRE(memcmp(rx_buf.get(), pkt.get(), 130) == 0);
+    }
+
+    SECTION("RX buffer == 1")
+    {
+        rx.setRXDataLength(30);
+        rx.calcChecksum();
+
+        wrap.bus->pushApiFrame(rx);
+
+        for (int i = 0; i < 30; i++)
+        {
+            REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1);
+        }
+
+        REQUIRE(memcmp(rx_buf.get(), pkt.get(), 30) == 0);
+    }
+
+    SECTION("RX packet with wrong checksum")
+    {
+        rx.setRXDataLength(20);
+        rx.calcChecksum();
+        wrap.bus->pushApiFrame(rx);
+
+        rx.setRXDataLength(21);
+        rx.calcChecksum();
+        rx.setRXDataLength(20);  // Now the checksum is wrong
+        wrap.bus->pushApiFrame(rx);
+
+        // Only receive one of the two frames
+        REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20);
+
+        auto dont_care = async(std::launch::async, [&]() {
+            miosix::Thread::sleep(1000);
+            wrap.xbee->wakeReceiver(true);
+        });
+
+        // This should block until we force it to return
+        REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == -1);
+    }
+
+    SECTION("receive() blocks until an interrupt is received")
+    {
+        rx.setRXDataLength(20);
+        rx.calcChecksum();
+
+        // Send an rx frame in one second
+        auto dont_care = async(std::launch::async, [&]() {
+            miosix::Thread::sleep(1000);
+            wrap.bus->pushApiFrame(rx);
+            wrap.xbee->wakeReceiver();
+        });
+
+        long long start = miosix::getTick();
+        REQUIRE(wrap.xbee->receive(rx_buf.get(), 50) == 20);
+
+        // Should not have returned before we sent the rx frame
+        REQUIRE(miosix::getTick() >= start + 1000);
+    }
+}
+
+TEST_CASE("[Xbee] Receive while sending")
+{
+    uint8_ptr rx_buf(new uint8_t[MAX_PACKET_PAYLOAD_LENGTH]);
+
+    uint8_ptr pkt_rx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH);
+    uint8_ptr pkt_tx = incrementalBytes(Xbee::MAX_PACKET_PAYLOAD_LENGTH);
+
+    XbeeWrapper wrap(1000);
+    wrap.bus->setRespondWithTxStatus(true, 0);
+
+    RXPacketFrame rx;
+    rx.setRXDataLength(MAX_PACKET_PAYLOAD_LENGTH);
+    memcpy(rx.getRXDataPointer(), pkt_rx.get(), MAX_PACKET_PAYLOAD_LENGTH);
+    rx.setReceiveOptions(RO_POINT_MULTIPOINT);
+    rx.setSourceAddress(0x1122334455667788);
+
+    int num_rx_while_tx = 0;
+
+    wrap.xbee->setOnFrameReceivedListener([&](APIFrame& api) {
+        if (api.frame_type == FTYPE_RX_PACKET_FRAME)
+        {
+            ++num_rx_while_tx;
+        }
+    });
+
+    SECTION("2 RX packet smaller than one TX packet")
+    {
+        rx.setRXDataLength(50);
+        rx.calcChecksum();
+
+        wrap.bus->pushApiFrame(rx);
+        wrap.bus->pushApiFrame(rx);
+
+        REQUIRE(wrap.xbee->send(pkt_tx.get(), 150));
+
+        REQUIRE(num_rx_while_tx == 2);
+
+        size_t rx_len = 0;
+        while (rx_len < 100)
+        {
+            rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200);
+        }
+
+        REQUIRE(rx_len == 100);
+        REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0);
+        REQUIRE(memcmp(rx_buf.get() + 50, pkt_rx.get(), 50) == 0);
+    }
+
+    SECTION("More RX packets than buffer size: drop the oldest")
+    {
+        rx.setRXDataLength(10);
+
+        // 1 packet too many
+        unsigned int numpkts = RX_FRAMES_BUF_SIZE + 1;
+        for (unsigned int i = 0; i < numpkts; i++)
+        {
+            memcpy(rx.getRXDataPointer(), pkt_rx.get() + i, 10);
+            rx.calcChecksum();
+            wrap.bus->pushApiFrame(rx);
+        }
+
+        // Should receive all the packets while sending
+        REQUIRE(wrap.xbee->send(pkt_tx.get(), MAX_PACKET_PAYLOAD_LENGTH));
+
+        REQUIRE(num_rx_while_tx == numpkts);
+
+        // Only receive the last three packets
+        size_t rx_len = 0;
+        while (rx_len < (numpkts - 1) * 10)
+        {
+            rx_len += wrap.xbee->receive(rx_buf.get() + rx_len, 200);
+        }
+
+        REQUIRE(rx_len == (numpkts - 1) * 10);
+
+        for (unsigned int i = 0; i < numpkts - 1; i++)
+        {
+            REQUIRE(memcmp(rx_buf.get() + i * 10, pkt_rx.get() + i + 1, 10) ==
+                    0);
+        }
+    }
+
+    SECTION("2 RX packet smaller than one TX packet, 1 byte receive")
+    {
+        rx.setRXDataLength(50);
+        rx.calcChecksum();
+
+        wrap.bus->pushApiFrame(rx);
+        wrap.bus->pushApiFrame(rx);
+
+        REQUIRE(wrap.xbee->send(pkt_tx.get(), 150));
+
+        REQUIRE(num_rx_while_tx == 2);
+
+        for (int i = 0; i < 50; i++)
+        {
+            REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1);
+        }
+        REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0);
+
+        for (int i = 0; i < 50; i++)
+        {
+            REQUIRE(wrap.xbee->receive(rx_buf.get() + i, 1) == 1);
+        }
+        REQUIRE(memcmp(rx_buf.get(), pkt_rx.get(), 50) == 0);
+    }
+}
+
+TEST_CASE("[Xbee] Test Xbee::sendAtCommand(...)")
+{
+    XbeeWrapper wrap(DEFAULT_TX_TIMEOUT);
+
+    SECTION("AT Command, no response required")
+    {
+        wrap.xbee->sendATCommand("AB");
+
+        REQUIRE(wrap.bus->getParsedFrames().size() > 0);
+        REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND);
+    }
+
+    SECTION("AT Command, response required but not received")
+    {
+        ATCommandResponseFrame response;
+        long long start = miosix::getTick();
+        REQUIRE_FALSE(
+            wrap.xbee->sendATCommand("AB", &response, nullptr, 0, 1000));
+        REQUIRE(miosix::getTick() >= start + 1000);
+
+        REQUIRE(wrap.bus->getParsedFrames().size() > 0);
+        REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND);
+    }
+
+    SECTION("AT Command, response required and received")
+    {
+        // Padding required in order to not receive the ATCommandResponse
+        // frame too early in testing
+        RXPacketFrame padding;
+        padding.setRXDataLength(50);
+        padding.calcChecksum();
+
+        ATCommandResponseFrame resp;
+        resp.setATCommand("AB");
+        resp.setFrameID(1);
+        resp.setCommandDataSize(1);
+        resp.getCommandDataPointer()[0] = 0xAB;
+        resp.calcChecksum();
+
+        wrap.bus->pushApiFrame(padding);
+        wrap.bus->pushApiFrame(resp);
+
+        ATCommandResponseFrame received_response;
+        long long start = miosix::getTick();
+
+        REQUIRE(wrap.xbee->sendATCommand("AB", &received_response, nullptr, 0,
+                                         1000));
+        REQUIRE(miosix::getTick() < start + 1000);
+
+        REQUIRE(wrap.bus->getParsedFrames().size() > 0);
+        REQUIRE(wrap.bus->getParsedFrames()[0].frame_type == FTYPE_AT_COMMAND);
+
+        REQUIRE(received_response.getCommandDataLength() == 1);
+        REQUIRE(received_response.getCommandDataPointer()[0] == 0xAB);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/catch/xbee/test-xbee-parser.cpp b/src/tests/catch/xbee/test-xbee-parser.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d51eafb6b3c417cc485c462d4b68d51ef7cf0a00
--- /dev/null
+++ b/src/tests/catch/xbee/test-xbee-parser.cpp
@@ -0,0 +1,574 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+// Disable asserts
+#define NDEBUG
+
+#include <cstdio>
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "../catch-tests-entry.cpp"
+#endif
+
+#include <cstdio>
+#include <cstring>
+
+#include "drivers/Xbee/APIFrameParser.h"
+#include "utils/testutils/catch.hpp"
+
+using namespace Xbee;
+
+/**
+ * @brief Calculates the checksum for a sequence of bytes representing an API
+ * frame.
+ *
+ * @param frame bytes (including start delimiter, and checksum set to 0)
+ * @param frame_size size of the frame, including start delimiter and checksum
+ */
+void calcChecksum(uint8_t* frame, size_t frame_size)
+{
+    frame[frame_size - 1] = 0;
+    for (size_t i = 3; i < frame_size - 1; i++)
+    {
+        frame[frame_size - 1] += frame[i];
+    }
+    frame[frame_size - 1] = 0xFF - frame[frame_size - 1];
+}
+
+void printHex(uint8_t* frame, size_t frame_size)
+{
+    for (size_t i = 0; i < frame_size; i++)
+    {
+        printf("%02X ", frame[i]);
+    }
+    printf("\n");
+}
+
+void printu64(uint64_t v)
+{
+    uint8_t* p = reinterpret_cast<uint8_t*>(&v);
+
+    for (int i = 0; i < 8; i++)
+    {
+        printf("%02X ", p[i]);
+    }
+
+    printf("\n");
+}
+
+void printBuf(uint8_t* buf, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        printf("%02X ", buf[i]);
+    }
+
+    printf("\n");
+}
+
+bool compareAPIFrames(const APIFrame& a, const APIFrame& b)
+{
+    bool result = a.start_del == b.start_del && a.frame_type == b.frame_type &&
+                  a.length == b.length && a.checksum == b.checksum;
+
+    return result &&
+           memcmp(a.frame_data, b.frame_data, a.getFrameDataLength()) == 0;
+}
+
+APIFrame parse(APIFrameParser& parser, uint8_t* data, size_t len)
+{
+    APIFrame out;
+    for (size_t i = 0; i < len; i++)
+    {
+        APIFrameParser::ParseResult pres = parser.parse(data[i], &out);
+
+        if (i < len - 1)
+        {
+            CAPTURE(i);
+            REQUIRE(pres == APIFrameParser::ParseResult::PARSING);
+        }
+        else
+        {
+            REQUIRE(pres == APIFrameParser::ParseResult::SUCCESS);
+        }
+    }
+
+    REQUIRE(out.verifyChecksum());
+
+    return out;
+}
+
+template <typename FrameType>
+void testParse(APIFrameParser& parser, FrameType orig)
+{
+    uint8_t bytes[MAX_API_FRAME_SIZE];
+
+    size_t len = orig.toBytes(bytes);
+
+    APIFrame parsed_api = parse(parser, bytes, len);
+
+    REQUIRE(compareAPIFrames(orig, parsed_api));
+}
+
+// Taken from examples in the datasheet
+TEST_CASE("Frame serialization")
+{
+    uint8_t bytes[MAX_API_FRAME_SIZE];
+    APIFrame deserialized;
+
+    SECTION("AT Command")
+    {
+        ATCommandFrame at_orig;
+        at_orig.setATCommand("NH");
+        REQUIRE(memcmp(at_orig.getATCommand(), "NH", 2) == 0);
+
+        at_orig.setFrameID(0x01);
+        REQUIRE(at_orig.getFrameID() == 0x01);
+
+        at_orig.setParameterSize(2);
+        at_orig.getCommandDataPointer()[0] = 0x07;
+        at_orig.getCommandDataPointer()[1] = 0x17;
+
+        REQUIRE(at_orig.getCommandDataLength() == 2);
+        REQUIRE(at_orig.getFrameDataLength() == 5);
+
+        at_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    6,    0x08, 1,
+                                    0x4E, 0x48, 0x07, 0x17, 0x00};
+
+        calcChecksum(expected_bytes, 10);
+
+        size_t len = at_orig.toBytes(bytes);
+
+        REQUIRE(len == 10);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, at_orig));
+    }
+
+    SECTION("AT Command, No parameters")
+    {
+        ATCommandFrame at_orig;
+
+        at_orig.setATCommand("NH");
+
+        at_orig.setFrameID(0x01);
+        REQUIRE(at_orig.getFrameID() == 0x01);
+
+        REQUIRE(at_orig.getCommandDataLength() == 0);
+        REQUIRE(at_orig.getFrameDataLength() == 3);
+
+        at_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0, 4, 0x08, 1, 0x4E, 0x48, 0x60};
+
+        size_t len = at_orig.toBytes(bytes);
+
+        REQUIRE(len == 8);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, at_orig));
+    }
+
+    SECTION("AT Command Response")
+    {
+        ATCommandResponseFrame at_orig;
+        at_orig.setATCommand("BD");
+        REQUIRE(memcmp(at_orig.getATCommand(), "BD", 2) == 0);
+
+        at_orig.setFrameID(0x01);
+        REQUIRE(at_orig.getFrameID() == 0x01);
+
+        at_orig.setCommandDataSize(3);
+        at_orig.getCommandDataPointer()[0] = 0x07;
+        at_orig.getCommandDataPointer()[1] = 0x17;
+        at_orig.getCommandDataPointer()[2] = 0x27;
+
+        REQUIRE(at_orig.getCommandDataLength() == 3);
+        REQUIRE(at_orig.getFrameDataLength() == 7);
+
+        at_orig.setCommandStatus(0x55);
+        REQUIRE(at_orig.getCommandStatus() == 0x55);
+
+        at_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    8,    0x88, 0x01, 0x42,
+                                    0x44, 0x55, 0x07, 0x17, 0x27, 0};
+
+        calcChecksum(expected_bytes, 12);
+
+        size_t len = at_orig.toBytes(bytes);
+        REQUIRE(len == 12);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, at_orig));
+    }
+
+    SECTION("AT Command Response, No cmd data")
+    {
+        ATCommandResponseFrame at_orig;
+        at_orig.setATCommand("BD");
+
+        at_orig.setFrameID(0x01);
+        REQUIRE(at_orig.getFrameID() == 0x01);
+
+        REQUIRE(at_orig.getCommandDataLength() == 0);
+        REQUIRE(at_orig.getFrameDataLength() == 4);
+
+        at_orig.setCommandStatus(0x0);
+        REQUIRE(at_orig.getCommandStatus() == 0x00);
+
+        at_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    5,    0x88, 0x01,
+                                    0x42, 0x44, 0x00, 0xF0};
+
+        calcChecksum(expected_bytes, 9);
+
+        size_t len = at_orig.toBytes(bytes);
+        REQUIRE(len == 9);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, at_orig));
+    }
+
+    SECTION("TX Request Frame")
+    {
+        TXRequestFrame tx_orig;
+
+        tx_orig.setFrameID(0x01);
+        REQUIRE(tx_orig.getFrameID() == 0x01);
+
+        tx_orig.setDestAddress(0x0013A200400A0127);
+
+        bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127;
+        REQUIRE(addr_cmp);
+
+        tx_orig.setBroadcastRadius(0x55);
+        REQUIRE(tx_orig.getBroadcastRadius() == 0x55);
+
+        tx_orig.setTransmitOptions(0x40);
+        REQUIRE(tx_orig.getTrasmitOptions() == 0x40);
+
+        uint8_t* rf_data = tx_orig.getRFDataPointer();
+
+        rf_data[0] = 0x54;
+        rf_data[1] = 0x78;
+        rf_data[2] = 0x44;
+        rf_data[3] = 0x61;
+        rf_data[4] = 0x74;
+        rf_data[5] = 0x61;
+        rf_data[6] = 0x30;
+        rf_data[7] = 0x41;
+
+        tx_orig.setRFDataLength(8);
+
+        REQUIRE(tx_orig.getRFDataLength() == 8);
+        REQUIRE(tx_orig.getFrameDataLength() == 21);
+
+        uint8_t expected_bytes[] = {0x7E, 0,    0x16, 0x10, 0x01, 0x00, 0x13,
+                                    0xA2, 0x00, 0x40, 0x0A, 0x01, 0x27, 0xFF,
+                                    0xFE, 0x55, 0x40, 0x54, 0x78, 0x44, 0x61,
+                                    0x74, 0x61, 0x30, 0x41, 0x00};
+
+        // Datasheet example checksum is wrong
+        calcChecksum(expected_bytes, 26);
+        tx_orig.calcChecksum();
+        size_t len = tx_orig.toBytes(bytes);
+
+        REQUIRE(len == 26);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, tx_orig));
+    }
+
+    SECTION("TX Request Frame - No payload")
+    {
+        TXRequestFrame tx_orig;
+
+        tx_orig.setFrameID(0x01);
+        REQUIRE(tx_orig.getFrameID() == 0x01);
+
+        tx_orig.setDestAddress(0x0013A200400A0127);
+        bool addr_cmp = tx_orig.getDestAddress() == 0x0013A200400A0127;
+        REQUIRE(addr_cmp);
+
+        tx_orig.setBroadcastRadius(0x55);
+        REQUIRE(tx_orig.getBroadcastRadius() == 0x55);
+
+        tx_orig.setTransmitOptions(0x40);
+        REQUIRE(tx_orig.getTrasmitOptions() == 0x40);
+
+        uint8_t* rf_data = tx_orig.getRFDataPointer();
+
+        REQUIRE(tx_orig.getRFDataLength() == 0);
+        REQUIRE(tx_orig.getFrameDataLength() == 13);
+
+        tx_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    14,   0x10, 0x01, 0x00,
+                                    0x13, 0xA2, 0x00, 0x40, 0x0A, 0x01,
+                                    0x27, 0xFF, 0xFE, 0x55, 0x40, 0x00};
+
+        calcChecksum(expected_bytes, 18);
+
+        size_t len = tx_orig.toBytes(bytes);
+
+        REQUIRE(len == 18);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, tx_orig));
+    }
+
+    SECTION("Modem status frame")
+    {
+        ModemStatusFrame mod_orig;
+
+        mod_orig.setStatus(0x55);
+
+        mod_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0, 2, 0x8A, 0x55, 0x00};
+
+        calcChecksum(expected_bytes, 6);
+
+        size_t len = mod_orig.toBytes(bytes);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, mod_orig));
+    }
+
+    SECTION("TX Status frame")
+    {
+        constexpr size_t frame_size = 11;
+        TXStatusFrame tx_orig{};
+
+        tx_orig.setFrameID(0x47);
+        REQUIRE(tx_orig.getFrameID() == 0x47);
+        tx_orig.setTransmitRetryCount(0x55);
+        REQUIRE(tx_orig.getTransmitRetryCount() == 0x55);
+
+        tx_orig.setDeliveryStatus(0x44);
+        REQUIRE(tx_orig.getDeliveryStatus() == 0x44);
+
+        tx_orig.setDiscoveryStatus(2);
+        REQUIRE(tx_orig.getDiscoveryStatus() == 2);
+
+        tx_orig.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    0x07, 0x8B, 0x47, 0xFF,
+                                    0xFE, 0x55, 0x44, 0x02, 0x00};
+
+        calcChecksum(expected_bytes, 11);
+
+        size_t len = tx_orig.toBytes(bytes);
+        REQUIRE(len == 11);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, tx_orig));
+    }
+
+    SECTION("RX Packet frame")
+    {
+        RXPacketFrame rx;
+
+        rx.setSourceAddress(0x0013A20040522BAA);
+        bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA;
+        REQUIRE(addr_cmp);
+
+        rx.setReceiveOptions(0x01);
+        REQUIRE(rx.getReceiveOptions() == 0x01);
+
+        uint8_t* rf_data = rx.getRXDataPointer();
+
+        rf_data[0] = 0x52;
+        rf_data[1] = 0x78;
+        rf_data[2] = 0x44;
+        rf_data[3] = 0x61;
+        rf_data[4] = 0x74;
+        rf_data[5] = 0x61;
+
+        rx.setRXDataLength(6);
+        REQUIRE(rx.getRXDataLength() == 6);
+        REQUIRE(rx.getFrameDataLength() == 0x11);
+
+        rx.calcChecksum();
+
+        uint8_t expected_bytes[] = {
+            0x7E, 0,    0x12, 0x90, 0x00, 0x13, 0xA2, 0x00, 0x40, 0x52, 0x2B,
+            0xAA, 0xFF, 0xFE, 0x01, 0x52, 0x78, 0x44, 0x61, 0x74, 0x61, 0x11};
+
+        // calcChecksum(expected_bytes, frame_size);
+
+        size_t len = rx.toBytes(bytes);
+
+        REQUIRE(len == 22);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, rx));
+    }
+
+    SECTION("RX Packet frame - no payload")
+    {
+        RXPacketFrame rx;
+
+        rx.setSourceAddress(0x0013A20040522BAA);
+        bool addr_cmp = rx.getSourceAddress() == 0x0013A20040522BAA;
+        REQUIRE(addr_cmp);
+
+        rx.setReceiveOptions(0x01);
+        REQUIRE(rx.getReceiveOptions() == 0x01);
+
+        REQUIRE(rx.getRXDataLength() == 0);
+        REQUIRE(rx.getFrameDataLength() == 11);
+
+        rx.calcChecksum();
+
+        uint8_t expected_bytes[] = {0x7E, 0,    12,   0x90, 0x00, 0x13,
+                                    0xA2, 0x00, 0x40, 0x52, 0x2B, 0xAA,
+                                    0xFF, 0xFE, 0x01, 0x00};
+
+        calcChecksum(expected_bytes, 16);
+
+        size_t len = rx.toBytes(bytes);
+
+        REQUIRE(len == 16);
+
+        REQUIRE(memcmp(bytes, expected_bytes, len) == 0);
+
+        memset(deserialized.frame_data, 0, FRAME_DATA_SIZE);
+        REQUIRE(APIFrame::fromBytes(bytes, len, &deserialized));
+        REQUIRE(compareAPIFrames(deserialized, rx));
+    }
+}
+
+TEST_CASE("Frame parsing")
+{
+    APIFrameParser parser;
+
+    SECTION("RXPacketFrame Parsing")
+    {
+        RXPacketFrame rx;
+        rx.setSourceAddress(0x0013A20040522BAA);
+        rx.setReceiveOptions(0x01);
+        uint8_t* rf_data = rx.getRXDataPointer();
+
+        rf_data[0] = 0x52;
+        rf_data[1] = 0x78;
+        rf_data[2] = 0x44;
+        rf_data[3] = 0x61;
+        rf_data[4] = 0x74;
+        rf_data[5] = 0x61;
+
+        rx.setRXDataLength(6);
+        rx.calcChecksum();
+
+        // Parse 3 times the same frame
+        testParse(parser, rx);
+        testParse(parser, rx);
+        testParse(parser, rx);
+    }
+}
+
+TEST_CASE("Parser edge cases")
+{
+    APIFrameParser parser;
+
+    SECTION("Wrong checksum")
+    {
+        RXPacketFrame rx;
+        rx.setSourceAddress(0x0013A20040522BAA);
+        rx.setReceiveOptions(0x01);
+        uint8_t* rf_data = rx.getRXDataPointer();
+
+        rf_data[0] = 0x52;
+        rf_data[1] = 0x78;
+        rf_data[2] = 0x44;
+        rf_data[3] = 0x61;
+        rf_data[4] = 0x74;
+        rf_data[5] = 0x61;
+
+        rx.setRXDataLength(6);
+        rx.calcChecksum();
+
+        uint8_t bytes[MAX_API_FRAME_SIZE];
+
+        size_t len = rx.toBytes(bytes);
+
+        bytes[len - 1] += 1;
+
+        APIFrame out;
+        for (size_t i = 0; i < len; i++)
+        {
+            APIFrameParser::ParseResult res = parser.parse(bytes[i], &out);
+            REQUIRE_FALSE(res == APIFrameParser::ParseResult::SUCCESS);
+
+            if (i == len - 1)
+            {
+                REQUIRE(res == APIFrameParser::ParseResult::FAIL);
+            }
+        }
+    }
+
+    SECTION("Wrong start delimiter")
+    {
+        RXPacketFrame rx;
+        rx.start_del = 0x55;
+        rx.setSourceAddress(0x0013A20040522BAA);
+        rx.setReceiveOptions(0x01);
+        uint8_t* rf_data = rx.getRXDataPointer();
+
+        rf_data[0] = 0x52;
+        rf_data[1] = 0x78;
+        rf_data[2] = 0x44;
+        rf_data[3] = 0x61;
+        rf_data[4] = 0x74;
+        rf_data[5] = 0x61;
+
+        rx.setRXDataLength(6);
+        rx.calcChecksum();
+
+        uint8_t bytes[MAX_API_FRAME_SIZE];
+
+        size_t len = rx.toBytes(bytes);
+
+        APIFrame out;
+        for (size_t i = 0; i < len; i++)
+        {
+            APIFrameParser::ParseResult res = parser.parse(bytes[i], &out);
+            REQUIRE(res == APIFrameParser::ParseResult::IDLE);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/analog/test-analog-pressure-sensors.cpp b/src/tests/drivers/analog/test-analog-pressure-sensors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ee0b704a0d763dc00e29c19fae8158e913b408cd
--- /dev/null
+++ b/src/tests/drivers/analog/test-analog-pressure-sensors.cpp
@@ -0,0 +1,122 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <diagnostic/PrintLogger.h>
+#include <miosix.h>
+
+#include "TimestampTimer.h"
+#include "drivers/adc/ADS1118/ADS1118.h"
+#include "drivers/spi/SPIDriver.h"
+#include "sensors/analog/pressure/AnalogPressureSensor.h"
+#include "sensors/analog/pressure/honeywell/HSCMAND015PA.h"
+#include "sensors/analog/pressure/honeywell/HSCMRNN030PA.h"
+
+#include "TimestampTimer.h"
+
+GpioPin sckPin  = GpioPin(GPIOB_BASE, 13);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 14);
+GpioPin mosiPin = GpioPin(GPIOB_BASE, 15);
+GpioPin csPin   = GpioPin(GPIOC_BASE, 1);
+
+constexpr ADS1118::ADS1118Mux channel1 = ADS1118::ADS1118Mux::MUX_AIN2_GND;
+constexpr ADS1118::ADS1118Mux channel2 = ADS1118::ADS1118Mux::MUX_AIN3_GND;
+
+void initBoard()
+{
+    // Enable SPI clock for SPI2 interface
+    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
+
+    // Alternate function configuration for SPI pins
+    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.alternateFunction(5);  // SPI function
+    mosiPin.mode(miosix::Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);  // SPI function
+    misoPin.mode(miosix::Mode::ALTERNATE);
+    misoPin.alternateFunction(5);  // SPI function
+
+    // Chip select pin as output starting high
+    csPin.mode(miosix::Mode::OUTPUT);
+    csPin.high();
+}
+
+int main()
+{
+    PrintLogger log = Logging::getLogger("test-analog-pressure-sensors");
+
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    TimestampTimer::enableTimestampTimer();
+
+    // SPI configuration setup
+    SPIBusConfig spiConfig;
+    spiConfig.clock_div = SPIClockDivider::DIV32;
+    spiConfig.mode      = SPIMode::MODE1;
+    SPIBus spiBus(SPI2);
+    SPISlave spiSlave(spiBus, csPin, spiConfig);
+
+    // Device initialization
+    ADS1118 ads1118(spiSlave);
+
+    std::function<ADCData()> get_voltage_function =
+        std::bind(&ADS1118::getVoltage, ads1118, channel1);
+    HSCMAND015PA analog_sensor(get_voltage_function);
+
+    analog_sensor.init();
+    analog_sensor.selfTest();
+
+    std::function<ADCData()> get_voltage_function2 =
+        std::bind(&ADS1118::getVoltage, ads1118, channel2);
+    HSCMRNN030PA analog_sensor2(get_voltage_function2);
+
+    analog_sensor2.init();
+    analog_sensor2.selfTest();
+
+    // Enable channels
+    ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+    ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+
+    // Initialize the device
+    ads1118.init();
+
+    // Self test
+    if (ads1118.selfTest())
+    {
+        TRACE("Self test successful!\n");
+    }
+    else
+    {
+        TRACE("Self test failed :( error: %d\n", ads1118.getLastError());
+    }
+
+    // Read samples with sample()
+    while (1)
+    {
+        ads1118.sample();
+        miosix::Thread::sleep(5);
+
+        printf("%.2f\t%.2f\n", ads1118.getVoltage(channel1).voltage,
+               ads1118.getVoltage(channel2).voltage);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/analog/test-battery-voltage.cpp b/src/tests/drivers/analog/test-battery-voltage.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..93e1d06f05791041688eca44371e105427899a7f
--- /dev/null
+++ b/src/tests/drivers/analog/test-battery-voltage.cpp
@@ -0,0 +1,97 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <miosix.h>
+
+#include "Debug.h"
+#include "TimestampTimer.h"
+#include "drivers/adc/InternalADC/InternalADC.h"
+#include "sensors/analog/battery/BatteryVoltageSensor.h"
+
+using namespace miosix;
+
+GpioPin battery_pin              = GpioPin(GPIOA_BASE, 3);
+InternalADC::Channel ADC_CHANNEL = InternalADC::Channel::CH3;
+ADC_TypeDef& ADCx                = *ADC3;
+InternalADC adc(ADCx);
+
+void initBoard()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        battery_pin.mode(miosix::Mode::INPUT_ANALOG);
+
+        // Set pins PA0 PA1 PA2 PA3 as analog input
+        // RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
+        // GPIOA->MODER = 0xFF;
+
+        // Enable ADC3 clock
+        RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;  // <- CHANGE THIS!
+
+        // Set the clock divider for the analog circuitry (/8)
+        ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1;
+    }
+
+    TimestampTimer::enableTimestampTimer();
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    adc.enableChannel(ADC_CHANNEL);
+
+    printf("Configuration completed\n");
+
+    std::function<ADCData()> get_voltage_function = []()
+    { return adc.getVoltage(ADC_CHANNEL); };
+    // std::bind(&InternalADC::getVoltage, adc, ADC_CHANNEL);
+
+    BatteryVoltageSensor battery_sensor(get_voltage_function, 0.2063);
+
+    if (!adc.init() || !adc.selfTest())
+    {
+        printf("ERROR : %d\n", adc.getLastError());
+    }
+    else
+    {
+        battery_sensor.init();
+
+        // Read samples with sample()
+        while (1)
+        {
+            adc.sample();
+
+            miosix::Thread::sleep(100);
+
+            battery_sensor.sample();
+
+            BatteryVoltageSensorData bat_data = battery_sensor.getLastSample();
+            printf("%llu %u %f %f \n", bat_data.adc_timestamp,
+                   bat_data.channel_id, bat_data.voltage, bat_data.bat_voltage);
+
+            miosix::Thread::sleep(100);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/analog/test-current-sensor.cpp b/src/tests/drivers/analog/test-current-sensor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..08df0b0553b9fc8541b89704d33e74b7dd312560
--- /dev/null
+++ b/src/tests/drivers/analog/test-current-sensor.cpp
@@ -0,0 +1,100 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <miosix.h>
+
+#include "Debug.h"
+#include "TimestampTimer.h"
+#include "drivers/adc/InternalADC/InternalADC.h"
+#include "sensors/analog/current/CurrentSensor.h"
+
+using namespace miosix;
+
+GpioPin battery_pin              = GpioPin(GPIOA_BASE, 3);
+InternalADC::Channel ADC_CHANNEL = InternalADC::Channel::CH3;
+ADC_TypeDef& ADCx                = *ADC3;
+InternalADC adc(ADCx);
+
+void initBoard()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        battery_pin.mode(miosix::Mode::INPUT_ANALOG);
+
+        // Set pins PA0 PA1 PA2 PA3 as analog input
+        // RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
+        // GPIOA->MODER = 0xFF;
+
+        // Enable ADC3 clock
+        RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;  // <- CHANGE THIS!
+
+        // Set the clock divider for the analog circuitry (/8)
+        ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1;
+    }
+
+    TimestampTimer::enableTimestampTimer();
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    adc.enableChannel(ADC_CHANNEL);
+
+    printf("Configuration completed\n");
+
+    std::function<ADCData()> get_voltage_function = []()
+    { return adc.getVoltage(ADC_CHANNEL); };
+    // std::bind(&InternalADC::getVoltage, adc, ADC_CHANNEL);
+    // example transfer function
+    std::function<float(float)> adc_to_current = [](float adc_in)
+    { return (adc_in - 107.0f) * 32.4f; };
+    CurrentSensor current_sensor(get_voltage_function, adc_to_current);
+
+    if (!adc.init() || !adc.selfTest())
+    {
+        printf("ERROR : %d\n", adc.getLastError());
+    }
+    else
+    {
+        current_sensor.init();
+
+        // Read samples with sample()
+        while (1)
+        {
+            adc.sample();
+
+            miosix::Thread::sleep(500);
+
+            current_sensor.sample();
+
+            CurrentSensorData current_data = current_sensor.getLastSample();
+            printf("%llu %u %f %f \n", current_data.adc_timestamp,
+                   current_data.channel_id, current_data.voltage,
+                   current_data.current);
+
+            miosix::Thread::sleep(100);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/flashmemory/FlashControllerTests.h b/src/tests/drivers/flashmemory/FlashControllerTests.h
new file mode 100644
index 0000000000000000000000000000000000000000..64d9f811f4fb9f90d08a961909faa9b9835d2aa1
--- /dev/null
+++ b/src/tests/drivers/flashmemory/FlashControllerTests.h
@@ -0,0 +1,708 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef FLASHCONTROLLERTESTS_H
+#define FLASHCONTROLLERTESTS_H
+
+#include <Common.h>
+#include <helper/MultiFlashController.h>
+#include <diagnostic/NewLogger.h>
+#include <drivers/timer.h>
+#include <Tests.h>
+#include <vector>
+#include <limits>
+#include <string>
+
+using std::vector;
+using logging::logger;
+using namespace flashmemory;
+
+namespace testing
+{
+namespace flashmemorytests
+{
+
+template<typename MemorySPI>
+class FlashTest : public Test
+{
+ public:
+
+  FlashTest(MultiFlashController* multi)
+      : Test(), multi(multi), controller(multi->flash0_), driver(
+          controller->flash_)
+  {
+
+  }
+ protected:
+  typedef FlashController<MemorySPI> FlashControllerType;
+
+  void controller_setBlockIndex(uint32_t block_index)
+  {
+    controller->block_index_ = block_index;
+  }
+
+  uint32_t controller_getBlockIndex()
+  {
+    return controller->block_index_;
+  }
+
+  bool controller_format()
+  {
+#ifdef FLASH_TEST
+    return FlashControllerType::format();
+#else
+    return FlashControllerType::fastFormat();
+#endif
+  }
+
+  void controller_init()
+  {
+    controller->init();
+  }
+
+  bool controller_readSectorHeader(uint32_t sector, SectorHeader* header)
+  {
+    return FlashControllerType::readSectorHeader(sector, header);
+  }
+
+  bool controller_readBlock(uint32_t block_index, uint8_t* buffer)
+  {
+    return FlashControllerType::readBlock(block_index, buffer);
+  }
+
+  bool controller_readDataBlock(uint32_t block_index, FlashDataBlock* block)
+  {
+    return FlashControllerType::readDataBlock(block_index, block);
+  }
+
+  bool controller_writeBlock(FlashDataBlock* block)
+  {
+    return controller->writeDataBlock(block);
+  }
+
+  bool controller_writeSectorHeader(uint32_t sector, SectorHeader& sheader)
+  {
+    return FlashControllerType::writeSectorHeader(sector, sheader);
+  }
+
+  MultiFlashController* multi;
+  FlashController<MemorySPI>* controller;
+  FlashDriver<MemorySPI>* driver;
+};
+
+/*
+ * Copy this to create a new FlashTest
+ *
+
+ template<typename MemorySPI>
+ class EmptyFlashTest : public FlashTest<MemorySPI>
+ {
+ typedef FlashTest<MemorySPI> Base;
+
+ public:
+
+ EmptyFlashTest(MultiFlashController* multi)
+ : Base::FlashTest(multi)
+ {
+
+ }
+
+ bool setup()
+ {
+ return true;
+ }
+
+ bool run()
+ {
+ return true;
+ }
+
+ string name()
+ {
+ return "EmptyFlashTest";
+ }
+ };
+
+ */
+
+class CompareFlashHeaderTest : public Test
+{
+ public:
+
+  CompareFlashHeaderTest()
+      : Test()
+  {
+
+  }
+
+  bool run()
+  {
+    bool success = true;
+    FlashHeader header1, header2;
+
+    bool test = header1 == header2;
+    success = success && test;
+    logger.info(logtag(), "Compare equal headers: ", (test ? "OK" : "Error"));
+
+    header1.read_only = 54;
+
+    test = header1 == header2;
+    success = success && (test == false);
+    logger.info(logtag(), "Compare different headers: ",
+                (test == false ? "OK" : "Error"));
+
+    return success;
+  }
+
+  string name()
+  {
+    return "CompareFlashHeaderTest";
+  }
+
+ private:
+  static string logtag()
+  {
+    return "CompareFHTest";
+  }
+};
+
+template<typename MemorySPI>
+class MeasureRebootTimeTest : public FlashTest<MemorySPI>
+{
+  typedef FlashTest<MemorySPI> Base;
+  typedef Timer32<2> Timer2;
+ public:
+  MeasureRebootTimeTest(MultiFlashController* multi)
+      : Base::FlashTest(multi)
+  {
+
+  }
+
+  bool run()
+  {
+#ifdef FLASH_TEST
+    logger.warning(logtag(), "You are using the mockup memory!");
+#endif
+    logger.info(logtag(), "Running 100 read repetitions!");
+    uint8_t result;
+    SectorHeader header;
+    float avg, min = std::numeric_limits<float>::max(), max = 0;
+    Timer2::start();
+    for (int j = 0; j < 100; j++)
+    {
+      uint32_t t0 = Timer2::tick();
+      for (uint32_t i = 0; i < SECTORS_NUM; i++)
+      {
+        Base::driver->read(&result, i * SECTOR_SIZE, (uint8_t*) (&header),
+                           sizeof(SectorHeader));
+        if (result != RESULT_OK)
+        {
+          break;
+        }
+      }
+      uint32_t t1 = Timer2::tick();
+      float t = Timer2::milliSeconds(t1 - t0);
+      avg += t;
+      if (t < min)
+      {
+        min = t;
+      } else if (t > max)
+      {
+        max = t;
+      }
+    }
+    Timer2::stop();
+    avg = avg / 100;
+    if (result == RESULT_OK)
+    {
+      logger.info(logtag(), "Avg time: ", avg, " ms");
+      logger.info(logtag(), "Min time: ", min, " ms, Max time: ", max, " ms");
+      return true;
+    } else
+    {
+      return false;
+    }
+  }
+
+  string name()
+  {
+    return "MeasureWorstRebootTimeTest";
+  }
+
+ private:
+  static string logtag()
+  {
+    return "RebootTimeTest";
+  }
+};
+
+template<typename MemorySPI>
+class FlashHeaderTest : public FlashTest<MemorySPI>
+{
+  typedef FlashTest<MemorySPI> Base;
+  //using Base::driver;
+  //using Base::controller;
+
+ public:
+
+  FlashHeaderTest(MultiFlashController* multi)
+      : Base::FlashTest(multi)
+  {
+
+  }
+
+  bool setup()
+  {
+    return cleanFirstSubsector();
+  }
+
+  bool run()
+  {
+    FlashHeader orig_header, read_header;
+    generateRandomHeader(&orig_header);
+    orig_header.first_boot = FIRST_BOOT_KEY;
+    logger.info(logtag(), "--SUBTEST 1: Write & read over clean memory");
+
+    if (!writeAndReadHeader(orig_header, read_header, false))
+    {
+      return false;
+    }
+    if (orig_header != read_header)
+    {
+      logger.error(logtag(), "Original & written headers are not the same!");
+      return false;
+    }
+    logger.info(logtag(), "Success.");
+
+    orig_header.first_boot = 0;
+    logger.info(logtag(), "--SUBTEST 2: Overwrite first_boot");
+
+    if (!writeAndReadHeader(orig_header, read_header, false))
+    {
+      return false;
+    }
+    if (orig_header != read_header)
+    {
+      logger.error(logtag(), "Original & written headers are not the same!");
+      return false;
+    }
+    logger.info(logtag(), "Success.");
+
+    logger.info(logtag(), "--SUBTEST 3: Write & read over dirty memory");
+    FlashHeader dirtywrite_header;
+    generateRandomHeader(&dirtywrite_header);
+
+    if (Base::FlashControllerType::writeFlashHeader(dirtywrite_header, false))
+    {
+      logger.error(
+          logtag(),
+          "Function writeFlashHeader should have returned RESULT_CHECK_FAIL!");
+      return false;
+    }
+    logger.info(logtag(), "Success.");
+
+    logger.info(logtag(),
+                "--SUBTEST 4: Write with erase & read over dirty memory");
+    generateRandomHeader(&dirtywrite_header);
+
+    if (!writeAndReadHeader(dirtywrite_header, read_header, true))
+    {
+      return false;
+    }
+    if (dirtywrite_header != read_header)
+    {
+      logger.error(logtag(), "Original & written headers are not the same!");
+      return false;
+    }
+    logger.info(logtag(), "Success.");
+    return true;
+  }
+
+  string name()
+  {
+    return "WriteFlashHeaderTest";
+  }
+
+ private:
+  static string logtag()
+  {
+    return "WrtFHeaderTest";
+  }
+
+  bool cleanFirstSubsector()
+  {
+    uint8_t result;
+    Base::driver->enableErase();
+    Base::driver->eraseSubsector(&result, 0);
+
+    return result == RESULT_OK;
+  }
+
+  void generateRandomHeader(FlashHeader* header)
+  {
+    header->start_index = (uint32_t) rand();
+    header->read_only = (uint32_t) rand();
+    header->first_boot = (uint32_t) rand();
+  }
+
+  bool writeAndReadHeader(FlashHeader& write_header, FlashHeader& read_header,
+                          bool erase)
+  {
+    if (!Base::FlashControllerType::writeFlashHeader(write_header, erase))
+    {
+      logger.error(logtag(), "Error writing header");
+      return false;
+    }
+
+    if (!Base::FlashControllerType::readFlashHeader(&read_header))
+    {
+      logger.error(logtag(), "Error reading header");
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template<typename MemorySPI>
+class FlashBlockTest : public FlashTest<MemorySPI>
+{
+  typedef FlashTest<MemorySPI> Base;
+
+ public:
+
+  FlashBlockTest(MultiFlashController* multi)
+      : Base::FlashTest(multi)
+  {
+
+  }
+
+  bool setup()
+  {
+    if (Base::controller_format())
+    {
+      Base::controller_init();
+      return true;
+    }
+    return false;
+  }
+
+  bool run()
+  {
+    logger.info(logtag(), "--SUBTEST 1: Just write and read");
+    if (!subtest1())
+      return false;
+    logger.info(logtag(), "Success.");
+
+    logger.info(logtag(), "--SUBTEST 2: Write near the end of a sector");
+    if (!subtest2())
+      return false;
+    logger.info(logtag(), "Success.");
+
+    logger.info(logtag(), "--SUBTEST 3: Write at the end of the memory");
+    if (!subtest3())
+      return false;
+    logger.info(logtag(), "Success.");
+
+    return true;
+  }
+
+  string name()
+  {
+    return "FlashBlockTest";
+  }
+
+ private:
+  static string logtag()
+  {
+    return "FBlockTest";
+  }
+
+  bool subtest1()
+  {
+    uint32_t block_index = SUBSECTOR_SIZE / BLOCK_SIZE;
+
+    Base::controller_setBlockIndex(block_index);
+    FlashDataBlock write_block, read_block;
+    generateDataBlock(&write_block);
+
+    if (!writeBlock(&write_block))
+    {
+      logger.error(logtag(), "Cannot write block");
+      return false;
+    }
+
+    if (!readBlock(block_index, &read_block))
+    {
+      logger.error(logtag(), "Cannot read block");
+      return false;
+    }
+
+    if (read_block != write_block)
+    {
+      logger.error(logtag(), "Written & read block do not match!");
+      return false;
+    }
+    return true;
+  }
+
+  bool subtest2()
+  {
+    uint32_t block_index = SECTOR_SIZE / BLOCK_SIZE - 1;
+
+    Base::controller_setBlockIndex(block_index);
+
+    FlashDataBlock write_blocks[2];
+    FlashDataBlock read_blocks[2];
+    for (int i = 0; i < 2; i++)
+    {
+      generateDataBlock(&write_blocks[i]);
+      if (!writeBlock(&write_blocks[i]))
+      {
+        logger.error(logtag(), "Cannot write block ", i + 1);
+        return false;
+      }
+    }
+
+    SectorHeader sheader;
+    if (!Base::controller_readSectorHeader(1, &sheader))
+    {
+      logger.error(logtag(), "Cannot read sector header");
+      return false;
+    }
+
+    if (sheader.content != SECTOR_WRITTEN)
+    {
+      logger.error(logtag(), "Sector header not correctly written");
+      logger.error(logtag(), "Sector header content: 0x%02X; should be: 0x%02X",
+                   sheader.content, SECTOR_WRITTEN);
+      return false;
+    }
+
+    if (!readBlock(block_index, &read_blocks[0]))
+    {
+      logger.error(logtag(), "Cannot read block 1");
+      return false;
+    }
+
+    if (!readBlock(block_index + 2, &read_blocks[1]))
+    {
+      logger.error(logtag(), "Cannot read block 2");
+      return false;
+    }
+
+    if (!compareBlocks(write_blocks, read_blocks, 2))
+    {
+      logger.error(logtag(), "Written & read blocks do not match.");
+      return false;
+    }
+    return true;
+  }
+
+  bool subtest3()
+  {
+    //Last block of the memory
+    uint32_t block_index = MEMORY_SIZE / BLOCK_SIZE - 1;
+    Base::controller_setBlockIndex(block_index);
+
+    //Keep the filesystem consistent.
+    SectorHeader sheader;
+    sheader.content = SECTOR_WRITTEN;
+    Base::controller_writeSectorHeader(MEMORY_SIZE / SECTOR_SIZE - 1, sheader);
+
+    FlashDataBlock write_blocks[2];
+    FlashDataBlock read_blocks[2];
+
+    for (int i = 0; i < 2; i++)
+    {
+      generateDataBlock(&write_blocks[i]);
+    }
+
+    if (!writeBlock(&write_blocks[0]))
+    {
+      logger.error(logtag(),
+                   "Write block failed on the last available address!");
+      return false;
+    }
+
+    if (writeBlock(&write_blocks[1]))
+    {
+      logger.error(
+          logtag(),
+          "Write block didn't fail while writing outside of the memory!");
+      return false;
+    }
+    if (!readBlock(block_index, &read_blocks[0]))
+    {
+      logger.error(logtag(), "Cannot read block 1");
+      return false;
+    }
+
+    if (readBlock(block_index + 2, &read_blocks[1]))
+    {
+      logger.error(
+          logtag(),
+          "readBlock didn't fail while reading outside of the memory!");
+      return false;
+    }
+
+    if (read_blocks[0] != write_blocks[0])
+    {
+      logger.error(logtag(), "Written & read blocks do not match.");
+      return false;
+    }
+
+    return true;
+  }
+
+  bool writeBlock(FlashDataBlock* block)
+  {
+    return Base::controller_writeBlock(block);
+  }
+
+  bool readBlock(uint32_t block_index, FlashDataBlock* block)
+  {
+    return Base::controller_readBlock(block_index, (uint8_t*) (block));
+  }
+
+  bool compareBlocks(FlashDataBlock* blocks1, FlashDataBlock* blocks2,
+                     size_t num_blocks)
+  {
+    for (size_t i = 0; i < num_blocks; i++)
+    {
+      if (blocks1[i] != blocks2[i])
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void generateDataBlock(FlashDataBlock* generated)
+  {
+    generated->id = (uint32_t) rand();
+    generated->crc = (uint16_t) rand();
+    generated->timestamp = (uint32_t) rand();
+
+    for (int i = 0; i < FLASH_BUFFER_SIZE; i++)
+    {
+      generated->buffer[i] = (uint8_t) (rand() % 256);
+    }
+  }
+};
+
+template<typename MemorySPI>
+class MultiFlashCTRLTest : public FlashTest<MemorySPI>
+{
+  typedef FlashTest<MemorySPI> Base;
+
+ public:
+
+  MultiFlashCTRLTest(MultiFlashController* multi)
+      : Base::FlashTest(multi)
+  {
+
+  }
+
+  bool setup()
+  {
+    if (!Base::controller_format())
+    {
+      return false;
+    }
+    Base::controller_init();
+    if (Base::controller->getStatus()
+        != Base::FlashControllerType::Status::WRITE_READY)
+    {
+      logger.error(logtag(), "Error initializing flash controller.");
+      return false;
+    }
+    return true;
+  }
+
+  bool run()
+  {
+    logger.info(logtag(), "--SUBTEST 1: Write @ beginning");
+    if (!subtest1())
+      return false;
+    logger.info(logtag(), "Success.");
+
+    return true;
+  }
+
+  string name()
+  {
+    return "EmptyFlashTest";
+  }
+
+ private:
+  bool subtest1()
+  {
+    uint32_t index = Base::controller_getBlockIndex();
+
+    uint8_t rand_buff1[FLASH_BUFFER_SIZE];
+    uint8_t rand_buff2[FLASH_BUFFER_SIZE];
+    Base::multi->addData(rand_buff1, FLASH_BUFFER_SIZE);
+    Base::multi->addData(rand_buff2, FLASH_BUFFER_SIZE);
+
+    Thread::sleep(500); //Wait for data to be written
+
+    logger.info(logtag(), "Reading blocks.");
+    FlashDataBlock read_block;
+
+    if(!Base::controller_readDataBlock(index, &read_block))
+    {
+      logger.error(logtag(), "Error reading first block.");
+      return false;
+    }
+
+    if(memcmp(rand_buff1, &(read_block.buffer), FLASH_BUFFER_SIZE) != 0)
+    {
+      logger.error(logtag(), "Read block 1 is not the same as written one.");
+      return false;
+    }
+
+    if(!Base::controller_readDataBlock(index+1, &read_block))
+    {
+      logger.error(logtag(), "Error reading second block.");
+      return false;
+    }
+
+    if(memcmp(rand_buff2, &(read_block.buffer), FLASH_BUFFER_SIZE) != 0)
+    {
+      logger.error(logtag(), "Read block 2 is not the same as written one.");
+      return false;
+    }
+
+    return true;
+  }
+
+  static string logtag()
+  {
+    return "MultiFlashCTRLTest";
+  }
+};
+
+}   //namespace flashmemorytests
+}   //namespace testing
+
+#endif /*FLASHCONTROLLERTESTS_H*/
diff --git a/src/tests/drivers/flashmemory/mockup/FlashDriver.h b/src/tests/drivers/flashmemory/mockup/FlashDriver.h
new file mode 100644
index 0000000000000000000000000000000000000000..f8658ea180a17a4d7753f22c8efaf74de41cb5a8
--- /dev/null
+++ b/src/tests/drivers/flashmemory/mockup/FlashDriver.h
@@ -0,0 +1,620 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef FLASHDRIVER_H
+#define FLASHDRIVER_H
+
+#include <miosix.h>
+#include <Singleton.h>
+#include <diagnostic/NewLogger.h>
+
+using miosix::Thread;
+using miosix::Mode;
+using logging::logger;
+
+//Forward declaration
+namespace testing
+{
+namespace flashmemorytests
+{
+template<typename>
+class FlashTest;
+}
+}
+
+namespace flashmemory
+{
+
+static const uint32_t PAGES_PER_SUBSECTOR = 4U;
+static const uint32_t PAGES_PER_SECTOR = 16U;
+static const uint32_t SUBSECTORS_PER_SECTOR = 4U;
+
+static const uint32_t SUBSECTORS_NUM = 48U;
+static const uint32_t SECTORS_NUM = 12U;
+static const uint32_t PAGE_SIZE = 256U;
+
+static const uint32_t SUBSECTOR_SIZE = PAGE_SIZE * PAGES_PER_SUBSECTOR;
+static const uint32_t SECTOR_SIZE = PAGE_SIZE * PAGES_PER_SECTOR;
+
+static const uint32_t BANK_SIZE = SECTOR_SIZE * SECTORS_NUM / 2;
+static const uint32_t MEMORY_SIZE = SECTOR_SIZE * SECTORS_NUM;
+
+/**
+ *  @brief Definitions for results of various flash operations.
+ *
+ *  Definitions for result flags of various flash operations.
+ */
+
+enum OpResultFlags
+{
+  RESULT_OK         = 0x00,
+
+
+  RESULT_F_OUT_OF_MEMORY  = 0x01,
+  RESULT_F_CHECK_FAIL     = 0x04,
+
+
+  //Bits reserved for future use
+  RESULT_F_UNUSED_1     = 0x08,
+  RESULT_F_UNUSED_2     = 0x40,
+  RESULT_F_UNUSED_3     = 0x80,
+
+
+  /**
+   * These bits are in the same positions of the corresponding bits in the
+   * FLAG STATUS REGISTER
+   */
+  RESULT_F_PROTECTION_ERROR = 0x02,
+  RESULT_F_PROGRAM_ERROR  = 0x10,
+  RESULT_F_ERASE_ERROR    = 0x20
+};
+
+template<typename Bus>
+class FlashDriver  : Singleton<FlashDriver<Bus>>
+{
+  typedef Singleton<FlashDriver<Bus>> SingletonType;
+
+  friend class Singleton<FlashDriver<Bus>>;
+
+  template<typename >
+  friend class FlashDriverTest;
+
+  template<typename >
+  friend class testing::flashmemorytests::FlashTest;
+
+ public:
+
+  ~FlashDriver()
+  {
+    delete fake_memory_;
+  }
+
+  /**
+   * @brief Read n bytes into a buffer, starting from the specified address
+   *
+   * @param result
+   * @param address Starting address
+   * @param buf Buffer to read data into
+   * @param size Number of bytes to read
+   */
+  void read(uint8_t *result, uint32_t address, uint8_t* buf, uint32_t size)
+  {
+    if (address + size > MEMORY_SIZE)
+    {
+      logger.error(logtag(), "Read outside of memory bounds addr + size: ",
+                   address + size, " MEMSIZE: ", MEMORY_SIZE);
+      *result = RESULT_F_OUT_OF_MEMORY;
+      return;
+    }
+
+    uint32_t rsize;
+    if (address < BANK_SIZE)
+      rsize = std::min(size, BANK_SIZE - address);
+    else
+      rsize = size;
+
+    //uint8_t addr_buf[4];
+    //addr_to_buf(addr_buf, address);
+
+    fakeRead(address, buf, rsize);
+    //Bus::read(READ, addr_buf, buf, 4, rsize);
+
+    size -= rsize;
+
+    //This means that we reached the end of the first die but we still need
+    //to read some data.
+    if (size > 0)
+    {
+      address += rsize;
+      buf += rsize;
+
+      //addr_to_buf(addr_buf, address);
+
+      //Read the remaining bytes
+      fakeRead(address, buf, size);
+      //Bus::read(READ, addr_buf, buf, 4, size);
+    }
+
+    *result = RESULT_OK;
+  }
+
+  /**
+   * @brief Writes data on the flash starting from the specified address.
+   *
+   * @param address
+   * @param data
+   * @param size
+   */
+  void write(uint8_t *result, uint32_t address, uint8_t* data, uint32_t size)
+  {
+    //Do not write outside of the memory
+    if (address + size > MEMORY_SIZE)
+    {
+      *result = RESULT_F_OUT_OF_MEMORY;
+      return;
+    }
+
+    *result = RESULT_OK;
+
+    //uint8_t addr_buf[4], status;
+
+    uint32_t next_page, wsize;
+
+    do
+    {
+      next_page = (address / 256) * 256 + 256;
+
+      wsize = std::min(next_page - address, size);
+
+      //addr_to_buf(addr_buf, address);
+      //_write_enable();
+
+      fakeWrite(address, data, wsize);
+      /*Bus::write(PROGRAM_PAGE, addr_buf, data, 4, wsize);
+
+       do {
+       status = _read_flag_status_reg();
+       }while((status & FSR_PRG_ERS_CTRL) == 0);*/
+
+      data += wsize;
+      size -= wsize;
+      address = next_page;
+
+    } while (size > 0 && *result == RESULT_OK);
+
+    /*//Clear flag status register if an error occurred
+     if(*result != RESULT_OK)
+     {
+     _clear_flag_status_reg();
+
+     //Manually reset write_enable latch
+     if((*result & RESULT_PROTECTION_ERROR) > 0)
+     {
+     _write_disable();
+     }
+     }*/
+  }
+
+  /**
+   * @brief Writes data on the flash starting from the specified address, then
+   * checks if everything was written correctly.
+   *
+   * @param address
+   * @param data
+   * @param size
+   */
+  void writeAndCheck(uint8_t *result, uint32_t address, uint8_t* data,
+                     uint32_t size)
+  {  //TODO: size_t
+    write(result, address, data, size);
+    if (*result == RESULT_OK)
+    {
+      uint8_t *check = new uint8_t[size];
+      read(result, address, check, size);
+      for (uint32_t i = 0; i < size; i++)
+      {
+        if (*check++ != *data++)
+        {
+          //Something was not written/read correctly.
+          *result = RESULT_F_CHECK_FAIL;
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * @brief Writes data on the flash starting from the specified address, only
+   * if the provided data fits in a single page.
+   *
+   * Writes data on the flash starting from the specified address, only
+   * if the provided data fits in a single page. If too much data is provided
+   * nothing will be written and result will be set to FLASH_OUT_OF_RANGE.
+   *
+   * @param result
+   * @param address
+   * @param data
+   * @param size
+   */
+  void programPage(uint8_t *result, uint32_t address, uint8_t* data,
+                   uint32_t size)
+  {
+    /*//Do now wrap around a page and do not try to write outside of the memory
+     uint32_t next_page = (address / 256)*256 + 256;
+     if(address + size > next_page || address > MEMORY_SIZE)
+     {
+     *result = RESULT_OUT_OF_RNG;
+     return;
+     }
+
+     uint8_t addr_buf[4], status;
+     addr_to_buf(addr_buf, address);
+
+     _write_enable();
+     Bus::write(PROGRAM_PAGE, addr_buf, data, 4, size);
+
+     do {
+     status = _read_flag_status_reg();
+     }while((status & FSR_PRG_ERS_CTRL) == 0);
+
+     *result = status & (FSR_PROGRAM | FSR_PROTECTION);
+
+     //Clear flag status register if an error has been encountered
+     if(*result != RESULT_OK)
+     {
+     _clear_flag_status_reg();
+
+     //Manually reset write_enable latch
+     if((*result & RESULT_PROTECTION_ERROR) > 0)
+     {
+     _write_disable();
+     }
+     }*/
+  }
+
+  /**
+   * @brief Enables or disables read only mode
+   */
+  void setReadOnly(bool readonly = true)
+  {
+    read_only_ = readonly;
+  }
+
+  bool isReadOnly() const
+  {
+    return read_only_;
+  }
+
+  /**
+   * @brief Enable the next erase operation. Call this before every erase op.
+   */
+  void enableErase()
+  {
+    m_erase_enable_ = true;
+  }
+
+  /**
+   * @brief Erases the subsector containing the specified address. For the operation
+   * to be successful, enable_erase_op must be called before this.
+   *
+   * @warning Estimated execution time is slightly less than 1 second.
+   *
+   * @param result
+   * @param address
+   */
+  void eraseSubsector(uint8_t* result, uint32_t address)
+  {
+    erase(result, SUBSECTOR_ERASE, address);
+  }
+
+  /**
+   * @brief Erases the sector containing the specified address. For the operation
+   * to be successful, enable_erase_op must be called before this.
+   * @warning Estimated execution time is about 2 seconds.
+   *
+   * @param result
+   * @param address
+   */
+  void eraseSector(uint8_t* result, uint32_t address)
+  {
+    erase(result, SECTOR_ERASE, address);
+  }
+
+  /**
+   * @brief Erases the entire die containing the specified address. For the operation
+   * to be successful, enable_erase_op must be called before this.
+   * @warning Estimated execution time is more than 4 minutes.
+   *
+   * @param result
+   * @param die: 0 to erase the first die, 1 to erase the second.
+   */
+  void eraseDie(uint8_t* result, uint8_t die)
+  {
+    if (die == 0)
+    {
+      erase(result, DIE_ERASE, 0);
+    } else if (die == 1)
+    {
+      erase(result, DIE_ERASE, BANK_SIZE);
+    }
+  }
+
+  /**
+   * Reads id information for this flash memory.
+   * @param buf Buffer with at least 20 bytes
+   */
+  void readId(uint8_t* buf)
+  {
+    //Bus::read(READ_ID, buf, 20);
+  }
+
+  /**
+   * @brief Software reset for the memory. All volatile bits are set to their
+   * default value.
+   */
+  void softReset()
+  {
+    /*Bus::write(RESET_ENABLE);
+     usleep(5);
+     Bus::write(RESET_MEMORY);
+     _wait_until_ready();*/
+  }
+
+ private:
+
+  FlashDriver()
+  {
+    printf("Mock memory size: %d\n", (int) MEMORY_SIZE);
+    memset((void*) (fake_memory_), 0xFF, MEMORY_SIZE);
+  }
+
+  void erase(uint8_t *result, uint8_t erase_cmd, uint32_t address)
+  {
+    if (address > MEMORY_SIZE)
+    {
+      *result = RESULT_F_OUT_OF_MEMORY;
+      return;
+    }
+
+    if (!m_erase_enable_)
+    {
+      *result = RESULT_F_ERASE_ERROR;
+      return;
+    }
+
+    //uint8_t addr_buf[4], status;
+    //addr_to_buf(addr_buf, address);
+    *result = RESULT_OK;
+    switch (erase_cmd)
+    {
+      case SUBSECTOR_ERASE:
+        address = address / SUBSECTOR_SIZE * SUBSECTOR_SIZE;
+        fakeErase(address, SUBSECTOR_SIZE);
+        break;
+      case SECTOR_ERASE:
+        address = address / SECTOR_SIZE * SECTOR_SIZE;
+        fakeErase(address, SECTOR_SIZE);
+        break;
+      case DIE_ERASE:
+        address = address / BANK_SIZE * BANK_SIZE;
+        fakeErase(address, BANK_SIZE);
+        break;
+      default:
+        *result = RESULT_F_ERASE_ERROR;
+    }
+
+    m_erase_enable_ = false;
+  }
+
+  /**
+   * @brief Checks if a program operation is still in progress.
+   * @param result
+   */
+  bool isBusy()
+  {
+    return false;		//_read_flag_status_reg() & FSR_PRG_ERS_CTRL;
+  }
+
+  /**
+   * @brief Waits until the memory is ready to perform a new write/erase op.
+   * @param result
+   */
+  void waitUntilReady()
+  {
+    /*while((_read_flag_status_reg() & FSR_PRG_ERS_CTRL) == 0)
+     {
+
+     }*/
+  }
+
+  uint8_t readStatusReg()
+  {
+    return 0;
+    /*uint8_t ret = Bus::read(READ_STATUS_REG);
+     return ret;*/
+  }
+
+  void writeStatusReg(uint8_t val)
+  {
+    /*_write_enable();
+     Bus::write(WRITE_STATUS_REG, val);
+     _write_disable();
+     _wait_until_ready();*/
+  }
+
+  uint8_t readFlagStatusReg()
+  {
+    /*uint8_t ret = Bus::read(READ_FLAG_STATUS_REG);
+     return ret;*/
+    return 0;
+  }
+
+  void clearFlagStatusReg()
+  {
+    //Bus::write(CLEAR_FLAG_STATUS_REG);
+  }
+
+  uint16_t readConfigReg()
+  {
+    /*uint8_t buf[2];
+     Bus::read(READ_NV_CONFIG_REG, buf, 2);
+
+     uint16_t res = buf[0] | (buf[1] << 8);*/
+
+    return 0;
+  }
+
+  void writeConfigReg(uint16_t val)
+  {
+    /*
+     * Mask some of the bits of the config register because
+     * we don't want to change them by accident.
+     */
+    /*val = val | 0x0FEC;
+
+     uint8_t buf[2];
+     buf[0] = val & 0xFF;
+     buf[1] = (val >> 8) & 0xFF;
+
+     _write_enable();
+     Bus::write(WRITE_NV_CONFIG_REG, buf, 2);
+     _write_disable();
+     _wait_until_ready();*/
+  }
+
+  void writeEnable()
+  {
+    /*if(!m_read_only) {
+     Bus::write(WRITE_ENABLE);
+     }*/
+  }
+
+  void writeDisable()
+  {
+    //Bus::write(WRITE_DISABLE);
+  }
+
+  static void addrToBuf(uint8_t* buf, uint32_t addr)
+  {
+    for (int i = 0; i < 4; i++)
+    {
+      buf[3 - i] = (addr >> 8 * i) & 0xFF;
+    }
+  }
+
+  void fakeRead(uint32_t address, uint8_t* data, uint32_t size)
+  {
+    //printf("Reading %d bytes from %d \n", (int)size, (int)address);
+    if (address + size > MEMORY_SIZE)
+    {
+      return;
+    }
+
+    memcpy((void*) (data), (void*) (fake_memory_ + address), size);
+  }
+
+  void fakeWrite(uint32_t address, uint8_t* data, uint32_t size)
+  {
+    if (address + size > MEMORY_SIZE)
+    {
+      return;
+    }
+
+    for (uint32_t i = 0; i < size; i++)
+    {
+      fake_memory_[address + i] &= data[i];
+    }
+
+    //memcpy((void*)(fake_memory_ + address), (void*)(data), size);
+  }
+
+  void fakeErase(uint32_t address, uint32_t size)
+  {
+    if (address + size > MEMORY_SIZE)
+    {
+      return;
+    }
+
+    memset((void*) (fake_memory_ + address), 0xFF, size);
+  }
+
+  static std::string logtag()
+  {
+    return "FlashDriver";
+  }
+
+  enum Commands
+  {
+    READ_ID = 0x9F,
+
+    WRITE_ENABLE = 0x06,
+    WRITE_DISABLE = 0x04,
+
+    READ_STATUS_REG = 0x05,
+    WRITE_STATUS_REG = 0x01,
+
+    READ_FLAG_STATUS_REG = 0x70,
+    CLEAR_FLAG_STATUS_REG = 0x50,
+
+    //non-volatile configuration register
+    READ_NV_CONFIG_REG = 0xB5,
+    WRITE_NV_CONFIG_REG = 0xB1,
+
+    //volatile configuration register
+    WRITE_VOL_CONFIG_REG = 0x85,
+    READ_VOL_CONFIG_REG = 0x81,
+
+    READ_EXT_ADDRESS_REG = 0xC8,
+    WRITE_EXT_ADDRESS_REG = 0xC5,
+
+    RESET_ENABLE = 0x66,
+    RESET_MEMORY = 0x99,
+
+    READ = 0x03,
+    PROGRAM_PAGE = 0x02,
+
+    SUBSECTOR_ERASE = 0x20,
+    SECTOR_ERASE = 0xD8,
+    DIE_ERASE = 0xC4
+  };
+
+  uint8_t* fake_memory_ = new uint8_t[MEMORY_SIZE];
+  bool read_only_ = false;
+  bool m_erase_enable_ = false;
+
+  /**
+   * Flag status register bit definitions
+   */
+  static const uint8_t FSR_ADDRESSING_MODE = 0x01;
+  static const uint8_t FSR_PROTECTION = 0x02;
+  static const uint8_t FSR_PROGRAM_SUSPEND = 0x04;
+  static const uint8_t FSR_VPP = 0x08;
+  static const uint8_t FSR_PROGRAM = 0x10;
+  static const uint8_t FSR_ERASE = 0x20;
+  static const uint8_t FSR_ERASE_SUSPEND = 0x40;
+  static const uint8_t FSR_PRG_ERS_CTRL = 0x80;
+};
+
+}   //namespace flashmemory
+
+
+#endif /* FLASHDRIVER_H */
diff --git a/src/tests/drivers/test-ad7994-bare.cpp b/src/tests/drivers/old_examples/test-ad7994-bare.cpp
similarity index 94%
rename from src/tests/drivers/test-ad7994-bare.cpp
rename to src/tests/drivers/old_examples/test-ad7994-bare.cpp
index 6ff83b86cc6d9c15659cb656635a1bd328faed45..e5ab760a4f089eb5143b36ca2e9f46affe5b177b 100644
--- a/src/tests/drivers/test-ad7994-bare.cpp
+++ b/src/tests/drivers/old_examples/test-ad7994-bare.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-ad7994.cpp b/src/tests/drivers/old_examples/test-ad7994.cpp
similarity index 95%
rename from src/tests/drivers/test-ad7994.cpp
rename to src/tests/drivers/old_examples/test-ad7994.cpp
index 93ccf70ca4281ea0c0e41fc6ce4cc1c498a05719..7fbd40b95a40c9580bd907f558e4f8abe50b6445 100644
--- a/src/tests/drivers/test-ad7994.cpp
+++ b/src/tests/drivers/old_examples/test-ad7994.cpp
@@ -1,5 +1,4 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
+/* Copyright (c) 2019 Skyward Experimental Rocketry
  * Authors: Luca Erbetta, Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-i2c-mpu9255.cpp b/src/tests/drivers/old_examples/test-i2c-mpu9255.cpp
similarity index 93%
rename from src/tests/drivers/test-i2c-mpu9255.cpp
rename to src/tests/drivers/old_examples/test-i2c-mpu9255.cpp
index 21d4e7ff3346c0ec805df7b3e6130ac44584d9a7..6ee890ebd7f852d88a9e44a2ffaa03fe8a4cd1bb 100644
--- a/src/tests/drivers/test-i2c-mpu9255.cpp
+++ b/src/tests/drivers/old_examples/test-i2c-mpu9255.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-imu-adis.cpp b/src/tests/drivers/old_examples/test-imu-adis.cpp
similarity index 82%
rename from src/tests/drivers/test-imu-adis.cpp
rename to src/tests/drivers/old_examples/test-imu-adis.cpp
index 52889cacb147e4563210b5c3d70aaa5ca3ea970b..4bdaf74114e6bf09d13dcc993033777e0205d3ab 100644
--- a/src/tests/drivers/test-imu-adis.cpp
+++ b/src/tests/drivers/old_examples/test-imu-adis.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -24,7 +24,7 @@
 #include <drivers/BusTemplate.h>
 #include <interfaces-impl/hwmapping.h>
 #include <sensors/ADIS16405/ADIS16405.h>
-#include <sensors/SensorSampling.h>
+#include <sensors/SensorSampler.h>
 #include <math/Stats.h>
 #include <diagnostic/CpuMeter.h>
 #include <drivers/spi/SensorSpi.h>
@@ -38,31 +38,27 @@ typedef Gpio<GPIOD_BASE, 5> rstPin; // PD5 for the HomeoneBoard
 // SPI1 binding to the sensor
 typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1; //Create SPI1
 typedef ProtocolSPI<busSPI1,miosix::sensors::adis16405::cs> spiADIS16405; //La lego al Chip Select 1 per la IMU 1
-typedef ADIS16405<spiADIS16405,rstPin> adis_t; //Passo il bus creato al sensore
 
 int main()
 {   
     spiADIS16405::init();
 
     Thread::sleep(1000);
-    adis_t adis(adis_t::GYRO_FS_300);
+    ADIS16405<spiADIS16405,rstPin>* adis = new ADIS16405<spiADIS16405,rstPin>(adis->GYRO_FS_300);
 
-    if(adis.init())
+    if(adis->init())
         printf("[ADIS16405] Init succeeded\n" );
     else
         printf("[ADIS16405] Init failed\n");
     
-    if(adis.selfTest())
+    if(adis->selfTest())
         printf("[ADIS16405] Self test succeeded\n" );
     else
         printf("[ADIS16405] Self test failed\n");
 
 
-    SimpleSensorSampler sampler;
-    sampler.AddSensor(&adis);
-
-    // DMASensorSampler sampler;
-    // sampler.AddSensor(&adis);
+    SimpleSensorSampler sampler(250, 1);
+    sampler.addSensor(adis, std::bind([&]() {}));
 
     StatsResult statResult;
     Stats stats;
@@ -70,7 +66,7 @@ int main()
     int counter = 0;
 
     while(true) {
-        sampler.Update();
+        sampler.sampleAndCallback();
 
         stats.add(averageCpuUtilization());
 
@@ -79,7 +75,7 @@ int main()
             printf("CPU usage: %f\n", statResult.mean);
             counter = 0;
 
-            const Vec3* last_data = adis.accelDataPtr();
+            const Vec3* last_data = adis->accelDataPtr();
             printf("%f %f %f\n", last_data->getX(), last_data->getY(),
                    last_data->getZ());
         }
diff --git a/src/tests/drivers/calibrate-mpu9250.cpp b/src/tests/drivers/old_examples/test-lsm.cpp
similarity index 52%
rename from src/tests/drivers/calibrate-mpu9250.cpp
rename to src/tests/drivers/old_examples/test-lsm.cpp
index 54467bace1bc84ba09b93fb321a99e2be543eaea..1dec3957b04706945c38a2eb653d7a85e22e84f8 100644
--- a/src/tests/drivers/calibrate-mpu9250.cpp
+++ b/src/tests/drivers/old_examples/test-lsm.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,69 +23,58 @@
 #include <Common.h>
 #include <drivers/BusTemplate.h>
 #include <interfaces-impl/hwmapping.h>
-#include <sensors/MPU9250/MPU9250.h>
+#include <sensors/LSM6DS3H/LSM6DS3H.h>
 
 #include <drivers/spi/SensorSpi.h>
-#include <sensors/SensorSampling.h>
+#include <sensors/SensorSampler.h>
+#include <math/Stats.h>
+#include <diagnostic/CpuMeter.h>
 
 using namespace miosix;
 using namespace miosix::interfaces;
 
+/* SPI1 binding to the sensor */
 typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1;
-typedef ProtocolSPI<busSPI1,sensors::mpu9250::cs> spiMPU9250_a;
-typedef MPU9250<spiMPU9250_a> mpu_t;
+typedef ProtocolSPI<busSPI1,sensors::lsm6ds3h::cs> spiLSM6DS3H0_a;
 
 int main()
 {   
-    float bias[3];
+    SimpleSensorSampler sampler(250, 1);
+    spiLSM6DS3H0_a::init();
 
-    SimpleSensorSampler sampler;
-
-    spiMPU9250_a::init();
-    mpu_t* mpu = new mpu_t(1, 1);
-
-    Thread::sleep(100);
+    LSM6DS3H<spiLSM6DS3H0_a>* lsm6ds3h = new LSM6DS3H<spiLSM6DS3H0_a>(3,3);
     
-    if(mpu->init()){
-        printf("MPU9250 Init succeeded\n" );
-        sampler.AddSensor(mpu);
+    if(lsm6ds3h->init())
+    {
+        printf("[LSM6DS3H] Init succeeded\n" );
+        sampler.addSensor(lsm6ds3h, std::bind([&]() {}));
     }
-    else {
-        printf("MPU9250 Init failed\n");
+    else
+    {
+        printf("[LSM6DS3H] Init failed\n");
 
-        while(!mpu->init()) {
-            printf("MPU9250 Init failed\n");
+        while(!lsm6ds3h->init()) {
+            printf("[LSM6DS3H] Init failed\n");
             Thread::sleep(1000);
         }
-    }
-
-    Thread::sleep(100);
 
-    printf("This script calculates the accelerometer's offsets, follow the instructions:\n");
-    printf("Leave the MPU9250 pointing up (aligned with the gravity vector) and steady, then press ENTER ");
-    getchar();
-    mpu->calibrateAccel();
-
-    printf("Calibration succeeded\n");
-    printf("Copy the following parameters into the MPU9250 class constructor: \n\n");
-
-    mpu->getAccelCalibParams(bias);
-    printf("_axb = %f;\n",bias[0]);
-    printf("_ayb = %f;\n",bias[1]);
-    printf("_azb = %f;\n",bias[2]);
-
-    printf("Press ENTER to read the accelerometer data and to check if the calibration is good\n");
-    getchar();
+    }
     
-    mpu->setAccelCalibParams(bias);
 
     while(true)
     {
-        sampler.Update();
+        sampler.sampleAndCallback();
+
+        // const Vec3* last_data = lsm6ds3h->gyroDataPtr();
+        // printf("%f %f %f\n", last_data->getX(), last_data->getY(),
+        //        last_data->getZ());
 
-        const Vec3* last_data = mpu->accelDataPtr();
+        const Vec3* last_data = lsm6ds3h->accelDataPtr();
         printf("%f %f %f\n", last_data->getX(), last_data->getY(),
                last_data->getZ());
+        
+        // const float* last_temp = lsm6ds3h->tempDataPtr();
+        // printf("temp: %f\n", *last_temp);
 
         Thread::sleep(100);
     }
diff --git a/src/tests/drivers/test-spi2.cpp b/src/tests/drivers/old_examples/test-spi2.cpp
similarity index 89%
rename from src/tests/drivers/test-spi2.cpp
rename to src/tests/drivers/old_examples/test-spi2.cpp
index 6a87562ff3e1c991b635c5e5ceedf6a9c3d6942b..212607b8d6f012ba3c611296d4ab47a6eda49ac9 100644
--- a/src/tests/drivers/test-spi2.cpp
+++ b/src/tests/drivers/old_examples/test-spi2.cpp
@@ -1,20 +1,19 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-tempSensor.cpp b/src/tests/drivers/old_examples/test-tempSensor.cpp
similarity index 94%
rename from src/tests/drivers/test-tempSensor.cpp
rename to src/tests/drivers/old_examples/test-tempSensor.cpp
index aba103075430985174ee64c8178afddba3a1a64e..5a954a68b7aff2d063551312d1c4287c955904d9 100644
--- a/src/tests/drivers/test-tempSensor.cpp
+++ b/src/tests/drivers/old_examples/test-tempSensor.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: 
+ * Author: Alessio Galluccio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/catch/test-unit-LM75B.cpp b/src/tests/drivers/old_examples/test-unit-LM75B.cpp
similarity index 93%
rename from src/tests/catch/test-unit-LM75B.cpp
rename to src/tests/drivers/old_examples/test-unit-LM75B.cpp
index f3f95248884ff9a694f4ea4a132f4b061635d983..5db478695da58d0b2226dad7248ab3da63f9913e 100644
--- a/src/tests/catch/test-unit-LM75B.cpp
+++ b/src/tests/drivers/old_examples/test-unit-LM75B.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-IMU.cpp b/src/tests/drivers/test-IMU.cpp
deleted file mode 100644
index 06e5aadd8972947e504ddfc34942c0c385c692ce..0000000000000000000000000000000000000000
--- a/src/tests/drivers/test-IMU.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include <sensors/LSM6DS3H.h>
-
-// SPI1
-typedef miosix::Gpio<GPIOA_BASE, 5> GpioSck;
-typedef miosix::Gpio<GPIOA_BASE, 6> GpioMiso;
-typedef miosix::Gpio<GPIOA_BASE, 7> GpioMosi;
-typedef miosix::Gpio<GPIOC_BASE, 3> CS_MPU9250;
-
-// SPI1 binding al sensore
-typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> busSPI1;  // Creo la SPI1
-typedef ProtocolSPI<busSPI1, CS_MPU9250>
-    spiProt;                      // La lego al Chip Select 1 per la IMU 1
-typedef LSM6DS3H<spiProt> lsm_t;  // Passo il bus creato al sensore
-
-int main (){
-    lsm_t* lsm = new  lsm_t(16, 500);
-    UNUSED(lsm);
-    while(1)
-    {
-
-    }
-}
\ No newline at end of file
diff --git a/src/tests/drivers/test-ads1118.cpp b/src/tests/drivers/test-ads1118.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eac7b6819722daf969b6818cbdacd1b5efd73edd
--- /dev/null
+++ b/src/tests/drivers/test-ads1118.cpp
@@ -0,0 +1,167 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/**
+ * @file test-ads1118.cpp
+ * @author Alberto Nidasio (alberto.nidasio@skywarder.eu)
+ * @brief Tests the ads1118 adc
+ * @version 1.0
+ * @date 2020-11-19
+ *
+ * This test has been setup for the following configuration:
+ *
+ * SPI pheripheral 2 (SPI2) with /32 divider
+ *
+ * Pins (STM32F407 - ADS1118):
+ *  PB12 (NSS)  - NC (we use pin C1 as chip select)
+ *  PB13 (SCK)  - SCK
+ *  PB14 (MISO) - DOUT
+ *  PB15 (MOSI) - DIN
+ *  PC1         - CS
+ *
+ * The ADS1118's input channels can be connected as follow:
+ *  AIN2 - GND
+ *  AIN3 - VCC (3V)
+ *
+ * In the developing test a function generator was used as variable source
+ */
+
+#include <Debug.h>
+#include <drivers/adc/ADS1118/ADS1118.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+
+#include "TimestampTimer.h"
+
+GpioPin sckPin  = GpioPin(GPIOB_BASE, 13);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 14);
+GpioPin mosiPin = GpioPin(GPIOB_BASE, 15);
+GpioPin csPin   = GpioPin(GPIOC_BASE, 1);
+
+constexpr ADS1118::ADS1118Mux channel1 = ADS1118::ADS1118Mux::MUX_AIN2_GND;
+constexpr ADS1118::ADS1118Mux channel2 = ADS1118::ADS1118Mux::MUX_AIN3_GND;
+
+void initBoard()
+{
+    // Enable SPI clock for SPI2 interface
+    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
+
+    // Alternate function configuration for SPI pins
+    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.alternateFunction(5);  // SPI function
+    mosiPin.mode(miosix::Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);  // SPI function
+    misoPin.mode(miosix::Mode::ALTERNATE);
+    misoPin.alternateFunction(5);  // SPI function
+
+    // Chip select pin as output starting high
+    csPin.mode(miosix::Mode::OUTPUT);
+    csPin.high();
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    TimestampTimer::enableTimestampTimer();
+
+    // SPI configuration setup
+    SPIBusConfig spiConfig = ADS1118::getDefaultSPIConfig();
+    SPIBus spiBus(SPI2);
+    SPISlave spiSlave(spiBus, csPin, spiConfig);
+
+    // Device initialization
+    ADS1118 ads1118(spiSlave);
+
+    // Initialize the device
+    ads1118.init();
+
+    // Enable the two channels and the temperature
+    ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+    ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+    ads1118.enableTemperature();
+
+    // Self test
+    if (ads1118.selfTest())
+    {
+        TRACE("Self test successful!\n");
+    }
+    else
+    {
+        TRACE("Self test failed :( error: %d\n", ads1118.getLastError());
+    }
+
+    // Read the two channels indipendently
+    TRACE("\nNow reading the two channels with 'readInputAndWait()'\n");
+    TRACE("Channel 1: %f\n", ads1118.readInputAndWait(channel1).voltage);
+    TRACE("Channel 2: %f\n", ads1118.readInputAndWait(channel2).voltage);
+
+    // Read samples with sample()
+    TRACE("\nNow reading the two channels with 'sample()'\n");
+    for (auto i = 0; i < 500; i++)
+    {
+        // Call 3 times sample() to read the 3 enabled channels
+        ads1118.sample();
+        miosix::Thread::sleep(5);
+        ads1118.sample();
+        miosix::Thread::sleep(5);
+        ads1118.sample();
+
+        printf("%.2f\t%.2f\t%.2f\n", ads1118.getTemperature().temp,
+               ads1118.getVoltage(channel1).voltage,
+               ads1118.getVoltage(channel2).voltage);
+    }
+
+    // Read a few times the first channel and then switch to the second one
+    TRACE(
+        "\nNow reading the first channel and then the second one with "
+        "'sample()'\n");
+
+    ads1118.disableAllInputs();
+    ads1118.enableInput(channel1, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+
+    TRACE("First channel:\n");
+    for (auto i = 0; i < 500; i++)
+    {
+        ads1118.sample();
+        miosix::Thread::sleep(5);
+
+        printf("%.2f\n", ads1118.getVoltage(channel1).voltage);
+    }
+
+    ads1118.disableAllInputs();
+    ads1118.enableInput(channel2, ADS1118::ADS1118DataRate::DR_250,
+                        ADS1118::ADS1118Pga::FSR_4_096);
+
+    TRACE("Second channel:\n");
+    for (auto i = 0; i < 500; i++)
+    {
+        ads1118.sample();
+        miosix::Thread::sleep(5);
+
+        printf("%.2f\n", ads1118.getVoltage(channel2).voltage);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-bme280.cpp b/src/tests/drivers/test-bme280.cpp
index c0bf9235984e8c185e02c6e10a14b078241e0906..e9db90a0cafd0612d42ede2b5cebfd01c13f458b 100644
--- a/src/tests/drivers/test-bme280.cpp
+++ b/src/tests/drivers/test-bme280.cpp
@@ -1,52 +1,127 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#include <sensors/BME280/BME280.h>
+/**
+ * This test has been setup for the following configuration:
+ *
+ * SPI pheripheral 2 (SPI2) with /32 divider
+ *
+ * Pins (STM32F407 - BME280):
+ *  PB12 (NSS)  - not connected (we use pin C1 as chip select)
+ *  PB13 (SCK)  - SCK
+ *  PB14 (MISO) - SDO
+ *  PB15 (MOSI) - SDA
+ *  PC1         - CBS
+ *
+ * The BME280 is powered by 3.3V
+ *
+ * In the developing test a function generator was used as variable source
+ */
+
+#include <Debug.h>
+#include <drivers/spi/SPIDriver.h>
 #include <miosix.h>
-#include <drivers/BusTemplate.h>
+#include <sensors/BME280/BME280.h>
 
-// SPI1
-typedef miosix::Gpio<GPIOA_BASE, 5> GpioSck;
-typedef miosix::Gpio<GPIOB_BASE, 4> GpioMiso;
-typedef miosix::Gpio<GPIOA_BASE, 7> GpioMosi;
-typedef miosix::Gpio<GPIOC_BASE, 1> CS;
+#include "TimestampTimer.h"
 
-// SPI1 binding al sensore
-typedef BusSPI<1, GpioMosi, GpioMiso, GpioSck> busSPI1;  // Creo la SPI1
-typedef ProtocolSPI<busSPI1, CS> spiProt;   // La lego al Chip Select 1 per la IMU 1
+GpioPin sckPin  = GpioPin(GPIOB_BASE, 13);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 14);
+GpioPin mosiPin = GpioPin(GPIOB_BASE, 15);
+GpioPin csPin   = GpioPin(GPIOC_BASE, 1);
 
-//typedef BME280<spiProt> bme_t;  // Passo il bus creato al sensore
+void initBoard()
+{
+    // Enable SPI clock for SPI2 interface
+    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
 
-int main ()
+    // Alternate function configuration for SPI pins
+    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.alternateFunction(5);  // SPI function
+    mosiPin.mode(miosix::Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);  // SPI function
+    misoPin.mode(miosix::Mode::ALTERNATE);
+    misoPin.alternateFunction(5);  // SPI function
+
+    // Chip select pin as output starting high
+    csPin.mode(miosix::Mode::OUTPUT);
+    csPin.high();
+}
+
+int main()
 {
-    BME280<spiProt> bme;
-    bme.init();
-    
-    while(1)
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    TimestampTimer::enableTimestampTimer();
+
+    // SPI configuration setup
+    SPIBusConfig spiConfig;
+    spiConfig.clock_div = SPIClockDivider::DIV32;
+    spiConfig.mode      = SPIMode::MODE0;
+    SPIBus spiBus(SPI2);
+    SPISlave spiSlave(spiBus, csPin, spiConfig);
+
+    // Device initialization
+    BME280 bme280(spiSlave);
+
+    bme280.init();
+
+    // In practice the self test reads the who am i reagister, this is already
+    // done in init()
+    if (!bme280.selfTest())
     {
-        bme.onSimpleUpdate();
-        printf("%d,%f,%f\n", bme.getDataPtr()->timestamp, bme.getDataPtr()->temperature, bme.getDataPtr()->pressure);
+        TRACE("Self test failed!\n");
+
+        return -1;
+    }
+
+    // Try forced mode
+    TRACE("Forced mode\n");
+    for (int i = 0; i < 10; i++)
+    {
+        bme280.setSensorMode(BME280::FORCED_MODE);
+
+        miosix::Thread::sleep(bme280.getMaxMeasurementTime());
+
+        bme280.sample();
+
+        TRACE("temp: %.2f DegC\tpress: %.2f hPa\thumid: %.2f %%RH\n",
+              bme280.getLastSample().temp, bme280.getLastSample().press,
+              bme280.getLastSample().humid);
+
+        miosix::Thread::sleep(1000);
+    }
+
+    TRACE("Normal mode\n");
+    bme280.setSensorMode(BME280::NORMAL_MODE);
+    while (true)
+    {
+        bme280.sample();
+
+        TRACE("temp: %.2f DegC\tpress: %.2f hPa\thumid: %.2f %%RH\n",
+              bme280.getLastSample().temp, bme280.getLastSample().press,
+              bme280.getLastSample().humid);
 
-        miosix::Thread::sleep(500);
+        miosix::Thread::sleep(40);  // 25Hz
     }
 }
\ No newline at end of file
diff --git a/src/tests/drivers/test-bmx160-with-correction.cpp b/src/tests/drivers/test-bmx160-with-correction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..499fbd6a3d2524a8e606699b617343376f06460f
--- /dev/null
+++ b/src/tests/drivers/test-bmx160-with-correction.cpp
@@ -0,0 +1,176 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Riccardo Musso, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <drivers/HardwareTimer.h>
+#include <drivers/interrupt/external_interrupts.h>
+#include <sensors/BMX160/BMX160.h>
+#include <sensors/BMX160/BMX160WithCorrection.h>
+
+constexpr const char *CORRECTION_PARAMETER_FILE = "/sd/bmx160_params.csv";
+
+constexpr int UPDATE_RATE = 5;  // Hz
+bool stopSamplingThread   = false;
+
+BMX160 *bmx160 = nullptr;
+
+// BMX160 Watermark interrupt
+void __attribute__((used)) EXTI5_IRQHandlerImpl()
+{
+    if (bmx160)
+    {
+        bmx160->IRQupdateTimestamp(TimestampTimer::getTimestamp());
+    }
+}
+
+void bmx160Sample(void *args);
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+
+    // Enable interrupt from BMX pin
+    enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE);
+
+    SPIBus bus(SPI1);
+
+    BMX160Config bmx_config;
+    bmx_config.fifo_mode      = BMX160Config::FifoMode::HEADER;
+    bmx_config.fifo_watermark = 500 / 4;
+    bmx_config.fifo_int       = BMX160Config::FifoInterruptPin::PIN_INT1;
+
+    bmx_config.temp_divider = 0;
+
+    bmx_config.acc_range = BMX160Config::AccelerometerRange::G_16;
+    bmx_config.gyr_range = BMX160Config::GyroscopeRange::DEG_2000;
+
+    bmx_config.acc_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.gyr_odr = BMX160Config::OutputDataRate::HZ_1600;
+    bmx_config.mag_odr = BMX160Config::OutputDataRate::HZ_50;
+
+    bmx_config.gyr_unit = BMX160Config::GyroscopeMeasureUnit::RAD;
+
+    bmx160 = new BMX160(bus, miosix::sensors::bmx160::cs::getPin(), bmx_config);
+
+    // Read the correction parameters
+    BMX160CorrectionParameters correctionParameters =
+        BMX160WithCorrection::readCorrectionParametersFromFile(
+            CORRECTION_PARAMETER_FILE);
+
+    // Show the user the current correction values
+    printf("\nCurrent bias vector\n");
+    printf("b = [    % 2.5f    % 2.5f    % 2.5f    ]\n\n",
+           correctionParameters.accelParams(0, 1),
+           correctionParameters.accelParams(1, 1),
+           correctionParameters.accelParams(2, 1));
+    printf("Matrix to be multiplied to the input vector\n");
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n",
+           correctionParameters.accelParams(0, 0), 0.f, 0.f);
+    printf("M = |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f,
+           correctionParameters.accelParams(1, 0), 0.f);
+    printf("    |    % 2.5f    % 2.5f    % 2.5f    |\n", 0.f, 0.f,
+           correctionParameters.accelParams(2, 0));
+
+    BMX160WithCorrection bmx160WithCorrection(
+        bmx160, correctionParameters,
+        {Direction::NEGATIVE_Z, Direction::NEGATIVE_Y});
+
+    TRACE("Initializing BMX160...\n");
+
+    if (!bmx160->init())
+    {
+        TRACE("Init failed! (code: %d)\n", bmx160->getLastError());
+        return -1;
+    }
+
+    TRACE("Performing self-test...\n");
+
+    if (!bmx160->selfTest())
+    {
+        TRACE("Self-test failed! (code: %d)\n", bmx160->getLastError());
+        return -1;
+    }
+
+    TRACE("Initialization and self-test completed!\n");
+
+    // Calibrate the sensor
+    miosix::Thread *samplingThread =
+        miosix::Thread::create(bmx160Sample, 2048, miosix::MAIN_PRIORITY,
+                               nullptr, miosix::Thread::JOINABLE);
+    if (!bmx160WithCorrection.calibrate())
+    {
+        TRACE("Calibration failed!\n");
+        return -1;
+    }
+    stopSamplingThread = true;
+    samplingThread->join();
+    TRACE("Calibration completed\n");
+
+    while (1)
+    {
+        bmx160->sample();
+        bmx160WithCorrection.sample();
+
+        BMX160Data data = bmx160->getLastSample();
+        BMX160WithCorrectionData correctedData =
+            bmx160WithCorrection.getLastSample();
+
+        printf("\n");
+
+        printf("[Raw]       ");
+        printf("Mag: %7.3f %7.3f %7.3f", data.mag_x, data.mag_y, data.mag_z);
+        printf("\tGyr: %7.3f %7.3f %7.3f", data.gyro_x, data.gyro_y,
+               data.gyro_z);
+        printf("\tAcc: %7.3f %7.3f %7.3f\n", data.accel_x, data.accel_y,
+               data.accel_z);
+
+        printf("[Corrected] ");
+        printf("Mag: %7.3f %7.3f %7.3f", correctedData.mag_x,
+               correctedData.mag_y, correctedData.mag_z);
+        printf("\tGyr: %7.3f %7.3f %7.3f", correctedData.gyro_x,
+               correctedData.gyro_y, correctedData.gyro_z);
+        printf("\tAcc: %7.3f %7.3f %7.3f\n", correctedData.accel_x,
+               correctedData.accel_y, correctedData.accel_z);
+
+        miosix::Thread::sleep(1000 / UPDATE_RATE);
+    }
+
+    return 0;
+}
+
+void bmx160Sample(void *args)
+{
+    UNUSED(args);
+    
+    while (!stopSamplingThread)
+    {
+        // Sample the bmx160
+        bmx160->sample();
+        if (bmx160->getLastError() != SensorErrors::NO_ERRORS)
+        {
+            TRACE("Failed to read data! (error: %d)\n", bmx160->getLastError());
+            continue;
+        }
+
+        miosix::Thread::sleep(1000 / UPDATE_RATE);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-bmx160.cpp b/src/tests/drivers/test-bmx160.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1c0372e497eb0a2b47e059f11448ad7be12f17c
--- /dev/null
+++ b/src/tests/drivers/test-bmx160.cpp
@@ -0,0 +1,125 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <drivers/HardwareTimer.h>
+#include <drivers/interrupt/external_interrupts.h>
+#include <sensors/BMX160/BMX160.h>
+
+SPIBus bus(SPI1);
+GpioPin cs(GPIOA_BASE, 8);
+
+GpioPin spi_sck(GPIOA_BASE, 5);
+GpioPin spi_miso(GPIOA_BASE, 6);
+GpioPin spi_mosi(GPIOA_BASE, 7);
+
+BMX160 *sensor = nullptr;
+uint32_t tick  = 0;
+
+void __attribute__((used)) EXTI5_IRQHandlerImpl()
+{
+    tick = TimestampTimer::getTimestamp();
+    if (sensor)
+    {
+        sensor->IRQupdateTimestamp(tick);
+    }
+}
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+
+    cs.high();
+
+    enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE);
+
+    BMX160Config config;
+    config.fifo_mode      = BMX160Config::FifoMode::HEADER;
+    config.fifo_int       = BMX160Config::FifoInterruptPin::PIN_INT1;
+    config.fifo_watermark = 100;
+    config.temp_divider   = 1;
+
+    sensor = new BMX160(bus, cs, config);
+
+    TRACE("Initializing BMX160...\n");
+
+    if (!sensor->init())
+    {
+        TRACE("Init failed! (code: %d)\n", sensor->getLastError());
+        while (1)
+        {
+        }
+        return -1;
+    }
+
+    TRACE("Performing self-test...\n");
+
+    if (!sensor->selfTest())
+    {
+        TRACE("Self-test failed! (code: %d)\n", sensor->getLastError());
+        return -1;
+    }
+
+    TRACE("Self-test successful!\n");
+
+    while (1)
+    {
+        miosix::delayUs(250 * 1000);
+
+        printf("----------------------------\n");
+
+        sensor->sample();
+        if (sensor->getLastError() != SensorErrors::NO_ERRORS)
+        {
+            TRACE("Failed to read data!\n");
+            continue;
+        }
+
+        uint64_t now = TimestampTimer::getTimestamp();
+
+        printf("Tick: %.4f s, Now: %.4f s\n", tick / 1000000.0f,
+               now / 1000000.0f);
+        printf("Temp: %.2f deg\n", sensor->getTemperature().temp);
+        printf("Fill: %d\n", sensor->getLastFifoSize());
+
+        printf("----------------------------\n");
+        uint8_t len = std::min(sensor->getLastFifoSize(), (uint8_t)5);
+
+        for (uint8_t i = 0; i < len; i++)
+        {
+            BMX160Data data = sensor->getFifoElement(i);
+            printf("Mag [%.4f s]:\t%.2f\t%.2f\t%.2f\n",
+                   data.mag_timestamp / 1000000.0f, data.mag_x, data.mag_y,
+                   data.mag_z);
+
+            printf("Gyr [%.4f s]:\t%.2f\t%.2f\t%.2f\n",
+                   data.gyro_timestamp / 1000000.0f, data.gyro_x, data.gyro_y,
+                   data.gyro_z);
+
+            printf("Acc [%.4f s]:\t%.2f\t%.2f\t%.2f\n",
+                   data.accel_timestamp / 1000000.0f, data.accel_x,
+                   data.accel_y, data.accel_z);
+        }
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-canbus.cpp b/src/tests/drivers/test-canbus.cpp
index 4f303017e52a13682b98608e59601857a07631cf..ff3afa46059416c08d3d71d87a3d73640ae7676c 100644
--- a/src/tests/drivers/test-canbus.cpp
+++ b/src/tests/drivers/test-canbus.cpp
@@ -1,3 +1,26 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Alvise de'Faveri Tron
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
 #include <Common.h>
 #include <drivers/canbus/CanManager.h>
 #include <drivers/canbus/CanUtils.h>
diff --git a/src/tests/drivers/test-dsgamma.cpp b/src/tests/drivers/test-dsgamma.cpp
index 5ff13c1baaaf45e57e67a4eb0b1f19bfada0b0dd..1c84aa7a97f81ac89139fc1213f4be2c220f7ce1 100644
--- a/src/tests/drivers/test-dsgamma.cpp
+++ b/src/tests/drivers/test-dsgamma.cpp
@@ -1,3 +1,25 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Authors: Alain Carlucci, Alvise de'Faveri Tron, Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #include <stdio.h>
 #include <string.h>
 #include "Common.h"
@@ -16,7 +38,6 @@ using miosix::Gpio;
 
 /* DISCOVERY F429I*/
 typedef Gpio<GPIOA_BASE, 0> button;
-typedef HardwareTimer<uint32_t, 4> Clock;
 // RTT calculation
 // long long sendTime = 0;
 enum State
diff --git a/src/tests/drivers/test-hbridge.cpp b/src/tests/drivers/test-hbridge.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..da00dea208bc397b9c932855dd0626d3ca781e92
--- /dev/null
+++ b/src/tests/drivers/test-hbridge.cpp
@@ -0,0 +1,130 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta, Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <drivers/HardwareTimer.h>
+#include <miosix.h>
+
+#include <iostream>
+#include <sstream>
+
+#include "Common.h"
+#include "drivers/hbridge/HBridge.h"
+
+using namespace miosix;
+using namespace std;
+
+static constexpr int PWM_DURATION = 60 * 1000;
+
+static const PWM::Timer HBRIDGE_TIM{
+    TIM3, &(RCC->APB1ENR), RCC_APB1ENR_TIM3EN,
+    TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB2)};
+
+static const PWMChannel HBRIDGE_PWM_CHANNEL = PWMChannel::CH2;
+
+GpioPin hbridge_in(GPIOB_BASE, 5);       // pwm pin
+GpioPin hbridge_inhibit(GPIOB_BASE, 7);  // inhibit pin for the hbridge
+
+bool print = true;  // print the elapsed time or not
+
+long long measured_time = 0;
+void wait()
+{
+    long long t = getTick();
+    long long t0 = t;
+
+    while(t < t0 + PWM_DURATION)
+    {
+        Thread::sleep(50);
+
+        t = getTick();
+
+        if (print)
+        {
+            TRACE("Elapsed time : %.2f \n", (t - t0) / 1000.0);
+        }
+    }
+
+    measured_time = t - t0;
+}
+
+int main()
+{
+    {
+        FastInterruptDisableLock l;
+        hbridge_in.mode(Mode::ALTERNATE);
+        hbridge_in.alternateFunction(2);
+        hbridge_inhibit.mode(Mode::OUTPUT);
+        hbridge_inhibit.low();
+    }
+
+    TimestampTimer::enableTimestampTimer();
+
+    for (;;)
+    {
+        uint32_t freq = 0;
+        float duty    = 0;
+        string temp;
+
+        do
+        {
+            cout << "Insert h-bridge frequency (1-30000 Hz): \n";
+            getline(cin, temp);
+            stringstream(temp) >> freq;
+
+            cout << "Insert h-bridge duty cycle (0-100 %): \n";
+            getline(cin, temp);
+            stringstream(temp) >> duty;
+
+            if (!(freq >= 1 && freq <= 30000 && duty >= 0.0f && duty <= 100.0f))
+            {
+                TRACE("Invalid inputs!\n");
+            }
+        } while (
+            !(freq >= 1 && freq <= 30000 && duty >= 0.0f && duty <= 100.0f));
+
+        do
+        {
+            cout << "READY!\nWrite 'yeet' to begin:\n";
+            getline(cin, temp);
+        } while (temp != "yeet");
+
+        HBridge hbridge(hbridge_inhibit, HBRIDGE_TIM, HBRIDGE_PWM_CHANNEL, freq,
+                        duty / 100);
+
+        hbridge.enable();
+        TRACE("Hbridge status : timestamp = %llu - state = %d \n",
+              hbridge.getStatus().timestamp, hbridge.getStatus().state);
+
+        wait();
+
+        hbridge.disable();
+        TRACE("Hbridge status : timestamp = %llu - state = %d \n",
+              hbridge.getStatus().timestamp, hbridge.getStatus().state);
+
+        Thread::sleep(500);
+
+        TRACE("Elapsed time: %.2f s\n", (measured_time) / 1000.0f);
+        TRACE("Done!\n\n");
+    }
+
+    return 0;
+}
diff --git a/src/tests/drivers/test-hwtimer-chain.cpp b/src/tests/drivers/test-hwtimer-chain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..de0259136c827d1189280a335391e9422dead78c
--- /dev/null
+++ b/src/tests/drivers/test-hwtimer-chain.cpp
@@ -0,0 +1,61 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Davide Mor
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <drivers/HardwareTimer.h>
+#include <Common.h>
+
+int main()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+        // Enable TIM2 + TIM3 + TIM4 peripheral clock
+        RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_TIM4EN;
+    }
+
+    uint32_t prescaler = TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1);
+    TRACE("Prescaler: %d\n", prescaler);
+
+    HardwareTimer<uint32_t, TimerMode::Chain> timer1(TIM3, TIM4,
+                                                    TimerTrigger::ITR2, 
+                                                    prescaler);
+
+    
+    HardwareTimer<uint32_t, TimerMode::Single> timer2(TIM2, prescaler);
+
+    timer1.start();
+    timer2.start();
+
+    while(true) {
+        miosix::Thread::sleep(1000);
+
+        uint32_t tick1 = timer1.tick();
+        uint32_t tick2 = timer2.tick();
+
+        TRACE("Timer1: %f\n", timer1.toMilliSeconds(tick1));
+        TRACE("Timer2: %f\n", timer2.toMilliSeconds(tick1));
+
+        // Delta should remain constant
+        TRACE("Delta: %d\n", tick1 - tick2);
+    }
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-internal-adc-dma.cpp b/src/tests/drivers/test-internal-adc-dma.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..01b3a984cde868f338a1b57e73e209c487298a52
--- /dev/null
+++ b/src/tests/drivers/test-internal-adc-dma.cpp
@@ -0,0 +1,126 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <drivers/adc/InternalADC/InternalADC.h>
+#include <miosix.h>
+
+ADC_TypeDef& ADCx                = *ADC3;
+DMA_Stream_TypeDef* DMAx_Streamx = DMA2_Stream1;
+
+int main()
+{
+
+    // DMA INIT ---------------------------------------------------------------
+    {
+        // 1: Disable the stream
+
+        // Disable stream
+        DMAx_Streamx->CR &= ~DMA_SxCR_EN;
+
+        // Wait for the stream to be disabled
+        while (DMAx_Streamx->CR & DMA_SxCR_EN)
+            ;
+
+        // Reset DMA configuration
+        DMAx_Streamx->CR = 0;
+
+        // Ensures all the status bits are cleared by explicitly clearing them
+        DMA2->LIFCR = 0x0F7D0F7D;  // Mask excluding reserved bits
+        DMA2->HIFCR = 0x0F7D0F7D;
+
+        // 2-4: The memory and peripheral addresses, also the number of data
+        // items will be set by the driver
+
+        // 5: Select the DMA channel
+        DMAx_Streamx->CR |= DMA_SxCR_CHSEL_1;  // <- CHANGE THIS!
+
+        // 6: The peripheral is not intended to be the flow controller
+        // (it will function only as a trigger)
+        DMAx_Streamx->CR &= ~DMA_SxCR_PFCTRL;
+
+        // 7: Configure the stream priority to very high
+        DMAx_Streamx->CR |= DMA_SxCR_PL;
+
+        // 8: Configure fifo usage (disable => direct mode)
+        DMAx_Streamx->FCR &= ~DMA_SxFCR_DMDIS;
+
+        // 9: Other configuration
+
+        // Data transfer peripheral-to-memory
+        DMAx_Streamx->CR &= ~DMA_SxCR_DIR;
+
+        // Address increment mode
+        DMAx_Streamx->CR &= ~DMA_SxCR_PINC;  // Fixed
+        DMAx_Streamx->CR |= DMA_SxCR_MINC;   // Incremented
+
+        // Single or burst transaction (single)
+        DMAx_Streamx->CR &= ~DMA_SxCR_MBURST;
+
+        // Data width (16 bit)
+        DMAx_Streamx->CR |= DMA_SxCR_PSIZE_0;
+        DMAx_Streamx->CR |= DMA_SxCR_MSIZE_0;
+
+        // Circular mode (on)
+
+        DMAx_Streamx->CR |= DMA_SxCR_CIRC;
+
+        // Double buffer mode (off)
+        DMAx_Streamx->CR &= ~DMA_SxCR_DBM;
+
+        // Interrupt not used
+
+        // 10: Activate the stream after peripheral configuration (see warning)
+    }
+
+    // Set pins PA0 PA1 PA2 PA3 as analog input
+    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
+    GPIOA->MODER = 0xFF;
+
+    // Enable ADC clock
+    RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;  // <-- CHANGE THIS!
+
+    // Set the clock divider for the analog circuitry (/8)
+    ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1;
+    // In this case I've set the maximum value, check the datasheet for the
+    // maximum frequency the analog circuitry supports and compare it with the
+    // parent clock
+
+    InternalADC adc(ADCx, 3.0, DMAx_Streamx);
+    adc.enableChannel(InternalADC::CH0);
+    adc.enableChannel(InternalADC::CH3);
+    adc.init();
+
+    printf("Configuration completed\n");
+
+    while (1)
+    {
+        adc.sample();
+
+        printf("%8lld:%.2f\t%8lld:%.2f\n",
+               adc.getVoltage(InternalADC::CH0).adc_timestamp,
+               adc.getVoltage(InternalADC::CH0).voltage,
+               adc.getVoltage(InternalADC::CH0).adc_timestamp,
+               adc.getVoltage(InternalADC::CH3).voltage);
+
+        miosix::delayMs(1000);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-internal-adc.cpp b/src/tests/drivers/test-internal-adc.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7803a536cc5bb5cf76e66520b97066e48298d95e
--- /dev/null
+++ b/src/tests/drivers/test-internal-adc.cpp
@@ -0,0 +1,59 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <drivers/adc/InternalADC/InternalADC.h>
+#include <miosix.h>
+
+#include "TimestampTimer.h"
+
+ADC_TypeDef& ADCx = *ADC3;
+
+int main()
+{
+    // Set the clock divider for the analog circuitry (/8)
+    ADC->CCR |= ADC_CCR_ADCPRE_0 | ADC_CCR_ADCPRE_1;
+    // In this case I've set the maximum value, check the datasheet for the
+    // maximum frequency the analog circuitry supports and compare it with the
+    // parent clock
+
+    TimestampTimer::enableTimestampTimer();
+
+    InternalADC adc(ADCx, 3.3);
+    adc.enableChannel(InternalADC::CH4);  // PF6
+    adc.enableChannel(InternalADC::CH5);  // PF7
+    adc.enableChannel(InternalADC::CH6);  // PF8
+    adc.init();
+
+    printf("Configuration completed\n");
+
+    while (1)
+    {
+        adc.sample();
+
+        printf("CH4:%1.3f\tCH5:%1.3f\tCH6:%1.3f\n",
+               adc.getVoltage(InternalADC::CH4).voltage,
+               adc.getVoltage(InternalADC::CH5).voltage,
+               adc.getVoltage(InternalADC::CH6).voltage);
+
+        miosix::delayMs(1000);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-l3gd20-fifo.cpp b/src/tests/drivers/test-l3gd20-fifo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64920761034b5efe40bee8f9d24f143278c4d47e
--- /dev/null
+++ b/src/tests/drivers/test-l3gd20-fifo.cpp
@@ -0,0 +1,252 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/**
+ * Reads data at the highest possible rate from the sensor (~760 Hz) using the
+ * FIFO on the sensor as a buffer to avoid polling too frequently.
+ * We perform polling to maintain compatibility with the SensorManager and
+ * simplify sampling multiple sensors on the same thread.
+ *
+ * We read the FIFO at an interval such that it fills up to about 25 samples, to
+ * leave us a bit of wiggle room (fifo size is 32 samples). A watermark
+ * interrupt is set at about half the samples we plan to read (12 samples), in
+ * order to estimate the timestamp of the samples as precisely as possible. The
+ * watermark is intentionally set in the "middle" of the fifo as to minimize
+ * the possibility of desyncronizations between the sensor and the polling loop:
+ * If the watermark is set too close to the end of the fifo, the watermark
+ * interrupt may arrive *after* we start emptying the fifo, causing the driver
+ * to use an incorrect timestamp for the data. A similar case happens if the
+ * timestamp is too close to the beginning of the FIFO.
+ *
+ * The timestamp for each sample in the fifo is calculated as follows (inside
+ * the sensor driver): Knowing the timestamp of the 12th sample (thanks to the
+ * watermark interrup) we can estimate the timestamp of every other (previous or
+ * subsequent) sample in the FIFO, by doing
+ *
+ * timestamp(i) = timestamp(12) + SAMPLE_PERIOD*(i-12)
+ *
+ * After we have collected enough samples, dump them on the serial in CSV format
+ * togheter with other useful data.
+ */
+
+#include <array>
+
+#include "diagnostic/CpuMeter.h"
+#include "drivers/HardwareTimer.h"
+#include "drivers/interrupt/external_interrupts.h"
+#include "drivers/spi/SPIDriver.h"
+#include "TimestampTimer.h"
+#include "sensors/L3GD20/L3GD20.h"
+
+using namespace miosix;
+using std::array;
+
+typedef Gpio<GPIOF_BASE, 7> GpioSck;
+typedef Gpio<GPIOF_BASE, 8> GpioMiso;
+typedef Gpio<GPIOF_BASE, 9> GpioMosi;
+typedef Gpio<GPIOA_BASE, 2> GpioINT2;
+
+// L3GD20 SPI
+SPIBus bus(SPI5);
+GpioPin cs(GPIOC_BASE, 1);
+
+L3GD20* gyro = nullptr;
+
+// Send the watermark interrupt when the FIFO contains FIFO_WATERMARK samples.
+static constexpr unsigned int FIFO_WATERMARK = 12;
+
+// Expected frequency from the datasheet is 760 Hz, but due to clock
+// misalignment / temperature errors and other factors, the observed clock (and
+// output data rate) is the following:
+static constexpr float SAMPLE_FREQUENCY = 782.3f;
+
+struct GyroSample
+{
+    int fifo_num;
+    L3GD20Data gyro;
+    int level;
+    uint64_t wtm_delta;
+    float cpu;
+    uint64_t update;
+};
+
+// How many samples to collect
+static constexpr int NUM_SAMPLES = SAMPLE_FREQUENCY * 20;
+
+GyroSample data[NUM_SAMPLES];
+int data_counter = 0;
+
+// High resolution hardware timer using TIM5
+HardwareTimer<uint32_t> hrclock{
+    TIM5, TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)};
+
+// Last interrupt received timer tick
+volatile uint32_t last_watermark_tick;  // Stores the high-res tick of the last
+                                        // interrupt (L3GD20 watermark event)
+volatile uint32_t watermark_delta;  // Tick delta between the last 2 watermark
+                                    // events
+
+/**
+ * Interrupt handling routine. Called each time the fifo is filled with
+ * FIFO_WATERMARK samples. Stores the timestamp of the interrupt, representing
+ * the time the 12th sample was collected. From this, knowing the sample rate,
+ * we can obtain the timestamp of all previous and subsequent samples in the
+ * FIFO. Also calculates the time delta from the previous watermark, then
+ * provides the timestamp (in microseconds) to the sensor.
+ */
+void __attribute__((used)) EXTI2_IRQHandlerImpl()
+{
+    // Current high resolution tick
+    uint32_t tick       = hrclock.tick();
+    watermark_delta     = tick - last_watermark_tick;
+    last_watermark_tick = tick;
+
+    // Pass timestamp to the sensor
+    if (gyro != nullptr)
+    {
+        gyro->IRQupdateTimestamp(hrclock.toIntMicroSeconds(tick));
+    }
+}
+
+void configure()
+{
+    {
+        FastInterruptDisableLock dLock;
+
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+
+        GpioSck::mode(Mode::ALTERNATE);
+        GpioMiso::mode(Mode::ALTERNATE);
+        GpioMosi::mode(Mode::ALTERNATE);
+
+        GpioSck::alternateFunction(5);
+        GpioMiso::alternateFunction(5);
+        GpioMosi::alternateFunction(5);
+
+        // Interrupt
+        GpioINT2::mode(Mode::INPUT_PULL_DOWN);
+
+        cs.mode(Mode::OUTPUT);
+    }
+
+    cs.high();
+
+    enableExternalInterrupt(GPIOA_BASE, 2, InterruptTrigger::RISING_EDGE);
+
+    TimestampTimer::enableTimestampTimer();
+
+    // High resolution clock configuration
+    hrclock.setPrescaler(382);
+    hrclock.start();
+}
+
+int main()
+{
+    configure();
+
+    // Setup sensor
+    gyro = new L3GD20(bus, cs, L3GD20::FullScaleRange::FS_250,
+                      L3GD20::OutPutDataRate::ODR_760, 0x03);
+
+    // Enable fifo with the specified watermark before calling init()
+    gyro->enableFifo(FIFO_WATERMARK);
+
+    // Init the gyro
+    while (!gyro->init())
+    {
+        printf("Gyro initialization failure!\n");
+        Thread::sleep(100);
+    }
+
+    // Sample NUM_SAMPLES data
+    int fifo_num = 0;
+    while (data_counter < NUM_SAMPLES)
+    {
+        long last_tick = miosix::getTick();
+
+        // Read the fifo
+        uint32_t update = hrclock.tick();
+        gyro->sample();
+
+        // Measure how long we take to read the fifo
+        update = hrclock.tick() - update;
+
+        uint8_t level =
+            gyro->getLastFifoSize();  // Current number of samples in the FIFO
+
+        // Obtain the FIFO
+        const array<L3GD20Data, L3GD20_FIFO_SIZE>& fifo =
+            gyro->getLastFifo();
+
+        // Store everything in the data buffer
+        for (int i = 0; i < level; i++)
+        {
+            // data[data_counter++] = fifo[i];
+            data[data_counter++] = {fifo_num,
+                                    fifo[i],
+                                    level,
+                                    hrclock.toIntMicroSeconds(watermark_delta),
+                                    averageCpuUtilization(),
+                                    hrclock.toIntMicroSeconds(update)};
+
+            // Stop if we have enough data
+            if (data_counter >= NUM_SAMPLES)
+            {
+                break;
+            }
+        }
+        ++fifo_num;
+
+        // Wait until fifo has about 25 samples. The fifo size is 32 samples, so
+        // we have 7 samples (~ 9 ms) of wiggle room before we start losing
+        // data, in case we sleep a bit too much (may happen if an higher
+        // priority thread has a long task to perform)
+        Thread::sleepUntil(last_tick + 25.5 * 1000 / SAMPLE_FREQUENCY);
+    }
+
+    // Dump buffer content as CSV on the serial (might take a while)
+    // printf("t,x,y,z\n");
+    printf("FIFO_num,timestamp,int_delta,read_time,sample_delta,x,y,z,cpu\n");
+
+    for (int i = 1; i < data_counter; i++)
+    {
+        // clang-format off
+        printf("%d,%llu,%llu,%llu,%llu,%f,%f,%f,%.2f\n", 
+                data[i].fifo_num,
+                data[i].gyro.gyro_timestamp, 
+                data[i].wtm_delta,
+                data[i].update,
+                (data[i].gyro.gyro_timestamp - data[i - 1].gyro.gyro_timestamp),
+                data[i].gyro.gyro_x, 
+                data[i].gyro.gyro_y, 
+                data[i].gyro.gyro_z,
+                data[i].cpu);
+        // clang-format on
+    }
+
+    printf("\n\n\nend.\n");
+    for (;;)
+    {
+        Thread::sleep(1000);
+    }
+}
diff --git a/src/tests/drivers/test-l3gd20.cpp b/src/tests/drivers/test-l3gd20.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..787fc27b7fe24c14143882dfa0c4d5d19a03f69e
--- /dev/null
+++ b/src/tests/drivers/test-l3gd20.cpp
@@ -0,0 +1,197 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/**
+ * Polls the sensor at an high rate (1000 Hz), while measuring a precise
+ * timestamp for each sample using the DataReady interrupt provided by the
+ * sensor. This interrupt is used to measure the timestamp of the last sample
+ * read. After reading a certain number of samples, dumps them on the serial in
+ * CSV format.
+ */
+
+#include <array>
+
+#include "diagnostic/CpuMeter.h"
+#include "drivers/HardwareTimer.h"
+#include "drivers/interrupt/external_interrupts.h"
+#include "drivers/spi/SPIDriver.h"
+#include "TimestampTimer.h"
+#include "sensors/L3GD20/L3GD20.h"
+
+using namespace miosix;
+using std::array;
+
+// GPIOs
+typedef Gpio<GPIOF_BASE, 7> GpioSck;
+typedef Gpio<GPIOF_BASE, 8> GpioMiso;
+typedef Gpio<GPIOF_BASE, 9> GpioMosi;
+typedef Gpio<GPIOA_BASE, 2> GpioINT2;
+
+// L3GD20 SPI
+SPIBus bus(SPI5);
+GpioPin cs(GPIOC_BASE, 1);
+
+L3GD20* gyro;
+
+static constexpr int SAMPLE_RATE = 1000;
+
+static constexpr int NUM_SAMPLES = SAMPLE_RATE * 5;
+
+struct GyroSample
+{
+    uint64_t timestamp;
+    uint64_t sample_delta;
+    L3GD20Data data;
+    float cpu;
+};
+
+GyroSample data[NUM_SAMPLES];
+int data_counter = 0;
+
+// High resolution hardware timer using TIM5
+HardwareTimer<uint32_t> hrclock{
+    TIM5, TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)};
+
+// Last interrupt received timer tick
+uint32_t last_sample_tick;  // Stores the high-res tick of the last
+                            // interrupt (L3GD20 watermark event)
+uint32_t sample_delta;      // Tick delta between the last 2 watermark
+                            // events
+
+/**
+ * Interrupt handling routine. Called each time a new sample is available from
+ * the gyroscope (Data Ready function on INT2 on the sensor).
+ * Stores the timestamp of the last sample and calculates the time delta from
+ * the previous sample, then provides the timestamp (in microseconds) to the
+ * sensor.
+ */
+void __attribute__((used)) EXTI2_IRQHandlerImpl()
+{
+    // Current high resolution tick
+    uint32_t tick    = hrclock.tick();
+    sample_delta     = tick - last_sample_tick;
+    last_sample_tick = tick;
+
+    // Pass timestamp to sensor
+    if (gyro != nullptr)
+        gyro->IRQupdateTimestamp(hrclock.toIntMicroSeconds(tick));
+}
+
+void configure()
+{
+    {
+        FastInterruptDisableLock dLock;
+
+        // Enable SPI5 and TIM5 peripheral clocks
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+
+        // Set SPI pins to correct alternate mode
+        GpioSck::mode(Mode::ALTERNATE);
+        GpioMiso::mode(Mode::ALTERNATE);
+        GpioMosi::mode(Mode::ALTERNATE);
+
+        GpioSck::alternateFunction(5);
+        GpioMiso::alternateFunction(5);
+        GpioMosi::alternateFunction(5);
+
+        // Setup interrupt pin (sensors pulls INT2 up, so we set it to PULL_DOWN
+        // to avoid spurious interrupts)
+        GpioINT2::mode(Mode::INPUT_PULL_DOWN);
+
+        // Set chip select pin to OUTPUT
+        cs.mode(Mode::OUTPUT);
+    }
+
+    // Chip select starts high (not asserted)
+    cs.high();
+
+    // Enable rising-edge interrupt detection on PA2
+    enableExternalInterrupt(GPIOA_BASE, 2, InterruptTrigger::RISING_EDGE);
+
+    TimestampTimer::enableTimestampTimer();
+
+    // High resolution clock configuration: Sets the prescaler as to obtain
+    // 1.8 hours run time and 1.5 microseconds of resolution
+    hrclock.setPrescaler(127);
+    hrclock.start();
+}
+
+int main()
+{
+    configure();
+
+    gyro = new L3GD20(bus, cs, L3GD20::FullScaleRange::FS_250,
+                      L3GD20::OutPutDataRate::ODR_760, 0x03);
+
+    // Init the gyro
+    while (!gyro->init())
+    {
+        printf("Gyro initialization failure!\n");
+        Thread::sleep(100);
+    }
+
+    Thread::sleep(500);
+
+    // Collect NUM_SAMPLE samples
+    while (data_counter < NUM_SAMPLES)
+    {
+        long last_tick = miosix::getTick();
+
+        // Read data from the sensor
+        gyro->sample();
+
+        // Obtain the data
+        L3GD20Data d = gyro->getLastSample();
+
+        // Store the sample in the array, togheter with other useful data
+        data[data_counter++] = {d.gyro_timestamp, sample_delta, d,
+                                averageCpuUtilization()};
+
+        // Wait until SAMPLE_PERIOD milliseconds from the start of this
+        // iteration have passed (SAMPLE_PERIOD = 1000 / SAMPLE_RATE)
+        Thread::sleepUntil(last_tick + 1000 / SAMPLE_RATE);
+    }
+
+    // Dump buffer content as CSV on the serial (might take a while)
+    printf("FIFO_num,timestamp,int_delta,sample_delta,x,y,z,cpu\n");
+    for (int i = 1; i < data_counter; i++)
+    {
+        // clang-format off
+         printf("%d,%llu,%llu,%llu,%f,%f,%f,%.2f\n", 
+                0,
+                data[i].timestamp, 
+                hrclock.toIntMicroSeconds(data[i].sample_delta),
+                (data[i].timestamp - data[i - 1].timestamp),
+                data[i].data.gyro_x, 
+                data[i].data.gyro_y, 
+                data[i].data.gyro_z,
+                data[i].cpu);
+        // clang-format on
+    }
+
+    printf("\n\n\nend.\n");
+    for (;;)
+    {
+        Thread::sleep(1000);
+    }
+}
diff --git a/src/tests/drivers/test-lis3dsh.cpp b/src/tests/drivers/test-lis3dsh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d33281a286293d635841b04f81dbd6c111d67d15
--- /dev/null
+++ b/src/tests/drivers/test-lis3dsh.cpp
@@ -0,0 +1,131 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+
+#include "Common.h"
+#include "sensors/LIS3DSH/LIS3DSH.h"
+
+using namespace std;
+using namespace miosix;
+
+SPIBus bus(SPI1);
+
+GpioPin spi_sck(GPIOA_BASE, 5);
+GpioPin spi_miso(GPIOA_BASE, 6);
+GpioPin spi_mosi(GPIOA_BASE, 7);
+GpioPin cs(GPIOE_BASE, 3);
+
+int main()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;  // Enable SPI1 bus
+
+        spi_sck.mode(miosix::Mode::ALTERNATE);
+        spi_sck.alternateFunction(5);
+        spi_miso.mode(miosix::Mode::ALTERNATE);
+        spi_miso.alternateFunction(5);
+        spi_mosi.mode(miosix::Mode::ALTERNATE);
+        spi_mosi.alternateFunction(5);
+
+        cs.mode(miosix::Mode::OUTPUT);
+    }
+    cs.high();
+
+    TimestampTimer::enableTimestampTimer();
+
+    LIS3DSH sensor(bus, cs, sensor.ODR_100_HZ, sensor.UPDATE_AFTER_READ_MODE,
+                   sensor.FULL_SCALE_4G);
+
+    LIS3DSHData data;
+
+    // sensor not initialized, should give the error
+    sensor.sample();
+    if (sensor.getLastError() != SensorErrors::NOT_INIT)
+    {
+        printf("\nTEST FAILED: sensor not initialized \n");
+        return -1;
+    }
+
+    bool initialized = false;
+    // initialize imu
+    if (!sensor.init())
+    {
+        if (sensor.getLastError() == SensorErrors::INVALID_WHOAMI)
+        {
+            printf("TEST FAILED: invalid WHO_AM_I value, init failed \n");
+        }
+        else
+        {
+            printf("TEST FAILED: init failed \n");
+        }
+        return -1;
+    }
+    initialized = true;
+
+    // check if the sensor is properly working
+    if (!sensor.selfTest())
+    {
+        printf("\nTEST FAILED: self-test failed \n");
+        return -1;
+    }
+
+    // if sensor already inizialized, init() should return false
+    if (initialized)
+    {
+        if (sensor.init())
+        {
+            printf("\nTEST FAILED: sensor is already initialized \n");
+            return -1;
+        }
+    }
+
+    Thread::sleep(500);
+
+    // sample some data from the sensor
+    for (int i = 0; i < 5; i++)
+    {
+        // sensor intitialized, should return error if no new data exist
+        sensor.sample();
+
+        if (sensor.getLastError() == SensorErrors::NO_NEW_DATA)
+        {
+            printf("\nWarning: no new data to be read \n");
+        }
+
+        data = sensor.getLastSample();
+
+        printf("\nTimestamp: %llu \n", data.accel_timestamp);
+        printf("Acc: x: %f | y: %f | z: %f \n", data.accel_x, data.accel_y,
+               data.accel_z);
+        printf("Temp: %.2f C \n", data.temp);
+
+        Thread::sleep(200);
+    }
+
+    printf("\nLIS3DSH TEST OK ! \n");
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-lis3mdl.cpp b/src/tests/drivers/test-lis3mdl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b3c1329e722feed5b22224deb747dc0b32ef17f9
--- /dev/null
+++ b/src/tests/drivers/test-lis3mdl.cpp
@@ -0,0 +1,94 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Riccardo Musso
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+
+#include "sensors/LIS3MDL/LIS3MDL.h"
+
+using namespace miosix;
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+    
+    GpioPin cs(GPIOB_BASE, 1), miso(GPIOB_BASE, 4), mosi(GPIOB_BASE, 5),
+        clk(GPIOB_BASE, 3);
+
+    {
+        FastInterruptDisableLock dLock;
+
+        /* Enable SPI1 */
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+
+        cs.mode(Mode::OUTPUT);
+        cs.high();
+
+        clk.mode(Mode::ALTERNATE);
+        clk.alternateFunction(5);
+
+        miso.mode(Mode::ALTERNATE);
+        miso.alternateFunction(5);
+
+        mosi.mode(Mode::ALTERNATE);
+        mosi.alternateFunction(5);
+    }
+    
+
+    SPIBus bus(SPI1);
+
+    SPIBusConfig busConfig;
+    busConfig.clock_div = SPIClockDivider::DIV32;
+
+    LIS3MDL::Config config;
+    config.odr                = LIS3MDL::ODR_560_HZ;
+    config.scale              = LIS3MDL::FS_16_GAUSS;
+    config.temperatureDivider = 5;
+
+    LIS3MDL sensor(bus, cs, busConfig, config);
+
+    if (!sensor.init())
+    {
+        TRACE("LIS3MDL: Init failed");
+        return 1;
+    }
+
+    TRACE("Doing self test!\n");
+    bool ok = sensor.selfTest();
+    if (!ok)
+    {
+        TRACE("Error: selfTest() returned false!\n");
+    }
+
+    TRACE("Now printing some sensor data:\n");
+    Thread::sleep(100);
+
+    while (true)
+    {
+        sensor.sample();
+        LIS3MDLData data = sensor.getLastSample();
+        TRACE("%f C | x: %f | y: %f | z %f\n", data.temp, data.mag_x,
+              data.mag_y, data.mag_z);
+        miosix::Thread::sleep(2000);
+    }
+}
diff --git a/src/tests/drivers/test-mavlink-multi.cpp b/src/tests/drivers/test-mavlink-multi.cpp
index 546ce2d4169c72d8b988ea93faf87336f3485644..3fb1646db74f86ff9e37863c173962fee007e24e 100644
--- a/src/tests/drivers/test-mavlink-multi.cpp
+++ b/src/tests/drivers/test-mavlink-multi.cpp
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-mavlink.cpp b/src/tests/drivers/test-mavlink.cpp
index e15a022eeb87ea186aebf8f7b93396af27976155..88c90246f88efd25beec11d9a4917e0ada714450 100644
--- a/src/tests/drivers/test-mavlink.cpp
+++ b/src/tests/drivers/test-mavlink.cpp
@@ -13,14 +13,20 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#include <libs/mavlink_skyward_lib/mavlink_lib/r2a/mavlink.h>
+// Ignore warnings, as we don't want to change third party generated files to 
+// fix them
+#pragma GCC diagnostic push 
+#pragma GCC diagnostic ignored "-Wcast-align"
+#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
+#include <libs/mavlink_skyward_lib/mavlink_lib/lynx/mavlink.h>
+#pragma GCC diagnostic pop 
 
 #include <Common.h>
 #include <drivers/gamma868/Gamma868.h>
diff --git a/src/tests/drivers/test-mpu9250.cpp b/src/tests/drivers/test-mpu9250.cpp
index f53889a6de00d03789cb215c2f6487b0878cf9c2..b7536ec816009c9380452552eb909ede30ff180d 100644
--- a/src/tests/drivers/test-mpu9250.cpp
+++ b/src/tests/drivers/test-mpu9250.cpp
@@ -1,5 +1,5 @@
-/* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,61 +13,78 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#include <Common.h>
-#include <drivers/BusTemplate.h>
-#include <interfaces-impl/hwmapping.h>
+#include <Debug.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
 #include <sensors/MPU9250/MPU9250.h>
 
-#include <drivers/spi/SensorSpi.h>
-#include <sensors/SensorSampling.h>
+#include "TimestampTimer.h"
 
-using namespace miosix;
-using namespace miosix::interfaces;
+GpioPin sckPin  = GpioPin(GPIOB_BASE, 13);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 14);
+GpioPin mosiPin = GpioPin(GPIOB_BASE, 15);
+GpioPin csPin   = GpioPin(GPIOC_BASE, 1);
 
-typedef BusSPI<1,spi1::mosi,spi1::miso,spi1::sck> busSPI1;
-typedef ProtocolSPI<busSPI1,sensors::mpu9250::cs> spiMPU9250_a;
-typedef MPU9250<spiMPU9250_a> mpu_t;
+void initBoard()
+{
+    // Enable SPI clock for SPI2 interface
+    RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;
+
+    // Alternate function configuration for SPI pins
+    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.alternateFunction(5);  // SPI function
+    mosiPin.mode(miosix::Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);  // SPI function
+    misoPin.mode(miosix::Mode::ALTERNATE);
+    misoPin.alternateFunction(5);  // SPI function
+
+    // Chip select pin as output starting high
+    csPin.mode(miosix::Mode::OUTPUT);
+    csPin.high();
+}
 
 int main()
-{   
-    SimpleSensorSampler sampler;
+{
+    // Enable SPI clock and set gpios
+    initBoard();
 
-    spiMPU9250_a::init();
-    mpu_t* mpu = new mpu_t(1, 1);
+    TimestampTimer::enableTimestampTimer();
 
-    Thread::sleep(100);
-    
-    if(mpu->init()){
-        printf("MPU9250 Init succeeded\n" );
-        sampler.AddSensor(mpu);
-    }
-    else {
-        printf("MPU9250 Init failed\n");
+    // SPI configuration setup
 
-        while(!mpu->init()) {
-            printf("MPU9250 Init failed\n");
-            Thread::sleep(1000);
-        }
-    }
+    SPIBusConfig spiConfig;
+    spiConfig.clock_div = SPIClockDivider::DIV64;
+    spiConfig.mode      = SPIMode::MODE3;
+    SPIBus spiBus(SPI2);
+    SPISlave spiSlave(spiBus, csPin, spiConfig);
 
-    Thread::sleep(100);
+    // Device initialization
+    MPU9250 mpu9250(spiSlave);
 
-    while(true)
-    {
-        sampler.Update();
-        mpu->updateMagneto();
+    // Initialize the device
+    mpu9250.init();
 
-        const Vec3* last_data = mpu->compassDataPtr();
-        printf("%f %f %f\n", last_data->getX(), last_data->getY(),
-               last_data->getZ());
+    while (true)
+    {
+        mpu9250.sample();
+        MPU9250Data data = mpu9250.getLastSample();
+        printf("%lld,%f,%f,%f;", data.accel_timestamp, data.accel_x,
+               data.accel_y, data.accel_z);
+        printf("%lld,%f,%f,%f;", data.gyro_timestamp, data.gyro_x, data.gyro_y,
+               data.gyro_z);
+        printf("%lld,%f,%f,%f\n", data.mag_timestamp, data.mag_x, data.mag_y,
+               data.mag_z);
 
-        Thread::sleep(100);
+        // Serial communicaion at 115200 baud takes aprx. 10ms
+        // miosix::delayMs(10);
     }
+
+    TRACE("Test completed\n");
 }
\ No newline at end of file
diff --git a/src/tests/drivers/test-ms5803.cpp b/src/tests/drivers/test-ms5803.cpp
index e35b80540ef18b2850160b9ba149b7592d233d2d..1e73fb96bc4cbcf9fea4e378fe35b6d0445f06e5 100644
--- a/src/tests/drivers/test-ms5803.cpp
+++ b/src/tests/drivers/test-ms5803.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Nuno Barcellos
+ * Author: Nuno Barcellos
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -21,58 +21,47 @@
  */
 
 #include <Common.h>
-#include <drivers/BusTemplate.h>
-#include <interfaces-impl/hwmapping.h>
-#include "sensors/MS580301BA07/MS580301BA07.h"
-
+#include <drivers/spi/SPIDriver.h>
 #include <drivers/spi/SensorSpi.h>
-#include <sensors/SensorSampling.h>
+#include <interfaces-impl/hwmapping.h>
+#include <sensors/MS5803/MS5803.h>
+#include <sensors/SensorSampler.h>
 
 using namespace miosix;
-using namespace miosix::interfaces;
-typedef Gpio<GPIOD_BASE, 7> cs_ms58;
 
-typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI1;
-typedef ProtocolSPI<busSPI1, cs_ms58> spiMS58;
-typedef MS580301BA07<spiMS58> ms58_t;
+/**
+ * This test is intended to be run on the Death Stack X
+ */
 
 int main()
 {
-    SimpleSensorSampler sampler;
+    TimestampTimer::enableTimestampTimer();
+
+    SPIBusConfig spiConfig;
+    SPIBus spiBus(SPI1);
+    SPISlave spiSlave(spiBus, miosix::sensors::ms5803::cs::getPin(), spiConfig);
 
-    spiMS58::init();
-    ms58_t* ms58 = new ms58_t();
+    // Sample temperature every 5 pressure samples
+    MS5803 sensor(spiSlave, 10);
 
     Thread::sleep(100);
 
-    if (ms58->init())
+    if (!sensor.init())
     {
-        printf("MS58 Init succeeded\n");
-        sampler.AddSensor(ms58);
-    }
-    else
-    {
-        printf("MS58 Init failed\n");
-
-        while (!ms58->init())
-        {
-            printf("MS58 Init failed\n");
-            Thread::sleep(1000);
-        }
+        printf("MS5803 Init failed\n");
     }
 
     Thread::sleep(100);
-    printf("raw_p,p,raw_t,t\n");
+    printf("press_timestamp,press,temp_timestamp,temp\n");
+
     while (true)
     {
-        sampler.Update();
+        sensor.sample();
 
-        const float* last_pressure = ms58->pressureDataPtr();
-        const float* last_temp     = ms58->tempDataPtr();
-        MS5803Data md              = ms58->getData();
-        printf("%d,%f,%d,%f\n", (int)md.raw_press,
-               *last_pressure, (int)md.raw_temp, *last_temp);
+        MS5803Data data = sensor.getLastSample();
+        printf("%llu,%f,%llu,%f\n", data.press_timestamp, data.press,
+               data.temp_timestamp, data.temp);
 
-        Thread::sleep(100);
+        Thread::sleep(10);
     }
 }
\ No newline at end of file
diff --git a/src/tests/drivers/test-piksi.cpp b/src/tests/drivers/test-piksi.cpp
index b4e01bc9e9c3983c7bcb311b4bbf29b12195000e..1965de769ef5dab31c770fd6d115994c654ba6fe 100644
--- a/src/tests/drivers/test-piksi.cpp
+++ b/src/tests/drivers/test-piksi.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2017 Skyward Experimental Rocketry
- * Authors: Federico Terraneo
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,14 +13,14 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#include <drivers/piksi/piksi.h>
+#include <drivers/old_examples/piksi/piksi.h>
 #include <time.h>
 #include <iostream>
 using namespace std;
diff --git a/src/tests/drivers/test-pwm.cpp b/src/tests/drivers/test-pwm.cpp
index 077899a25b9031fed687a71e9f3e771c6ede4148..597a5e3608c3ae4f4072d6825aef246e07921f5c 100644
--- a/src/tests/drivers/test-pwm.cpp
+++ b/src/tests/drivers/test-pwm.cpp
@@ -1,3 +1,26 @@
+/* Copyright (c) 2018-2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
 #include "Common.h"
 #include "drivers/HardwareTimer.h"
 #include "drivers/pwm/pwm.h"
diff --git a/src/tests/drivers/test-servo.cpp b/src/tests/drivers/test-servo.cpp
index 2f7cf6249bb430ad343ede68f42080854e6bc567..19940b6a2d8375ab7553a64065ba8e874a2e4179 100644
--- a/src/tests/drivers/test-servo.cpp
+++ b/src/tests/drivers/test-servo.cpp
@@ -1,6 +1,5 @@
-/**
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/drivers/test-spi.cpp b/src/tests/drivers/test-spi.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf785972687dd8c3a33d12ec30d1594011e4e453
--- /dev/null
+++ b/src/tests/drivers/test-spi.cpp
@@ -0,0 +1,89 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <cstdio>
+#include <iostream>
+#include <sstream>
+#include "drivers/spi/SPIDriver.h"
+
+using namespace std;
+using namespace miosix;
+
+SPIBus bus(SPI1);
+
+GpioPin spi_sck(GPIOA_BASE, 5);
+GpioPin spi_miso(GPIOA_BASE, 6);
+GpioPin spi_mosi(GPIOA_BASE, 7);
+GpioPin cs(GPIOE_BASE, 3);
+
+int main()
+{
+    {
+        miosix::FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;  // Enable SPI1 bus
+
+        spi_sck.mode(miosix::Mode::ALTERNATE);
+        spi_sck.alternateFunction(5);
+        spi_miso.mode(miosix::Mode::ALTERNATE);
+        spi_miso.alternateFunction(5);
+        spi_mosi.mode(miosix::Mode::ALTERNATE);
+        spi_mosi.alternateFunction(5);
+
+        cs.mode(miosix::Mode::OUTPUT);
+    }
+    cs.high();
+
+    SPISlave spi_slave(bus, cs, {});
+    spi_slave.config.clock_div = SPIClockDivider::DIV64;
+
+    while (true)
+    {
+        printf("Insert a number (0-255): \n");
+
+        string temp;
+        int cmd;
+        getline(cin, temp);
+        stringstream(temp) >> cmd;
+
+        {
+            SPITransaction spi(spi_slave);
+
+            // spam over spi in order to easily find
+            // the data with a logic analyzer
+            for (int i = 0; i < 10; i++)
+            {
+                for (int i = 0; i < 10; i++)
+                {
+                    spi.write((uint8_t)cmd);
+                }
+
+                Thread::sleep(10);
+            }
+        }
+
+        Thread::sleep(100);
+    }
+
+    return 0;
+}
diff --git a/src/tests/drivers/test-ublox.cpp b/src/tests/drivers/test-ublox.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bbd3c3dee206cf0fd561d371fb16d2abc01f0b5a
--- /dev/null
+++ b/src/tests/drivers/test-ublox.cpp
@@ -0,0 +1,88 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Authors: Davide Bonomini, Davide Mor, Alberto Nidasio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <Common.h>
+#include <drivers/gps/ublox/UbloxGPS.h>
+
+#include <cstdio>
+
+#include "TimestampTimer.h"
+
+using namespace miosix;
+
+#define RATE 4
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+
+    printf("Welcome to the ublox test\n");
+
+    // Keep GPS baud rate at default for easier testing
+    UbloxGPS gps(921600, RATE, 2, "gps", 38400);
+    UbloxGPSData dataGPS;
+    printf("Gps allocated\n");
+
+    // Init the gps
+    if (gps.init())
+    {
+        printf("Successful gps initialization\n");
+    }
+    else
+    {
+        printf("Failed gps initialization\n");
+    }
+
+    // Perform the selftest
+    if (gps.selfTest())
+    {
+        printf("Successful gps selftest\n");
+    }
+    else
+    {
+        printf("Failed gps selftest\n");
+    }
+
+    // Start the gps thread
+    gps.start();
+    printf("Gps started\n");
+
+    while (true)
+    {
+        // Give time to the thread
+        Thread::sleep(1000 / RATE);
+
+        // Sample
+        gps.sample();
+        dataGPS = gps.getLastSample();
+
+        // Print out the latest sample
+        TRACE(
+            "[gps] timestamp: % 4.3f, fix: %01d lat: % f lon: % f "
+            "height: %4.1f nsat: %2d speed: %3.2f velN: % 3.2f velE: % 3.2f "
+            "track %3.1f\n",
+            (float)dataGPS.gps_timestamp / 1000000, dataGPS.fix,
+            dataGPS.latitude, dataGPS.longitude, dataGPS.height,
+            dataGPS.num_satellites, dataGPS.speed, dataGPS.velocity_north,
+            dataGPS.velocity_east, dataGPS.track);
+    }
+}
diff --git a/src/tests/drivers/xbee/EnergyScanData.h b/src/tests/drivers/xbee/EnergyScanData.h
new file mode 100644
index 0000000000000000000000000000000000000000..48e48fb402438aaeb7c12ef5e0b3bdd332e56841
--- /dev/null
+++ b/src/tests/drivers/xbee/EnergyScanData.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <ostream>
+
+using std::array;
+using std::string;
+using std::to_string;
+
+struct EnergyScanData
+{
+    long long timestamp;
+    int channel_data[30];
+
+    EnergyScanData() = default;
+
+    EnergyScanData(long long ts, array<int, 30> scan)
+    {
+        for(int i = 0; i < 30; i++)
+        {
+            channel_data[i] = scan[i];
+        }
+    }
+
+    static string header()
+    {
+        string out = "timestamp";
+        for(int i = 0; i < 30; i++)
+        {
+            out += ",channel_" + to_string(i);
+        }
+        return out + "\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp;
+
+        for(int i = 0; i < 30; i++)
+        {
+            os << "," << channel_data[i];
+        }
+
+        os << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/Mark.h b/src/tests/drivers/xbee/Mark.h
new file mode 100644
index 0000000000000000000000000000000000000000..e2068e9bbebabb587f04025c55898e1b4cff8170
--- /dev/null
+++ b/src/tests/drivers/xbee/Mark.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <ostream>
+
+using std::string;
+/**
+ * @brief Used to mark a specific instant in the log
+ */
+struct Mark
+{
+    long long timestamp;
+    unsigned int seq;
+
+    static string header()
+    {
+        return "timestamp,seq\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << seq << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/XbeeTestData.h b/src/tests/drivers/xbee/XbeeTestData.h
new file mode 100644
index 0000000000000000000000000000000000000000..bab88b1dcabcc80bd81c382c87e02f3d3e24e2b5
--- /dev/null
+++ b/src/tests/drivers/xbee/XbeeTestData.h
@@ -0,0 +1,152 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <ostream>
+#include <array>
+#include <string>
+
+#include "math/Stats.h"
+
+using std::array;
+using std::string;
+using std::to_string;
+
+struct XbeeConfig
+{
+    long long timestamp;
+    bool tx_enabled            = false;
+    uint16_t packet_size       = 256;
+    unsigned int send_interval = 0;
+    bool freq_hop              = true;
+    bool data_rate_80k         = false;
+
+    void print()
+    {
+        printf("+++XBee configuration+++\n\n");
+        printf("Tx: %s, pkt: %u B, int: %d ms\n",
+               tx_enabled ? "enabled" : "disabled", packet_size, send_interval);
+        printf("Freq hop: %s, data rate: %s \n", freq_hop ? "on" : "off",
+               data_rate_80k ? "80 kbps" : "10 kbps");
+    }
+
+    static string header()
+    {
+        return "timestamp,tx_enabled,packet_size,send_interval,freq_hop,data_"
+               "rate_80k\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << tx_enabled << "," << packet_size << ","
+           << send_interval << "," << freq_hop << "," << data_rate_80k << "\n";
+    }
+};
+
+struct TxData
+{
+    long long timestamp               = 0LL;
+    unsigned int packet_size          = 0;
+    unsigned int time_since_last_send = 0;
+    unsigned int time_to_send         = 0;
+    unsigned int tx_success_counter   = 0;
+    unsigned int tx_fail_counter      = 0;
+
+    static string header()
+    {
+        return "timestamp,packet_size,time_since_last_send,time_to_send,tx_"
+               "success_cnt,tx_fail_cnt\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << packet_size << "," << time_since_last_send
+           << "," << time_to_send << "," << tx_success_counter << ","
+           << tx_fail_counter << "\n";
+    }
+};
+
+struct RxData
+{
+    long long timestamp;
+    size_t pkt_size                 = 0;
+    long long last_packet_timestamp = 0;
+    int RSSI                        = 0;
+    unsigned int rcv_count          = 0;
+    unsigned int packets_lost       = 0;
+    unsigned int rcv_errors         = 0;
+    unsigned int rcv_wrong_payload  = 0;
+
+    static string header()
+    {
+        return "timestamp,packet_size,last_packet_timestamp,RSSI,rcv_cnt,rcv_errors,"
+               "rcv_wrong\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp << "," << pkt_size << "," << last_packet_timestamp << ","
+           << RSSI << "," << rcv_count << "," << rcv_errors << ","
+           << rcv_wrong_payload << "\n";
+    }
+};
+
+struct EnergyScanData
+{
+    long long timestamp;
+    int channel_data[30];
+
+    EnergyScanData() = default;
+
+    EnergyScanData(long long ts, array<int, 30> scan)
+    {
+        for(int i = 0; i < 30; i++)
+        {
+            channel_data[i] = scan[i];
+        }
+
+        timestamp = ts;
+    }
+
+    static string header()
+    {
+        string out = "timestamp";
+        for(int i = 0; i < 30; i++)
+        {
+            out += ",channel_" + to_string(i);
+        }
+        return out + "\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << timestamp;
+
+        for(int i = 0; i < 30; i++)
+        {
+            os << "," << channel_data[i];
+        }
+
+        os << "\n";
+    }
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/XbeeTransceiver.h b/src/tests/drivers/xbee/XbeeTransceiver.h
new file mode 100644
index 0000000000000000000000000000000000000000..fc30fe06d4db69f4fe41c0bec3fe661c4ae7cd50
--- /dev/null
+++ b/src/tests/drivers/xbee/XbeeTransceiver.h
@@ -0,0 +1,423 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <arch/common/drivers/stm32_hardware_rng.h>
+#include <miosix.h>
+
+#include <functional>
+
+#include "ActiveObject.h"
+#include "XbeeTestData.h"
+#include "drivers/Xbee/APIFramesLog.h"
+#include "drivers/Xbee/Xbee.h"
+#include "logger/Logger.h"
+#include "utils/testutils/ThroughputCalculator.h"
+
+using miosix::getTick;
+using std::bind;
+using std::function;
+
+static constexpr size_t RCV_BUF_SIZE       = 256;
+static constexpr uint32_t PACKET_FIRST_INT = 0xF0E1C2B3;
+
+/**
+ * @brief Repeats the provided 32 bit value in the buffer, filling it.
+ *
+ * @param buf Buffer to fill
+ * @param val Value to fill the buffer with
+ * @param buf_size size of the buffer
+ */
+void memset32(uint8_t* buf, uint32_t val, int buf_size)
+{
+    int i = 0;
+    while (i <= buf_size - (int)sizeof(val))
+    {
+        memcpy(buf + i, &val, sizeof(val));
+        i += sizeof(val);
+    }
+
+    memcpy(buf + i, &val, buf_size % sizeof(val));
+}
+
+struct SendIntervalBase
+{
+    virtual unsigned int getInterval() const = 0;
+};
+
+struct ConstSendInterval : public SendIntervalBase
+{
+    ConstSendInterval(unsigned int interval) : interval(interval) {}
+    unsigned int interval;
+    unsigned int getInterval() const override { return interval; }
+};
+
+struct RandSendInterval : public SendIntervalBase
+{
+    RandSendInterval(unsigned int min, unsigned int max)
+        : min(min), diff(max - min)
+    {
+    }
+    unsigned int min;
+    unsigned int diff;
+    unsigned int getInterval() const override
+    {
+        return (miosix::HardwareRng::instance().get() % diff) + min;
+    }
+};
+
+class Sender : public ActiveObject
+{
+    friend class XbeeTransceiver;
+
+public:
+    Sender(Xbee::Xbee& xbee, Logger& logger, const SendIntervalBase& interval,
+           size_t packet_size, long long expected_packet_interval)
+        : xbee(xbee), logger(logger), dr_calc(expected_packet_interval),
+          interval(interval)
+    {
+        data.packet_size = packet_size;
+        packet_counter   = 0;
+    }
+
+    TxData getTxData() const { return data; }
+
+    DataRateResult getDataRate() { return dr_calc.getResult(); }
+
+protected:
+    void run() override
+    {
+        long long loop_start_ts = getTick();
+        while (!shouldStop())
+        {
+            data.time_since_last_send = getTick() - loop_start_ts;
+            loop_start_ts             = getTick();
+
+            // Create packet
+            memcpy(buf, &PACKET_FIRST_INT, sizeof(uint32_t));
+            memset32(buf + sizeof(uint32_t), packet_counter++,
+                     data.packet_size - sizeof(uint32_t));
+
+            long long send_start_ts = getTick();
+
+            bool result = xbee.send(buf, data.packet_size);
+
+            data.time_to_send = getTick() - send_start_ts;
+            data.timestamp    = send_start_ts;
+
+            if (result)
+            {
+                ++data.tx_success_counter;
+                dr_calc.addPacket(data.packet_size);
+            }
+            else
+            {
+                ++data.tx_fail_counter;
+            }
+
+            logger.log(data);
+
+            miosix::Thread::sleepUntil(loop_start_ts + interval.getInterval());
+        }
+    }
+
+private:
+    Xbee::Xbee& xbee;
+    Logger& logger;
+    ThroughputCalculator dr_calc;
+    TxData data;
+    const SendIntervalBase& interval;
+
+    uint32_t packet_counter = 0;
+
+    uint8_t buf[Xbee::MAX_PACKET_PAYLOAD_LENGTH];
+};
+
+class Receiver : public ActiveObject
+{
+    friend class XbeeTransceiver;
+
+public:
+    Receiver(Xbee::Xbee& xbee, Logger& logger,
+             long long expected_packet_interval)
+        : xbee(xbee), logger(logger), dr_calc(expected_packet_interval)
+    {
+    }
+
+    void stop() override
+    {
+        should_stop = true;
+        xbee.wakeReceiver(true);
+        ActiveObject::stop();
+    }
+
+    RxData getRxData() const { return data; }
+
+    DataRateResult getDataRate() { return dr_calc.getResult(); }
+
+protected:
+    void run() override
+    {
+        while (!shouldStop())
+        {
+            long long start = getTick();
+
+            size_t len = xbee.receive(buf, RCV_BUF_SIZE);
+
+            data.last_packet_timestamp = getTick();
+            data.timestamp             = start;
+
+            ++data.rcv_count;
+
+            if (len > sizeof(uint32_t))
+            {
+                data.pkt_size = len;
+
+                // Packet payload is a repeated sequential 32 bit number
+                uint32_t num, strt;
+                memcpy(&strt, buf, sizeof(uint32_t));
+                memcpy(&num, buf + sizeof(uint32_t), sizeof(uint32_t));
+
+                if (checkPacket(num, buf, len))
+                {
+                    dr_calc.addPacket(len);
+                    if (last_packet_num > 0)
+                    {
+                        if (num > last_packet_num)
+                            data.packets_lost += num - last_packet_num - 1;
+                    }
+                    last_packet_num = num;
+                }
+                else
+                {
+                    TRACE("[XBeeTransceiver] Wrong packet payload! num: %u, start: 0x%08X\n", num, strt);
+                    ++data.rcv_wrong_payload;
+                }
+            }
+
+            logger.log(data);
+        }
+    }
+
+private:
+    bool checkPacket(uint32_t expected_num, uint8_t* packet, size_t packet_size)
+    {
+        if (memcmp(packet, &PACKET_FIRST_INT, sizeof(uint32_t)) == 0)
+        {
+            int i = sizeof(uint32_t);
+            while (i <= (int)packet_size - (int)sizeof(expected_num))
+            {
+                if (memcmp(packet + i, &expected_num, sizeof(expected_num)) !=
+                    0)
+                {
+                    return false;
+                }
+                i += sizeof(expected_num);
+            }
+            memcpy(packet + i, &expected_num,
+                   packet_size % sizeof(expected_num));
+            return true;
+        }
+        return false;
+    }
+
+    Xbee::Xbee& xbee;
+    Logger& logger;
+    ThroughputCalculator dr_calc;
+    RxData data{};
+
+    uint8_t buf[RCV_BUF_SIZE];
+    uint32_t last_packet_num = 0;
+};
+
+class XbeeTransceiver
+{
+public:
+    XbeeTransceiver(Xbee::Xbee& xbee, Logger& logger,
+                    const SendIntervalBase& snd_interval, size_t tx_pkt_size,
+                    long long expected_packet_interval)
+        : sender(new Sender(xbee, logger, snd_interval, tx_pkt_size,
+                            expected_packet_interval)),
+          receiver(new Receiver(xbee, logger, expected_packet_interval)),
+          logger(logger)
+    {
+        using namespace std::placeholders;
+        xbee.setOnFrameReceivedListener(
+            bind(&XbeeTransceiver::handleApiFrame, this, _1));
+    }
+
+    ~XbeeTransceiver()
+    {
+        sender->stop();
+        receiver->stop();
+
+        delete sender;
+        delete receiver;
+    }
+
+    void disableSender() { sender_enabled = false; }
+
+    void disableReceiver() { receiver_enabled = false; }
+
+    void start()
+    {
+        if (sender_enabled)
+        {
+            sender->start();
+        }
+        if (receiver_enabled)
+        {
+            receiver->start();
+        }
+    }
+
+    void stop()
+    {
+        sender->stop();
+        receiver->stop();
+    }
+
+    void setOnFrameReceivedListener(
+        Xbee::Xbee::OnFrameReceivedListener frame_listener)
+    {
+        this->frame_listener = frame_listener;
+    }
+
+    Sender& getSender() { return *sender; }
+
+    Receiver& getReceiver() { return *receiver; }
+
+private:
+    void handleApiFrame(Xbee::APIFrame& frame)
+    {
+        logAPIFrame(frame);
+
+        if (frame_listener)
+        {
+            frame_listener(frame);
+        }
+
+        if (frame.frame_type == Xbee::FTYPE_AT_COMMAND_RESPONSE)
+        {
+            Xbee::ATCommandResponseFrame* at =
+                frame.toFrameType<Xbee::ATCommandResponseFrame>();
+
+            if (strncmp(at->getATCommand(), "DB", 2) == 0)
+            {
+                receiver->data.RSSI = -*at->getCommandDataPointer();
+            }
+
+            if (strncmp(at->getATCommand(), "ER", 2) == 0)
+            {
+                uint16_t errs;
+                memcpy(&errs, at->getCommandDataPointer(), 2);
+                receiver->data.rcv_errors = swapBytes16(errs);
+            }
+        }
+    }
+
+    void logAPIFrame(Xbee::APIFrame& frame)
+    {
+        using namespace Xbee;
+        bool logged = false;
+        switch (frame.frame_type)
+        {
+            case FTYPE_AT_COMMAND:
+            {
+                ATCommandFrameLog dest;
+                logged = ATCommandFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+            case FTYPE_AT_COMMAND_RESPONSE:
+            {
+                ATCommandResponseFrameLog dest;
+                logged = ATCommandResponseFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+            case FTYPE_MODEM_STATUS:
+            {
+                ModemStatusFrameLog dest;
+                logged = ModemStatusFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+            case FTYPE_TX_REQUEST:
+            {
+                TXRequestFrameLog dest;
+                logged = TXRequestFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+            case FTYPE_TX_STATUS:
+            {
+                TXStatusFrameLog dest;
+                logged = TXStatusFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+            case FTYPE_RX_PACKET_FRAME:
+            {
+                RXPacketFrameLog dest;
+                logged = RXPacketFrameLog::toFrameType(frame, &dest);
+                if (logged)
+                {
+                    logger.log(dest);
+                }
+                break;
+            }
+        }
+
+        if (!logged)
+        {
+            APIFrameLog api;
+            APIFrameLog::fromAPIFrame(frame, &api);
+            logger.log(api);
+        }
+    }
+
+    bool sender_enabled   = true;
+    bool receiver_enabled = true;
+
+    Sender* sender;
+    Receiver* receiver;
+    Logger& logger;
+
+    Xbee::Xbee::OnFrameReceivedListener frame_listener;
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/ConfigScreen.h b/src/tests/drivers/xbee/gui/ConfigScreen.h
new file mode 100644
index 0000000000000000000000000000000000000000..b72fbc7d163a699dbda10cc93217ca2d107a9d6a
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/ConfigScreen.h
@@ -0,0 +1,223 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <mxgui/display.h>
+
+#include "utils/gui/GridLayout.h"
+#include "utils/gui/TextView.h"
+#include "utils/gui/VerticalLayout.h"
+#include "utils/gui/OptionView.h"
+
+#include "../XbeeTestData.h"
+
+struct ConfigScreen
+{
+    XbeeConfig config;
+
+    enum OptSendEnableIDs : uint8_t
+    {
+        TX_ENABLED,
+        TX_DISABLED
+    };
+
+    enum OptFreqHopIDs : uint8_t
+    {
+        FH_ENABLED,
+        FH_DISABLED
+    };
+
+    enum OptDataRateIDs : uint8_t
+    {
+        DR_10K,
+        DR_80K
+    };
+
+    enum OptPacketSizeIDs : uint8_t
+    {
+        PKTSIZE_64,
+        PKTSIZE_128,
+        PKTSIZE_256
+    };
+
+    enum OptSendIntervalIDs : uint8_t
+    {
+        TXINT_CONTINUOUS,
+        TXINT_200MS,
+        TXINT_250MS,
+        TXINT_333MS,
+        TXINT_500MS,
+        TXINT_1000MS
+    };
+
+    ConfigScreen()
+    {
+        title.setFont(mxgui::miscFixedBold);
+        title.setTextColor(mxgui::black);
+        title.setBackgroundColor(mxgui::green);
+        title.setAlignment(HorizAlignment::LEFT, VertAlignment::CENTER);
+
+        tv_log_status.setFont(mxgui::miscFixedBold);
+        tv_log_status.setTextColor(mxgui::white);
+        tv_log_status.setBackgroundColor(mxgui::red);
+        tv_log_status.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+
+        grid_title.setCell(&title, 0, 0);
+        grid_title.setCell(&tv_log_status, 0, 1);
+
+        grid.setCell(&btn_energy, 0);
+        grid.setCell(&btn_start, 1);
+        grid.setDrawBorder(true);
+
+        btn_start.setSelectable(true);
+        btn_start.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_start.setBackgroundColor(mxgui::darkGrey);
+        
+        btn_energy.setSelectable(true);
+        btn_energy.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_energy.setBackgroundColor(mxgui::darkGrey);
+
+        root.addView(&grid_title, 0.6);
+        root.addView(&opt_send_enable, 1);
+        root.addView(&opt_packet_size, 1);
+
+        root.addView(&opt_send_interval, 2);
+
+        root.addView(&opt_freq_hop, 1);
+        root.addView(&opt_data_rate, 1);
+        root.addView(&grid, 1);
+
+        addOptionCallbacks();
+    }
+
+    void updateLogStatus(Logger& logger)
+    {
+        if (logger.getLogNumber() >= 0)
+        {
+            string log_name = logger.getFileName(logger.getLogNumber());
+
+            tv_log_status.setText(log_name);
+            tv_log_status.setTextColor(mxgui::black);
+            tv_log_status.setBackgroundColor(mxgui::green);
+        }
+        else
+        {
+            tv_log_status.setText("SD ERR");
+            tv_log_status.setTextColor(mxgui::white);
+            tv_log_status.setBackgroundColor(mxgui::red);
+        }
+    }
+
+    VerticalLayout root{10};
+
+    TextView btn_start{"Start"};
+    TextView btn_energy{"Energy scan"};
+
+private:
+    void addOptionCallbacks()
+    {
+        opt_send_enable.addOnOptionChosenListener(
+            [&](unsigned int id) { config.tx_enabled = id == TX_ENABLED; });
+
+        opt_send_interval.addOnOptionChosenListener([&](unsigned int id) {
+            switch (id)
+            {
+                case TXINT_CONTINUOUS:
+                    config.send_interval = 0;
+                    break;
+                case TXINT_200MS:
+                    config.send_interval = 200;
+                    break;
+                case TXINT_250MS:
+                    config.send_interval = 250;
+                    break;
+                case TXINT_333MS:
+                    config.send_interval = 333;
+                    break;
+                case TXINT_500MS:
+                    config.send_interval = 500;
+                    break;
+                case TXINT_1000MS:
+                    config.send_interval = 1000;
+                    break;
+            }
+        });
+
+        opt_packet_size.addOnOptionChosenListener([&](unsigned int id) {
+            switch (id)
+            {
+                case PKTSIZE_64:
+                    config.packet_size = 64;
+                    break;
+                case PKTSIZE_128:
+                    config.packet_size = 128;
+                    break;
+                case PKTSIZE_256:
+                    config.packet_size = 256;
+                    break;
+            }
+        });
+
+        opt_freq_hop.addOnOptionChosenListener(
+            [&](unsigned int id) { config.freq_hop = id == FH_ENABLED; });
+
+        opt_data_rate.addOnOptionChosenListener(
+            [&](unsigned int id) { config.data_rate_80k = id == DR_80K; });
+    }
+
+    OptionView opt_send_enable{
+        "Transmission",
+        {{TX_ENABLED, "Enabled"}, {TX_DISABLED, "Disabled"}},
+        TX_DISABLED,
+        2};
+
+    OptionView opt_packet_size{
+        "TX Packet Size",
+        {{PKTSIZE_64, "64 B"}, {PKTSIZE_128, "128 B"}, {PKTSIZE_256, "256 B"}},
+        PKTSIZE_256,
+        3};
+
+    OptionView opt_send_interval{"Send Interval",
+                                 {{TXINT_CONTINUOUS, "Cont"},
+                                  {TXINT_200MS, "200 ms"},
+                                  {TXINT_250MS, "250 ms"},
+                                  {TXINT_333MS, "333 ms"},
+                                  {TXINT_500MS, "500 ms"},
+                                  {TXINT_1000MS, "1 sec"}},
+                                 TXINT_CONTINUOUS,
+                                 4};
+
+    OptionView opt_freq_hop{
+        "Frequency hopping",
+        {{FH_ENABLED, "Enabled"}, {FH_DISABLED, "Disabled"}},
+        FH_ENABLED,
+        2};
+    OptionView opt_data_rate{
+        "Xbee datarate", {{DR_10K, "10 kbps"}, {DR_80K, "80 kbps"}}, DR_10K, 2};
+
+    GridLayout grid{1, 2};
+    GridLayout grid_title{1,2};
+    TextView title{"Xbee Setup"};
+    TextView tv_log_status{"SD ERR!"};
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/EndScreen.h b/src/tests/drivers/xbee/gui/EndScreen.h
new file mode 100644
index 0000000000000000000000000000000000000000..89576496b78c37453d7bd6d4ec4d28241b6f31cf
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/EndScreen.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <mxgui/display.h>
+
+#include "utils/gui/GridLayout.h"
+#include "utils/gui/TextView.h"
+#include "utils/gui/VerticalLayout.h"
+#include "utils/gui/OptionView.h"
+
+
+
+struct EndScreen
+{
+    EndScreen()
+    {
+        tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        tv_f.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        tv_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+
+        tv_f.setSelectable(true);
+        tv_reset.setSelectable(true);
+
+        root.addView(&tv_title, 1);
+        root.addView(&tv_f, 1);
+        root.addView(&tv_reset, 1);
+    }
+
+    VerticalLayout root;
+
+    TextView tv_f{"Press F to pay respects"};
+    TextView tv_reset{"Or RESET to start again"};
+private:
+    TextView tv_title{"You killed the program, you maniac!"}; 
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/EnergyScanScreen.h b/src/tests/drivers/xbee/gui/EnergyScanScreen.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb6ffb6f650bf23824e70a66894b6ba18e708561
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/EnergyScanScreen.h
@@ -0,0 +1,231 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/display.h>
+
+#include <array>
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <utility>
+
+#include "math/Stats.h"
+#include "utils/gui/GridLayout.h"
+#include "utils/gui/TextView.h"
+#include "utils/gui/VerticalLayout.h"
+
+using std::array;
+using std::to_string;
+
+struct EnergyScanScreen
+{
+    static constexpr unsigned int NUM_CHANNELS     = 30;
+    static constexpr unsigned int NUM_CHANNEL_ROWS = (NUM_CHANNELS + 1) / 2;
+    static constexpr unsigned int NUM_COLS         = 8;
+
+    static constexpr float COLOR_PERCENTILE = 0.15f;
+    EnergyScanScreen()
+    {
+        tv_title.setFont(mxgui::miscFixedBold);
+        tv_title.setTextColor(mxgui::black);
+        tv_title.setBackgroundColor(mxgui::green);
+        tv_title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+
+        tv_log_status.setFont(mxgui::miscFixedBold);
+        tv_log_status.setTextColor(mxgui::white);
+        tv_log_status.setBackgroundColor(mxgui::red);
+        tv_log_status.setAlignment(HorizAlignment::CENTER,
+                                   VertAlignment::CENTER);
+
+        grid_title.setCell(&tv_title, 0, 0);
+        grid_title.setCell(&tv_log_status, 0, 1);
+
+        for (unsigned int i = 0; i < NUM_COLS; i++)
+        {
+            col_titles[i] = new TextView(titles[i % (NUM_COLS / 2)]);
+            col_titles[i]->setTextColor(mxgui::blue);
+
+            grid_channels.setCell(col_titles[i], 0, i);
+        }
+
+        mxgui::Color pink(0xFC59);
+        for (unsigned int i = 0; i < NUM_CHANNELS; ++i)
+        {
+            char buf[3];
+            snprintf(buf, 3, "%02d", i);
+
+            col_names[i] = new TextView(buf);
+            col_names[i]->setTextColor(pink);
+
+            col_current[i] = new TextView("-40");
+            col_min[i]    = new TextView("-40");
+            col_max[i]     = new TextView("-40");
+
+            grid_channels.setCell(col_names[i], (i % NUM_CHANNEL_ROWS) + 1,
+                                  (i / NUM_CHANNEL_ROWS) * 4 + 0);
+            grid_channels.setCell(col_current[i], (i % NUM_CHANNEL_ROWS) + 1,
+                                  (i / NUM_CHANNEL_ROWS) * 4 + 1);
+            grid_channels.setCell(col_min[i], (i % NUM_CHANNEL_ROWS) + 1,
+                                  (i / NUM_CHANNEL_ROWS) * 4 + 2);
+            grid_channels.setCell(col_max[i], (i % NUM_CHANNEL_ROWS) + 1,
+                                  (i / NUM_CHANNEL_ROWS) * 4 + 3);
+        }
+
+        btn_mark.setSelectable(true);
+        btn_mark.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_mark.setBackgroundColor(mxgui::darkGrey);
+
+        btn_stop.setSelectable(true);
+        btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_stop.setBackgroundColor(mxgui::darkGrey);
+
+        btn_reset.setSelectable(true);
+        btn_reset.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_reset.setBackgroundColor(mxgui::darkGrey);
+
+        grid_buttons.setCell(&btn_mark, 0);
+        grid_buttons.setCell(&btn_reset, 1);
+        grid_buttons.setCell(&btn_stop, 2);
+        grid_buttons.setDrawBorder(true);
+
+        root.addView(&grid_title, 0.8);
+        root.addView(&grid_channels, 10);
+        root.addView(&grid_buttons, 1);
+    }
+
+    ~EnergyScanScreen()
+    {
+        for (unsigned int i = 0; i < NUM_COLS; i++)
+        {
+            delete col_titles[i];
+        }
+
+        for (unsigned int i = 0; i < NUM_CHANNELS; ++i)
+        {
+            delete col_names[i];
+            delete col_current[i];
+            delete col_min[i];
+            delete col_max[i];
+        }
+    }
+
+    void updateScan(array<int, NUM_CHANNELS> scan)
+    {
+        for (unsigned int i = 0; i < NUM_CHANNELS; i++)
+        {
+            ch_stats[i].add(scan[i]);
+
+            StatsResult r = ch_stats[i].getStats();
+
+            abs_max = std::max(r.maxValue, abs_max);
+            abs_min = std::min(r.minValue, abs_min);
+        }
+
+        for (unsigned int i = 0; i < NUM_CHANNELS; i++)
+        {
+            StatsResult r = ch_stats[i].getStats();
+
+            col_min[i]->setText(to_string((int)r.minValue));
+            setColor(col_min[i], r.minValue);
+
+            col_current[i]->setText(to_string(scan[i]));
+            setColor(col_current[i], scan[i]);
+
+            col_max[i]->setText(to_string((int)r.maxValue));
+            setColor(col_max[i], r.maxValue);
+        }
+    }
+
+    void resetStats()
+    {
+        for (unsigned int i = 0; i < NUM_CHANNELS; i++)
+        {
+            ch_stats[i].reset();
+        }
+
+        abs_max = std::numeric_limits<float>::lowest();
+        abs_min = std::numeric_limits<float>::max();
+    }
+
+    void updateLogStatus(Logger& logger)
+    {
+        if (logger.getLogNumber() >= 0)
+        {
+            string log_name = logger.getFileName(logger.getLogNumber());
+
+            tv_log_status.setText(log_name);
+            tv_log_status.setTextColor(mxgui::black);
+            tv_log_status.setBackgroundColor(mxgui::green);
+        }
+        else
+        {
+            tv_log_status.setText("SD ERR");
+            tv_log_status.setTextColor(mxgui::white);
+            tv_log_status.setBackgroundColor(mxgui::red);
+        }
+    }
+
+    VerticalLayout root{5};
+
+    TextView btn_mark{"Mark Log (1)"};
+    TextView btn_stop{"Stop"};
+    TextView btn_reset{"Reset"};
+
+private:
+    void setColor(TextView* tv, float val)
+    {
+        float delta = abs_max - abs_min;
+        if (val >= abs_max - delta * COLOR_PERCENTILE)
+        {
+            tv->setTextColor(mxgui::green);
+        }
+        else if (val <= abs_min + delta * COLOR_PERCENTILE)
+        {
+            tv->setTextColor(0xFDC0);  // Light orange
+        }
+        else
+        {
+            tv->setTextColor(mxgui::white);
+        }
+    }
+    float abs_max = std::numeric_limits<float>::lowest();
+    float abs_min = std::numeric_limits<float>::max();
+
+    GridLayout grid_channels{NUM_CHANNEL_ROWS + 1, NUM_COLS};
+    GridLayout grid_title{1, 2};
+    GridLayout grid_buttons{1, 3};
+
+    TextView tv_log_status{"SD ERR"};
+    TextView tv_title{"Energy Scan"};
+
+    array<std::string, NUM_COLS / 2> titles = {"CH", "Curr", "Min", "Max"};
+    array<TextView*, NUM_COLS> col_titles;
+
+    array<TextView*, NUM_CHANNELS> col_names;
+    array<TextView*, NUM_CHANNELS> col_current;
+    array<TextView*, NUM_CHANNELS> col_min;
+    array<TextView*, NUM_CHANNELS> col_max;
+
+    array<Stats, NUM_CHANNELS> ch_stats;
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/RespectScreen.h b/src/tests/drivers/xbee/gui/RespectScreen.h
new file mode 100644
index 0000000000000000000000000000000000000000..5b82d461974a8b544b2c0824ca370aec74a79902
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/RespectScreen.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <mxgui/display.h>
+
+#include "res/respect.h"
+#include "utils/gui/ImageView.h"
+
+struct RespectScreen
+{
+    RespectScreen()
+    {
+        
+    }
+
+    ImageView root{&respect};
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/StatusScreen.h b/src/tests/drivers/xbee/gui/StatusScreen.h
new file mode 100644
index 0000000000000000000000000000000000000000..66b0e63241b73593df40a7042c1085f36d89e63a
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/StatusScreen.h
@@ -0,0 +1,333 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/display.h>
+
+#include <cstdint>
+#include <string>
+#include <cstring>
+
+#include "utils/testutils/ThroughputCalculator.h"
+#include "../XbeeTestData.h"
+#include "logger/Logger.h"
+
+#include "utils/gui/GridLayout.h"
+#include "utils/gui/OptionView.h"
+#include "utils/gui/TextView.h"
+#include "utils/gui/VerticalLayout.h"
+
+using std::to_string;
+
+/**
+ * @brief Converts tick in milliseconds to the HH:MM:SS format
+ */
+std::string tickToHMS(long long tick)
+{
+    char buf[15];
+
+    int h = tick / (1000 * 3600);
+    tick -= h * (1000 * 3600);
+    int m = tick / (1000 * 60);
+    tick -= m * (1000 * 60);
+    int s = tick/1000;
+
+    snprintf(buf, 15, "%02d:%02d:%02d", h, m, s);
+
+    return string(buf);
+}
+
+struct StatusScreen
+{
+    XbeeConfig config;
+
+    StatusScreen()
+    {
+        title.setFont(mxgui::miscFixedBold);
+        title.setTextColor(mxgui::black);
+        title.setBackgroundColor(mxgui::green);
+        title.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+
+        tv_log_status.setFont(mxgui::miscFixedBold);
+        tv_log_status.setTextColor(mxgui::white);
+        tv_log_status.setBackgroundColor(mxgui::red);
+        tv_log_status.setAlignment(HorizAlignment::CENTER,
+                                   VertAlignment::CENTER);
+
+        grid_title.setCell(&title, 0, 0);
+        grid_title.setCell(&tv_log_status, 0, 1);
+
+        grid_config.setCell(&tv_cfg_txt_tx_enabled, 0, 0);
+        grid_config.setCell(&tv_cfg_tx_enabled, 0, 1);
+
+        grid_config.setCell(&tv_cfg_txt_pkt_size, 0, 2);
+        grid_config.setCell(&tv_cfg_pkt_size, 0, 3);
+
+        grid_config.setCell(&tv_cfg_txt_snd_interval, 1, 0);
+        grid_config.setCell(&tv_cfg_snd_interval, 1, 1);
+
+        grid_config.setCell(&tv_cfg_txt_freq_hop, 1, 2);
+        grid_config.setCell(&tv_cfg_freq_hop, 1, 3);
+
+        grid_config.setCell(&tv_cfg_txt_data_rate, 2, 0);
+        grid_config.setCell(&tv_cfg_data_rate, 2, 1);
+
+        tv_log_title.setTextColor(mxgui::blue);
+
+        grid_log_status.setCell(&tv_log_title, 0, 0);
+
+        grid_log_status.setCell(&tv_log_txt_buf_written, 1, 0);
+        grid_log_status.setCell(&tv_log_buf_written, 1, 1);
+
+        grid_log_status.setCell(&tv_log_txt_buf_ttw, 1, 2);
+        grid_log_status.setCell(&tv_log_buf_ttw, 1, 3);
+
+        grid_log_status.setCell(&tv_log_txt_buf_dropped, 2, 0);
+        grid_log_status.setCell(&tv_log_buf_dropped, 2, 1);
+
+        grid_log_status.setCell(&tv_log_txt_buf_failed, 2, 2);
+        grid_log_status.setCell(&tv_log_buf_failed, 2, 3);
+
+        tv_tx_title.setTextColor(mxgui::blue);
+
+        grid_data.setCell(&tv_tx_title, 0, 0);
+
+        grid_data.setCell(&tv_tx_txt_num_pkt, 1, 0);
+        grid_data.setCell(&tv_tx_num_pkt, 1, 1);
+
+        grid_data.setCell(&tv_tx_txt_num_fail, 2, 0);
+        grid_data.setCell(&tv_tx_num_fail, 2, 1);
+
+        grid_data.setCell(&tv_tx_txt_pps, 3, 0);
+        grid_data.setCell(&tv_tx_pps, 3, 1);
+
+        grid_data.setCell(&tv_tx_txt_TTS, 4, 0);
+        grid_data.setCell(&tv_tx_tts, 4, 1);
+
+        grid_data.setCell(&tv_tx_txt_last_status, 5, 0);
+        grid_data.setCell(&tv_tx_last_status, 5, 1);
+
+        grid_data.setCell(&tv_tx_txt_last_err, 6, 0);
+        grid_data.setCell(&tv_tx_last_err, 6, 1);
+
+        tv_rx_title.setTextColor(mxgui::blue);
+        grid_data.setCell(&tv_rx_title, 0, 2);
+
+        grid_data.setCell(&tv_rx_txt_num_pkt, 1, 2);
+        grid_data.setCell(&tv_rx_num_pkt, 1, 3);
+
+        grid_data.setCell(&tv_rx_txt_num_fail, 2, 2);
+        grid_data.setCell(&tv_rx_num_fail, 2, 3);
+
+        grid_data.setCell(&tv_rx_txt_lost, 3, 2);
+        grid_data.setCell(&tv_rx_lost, 3, 3);
+
+        grid_data.setCell(&tv_rx_txt_RSSI, 4, 2);
+        grid_data.setCell(&tv_rx_RSSI, 4, 3);
+
+        grid_data.setCell(&tv_rx_txt_data_rate, 5, 2);
+        grid_data.setCell(&tv_rx_data_rate, 5, 3);
+
+        grid_data.setCell(&tv_rx_txt_pps, 6, 2);
+        grid_data.setCell(&tv_rx_pps, 6, 3);
+
+        grid_data.setCell(&tv_rx_txt_time_since_last_rx, 7, 2);
+        grid_data.setCell(&tv_rx_time_since_last_rx, 7, 3);
+
+        btn_mark.setSelectable(true);
+        btn_mark.setAlignment(HorizAlignment::CENTER,
+                                     VertAlignment::CENTER);
+        btn_mark.setBackgroundColor(mxgui::darkGrey);
+
+        btn_stop.setSelectable(true);
+        btn_stop.setAlignment(HorizAlignment::CENTER, VertAlignment::CENTER);
+        btn_stop.setBackgroundColor(mxgui::darkGrey);
+
+        grid_buttons.setCell(&btn_mark, 0);
+        grid_buttons.setCell(&btn_stop, 1);
+        grid_buttons.setDrawBorder(true);
+
+        root.addView(&grid_title, 0.8);
+        root.addView(&grid_config, 1.5);
+        root.addView(&grid_log_status, 1.5);
+        root.addView(&grid_data, 5);
+        root.addView(&grid_buttons, 1);
+    }
+
+    void updateConfig(XbeeConfig cfg)
+    {
+        // Update GUI with selected config values
+        tv_cfg_tx_enabled.setText(cfg.tx_enabled ? "Enabled" : "Disabled");
+        tv_cfg_pkt_size.setText(std::to_string(cfg.packet_size));
+        tv_cfg_snd_interval.setText(cfg.send_interval == 0
+                                        ? "Cont"
+                                        : std::to_string(cfg.send_interval));
+
+        tv_cfg_freq_hop.setText(cfg.freq_hop ? "Enabled" : "Disabled");
+        tv_cfg_data_rate.setText(cfg.data_rate_80k ? "80 kbps" : "10 kbps");
+    }
+
+    void updateLogStatus(Logger& logger)
+    {
+        LogStats stats = logger.getLogStats();
+
+        if (logger.getLogNumber() >= 0)
+        {
+            string log_name = logger.getFileName(logger.getLogNumber());
+
+            tv_log_status.setText(log_name);
+            tv_log_status.setTextColor(mxgui::black);
+            tv_log_status.setBackgroundColor(mxgui::green);
+        }
+        else
+        {
+            tv_log_status.setText("SD ERR");
+            tv_log_status.setTextColor(mxgui::white);
+            tv_log_status.setBackgroundColor(mxgui::red);
+        }
+
+        tv_log_buf_dropped.setText(to_string(stats.statDroppedSamples));
+
+        if (stats.statDroppedSamples > 0)
+        {
+            tv_log_buf_dropped.setBackgroundColor(mxgui::red);
+        }
+
+        tv_log_buf_failed.setText(to_string(stats.statWriteFailed) + "   (" +
+                                  to_string(stats.statWriteError) + ")");
+
+        if (stats.statWriteError != 0)
+        {
+            tv_log_buf_failed.setBackgroundColor(mxgui::red);
+        }
+
+        tv_log_buf_written.setText(to_string(stats.statBufferWritten));
+        tv_log_buf_ttw.setText(to_string(stats.statWriteTime) + " ms");
+    }
+
+    void updateXbeeStatus(DataRateResult res_rcv, DataRateResult res_snd,
+                          TxData txd, RxData rxd, Xbee::XbeeStatus xbee_status)
+    {
+        char str_buf[30];
+
+        tv_tx_num_pkt.setText(
+            to_string(txd.tx_success_counter + txd.tx_fail_counter));
+        tv_tx_num_fail.setText(to_string(txd.tx_fail_counter));
+
+        snprintf(str_buf, 30, "%.1f pkt/s", res_snd.packets_per_second);
+
+        tv_tx_pps.setText(str_buf);
+        tv_tx_tts.setText(to_string(txd.time_to_send) + " ms");
+
+        tv_tx_last_status.setText(to_string(xbee_status.last_tx_status));
+        tv_tx_last_err.setText(to_string(xbee_status.last_tx_status_error));
+
+        tv_rx_num_pkt.setText(to_string(rxd.rcv_count));
+        tv_rx_num_fail.setText(to_string(rxd.rcv_errors));
+        // tv_rx_num_fail.setText(to_string(int_counter) + " " +
+        //                               to_string(GpioATTN::value()));
+        tv_rx_lost.setText(to_string(rxd.packets_lost));
+
+        tv_rx_RSSI.setText(to_string(rxd.RSSI) + " dB");
+
+        snprintf(str_buf, 30, "%.0f B/s", res_rcv.data_rate);
+        tv_rx_data_rate.setText(str_buf);
+
+        snprintf(str_buf, 30, "%.1f pkt/s", res_rcv.packets_per_second);
+        tv_rx_pps.setText(str_buf);
+
+        tv_rx_time_since_last_rx.setText(tickToHMS(miosix::getTick() - rxd.last_packet_timestamp));
+    }
+
+    VerticalLayout root{10};
+
+    TextView tv_cfg_tx_enabled{"Disabled"};
+    TextView tv_cfg_pkt_size{"256 B"};
+    TextView tv_cfg_snd_interval{"Cont"};
+    TextView tv_cfg_freq_hop{"Enabled"};
+    TextView tv_cfg_data_rate{"10 kbps"};
+
+    TextView tv_log_status{"SD ERR"};
+
+    TextView tv_log_buf_dropped{"0"};
+    TextView tv_log_buf_failed{"0 (0)"};
+    TextView tv_log_buf_written{"0"};
+    TextView tv_log_buf_ttw{"0 ms"};
+
+    TextView tv_tx_num_pkt{"0"};
+    TextView tv_tx_num_fail{"0"};
+    TextView tv_tx_pps{"0 pkt/s"};
+    TextView tv_tx_tts{"- ms"};
+    TextView tv_tx_last_status{"0"};
+    TextView tv_tx_last_err{"0"};
+
+    TextView tv_rx_num_pkt{"0"};
+    TextView tv_rx_num_fail{"0"};
+    TextView tv_rx_lost{"0"};
+    TextView tv_rx_RSSI{"-40 dB"};
+    TextView tv_rx_data_rate{"0 B/s"};
+    TextView tv_rx_pps{"0 pkt/s"};
+    TextView tv_rx_time_since_last_rx{"00:00:00"};
+
+    TextView btn_mark{"Mark Log (1)"};
+    TextView btn_stop{"Stop"};
+
+private:
+    TextView title{"Xbee Status"};
+    GridLayout grid_title{1, 2};
+    GridLayout grid_config{3, 4};
+    GridLayout grid_buttons{1, 2};
+    GridLayout grid_log_status{3, 4};
+    GridLayout grid_data{8, 4};
+
+    TextView tv_cfg_txt_tx_enabled{"TX"};
+    TextView tv_cfg_txt_pkt_size{"Pkt size"};
+    TextView tv_cfg_txt_snd_interval{"Interv"};
+    TextView tv_cfg_txt_freq_hop{"Freq hop"};
+    TextView tv_cfg_txt_data_rate{"Data rate"};
+
+    TextView tv_log_title{"LOG"};
+    TextView tv_log_txt_buf_dropped{"Buf drops"};
+    TextView tv_log_txt_buf_failed{"Wrt fails"};
+    TextView tv_log_txt_buf_written{"Wrt succ"};
+    TextView tv_log_txt_buf_ttw{"TTW"};
+
+    TextView tv_tx_title{"TX"};
+
+    TextView tv_tx_txt_num_pkt{"Sent"};
+    TextView tv_tx_txt_num_fail{"Fails"};
+    TextView tv_tx_txt_pps{"PPS"};
+    TextView tv_tx_txt_TTS{"TTS"};
+    TextView tv_tx_txt_last_status{"Status"};
+    TextView tv_tx_txt_last_err{"Last err"};
+
+    TextView tv_rx_title{"RX"};
+
+    TextView tv_rx_txt_num_pkt{"Recv"};
+    TextView tv_rx_txt_num_fail{"Fails"};
+    TextView tv_rx_txt_lost{"Lost"};
+    TextView tv_rx_txt_RSSI{"RSSI"};
+    TextView tv_rx_txt_data_rate{"DR"};
+    TextView tv_rx_txt_pps{"PPS"};
+    TextView tv_rx_txt_time_since_last_rx{"No RX dt"};
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/XbeeGui.h b/src/tests/drivers/xbee/gui/XbeeGui.h
new file mode 100644
index 0000000000000000000000000000000000000000..b24af1551bda6c358085cd6b312590a6621e9d09
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/XbeeGui.h
@@ -0,0 +1,73 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <mxgui/display.h>
+
+#include <cstdint>
+#include <functional>
+
+#include "ConfigScreen.h"
+#include "StatusScreen.h"
+#include "EnergyScanScreen.h"
+#include "EndScreen.h"
+#include "RespectScreen.h"
+#include "utils/gui/ScreenManager.h"
+
+class XbeeGUI
+{
+public:
+    enum Screens : uint8_t
+    {
+        SCREEN_CONFIG,
+        SCREEN_STATUS,
+        SCREEN_ENERGYSCAN,
+        SCREEN_END,
+        SCREEN_RESPECT
+    };
+
+    XbeeGUI()
+        : screen_manager(mxgui::DisplayManager::instance(), 4)
+    {
+        screen_manager.addScreen(SCREEN_CONFIG, &screen_config.root);
+        screen_manager.addScreen(SCREEN_STATUS, &screen_status.root);
+        screen_manager.addScreen(SCREEN_ENERGYSCAN, &screen_energy.root);
+        screen_manager.addScreen(SCREEN_END, &screen_end.root);
+        screen_manager.addScreen(SCREEN_RESPECT, &screen_respect.root);
+
+        screen_manager.start();
+    }
+
+    ~XbeeGUI()
+    {
+        screen_manager.stop();
+    }
+
+    ScreenManager screen_manager;
+
+    ConfigScreen screen_config{};
+    StatusScreen screen_status{};
+    EnergyScanScreen screen_energy{};
+    EndScreen screen_end{};
+    RespectScreen screen_respect{};
+};
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/res/respect.cpp b/src/tests/drivers/xbee/gui/res/respect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f32d2330f632a2cb03d1776bb267480a596fa5e2
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/res/respect.cpp
@@ -0,0 +1,9635 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+//This file has been automatcally generated by pngconverter utility
+//Please do not edit
+#include "respect.h"
+
+using namespace mxgui;
+
+static const short int height=320;
+static const short int width =240;
+
+static const unsigned short pixelData[]={
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,6371,19049,
+ 29614,35921,38066,38034,35953,29614,21130,10597,
+ 2145,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,10597,33808,50712,61277,65503,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 54970,40147,21130,4258,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,10597,42260,63422,65535,65535,65535,65535,
+ 65503,65503,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,57051,31727,6371,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 29582,61277,65503,65535,65503,59164,42260,31727,
+ 23243,19017,16936,21162,27501,35953,48631,61277,
+ 63422,65535,65535,65535,65535,61277,35921,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,4226,40179,
+ 65535,65535,61277,38034,14823,32,0,0,
+ 0,0,0,32,0,0,32,2145,
+ 19017,33840,57083,65535,65535,65535,65535,52857,
+ 14823,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4226,44405,65503,
+ 63390,38034,6371,0,32,4226,14823,23275,
+ 27469,25388,21162,16904,10565,4258,32,32,
+ 0,0,2145,25388,57051,65535,65535,65535,
+ 61309,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,42260,65503,57083,
+ 19017,2113,32,4258,33840,57083,65503,65503,
+ 65535,65535,65503,65503,63422,61277,50744,38066,
+ 23243,6339,32,32,4258,38066,65535,65535,
+ 65535,59196,10565,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,31727,65503,52857,12678,
+ 0,0,14791,52857,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65503,63390,44373,19049,2113,32,27501,63390,
+ 65535,65535,50744,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,14823,63390,57051,8452,0,
+ 0,12710,59164,65535,65535,63390,63390,63422,
+ 65535,65535,65535,65535,65535,65503,65535,65535,
+ 65535,65503,65503,65503,48599,10565,0,27501,
+ 65503,65535,65503,25388,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2145,54938,63422,16936,0,0,
+ 4258,54938,65535,63390,31695,12678,6371,12678,
+ 14823,19017,23243,27469,29582,31727,35953,40179,
+ 52825,63390,65535,65535,65503,57083,12678,32,
+ 40147,65535,65535,52857,2113,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,31727,65535,31695,32,0,0,
+ 35953,65535,63390,21162,0,0,0,0,
+ 0,0,0,32,0,0,0,32,
+ 0,12678,38066,63422,65535,65535,52825,4258,
+ 6371,59164,65535,65503,19049,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,10565,61309,54938,4226,0,0,14791,
+ 63422,65503,31695,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,19017,59196,65535,65535,27469,
+ 0,29582,65535,65535,42260,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,0,
+ 32,42292,65535,23243,0,0,2145,48599,
+ 65535,44405,2113,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,16904,61309,65535,52857,
+ 2113,6371,61277,65535,57051,2145,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 12710,63390,52857,2113,32,0,19049,63422,
+ 57083,8484,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,27501,65535,63422,
+ 12710,32,46486,65535,63422,12710,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,32,
+ 42260,65535,29582,0,0,2113,50712,65535,
+ 27501,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,50744,65503,
+ 33808,0,21130,65535,65535,25356,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,6371,
+ 65503,61277,8452,0,0,16936,65535,54970,
+ 4226,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,21130,65503,
+ 50712,0,6371,61309,65535,40179,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,33808,
+ 65535,44373,32,0,0,44405,65503,29582,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,57083,
+ 61309,4258,32,52857,65535,54970,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2145,54970,
+ 65535,23275,0,0,12678,63422,61277,8452,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,42292,
+ 65535,16904,0,42292,65535,59196,4258,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16936,65535,
+ 61277,6339,0,0,35953,65535,52825,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,27469,
+ 65535,27469,32,29582,65535,63422,12678,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,42260,65535,
+ 50712,0,0,6339,57083,65535,38034,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,14823,
+ 65535,31727,0,23243,65535,65535,19017,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,4258,59164,65535,
+ 33808,0,32,19049,65503,65535,29582,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,32,0,8484,
+ 63422,35953,0,16936,65535,65535,25388,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,16936,65503,65503,
+ 14823,0,0,40179,65535,65535,25388,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4258,
+ 57083,35921,0,12710,65535,65535,31695,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,35921,65535,59196,
+ 4226,0,4226,57083,65535,65535,27469,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,2113,
+ 52857,31695,0,12678,65535,65535,38066,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,52857,65535,48599,
+ 0,0,12710,65535,65535,65535,33808,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,8452,
+ 61309,27501,0,10565,65535,65535,44373,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,8484,63390,65535,31727,
+ 0,32,31727,65535,65535,65535,44373,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 63390,21162,0,12678,65535,65503,44405,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,23275,65503,65503,19017,
+ 0,32,48631,65535,65535,65535,50712,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 63390,16936,0,14791,65535,65535,44373,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,40147,65535,63390,8452,
+ 0,4226,59196,65535,65535,65535,57083,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 61309,10565,0,16904,65535,65535,42292,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,57083,2113,
+ 32,16904,65535,65535,65535,65535,59196,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 61309,6339,0,19017,65503,65535,42260,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,63390,65535,46486,32,
+ 0,29582,65535,65535,65535,65535,61309,2145,
+ 0,0,0,0,0,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,19049,
+ 59196,2145,0,25356,65535,65535,38066,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,14823,65503,65535,35953,0,
+ 0,42260,65535,65535,65535,65535,61309,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,29614,
+ 57051,2113,0,31695,65535,65535,33840,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,29582,65535,65535,25356,0,
+ 2113,54938,65535,65535,65503,65535,63390,8484,
+ 32,0,2113,4226,10597,19017,27469,31695,
+ 31695,25356,14823,8484,2113,32,0,32,
+ 0,0,0,0,0,0,2113,46486,
+ 50744,32,0,40147,65535,65535,27469,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,40147,65535,65503,16936,0,
+ 4258,61309,65535,65535,65535,65535,65503,23243,
+ 2145,16904,40147,59164,65503,65535,65535,65535,
+ 65535,65535,65503,63422,52857,31727,8484,2145,
+ 0,0,0,0,32,0,4258,57083,
+ 48599,0,32,46486,65535,65503,21162,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,48599,65535,63390,10565,0,
+ 10597,65503,65535,65535,65535,65535,65503,63422,
+ 57051,65503,65535,65535,65535,65535,65503,65535,
+ 65535,65535,65535,65535,65535,65535,63390,44373,
+ 16904,32,0,0,0,32,23275,65535,
+ 42292,32,2113,52825,65535,65503,16936,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2145,54938,65535,61309,8452,0,
+ 21162,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,59164,48631,35921,
+ 29582,29582,38066,50712,59164,65535,65535,65535,
+ 63422,44405,12678,32,0,4258,54938,65535,
+ 35953,0,4226,54970,65535,63390,12678,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,61277,65535,59196,4258,0,
+ 31727,63390,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,54970,23275,4258,0,32,
+ 0,0,32,32,6371,27469,54938,65535,
+ 65535,65503,61277,31727,10565,42260,65535,65535,
+ 29582,0,6371,61277,65535,59196,4258,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,8452,63390,65535,59164,2145,0,
+ 21130,48599,59164,63422,65535,65535,65535,65535,
+ 65535,65503,65535,52825,40179,33808,23275,14791,
+ 10565,6339,2113,0,0,0,4226,35953,
+ 65535,65535,65535,65535,63390,65535,65535,65535,
+ 23275,0,8484,63422,65535,57083,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,10565,65503,65535,52825,32,0,
+ 32,32,6339,12710,31695,46486,59196,63422,
+ 65503,65535,65535,65535,65535,65535,65535,65535,
+ 63422,59196,54970,48599,40147,29582,19017,14823,
+ 59196,65535,65535,65503,65535,65535,65535,65503,
+ 16936,0,14791,65535,65535,46486,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,12678,65535,65535,50744,32,0,
+ 32,0,0,32,0,0,4226,12678,
+ 25356,38066,50744,59196,65503,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65503,
+ 65503,65535,65535,65535,65535,65535,65535,63390,
+ 10565,32,23275,65535,65535,38066,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,14823,65535,65535,48631,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,4258,12710,23275,38066,48631,
+ 57083,63390,65503,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,63390,42292,
+ 32,0,31727,65535,65535,25388,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16936,65535,65535,46518,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4226,8484,16936,29582,42292,52857,61309,63422,
+ 65503,65503,65535,65535,65503,59164,31727,2145,
+ 0,32,38066,65535,65535,16936,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16936,65535,65535,46486,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,12678,
+ 21130,27501,29614,25388,16904,2145,0,2113,
+ 0,0,48599,65535,63390,8484,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16936,65535,65535,44373,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,32,0,
+ 0,32,50712,65535,61277,6371,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16936,65535,65535,42260,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 0,2145,52857,65535,57083,2145,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16936,65535,65535,40179,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,32,
+ 0,0,0,32,0,0,0,0,
+ 0,4226,57051,65535,54970,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,12710,65535,65535,42260,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,4258,59196,65503,48599,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,10597,65503,65535,42260,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,6371,61277,65535,48599,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,8484,63422,65535,42260,32,0,
+ 0,0,0,0,0,0,0,0,
+ 29582,27501,14791,6339,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,8452,63390,65535,48599,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,6371,63390,65535,42260,32,0,
+ 0,0,0,0,0,0,0,0,
+ 21162,59196,65535,61277,52825,42260,31695,29582,
+ 21162,19017,14791,12678,8484,6339,2145,2145,
+ 2145,2113,32,32,0,0,0,0,
+ 32,10565,63422,65535,48599,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4226,59196,65535,44373,32,0,
+ 0,0,0,0,0,0,0,0,
+ 32,12710,52857,65503,65535,65535,65535,65535,
+ 65535,65535,65503,65503,65535,65503,65503,61277,
+ 59164,54970,54938,46518,16936,0,0,32,
+ 0,12678,65535,65535,46518,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2145,54970,65535,44373,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,50744,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 63422,63390,54970,42292,14823,0,0,0,
+ 0,14791,65535,65535,46518,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,48631,65535,42292,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,21130,54970,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65503,59164,
+ 29582,10597,2113,32,0,0,0,32,
+ 0,19017,65535,65535,46518,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,44405,65535,42292,0,0,
+ 0,0,0,0,0,0,0,0,
+ 16904,46518,65503,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65503,
+ 38066,8452,0,0,0,0,0,0,
+ 0,21162,65535,65503,46518,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,42292,65535,42292,0,0,
+ 0,0,0,0,0,0,32,2145,
+ 52857,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,57051,21130,0,0,0,0,0,
+ 0,23275,65535,65535,44405,32,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,42292,65535,42292,0,0,
+ 0,0,0,0,0,0,0,32,
+ 6339,31695,38066,40179,40147,40179,42292,44373,
+ 46486,46518,48631,50744,54938,57051,59164,59196,
+ 65503,65535,61309,35953,0,0,0,0,
+ 0,25388,65535,65535,44405,32,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,42292,65535,42292,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,32,
+ 0,0,32,32,32,2113,2113,2113,
+ 2113,4258,8452,6339,0,0,0,0,
+ 0,27501,65535,65535,44373,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,42292,65535,42292,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,0,0,2113,4226,6339,
+ 6339,6371,6371,6371,6371,6371,8452,6371,
+ 4258,0,0,0,0,0,0,0,
+ 0,29614,65535,65535,44373,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,46518,65535,40179,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,44373,61277,
+ 63390,61309,61277,61309,61309,61277,63390,54970,
+ 16904,0,0,0,0,0,0,0,
+ 0,33808,65535,65535,42292,2113,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,52825,65535,38034,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,23243,
+ 38066,48599,54938,54970,57051,48631,31727,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,33840,65535,65535,42260,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,6339,61277,65535,33840,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,2113,2113,32,0,0,0,
+ 0,0,0,32,0,0,0,0,
+ 0,38034,65535,65535,40147,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,32,12710,65503,65535,31695,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,0,0,0,0,0,
+ 0,38066,65535,65503,38066,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,31695,65535,65535,27469,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,42260,65535,65535,38066,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,52825,65535,65535,19049,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,44373,65535,65535,38034,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,10597,65535,65535,63390,10565,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,46486,65535,65535,33840,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,33840,65535,65535,61277,4226,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,48599,65535,65535,31727,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2145,52857,65535,65535,50744,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,50744,65535,65535,27501,32,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 10565,63422,65535,65535,35921,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,52825,65535,65535,25356,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 29582,65535,65535,65535,19049,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,54938,65535,65503,19017,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 44373,65535,65535,63390,10565,0,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,57051,65535,65503,14823,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,4258,
+ 57083,65535,65535,59164,2113,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2145,59196,65535,63390,6371,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,12678,
+ 63422,65535,65535,46486,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4226,59196,65535,61277,4226,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,23275,
+ 65535,65535,65535,31695,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 6339,61309,65535,54938,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,38066,
+ 65535,65535,65503,19049,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 8484,63390,65535,48631,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,2113,52857,
+ 65503,65535,63390,8452,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 10565,65503,65535,40179,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,61309,
+ 65535,65535,57083,4226,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 14791,65535,65535,27501,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16936,65535,
+ 65535,65535,46518,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 21162,65535,65535,16936,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,29582,65535,
+ 65535,65535,35953,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 33808,65535,63422,12678,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,46486,65535,
+ 65535,65535,25356,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,0,
+ 42292,65535,61277,6371,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,52857,65535,
+ 65535,65535,16936,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,2113,
+ 50744,65535,59164,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4226,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,6339,61277,65535,
+ 65535,63390,8484,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4258,
+ 57083,65535,54970,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8452,57051,42292,14823,6371,
+ 4258,2145,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63422,65535,
+ 65535,61277,6339,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,6371,
+ 61309,65535,52825,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,33808,65535,65535,65535,61309,
+ 61277,59164,14823,0,0,0,0,0,
+ 0,0,0,0,0,19049,65535,65535,
+ 65535,57083,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,12678,
+ 65535,65535,44373,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,6371,59164,65535,65535,65535,65535,
+ 65535,65535,59196,10565,0,0,0,32,
+ 0,0,0,0,0,27469,65535,65535,
+ 65535,52825,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,21130,
+ 65535,65535,42292,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,38066,65535,65535,65535,65535,59196,
+ 54938,65503,65535,52857,6371,0,0,0,
+ 0,0,0,0,0,38034,65535,65535,
+ 65535,44373,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,33808,
+ 65535,65535,40179,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,27501,65503,65535,65503,65535,54938,10565,
+ 6339,54938,65535,65535,46518,4226,0,0,
+ 0,0,0,0,0,44405,65535,65535,
+ 65535,38066,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,42260,
+ 65535,65535,38066,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,0,0,0,2145,
+ 29614,63390,65535,65535,65535,65503,25356,32,
+ 0,21130,65535,65535,65503,35921,0,0,
+ 0,0,0,0,32,50712,65535,65535,
+ 65535,25388,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,50712,
+ 65535,65535,38034,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,44405,
+ 65535,61277,57083,65503,65535,61309,6371,0,
+ 0,8452,63390,65535,65535,63390,16904,0,
+ 0,0,0,0,2113,54970,65535,65535,
+ 65535,21162,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2113,52857,
+ 65535,65535,35953,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,32,31695,61277,65503,
+ 44373,6371,10597,65503,65535,46486,32,0,
+ 0,32,59164,65535,65535,65535,48599,2145,
+ 0,0,0,0,4258,61277,65535,65535,
+ 63422,14791,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,59164,
+ 65535,65535,33840,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,32,4258,44405,65535,65503,40147,
+ 2145,32,27469,65535,65503,19049,0,0,
+ 0,0,50712,65535,65535,65503,63422,12710,
+ 0,0,0,0,8452,63390,65535,65535,
+ 63390,10565,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,61309,
+ 65535,65535,31727,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,48631,65535,65535,38066,32,
+ 32,10597,61309,65535,46518,32,0,32,
+ 0,32,38066,65535,65535,65535,65535,33808,
+ 32,0,0,0,12710,65535,65535,65535,
+ 61277,8484,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6371,63390,
+ 65535,65535,31695,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,44405,65535,65503,44405,32,0,
+ 32,46486,65535,61277,10565,0,32,0,
+ 0,0,31695,65535,65535,65535,65535,50744,
+ 32,0,0,0,19017,65535,65535,65535,
+ 59196,4226,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6371,61309,
+ 65535,65535,31695,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,29582,65535,65535,54970,6371,0,0,
+ 16904,63422,65503,27469,0,0,0,0,
+ 0,0,27469,65535,65535,65535,65535,61277,
+ 6339,0,0,0,23275,65535,65535,65535,
+ 54970,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6339,61277,
+ 65535,65535,31695,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 10597,63390,65535,63390,14823,0,32,6339,
+ 54970,65535,44373,32,0,32,0,0,
+ 0,0,27469,65535,65535,65535,65535,65535,
+ 14791,32,0,0,27501,65535,65535,65535,
+ 50712,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,2145,4226,8452,10565,8484,4258,
+ 2145,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,59196,
+ 65535,65535,29614,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,2113,
+ 46518,65535,65535,42260,32,0,8452,48599,
+ 65535,52857,4258,0,0,0,0,32,
+ 32,0,42260,65535,65535,65535,65535,65535,
+ 31727,0,0,0,35953,65535,65535,65535,
+ 42292,32,0,0,0,0,0,0,
+ 0,0,0,0,0,32,32,6371,
+ 27469,44373,54970,63390,63422,65503,65503,63422,
+ 59164,50744,40179,29582,8452,32,0,0,
+ 0,0,0,0,0,0,4226,57083,
+ 65535,65535,29582,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,32,21162,
+ 63422,65535,61277,10565,0,12678,52857,65535,
+ 61277,14791,0,0,0,0,0,0,
+ 2113,21162,63422,65535,65535,65535,65535,65535,
+ 46518,32,0,0,42292,65535,65535,65535,
+ 38034,0,0,0,0,0,0,0,
+ 0,0,0,32,0,6339,38034,61309,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,63390,40147,8484,0,
+ 0,0,0,0,0,0,2113,54970,
+ 65535,65535,29582,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,2145,54970,
+ 65535,65535,31695,0,19049,59196,65535,63390,
+ 21162,32,0,0,0,0,4258,21162,
+ 44405,63390,65535,65535,65535,65535,65535,65535,
+ 54938,2145,0,32,50744,65535,65535,65535,
+ 29582,0,0,0,0,0,0,0,
+ 0,0,32,32,21162,59164,65503,65535,
+ 65535,63390,61309,59164,52857,54970,57083,57083,
+ 63390,65503,65503,65503,65535,65535,59164,14823,
+ 0,0,0,0,0,0,32,52857,
+ 65535,65535,29582,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,31695,65535,
+ 65535,61309,23275,33840,63422,65535,61309,23275,
+ 32,0,0,0,8484,38066,61277,65503,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 61309,10565,32,2145,57051,65535,65535,65535,
+ 23275,0,32,0,0,0,0,0,
+ 0,32,32,27501,61277,52825,35953,25356,
+ 16904,6371,2113,2113,2113,2113,32,0,
+ 6339,12710,23275,35921,52825,63422,65503,61277,
+ 12710,0,0,0,32,0,0,50744,
+ 65535,65535,29582,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,10565,63390,65503,
+ 65535,65535,65535,65535,65535,59164,16936,0,
+ 0,0,4258,29582,61309,65503,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,23275,0,4258,61277,65535,65535,65503,
+ 19017,0,32,0,0,0,0,0,
+ 0,0,6371,21162,4258,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,8484,33808,52857,
+ 44405,2113,0,0,0,0,32,48631,
+ 65535,65535,35921,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,46486,65535,65535,
+ 65535,65535,65535,65535,50744,10565,0,0,
+ 0,21162,54970,65503,65503,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,48599,2145,21162,63422,65535,65535,65535,
+ 14791,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,32,
+ 16936,4258,32,32,0,0,0,48631,
+ 65535,65535,42260,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,0,2113,19049,63422,65535,65535,
+ 65535,65535,65535,46486,4226,0,32,12710,
+ 46518,65503,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65503,42260,61277,65535,65535,65535,63422,
+ 8452,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,0,0,0,0,48631,
+ 65535,65535,50744,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,4258,57051,65535,65535,65503,
+ 65535,65535,65535,16904,32,10565,40147,65535,
+ 65535,65503,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65503,65535,65535,65503,65535,65535,61277,
+ 6371,0,0,32,0,0,0,0,
+ 0,0,0,0,32,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,0,0,0,0,48631,
+ 65503,65535,59196,2145,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,33808,65535,65535,65535,65535,
+ 65535,65535,65535,44405,40179,61277,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,57051,
+ 2145,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,50744,
+ 65535,65535,65503,16936,32,0,0,0,
+ 0,0,0,32,0,0,0,0,
+ 0,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,10565,61277,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65503,65503,52857,38066,27469,21130,14791,
+ 12678,16904,23243,29582,42260,54970,65503,65535,
+ 65535,65535,65535,65535,65535,65535,65535,52825,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,52825,
+ 65535,65535,65535,40147,32,0,0,0,
+ 0,32,16936,35921,38034,31695,21130,10565,
+ 2145,32,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,44405,65535,65535,65535,65535,65535,
+ 65535,65535,65503,65535,65535,65535,65535,65535,
+ 61309,42292,14791,4258,8452,6339,2113,0,
+ 32,0,0,0,0,32,16904,42292,
+ 63422,65535,65535,65535,65535,65535,65535,46486,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,2113,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2113,52857,
+ 65535,65535,65535,59164,10565,8484,12710,19049,
+ 27469,40179,65535,65535,65535,65535,65535,63422,
+ 59196,46486,29614,16936,8484,4226,32,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,21130,65503,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65503,48631,
+ 12710,2145,25388,57051,63422,61309,52857,38034,
+ 19017,4258,0,32,0,0,0,2113,
+ 10597,46518,65535,65535,65535,65535,65535,42292,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,29614,54970,50744,46486,
+ 38034,35921,31727,29614,27501,27469,23275,23243,
+ 19049,16904,10565,6371,0,0,0,0,
+ 0,0,0,0,0,0,2145,54938,
+ 65535,65535,65535,65535,63390,63422,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65503,65535,65535,63390,61277,54938,48599,
+ 33808,19017,2145,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4258,54970,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,63422,65535,63390,33808,32,
+ 32,8452,46518,57051,63390,65535,65535,65503,
+ 65535,61277,42292,23243,4258,32,0,32,
+ 0,4258,46486,65535,65535,65535,65535,35953,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,8452,16936,23275,
+ 29614,33840,42260,46486,50744,54938,59164,61309,
+ 63422,63422,63390,63390,54970,42260,23275,10597,
+ 32,0,0,0,0,0,4226,59164,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65503,65535,65535,
+ 65535,65503,57083,31727,8452,32,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 35921,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,61309,19049,32,0,
+ 0,0,2145,12678,59196,65535,65535,65535,
+ 65535,65535,65535,65503,54970,16904,0,0,
+ 0,0,8452,57083,65535,65535,65535,29614,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,32,2113,2145,4258,
+ 10597,16904,25356,29614,40147,44373,50712,54938,
+ 38034,2113,0,0,0,0,6339,59196,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,63422,61277,59164,61277,
+ 61277,63422,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,61277,25388,32,0,
+ 32,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,12678,
+ 61309,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,59196,14823,0,32,0,
+ 0,14791,40147,61277,65535,65535,65535,65535,
+ 65535,65535,65535,52857,16936,2113,32,0,
+ 32,0,0,27501,65535,65535,65535,23275,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 0,0,0,0,0,0,32,2145,
+ 4226,0,0,0,0,0,8452,61309,
+ 65535,65535,65535,65535,65535,65535,65535,63390,
+ 50712,38034,21130,16904,16904,16904,12678,6371,
+ 4258,10565,19017,31695,44405,59164,65503,65535,
+ 65535,65535,65535,65535,65535,63422,44373,4258,
+ 0,32,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2113,48631,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,63390,16936,0,0,0,4258,
+ 42292,63422,65535,65503,65535,65535,65503,65535,
+ 65535,65535,65535,65503,63390,50712,25388,4258,
+ 0,0,32,8484,63390,65535,65535,21162,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,32,32,0,0,8484,63390,
+ 65535,65535,65535,65535,65535,59196,33840,8452,
+ 2113,2145,42292,61277,63422,65503,63390,61309,
+ 54938,38034,23275,10565,2113,2145,12678,33808,
+ 48599,61277,65535,65535,65535,65535,65535,50744,
+ 8484,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,27501,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,63390,21130,0,0,0,0,35953,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,54938,
+ 6371,0,0,2113,48631,65535,65535,16936,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,0,0,0,32,0,
+ 0,32,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10597,65503,
+ 65535,65535,65535,65535,61309,16904,32,0,
+ 0,0,14823,54938,65535,65535,65535,65535,
+ 65503,65503,65503,65503,52857,27501,6371,32,
+ 0,6371,35953,61309,65535,65535,65535,65535,
+ 52857,6371,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,61277,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,31695,0,0,32,0,0,19049,
+ 25356,25356,25356,25356,25356,25356,25356,23275,
+ 23275,23275,23275,25356,23275,23243,23275,23275,
+ 10597,0,0,0,38066,65535,65535,16904,
+ 0,0,0,0,0,0,0,0,
+ 6339,42292,35921,14791,2145,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,14791,65503,
+ 65535,65535,65535,65535,31695,0,0,0,
+ 0,0,0,4226,19017,42292,61309,65535,
+ 65535,65535,65535,65535,65535,65535,61309,46486,
+ 23243,4226,0,8452,44373,65503,65535,65535,
+ 65535,52857,8452,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,42292,65535,65535,
+ 65535,65535,65535,65503,65535,65535,65535,65535,
+ 46518,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 0,0,0,32,0,0,0,0,
+ 0,0,0,0,25356,65535,65535,14791,
+ 0,0,0,0,0,0,0,0,
+ 0,31695,65503,63422,59164,38034,16936,6339,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16936,65535,
+ 65535,65535,65535,59164,4226,0,0,32,
+ 0,32,2145,6371,8452,14791,33808,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65503,61277,35921,12710,2145,23243,61277,65535,
+ 65535,65535,50744,4258,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,21130,65535,65535,65535,
+ 65535,65535,61277,31727,65535,65535,65535,65535,
+ 12678,0,0,0,0,0,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,0,
+ 32,0,0,0,14791,65535,65535,12710,
+ 0,0,0,0,0,0,0,0,
+ 0,32,33808,65503,65535,65535,65503,61277,
+ 42292,19049,4226,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,19049,65535,
+ 65535,65535,65535,38066,0,0,0,0,
+ 10597,33808,50744,59196,63422,63422,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,63422,
+ 61277,63390,63390,61309,21130,0,16904,59164,
+ 65535,65535,63422,44373,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6371,57083,65503,65535,65535,
+ 65535,63422,27469,19017,65535,65503,65535,42260,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,6339,63390,65503,12678,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2113,35921,65503,65535,65535,65535,
+ 65535,65503,59164,48599,27501,12710,4258,0,
+ 0,0,0,0,0,32,0,0,
+ 0,4226,10565,32,0,0,23243,65535,
+ 65535,65535,63422,16936,0,0,0,0,
+ 0,0,2145,10565,19049,33808,48599,54970,
+ 63422,65535,65535,65535,65535,65535,65535,65503,
+ 40179,12678,6371,8452,2145,0,32,14823,
+ 57083,65535,65535,63422,21130,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,40179,65535,65535,65535,65535,
+ 65535,44405,2113,21162,65535,65535,63422,12710,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,57051,65503,10597,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,2113,27501,61277,65535,65535,
+ 65535,65535,65535,65503,65535,65503,59196,48631,
+ 38034,31695,27469,27501,29582,29582,31695,31727,
+ 44373,44373,8452,0,0,0,23275,65535,
+ 65535,65535,59196,4226,0,0,0,0,
+ 0,0,0,0,32,0,32,2145,
+ 8452,23243,40147,48599,57083,63422,65535,65535,
+ 65535,61277,33808,10597,0,0,0,0,
+ 14791,57083,65535,65535,50744,32,0,0,
+ 0,32,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,23243,65535,65535,65535,65535,65535,
+ 54970,8452,0,27501,65503,65535,50744,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,50744,63422,10565,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,12678,50744,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65503,65535,63422,
+ 33840,4226,0,0,0,0,25356,65535,
+ 65535,65535,44373,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,4226,12678,25356,38066,
+ 52825,63390,65503,63390,33840,32,0,0,
+ 0,10565,54938,65535,65503,16904,0,0,
+ 2113,19017,35921,38066,29582,10597,32,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,10565,59196,65535,65535,65503,65535,63422,
+ 16936,0,0,35921,65535,65535,29614,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,48599,65503,8484,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2145,27469,
+ 54938,65503,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,61277,42260,10597,
+ 32,0,0,0,0,0,25388,65535,
+ 65535,65535,27469,0,0,0,0,0,
+ 0,0,0,32,0,0,0,0,
+ 0,0,0,0,32,0,32,0,
+ 2113,6339,14823,27501,42260,19049,32,0,
+ 0,0,8452,52857,65535,44405,32,8452,
+ 46486,63422,65535,65535,65535,61277,44405,14823,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4258,50712,65535,65535,65503,65535,65503,27501,
+ 0,0,0,44373,65535,63390,12678,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,46486,63422,10565,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2145,19017,44373,57083,63390,65503,65535,65503,
+ 65535,65503,59164,50744,27501,6371,32,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,65535,19049,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8484,57083,63390,14823,52825,
+ 65535,65535,65535,65535,65535,65535,65503,63422,
+ 44405,10565,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,32,
+ 33840,65535,65535,65535,65535,65535,42260,32,
+ 0,32,32,50744,65503,61309,2145,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,48599,63390,8452,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2145,8452,14823,21162,21162,
+ 16936,10597,4226,32,0,0,0,0,
+ 0,0,0,0,0,0,19049,65535,
+ 65535,65535,16936,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,19049,63422,61277,65535,
+ 65535,65535,65535,63422,61277,63422,65535,65535,
+ 65535,61277,33840,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,19049,
+ 63390,65535,65535,65535,65535,52825,4258,0,
+ 0,32,4226,59196,65535,48599,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,52857,63390,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 0,0,0,0,0,0,14823,65535,
+ 65535,65503,14791,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,32,32,50744,65535,65535,
+ 65535,65535,57083,19049,4226,8452,23275,50744,
+ 65535,65535,65503,52857,16936,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10565,59164,
+ 65535,65535,65535,65535,59196,10597,0,0,
+ 0,0,8452,65503,65535,35953,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2145,57051,63422,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,12678,65535,
+ 65535,65503,12678,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,33840,65535,65535,
+ 65535,57083,10597,0,0,32,0,2113,
+ 25356,59196,65535,65535,63390,33840,4226,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,0,0,0,2113,48599,65535,
+ 65535,65535,65535,63390,21130,0,32,0,
+ 0,0,21130,65535,65535,27501,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,61277,63390,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63390,
+ 65535,63422,10597,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,19049,65535,65535,
+ 65503,16936,2145,16936,29614,29614,21162,10565,
+ 2145,10597,52825,65535,65535,65535,54938,14823,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,33840,65503,65535,
+ 65535,65535,65535,33808,2113,0,0,0,
+ 0,0,31695,65503,65535,23243,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,10565,63422,61309,6339,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8452,61309,
+ 65535,63390,10565,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,14823,63422,65535,
+ 48631,32,29614,59164,65535,65535,65535,65503,
+ 52857,33840,23243,61309,65535,63422,65535,63390,
+ 31695,2145,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,0,19049,65503,65535,65535,
+ 65535,65503,46486,2113,0,0,0,0,
+ 0,0,42292,65503,63422,16904,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,16936,65535,61309,6339,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6371,61277,
+ 65535,61309,8484,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,61309,65535,
+ 33808,0,0,6371,31695,54938,65535,65535,
+ 65535,65535,65503,63422,65535,65535,65535,65535,
+ 65535,48599,8484,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6371,59164,65535,65535,65535,
+ 65535,57083,6371,0,0,0,0,0,
+ 0,2145,48631,65535,63390,12678,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,25356,63422,61309,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6339,59196,
+ 65535,61277,8452,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,4226,59196,65535,
+ 21130,0,32,32,0,2113,21130,44405,
+ 61309,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,59164,21162,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,2113,44373,65535,65535,65535,65535,
+ 61277,16936,0,0,0,0,0,0,
+ 0,2113,54970,65535,63390,6339,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,33840,65535,61277,6339,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,6339,61277,
+ 65535,61309,8452,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,59164,65503,
+ 10565,0,0,0,0,0,0,32,
+ 8484,31727,57051,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65503,33808,2113,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,27469,63390,65503,65535,65535,65535,
+ 31695,0,0,0,0,0,0,0,
+ 0,4258,59196,65535,61277,2145,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,40179,65535,61277,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6371,61309,
+ 65535,61277,6371,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,59164,61309,
+ 6339,0,0,0,0,0,0,32,
+ 4258,4226,8484,42292,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65503,42260,2145,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,10597,61277,65535,65535,65535,65503,46486,
+ 2113,0,0,0,0,0,0,0,
+ 0,8452,63390,65535,52857,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,46518,65535,61277,4226,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63390,
+ 65535,59196,6339,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,59164,57051,
+ 2145,0,32,0,32,0,32,4258,
+ 50744,61309,61309,63422,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,40147,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,46486,65535,65535,65535,65535,59164,8484,
+ 0,0,32,0,0,0,0,0,
+ 0,23243,65503,65535,48599,2113,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,50744,65535,61309,6339,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10565,63422,
+ 65535,59164,4258,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,59164,52857,
+ 32,0,0,0,0,0,0,0,
+ 12678,52825,63422,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,63390,23243,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 23275,65535,65535,65535,65535,65503,21162,0,
+ 0,0,0,0,0,0,0,2113,
+ 12678,54970,65535,65535,42260,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,61277,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,12678,63422,
+ 65535,59164,4258,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4258,59196,48599,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,16904,29582,44373,54970,63390,65535,
+ 65535,65535,65535,65535,65535,65535,65535,57083,
+ 2113,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4258,
+ 57083,65535,65535,65535,65535,44373,32,0,
+ 0,0,0,0,0,0,0,31727,
+ 65503,65535,65535,65535,33840,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,61277,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,12710,65503,
+ 65535,57083,4226,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,6339,61277,46486,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,0,0,32,10565,31727,
+ 54938,65503,65503,65535,65535,65535,65535,65503,
+ 21162,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,29582,
+ 65535,65535,65535,65535,63390,12678,0,0,
+ 0,0,0,0,0,0,0,12678,
+ 63422,65535,65535,65535,25388,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,61277,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16904,65535,
+ 65535,57051,2145,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8452,61309,44405,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 2145,25388,57083,65535,65535,65535,65535,65535,
+ 38066,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,2145,54970,
+ 65535,65535,65535,65535,42292,0,32,0,
+ 0,0,0,0,0,0,0,32,
+ 50712,65535,65535,65503,19049,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,61277,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19017,65535,
+ 65535,54970,2113,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63390,44405,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,6371,48599,65503,65535,65535,65535,
+ 50744,32,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19017,65535,
+ 65535,65535,65535,63422,14791,32,0,0,
+ 0,0,0,0,2113,19049,10565,2113,
+ 31695,65535,65535,65535,12678,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,52857,65535,61277,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,54970,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,12678,63390,50712,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,48599,65535,65535,65535,
+ 57083,2113,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,32,42292,65535,
+ 65535,65535,65535,57051,2145,0,0,0,
+ 0,0,0,0,4258,59196,63390,54970,
+ 50744,65535,65535,63390,6371,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,50744,65535,59196,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,25356,65535,
+ 65535,52857,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,14791,65535,52857,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,8452,54938,65535,65535,
+ 61277,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,59196,65535,
+ 65503,65535,65535,44373,32,0,0,0,
+ 0,0,0,0,32,44405,65535,65535,
+ 65503,65535,65535,59196,2145,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,48599,65535,59164,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,29582,65535,
+ 65535,50744,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,19049,65535,54970,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,19049,65503,65535,
+ 65503,6339,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,12710,63390,65535,
+ 65535,65535,65535,33840,0,0,0,0,
+ 0,0,0,0,0,16936,65503,65535,
+ 65535,65535,65535,59164,2145,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,46518,65535,59164,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,31727,65535,
+ 65535,50744,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,25356,65503,57051,
+ 2113,32,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,48599,65535,
+ 65535,10597,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,19049,65535,65535,
+ 65535,65535,65535,27469,0,0,0,0,
+ 0,0,0,0,0,2145,54938,65535,
+ 65535,65535,65535,54938,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,46486,65535,59164,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,33840,65535,
+ 65535,48599,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,29614,65535,57051,
+ 2113,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,23275,65503,
+ 65503,16904,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,25356,65535,65535,
+ 65535,65535,65535,23275,0,0,0,0,
+ 0,0,0,0,0,0,27501,65503,
+ 65535,65535,65535,48631,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,44373,65535,59164,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,38034,65535,
+ 65535,46486,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,35921,65535,57051,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8452,63390,
+ 65535,21162,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,27469,65535,65535,
+ 65535,65535,65535,25388,0,0,0,0,
+ 0,0,0,0,0,0,6339,59164,
+ 65535,65535,65535,42260,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,42260,65535,59164,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,42260,65535,
+ 65535,44405,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,42260,65535,54970,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2113,54970,
+ 65535,29582,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,25356,65535,65535,
+ 65535,65535,65535,31695,0,0,0,0,
+ 0,0,0,0,0,0,0,44373,
+ 65535,65535,65535,38034,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,40179,65535,59164,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,44405,65535,
+ 65535,44373,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,44373,65535,52857,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,44373,
+ 65535,33840,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,16936,65535,65535,
+ 65535,65535,65535,44405,32,0,0,0,
+ 0,0,0,0,0,0,0,21162,
+ 65503,65535,65535,29582,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,57083,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,46518,65535,
+ 65535,40147,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,50712,65535,50744,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,35953,
+ 65535,40147,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,10597,63422,65535,
+ 65535,65535,65535,54938,2145,0,0,32,
+ 0,0,0,0,0,32,0,8484,
+ 65535,65535,65503,21162,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,57083,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,50712,65535,
+ 65535,38066,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,52857,65535,46518,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,29614,
+ 65535,44373,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,4258,57051,65535,
+ 65535,65535,65535,65503,10565,0,32,0,
+ 0,0,0,0,0,0,0,4258,
+ 61277,65535,63422,16904,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,57083,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,54938,65535,
+ 65535,35953,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,57083,65535,40179,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,27469,
+ 65535,50712,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,33840,65535,
+ 65535,65535,65535,65535,33808,0,0,0,
+ 0,0,0,0,0,0,0,2145,
+ 59164,65535,63390,10597,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,57083,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,57083,65535,
+ 65535,35921,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,61277,65535,33840,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,27501,
+ 65535,54938,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10597,61309,
+ 65535,65535,65535,65535,57083,2145,0,0,
+ 0,0,0,0,0,0,0,32,
+ 57051,65535,63390,4226,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,57051,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4258,61309,65535,
+ 65503,33808,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,6339,61309,65535,27501,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,29582,
+ 65535,57083,2145,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,42260,
+ 65535,65535,65535,65503,65535,25356,0,0,
+ 0,0,0,0,0,0,0,2145,
+ 59164,65535,59164,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,57051,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,6339,61309,65535,
+ 65535,31695,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,10565,65503,65535,19049,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,35953,
+ 65535,61277,4226,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 61277,65535,65535,65535,65535,54938,2145,0,
+ 0,0,0,0,0,0,0,4226,
+ 61309,65535,48599,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,57051,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8452,63390,65535,
+ 65535,29614,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,14791,65503,65503,12678,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,46518,
+ 65535,63390,6339,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,32,
+ 27501,65535,65535,65535,65535,65535,27501,0,
+ 0,0,0,0,0,0,0,6339,
+ 65503,65535,40147,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,57051,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63390,65535,
+ 65535,29582,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,21162,65535,63390,6339,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,54938,
+ 65535,63390,8484,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,46486,65535,65535,65535,65535,59196,8484,
+ 0,0,0,0,0,0,0,14823,
+ 65535,65535,29582,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,54970,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63422,65535,
+ 65535,27469,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,25388,65535,57083,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,61309,
+ 65535,63422,10597,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,6371,57083,65535,65535,65535,65535,46486,
+ 2145,0,0,0,0,0,32,29582,
+ 65535,65535,21130,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38034,65535,54970,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63422,65535,
+ 65535,25388,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,29614,65535,54938,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,6371,63390,
+ 65535,65503,14791,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,19049,63422,65535,65535,65535,65535,
+ 27501,0,0,0,32,0,0,40179,
+ 65535,63390,10597,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35953,65535,54970,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63422,65535,
+ 65535,23275,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35921,65535,48631,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,12678,65503,
+ 65535,65503,19049,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,35953,63422,65535,65535,65535,
+ 61309,12678,0,0,0,0,32,50744,
+ 65535,61277,6339,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35953,65535,54970,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,63422,65535,
+ 65535,23243,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,40147,65535,46486,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16904,65535,
+ 65535,65503,23243,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,4226,52825,65535,65535,65535,
+ 65535,50712,4258,0,0,0,4258,59164,
+ 65535,52857,32,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35921,65535,54970,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,6339,63390,65535,
+ 65535,21162,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,44405,65535,42292,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19049,65535,
+ 65535,65535,29582,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,12678,61277,65535,65535,
+ 65535,65535,40147,2113,32,0,21130,65535,
+ 65535,44405,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35953,65535,54938,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4258,61309,65535,
+ 65535,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,48599,65535,42292,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19049,65535,
+ 65535,65535,31727,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,31695,63422,65535,
+ 65535,65535,65503,42292,16936,16904,54970,65535,
+ 65535,33840,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,52857,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4226,61277,65535,
+ 65535,16936,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,50744,65535,42292,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19049,65535,
+ 65535,65535,35953,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4226,52825,65535,
+ 65535,65535,65535,65535,65535,65503,65535,65535,
+ 65535,27469,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,52857,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,59196,65535,
+ 65503,16904,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,54938,65535,44373,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19049,65535,
+ 65535,65535,40179,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16936,63422,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,16936,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,38066,65535,52857,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,57083,65535,
+ 65503,14791,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2113,57083,65535,44373,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16904,65535,
+ 65535,65535,42260,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,32,44405,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65503,10565,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,42260,65535,52825,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,54938,65535,
+ 65503,14791,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,2145,59164,65535,44405,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,14791,65535,
+ 65535,65535,44373,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10597,
+ 63390,65535,65503,65535,65535,65535,65535,65535,
+ 63390,6339,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,44405,65535,52825,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,50712,65535,
+ 65503,14791,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,4258,59164,65535,46486,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,12678,65535,
+ 65535,65535,46486,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,0,
+ 40179,65535,65535,65535,65535,65535,65535,65535,
+ 61277,6371,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,48599,65503,52825,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,46518,65535,
+ 63422,12710,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,59196,65535,48599,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10597,65503,
+ 65535,65535,48599,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 10597,63390,65535,65535,65535,65535,65535,65535,
+ 61277,6339,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,4226,54938,65535,52825,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,44373,65535,
+ 63422,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,0,6339,59196,65535,50712,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63422,
+ 65535,65535,50712,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,44373,65535,65535,65535,65535,65535,65535,
+ 61309,4258,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,61277,65535,50712,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,40179,65535,
+ 63422,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6371,61277,65535,50744,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63422,
+ 65535,65535,50712,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,12678,63422,65535,65535,65535,65535,65535,
+ 63422,8452,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,12710,65535,65535,50712,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,35953,65535,
+ 63422,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8452,61309,65535,52857,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63422,
+ 65535,65535,50712,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,48599,65503,65535,65535,65535,65535,
+ 65503,12710,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,21162,65535,65535,50744,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,33840,65535,
+ 65503,12710,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8452,63390,65535,54938,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,8484,63422,
+ 65535,65535,50712,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,19017,63422,65535,65535,65535,65503,
+ 65503,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,38034,65535,65535,48631,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,31695,65535,
+ 63422,16936,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8484,63390,65535,57083,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10565,65535,
+ 65535,65535,44405,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2145,52857,65535,65535,65535,65535,
+ 65535,23243,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,32,52825,65535,65535,48631,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,29582,65535,
+ 65535,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,10565,63422,65535,59196,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,10597,65503,
+ 65535,65535,40179,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,27469,65503,65535,65535,65535,
+ 65535,27469,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,57051,65535,65535,48631,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,27469,65535,
+ 65535,25356,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,12678,65503,65535,63390,8484,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19017,65503,
+ 65535,65535,31695,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,59164,65535,65535,65535,
+ 65535,27501,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,10565,61309,65535,65535,48631,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,25388,65535,
+ 65535,31695,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,12710,65535,65535,63422,10597,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,27469,65535,
+ 65535,65535,21162,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35953,65535,65535,65535,
+ 65535,29582,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,12678,65503,65535,65535,57051,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,42260,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,14823,65503,65535,65535,16904,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,32,48599,65535,
+ 65535,63422,14791,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,8484,61309,65535,65535,
+ 65535,27501,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16904,65535,65535,65535,61277,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,50744,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,16936,65503,65535,63422,21130,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,16904,65503,65535,
+ 65535,61309,4226,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,52857,65535,65535,
+ 65535,25388,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,16904,65535,65535,65535,65503,12678,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,59196,4258,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,25356,65535,65535,65535,27469,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,50744,65535,65535,
+ 65535,63390,6339,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,48599,65535,65535,
+ 65535,25356,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,12678,65503,65535,65535,65535,23243,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,63390,10597,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,35921,65535,65535,65535,33840,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,25356,65503,65503,65535,
+ 65535,65503,19017,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,48599,65535,65535,
+ 65535,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,6339,59196,65535,65535,65535,35953,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,65535,
+ 65535,65535,19049,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,52825,65535,65535,65535,44373,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,42292,65535,65535,65535,
+ 65535,65535,38066,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,50744,65535,65535,
+ 65503,16936,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,42260,65535,65535,65535,38066,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,25388,65535,
+ 65535,65535,31727,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,4258,59196,65535,65535,65535,54970,
+ 2113,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,35953,65535,65535,65535,
+ 65535,65535,54970,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2145,54970,65535,65535,
+ 65503,14791,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,12678,63390,65535,63390,19049,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,29614,65535,
+ 65535,65535,44373,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,10597,63422,65535,65535,65535,65503,
+ 12678,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,12678,63390,65535,65535,
+ 65535,65535,63422,8484,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4226,59164,65535,65535,
+ 63422,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,33808,63422,29614,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,33808,65535,
+ 65535,65503,48631,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,14823,63390,65535,65535,65535,65535,
+ 25388,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,42260,65535,65535,
+ 65535,65535,65535,25356,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,61309,65535,65535,
+ 63422,10597,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2145,21162,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,23243,63390,
+ 65535,65535,42292,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,14791,63422,65535,65535,65535,65535,
+ 46518,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,10565,63390,65535,
+ 65535,65535,65535,40179,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,61309,65535,65535,
+ 63422,10597,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,21162,
+ 59164,63390,19017,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2145,52825,65535,65535,65535,65535,
+ 57083,4258,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,31695,65503,
+ 65535,65535,65535,54938,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,6371,63390,65535,65535,
+ 65503,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 6339,12710,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,8452,40147,57083,61309,59164,
+ 31727,2145,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,50744,
+ 65503,65535,65535,59196,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,8452,63422,65535,65535,
+ 65503,12678,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,6371,4226,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,10565,
+ 61277,65535,65535,63422,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,6339,63422,65535,65535,
+ 65503,16936,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 38034,65535,65535,57083,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,61309,65535,65535,
+ 65535,19049,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 19017,65535,65535,48631,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,59196,65535,65535,
+ 65535,21162,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 21130,65535,65535,42260,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,4258,59164,65535,65535,
+ 65535,23243,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 33808,65535,65535,31695,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,52857,65535,65535,
+ 65535,21162,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,32,
+ 48631,65535,65503,19017,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,48631,65535,65535,
+ 65535,21162,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,4258,
+ 61309,65535,63422,8484,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,40147,65535,65535,
+ 65535,23275,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,21162,
+ 65503,65535,57083,2145,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,29582,65503,65535,
+ 65535,25388,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,12710,
+ 2145,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,14823,25356,6371,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,40179,
+ 65535,65535,44373,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,16936,65503,65535,
+ 65503,27501,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,0,0,10597,54938,
+ 6339,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,12710,63390,59164,19049,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,59196,
+ 65535,65503,21130,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,8484,65503,65535,
+ 65535,31727,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,0,10565,54938,63390,
+ 8452,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,50744,65535,61309,14791,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,21130,65535,
+ 65535,59196,4258,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2113,52857,65503,
+ 65535,40179,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,32,0,0,12710,57051,65535,61277,
+ 6339,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,27469,65535,65535,50744,2113,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,46486,65535,
+ 65535,42260,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,38034,65535,
+ 65535,48631,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,14791,59164,65535,65535,52857,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,52825,65503,65503,19017,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,14823,65503,65535,
+ 63390,10565,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,19017,65535,
+ 65535,59164,4258,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,14823,59196,65535,65535,65535,35921,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,14823,65503,65535,44373,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2145,50744,65535,65535,
+ 35921,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,57083,
+ 65535,65503,16904,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2113,8452,59164,65503,65535,65535,61277,6371,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,48599,65535,54970,2113,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,33840,65535,65535,52857,
+ 4226,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,40147,
+ 65535,65535,35921,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,40179,65535,65535,65535,65535,31695,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,31727,65535,59196,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,25388,65503,65535,57051,8484,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,12710,
+ 65503,65535,59164,6339,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 8452,63390,65535,65535,65535,54938,6339,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,27469,65535,59164,4226,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2145,27469,63390,65503,57051,14791,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 40147,65535,65503,31727,32,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 19049,65503,65535,65535,65503,25388,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,40147,65535,52825,32,
+ 0,0,0,0,0,32,32,0,
+ 0,0,0,0,0,32,0,32,
+ 8452,44373,65535,65535,52857,8484,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 6339,57083,65503,59164,6371,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 35921,65535,65535,65535,59164,4226,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,10597,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,4258,57083,65535,33808,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,29614,
+ 59164,65535,65535,52825,6371,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,16936,63390,65535,44373,2113,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 50744,65535,65535,65535,40147,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,42260,19017,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,23275,65535,61277,8452,0,
+ 0,0,0,0,0,0,0,32,
+ 0,0,32,4226,14823,35953,61277,65535,
+ 65535,65535,48599,6339,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,35921,65535,65503,23275,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,2145,
+ 59164,65535,65535,65535,25356,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,19049,61277,21162,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,44405,65503,29614,32,0,
+ 0,0,0,0,0,0,32,4226,
+ 14823,27469,46486,61277,65503,65535,65535,65535,
+ 65503,38066,2145,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,2113,48631,65535,59196,12710,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,6339,
+ 61277,65535,65535,63422,19017,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,52857,63390,
+ 23275,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,21162,33808,2113,0,32,
+ 0,0,0,0,2145,27469,50744,61277,
+ 65535,65535,65535,65535,65535,65535,65535,59164,
+ 25388,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,12678,61309,65535,54938,8452,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,6371,
+ 61277,65535,65535,65535,19049,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,25388,65503,
+ 61309,25356,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,2113,32,0,0,0,
+ 0,32,0,19017,54938,65503,65535,65535,
+ 65535,65535,65503,65535,65535,61309,42292,8484,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,27469,65503,65535,48631,
+ 4258,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4258,
+ 59196,65535,65535,65535,23275,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4258,59196,
+ 65535,63390,14823,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,32,0,
+ 0,32,29614,63422,65535,65503,65535,65535,
+ 65503,65503,61277,54938,35953,10565,0,0,
+ 0,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,0,0,2113,46486,65535,65535,
+ 48631,6339,32,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,4226,
+ 57083,65535,65535,65535,31695,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,38066,
+ 65535,65535,52857,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2145,40147,65535,65535,61277,42292,23275,14791,
+ 12710,10597,6339,32,0,0,0,0,
+ 0,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,6371,57051,65503,
+ 65535,52857,8484,32,0,0,32,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 46486,65535,65535,65535,42292,0,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,14791,
+ 65503,65535,65503,14823,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,4226,
+ 44373,65503,65503,42260,6371,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,14823,63390,
+ 65535,65535,59196,19017,0,0,32,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 23243,65535,65535,65535,48631,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4226,
+ 59196,65535,65535,25356,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,4226,46486,
+ 65535,63422,33840,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,27469,
+ 63422,65535,65535,63390,35921,6371,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 2145,54938,65535,65535,57051,2113,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,2145,
+ 57051,65535,65535,25388,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,2145,42292,65535,
+ 65535,44373,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,0,
+ 31727,65503,65535,65535,65535,57051,21162,2145,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,23243,63422,65535,61277,4258,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,2145,
+ 57083,65535,63422,16904,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,40179,65535,65535,
+ 57083,8452,0,32,0,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,29614,65503,65535,65535,65535,65535,50744,
+ 21162,2113,0,0,0,32,0,0,
+ 0,32,0,32,0,0,0,0,
+ 0,32,40147,65535,63422,6371,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4226,
+ 59164,65503,57051,2145,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,31695,65535,65535,65535,
+ 25356,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,19049,59164,65535,65535,65535,65535,
+ 65503,52825,27469,8452,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,4258,48631,63390,6371,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,6339,
+ 61309,65503,25356,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,23275,63422,65535,65535,46486,
+ 2113,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,38066,61277,65535,65535,
+ 65535,65535,65535,61277,46486,21130,4258,32,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,10597,52825,4226,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4226,
+ 54938,33840,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,16936,61309,65535,65535,61277,8484,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,10597,38034,59196,
+ 65535,65535,65535,65535,65535,65535,61277,46518,
+ 27469,12710,2145,32,0,0,0,0,
+ 0,0,0,0,10565,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4226,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,10565,57051,65535,65535,65503,23243,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,8484,
+ 31727,57083,65503,65535,65535,65535,65535,65535,
+ 65503,65503,57051,44373,19049,4258,0,0,
+ 32,32,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 6339,50744,65535,65535,65535,31727,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,4226,23243,50712,63390,65535,65535,65535,
+ 65535,65535,65535,65535,63422,52857,19049,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,4258,
+ 48599,65535,65535,65503,40147,2113,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,0,0,0,
+ 0,0,0,2113,14791,40179,61277,65535,
+ 65535,65535,65535,65535,65535,65503,65503,38034,
+ 4226,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2145,42260,
+ 65503,65535,65503,46518,2145,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,6371,29614,
+ 52857,65503,65535,65535,65535,65535,65535,65503,
+ 48631,8452,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,4258,46486,65503,
+ 65535,65535,50744,6371,0,0,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 4226,19049,46486,63390,65535,65535,65535,65535,
+ 65535,52857,10565,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,32,8452,46518,65535,65535,
+ 65535,54970,8484,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,10565,33840,52857,65535,65535,
+ 65503,65535,54970,10597,0,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,14823,54938,65535,65535,65535,
+ 57083,10597,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,32,0,32,0,4226,21130,50712,
+ 65535,65535,65535,57051,12710,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,4258,35921,63390,65535,65535,65503,59164,
+ 14823,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,2113,
+ 29614,63422,65535,65535,54970,10597,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,4226,
+ 27469,54970,65535,65535,65535,65535,61309,19017,
+ 0,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,27469,65535,65535,65535,54970,12678,0,
+ 0,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2113,10565,31695,59164,
+ 65535,65535,65535,65535,65535,61309,23275,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,38034,65535,65535,65535,57083,12678,
+ 32,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,2113,4226,
+ 4226,4258,8452,10597,12678,14791,12678,6371,
+ 6339,4258,2145,2113,2113,0,0,2113,
+ 2145,4258,16936,33808,52825,63390,65535,65535,
+ 65535,65535,65535,65535,63422,27469,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,8452,59164,65535,65535,65535,54938,
+ 8484,0,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2145,10597,29582,40147,44405,52825,57083,
+ 61309,63422,65535,65535,65535,65535,65503,63422,
+ 63422,61277,54970,50744,48631,46518,46518,48631,
+ 57083,63390,65535,65535,65535,65503,65535,65535,
+ 65535,65535,65535,65503,29614,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,31727,65503,65535,65535,65535,
+ 54970,10565,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,6339,
+ 31695,54938,65503,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,63422,33840,32,32,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6371,57083,65503,65535,65503,
+ 65503,54970,10565,0,0,32,0,32,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,32,8452,35953,59196,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,63390,25388,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,27469,65535,65535,65535,
+ 65535,65535,57051,10597,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,6339,31727,59196,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65503,
+ 63390,59196,54970,52825,50744,48631,48599,48599,
+ 50712,54970,61309,65503,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65503,
+ 48631,12710,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,2145,48631,65535,65503,
+ 65535,65535,65535,57083,14791,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,2113,21162,54970,65503,65535,65535,65535,
+ 65535,65535,65535,65535,65503,54970,40179,21162,
+ 6371,2113,0,32,0,0,0,32,
+ 0,0,6371,19017,38034,50712,61309,65503,
+ 65535,65535,65535,65535,65503,61277,48599,19049,
+ 32,32,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,10565,59164,65535,
+ 65535,65503,65535,65535,61277,21130,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 16904,48599,65503,65535,65535,65535,65535,65535,
+ 65535,65535,63422,46486,16936,2145,0,32,
+ 0,32,0,0,32,0,0,0,
+ 0,0,0,0,0,32,6371,14823,
+ 25388,31695,31695,25388,16904,6371,32,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,16936,61309,
+ 65535,65535,65503,65535,65535,63390,35953,6371,
+ 0,0,32,0,32,0,0,0,
+ 0,0,0,0,2113,6371,25356,46518,
+ 65503,65535,65535,65535,65535,65535,65535,61309,
+ 50712,33808,10597,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,32,21162,
+ 61309,65535,65535,65535,65535,65535,65535,61277,
+ 35921,14823,4258,32,32,0,0,32,
+ 2145,4258,10597,27469,46486,61309,65503,65503,
+ 65535,65535,65503,65535,65535,54970,25356,4258,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,32,0,0,
+ 21162,63390,65535,65535,65535,65535,63422,65535,
+ 65535,63390,59196,52857,48599,44405,44405,48599,
+ 52857,61277,63390,65503,65535,65535,65535,65535,
+ 65535,65535,65535,65503,50744,8452,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,21130,59164,65503,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,63422,61277,50744,10597,0,32,0,
+ 0,32,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 32,0,10597,50744,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,65535,65535,61277,46486,
+ 31695,19017,4258,2113,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,32,0,2145,29582,57083,63422,65535,
+ 65535,65535,65535,65535,65535,65535,65535,65535,
+ 65535,65535,65535,65535,52857,25356,4258,2113,
+ 0,0,0,32,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,4258,27501,48631,
+ 61309,65503,65503,65535,65535,65535,65503,63422,
+ 63390,59196,44405,23243,2113,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,32,
+ 4258,16904,25388,33840,35953,35921,29614,23243,
+ 10597,4226,32,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0
+};
+
+const basic_image<unsigned short> respect(height,width,pixelData);
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/gui/res/respect.h b/src/tests/drivers/xbee/gui/res/respect.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d54b974112dd36f34169c5fa5af744c0fe80e25
--- /dev/null
+++ b/src/tests/drivers/xbee/gui/res/respect.h
@@ -0,0 +1,32 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+//This file has been automatcally generated by pngconverter utility
+//Please do not edit
+#ifndef RESPECT_H
+#define RESPECT_H
+
+#include "mxgui/image.h"
+
+extern const mxgui::Image respect;
+
+#endif //RESPECT_H
diff --git a/src/tests/drivers/xbee/logdecoder/.gitignore b/src/tests/drivers/xbee/logdecoder/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8b193293c00f3d4483c23b0d5c2e705d0617e209
--- /dev/null
+++ b/src/tests/drivers/xbee/logdecoder/.gitignore
@@ -0,0 +1 @@
+logdecoder
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/logdecoder/LogTypes.h b/src/tests/drivers/xbee/logdecoder/LogTypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..1db90d51c8c8d3b3149c5939d3c0dbf302dbe799
--- /dev/null
+++ b/src/tests/drivers/xbee/logdecoder/LogTypes.h
@@ -0,0 +1,68 @@
+/* Copyright (c) 2015-2018 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#pragma once
+
+#include <fstream>
+#include <iostream>
+
+#include "drivers/Xbee/APIFramesLog.h"
+#include "drivers/Xbee/XbeeStatus.h"
+#include "drivers/xbee/Mark.h"
+#include "drivers/xbee/XbeeTestData.h"
+#include "logger/Deserializer.h"
+#include "logger/LogStats.h"
+
+// Serialized classes
+using std::ofstream;
+
+template <typename T>
+void print(T& t, ostream& os)
+{
+    t.print(os);
+}
+
+template <typename T>
+void registerType(Deserializer& ds)
+{
+    ds.registerType<T>(print<T>, T::header());
+}
+
+void registerTypes(Deserializer& ds)
+{
+    registerType<LogStats>(ds);
+
+    registerType<Xbee::APIFrameLog>(ds);
+    registerType<Xbee::ATCommandFrameLog>(ds);
+    registerType<Xbee::ATCommandResponseFrameLog>(ds);
+    registerType<Xbee::ModemStatusFrameLog>(ds);
+    registerType<Xbee::TXRequestFrameLog>(ds);
+    registerType<Xbee::TXStatusFrameLog>(ds);
+    registerType<Xbee::RXPacketFrameLog>(ds);
+    registerType<Xbee::XbeeStatus>(ds);
+
+    registerType<TxData>(ds);
+    registerType<RxData>(ds);
+    registerType<Mark>(ds);
+    registerType<XbeeConfig>(ds);
+    registerType<EnergyScanData>(ds);
+}
diff --git a/src/tests/drivers/xbee/logdecoder/Makefile b/src/tests/drivers/xbee/logdecoder/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..47258b629723d159885432ac1e485023f5d40002
--- /dev/null
+++ b/src/tests/drivers/xbee/logdecoder/Makefile
@@ -0,0 +1,13 @@
+BASE := ../../../../../
+
+all:
+	g++ -std=c++17 -O2 -o logdecoder logdecoder.cpp \
+					-DCOMPILE_FOR_X86 \
+					$(BASE)libs/tscpp/stream.cpp \
+	 				-I$(BASE)src/shared \
+	 				-I$(BASE)src/tests \
+					-I$(BASE)libs \
+					-I$(BASE)libs/miosix-kernel/miosix
+
+clean:
+	rm logdecoder
diff --git a/src/tests/drivers/xbee/logdecoder/logdecoder.cpp b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28816fed14de875fc3c0a861db3f45c7823dc9aa
--- /dev/null
+++ b/src/tests/drivers/xbee/logdecoder/logdecoder.cpp
@@ -0,0 +1,163 @@
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Federico Terraneo
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*
+ * This is a stub program for the program that will decode the logged data.
+ * Fill in the TODO to make it work.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tscpp/stream.h>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "LogTypes.h"
+
+using namespace std;
+using namespace tscpp;
+
+namespace fs = filesystem;
+
+void showHelp(string cmdName)
+{
+    std::cerr << "Usage: " << cmdName
+              << " {-a [logs_diretory] | <log_file_path> | -h}"
+              << "Options:\n"
+              << "\t-h,--help\t\tShow help message\n"
+              << "\t-a,--all [dir=\".\"] Deserialize all logs in the provided directory\n"
+              << std::endl;
+}
+
+vector<fs::path> listLogFiles(fs::path dir)
+{
+    vector<fs::path> out;
+    for (const auto& entry : fs::directory_iterator(dir))
+    {
+        if (entry.exists() && entry.is_regular_file())
+        {
+            if (entry.path().extension() == ".dat")
+            {
+                out.push_back(entry.path());
+            }
+        }
+    }
+    return out;
+}
+
+bool deserialize(fs::path log_path)
+{
+    cout << "Deserializing " << log_path << ".dat...\n";
+
+    // remove extension
+    log_path.replace_extension("");
+    Deserializer d(log_path);
+    registerTypes(d);
+
+    return d.deserialize();
+}
+
+bool deserializeAll(fs::path dir = ".")
+{
+    vector<fs::path> logs = listLogFiles(dir);
+    for (auto log : logs)
+    {
+        if (!deserialize(log.string()))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+int main(int argc, char* argv[])
+{
+    if (argc < 2)
+    {
+        showHelp(string(argv[0]));
+        return 1;
+    }
+
+    bool success = false;
+    string arg1  = string(argv[1]);
+    if (arg1 == "-h" || arg1 == "--help")
+    {
+        showHelp(string(argv[0]));
+        return 0;
+    }
+
+    if (arg1 == "-a" || arg1 == "--all")
+    {
+        fs::path dir = ".";
+        if (argc == 3)
+        {
+            string arg2 = string(argv[2]);
+            fs::directory_entry entry(arg2);
+            if (entry.exists() && entry.is_directory())
+            {
+                dir = arg2;
+            }
+            else
+            {
+                cout << "Second argument after '-a' or '--all' must be a "
+                        "directory\n";
+                showHelp(string(argv[0]));
+                return 1;
+            }
+        }
+        cout << "Deserializing all logs...\n";
+        success = deserializeAll(dir);
+    }
+    else if (arg1[0] == '-')
+    {
+        cerr << "Unknown option\n";
+        return 1;
+    }
+    else
+    {
+        fs::directory_entry entry(arg1);
+        if (entry.exists() && entry.is_regular_file())
+        {
+            success = deserialize(arg1);
+        }
+        else
+        {
+            showHelp(string(argv[0]));
+            return 1;
+        }
+    }
+
+    if (success)
+    {
+        cout << "Deserialization completed successfully.\n";
+    }
+    else
+    {
+        cout << "Deserialization ended with errors.\n";
+    }
+}
diff --git a/src/tests/drivers/xbee/test-xbee-bidir.cpp b/src/tests/drivers/xbee/test-xbee-bidir.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..55bf6cb9b08ded7103ece1aa186ce87b8f86aae0
--- /dev/null
+++ b/src/tests/drivers/xbee/test-xbee-bidir.cpp
@@ -0,0 +1,266 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#ifndef RUN_SENDER
+#define RUN_SENDER true
+#endif
+#ifndef RUN_RECEIVER
+#define RUN_RECEIVER true
+#endif
+
+#include <miosix.h>
+
+#include <cstdio>
+#include <stdexcept>
+
+#include "ActiveObject.h"
+#include "XbeeTransceiver.h"
+#include "drivers/Xbee/APIFramesLog.h"
+#include "drivers/Xbee/ATCommands.h"
+#include "drivers/Xbee/Xbee.h"
+#include "drivers/interrupt/external_interrupts.h"
+#include "logger/Logger.h"
+
+using namespace miosix;
+
+#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X
+#include "interfaces-impl/hwmapping.h"
+using GpioMiso = miosix::interfaces::spi2::miso;
+using GpioMosi = miosix::interfaces::spi2::mosi;
+using GpioSck  = miosix::interfaces::spi2::sck;
+
+using GpioCS   = xbee::cs;
+using GpioATTN = xbee::attn;
+using GpioRST  = xbee::reset;
+
+using GpioLedLog = Gpio<GPIOG_BASE, 2>;
+
+#define XBEE_SPI SPI2
+#else
+using GpioMiso = Gpio<GPIOB_BASE, 4>;
+using GpioMosi = Gpio<GPIOA_BASE, 7>;
+using GpioSck  = Gpio<GPIOA_BASE, 5>;
+
+using GpioCS   = Gpio<GPIOC_BASE, 1>;
+using GpioATTN = Gpio<GPIOE_BASE, 5>;
+using GpioRST  = Gpio<GPIOE_BASE, 6>;
+
+using GpioLedLog = Gpio<GPIOC_BASE, 13>;
+#define XBEE_SPI SPI1
+#endif
+
+using GpioUserBtn = Gpio<GPIOA_BASE, 0>;
+
+Xbee::Xbee* xbee_driver = nullptr;
+Logger& logger          = Logger::instance();
+
+#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X
+void __attribute__((used)) EXTI10_IRQHandlerImpl()
+#else
+void __attribute__((used)) EXTI5_IRQHandlerImpl()
+#endif
+{
+    if (xbee_driver != nullptr)
+    {
+        xbee_driver->handleATTNInterrupt();
+    }
+}
+
+int getUserBtnValue()
+{
+#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X
+    return 0;
+#else
+    return GpioUserBtn::value();
+#endif
+}
+
+void configure()
+{
+#ifndef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X
+    {
+        FastInterruptDisableLock dLock;
+
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+
+        // Set SPI pins to correct alternate mode
+        GpioSck::mode(Mode::ALTERNATE);
+        GpioMiso::mode(Mode::ALTERNATE);
+        GpioMosi::mode(Mode::ALTERNATE);
+
+        GpioSck::alternateFunction(5);
+        GpioMiso::alternateFunction(5);
+        GpioMosi::alternateFunction(5);
+
+        GpioATTN::mode(Mode::INPUT);
+
+        GpioLedLog::mode(Mode::OUTPUT);
+
+        GpioUserBtn::mode(Mode::INPUT_PULL_DOWN);
+
+        GpioCS::mode(Mode::OUTPUT);
+    }
+
+    GpioCS::high();
+    GpioLedLog::low();
+#endif
+
+#ifdef _BOARD_STM32F429ZI_SKYWARD_DEATHST_X
+    enableExternalInterrupt(GPIOF_BASE, 10, InterruptTrigger::FALLING_EDGE);
+#else
+    enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE);
+#endif
+}
+
+void setupXbee(XbeeConfig config)
+{
+    if (xbee_driver)
+    {
+        if (config.data_rate_80k)
+        {
+            if (!Xbee::setDataRate(*xbee_driver, true))
+            {
+                TRACE("[main] Error setting xbee_driver data rate!\n");
+            }
+        }
+
+        if (!config.freq_hop)
+        {
+            if (!Xbee::disableFrequencyHopping(*xbee_driver))
+            {
+                TRACE("[main] Error disabling frequency hop!\n");
+            }
+        }
+    }
+}
+
+int main()
+{
+    XbeeConfig config;
+    config.data_rate_80k = true;
+    config.freq_hop      = true;
+
+    config.packet_size   = 256;
+    config.send_interval = 333;
+    config.tx_enabled    = RUN_SENDER;
+    config.timestamp     = getTick();
+
+    configure();
+
+    int filenum;
+    try
+    {
+        filenum = logger.start();
+
+        printf("\nLog file opened! (%s)\n\n",
+               logger.getFileName(filenum).c_str());
+    }
+    catch (const std::runtime_error& err)
+    {
+        GpioLedLog::high();
+        printf("\n!!!!!!Error opening log file!!!!!!!\n\n");
+    }
+
+    logger.log(config);
+
+    SPIBus spi_bus(XBEE_SPI);
+    SPIBusConfig cfg{};
+    cfg.clock_div = SPIClockDivider::DIV16;
+
+    GpioPin cs   = GpioCS::getPin();
+    GpioPin attn = GpioATTN::getPin();
+    GpioPin rst  = GpioRST::getPin();
+
+    xbee_driver = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst);
+
+    setupXbee(config);
+
+    // RandSendInterval intv(333, 1500);
+    ConstSendInterval intv(config.send_interval);
+    XbeeTransceiver* trans =
+        new XbeeTransceiver(*xbee_driver, logger, intv, 256, 333);
+    if (!config.tx_enabled)
+    {
+        trans->disableSender();
+    }
+    if (!RUN_RECEIVER)
+    {
+        trans->disableReceiver();
+    }
+    trans->start();
+
+    while (getUserBtnValue() == 0)
+    {
+        long long loop_start = getTick();
+
+        DataRateResult res_rcv = trans->getReceiver().getDataRate();
+        DataRateResult res_snd = trans->getSender().getDataRate();
+
+        TxData txd = trans->getSender().getTxData();
+        RxData rxd = trans->getReceiver().getRxData();
+
+        logger.log(xbee_driver->getStatus());
+        logger.log(logger.getLogStats());
+
+        long long tick = getTick();
+        unsigned int h = tick / (1000 * 3600);
+        unsigned int m = (tick - h * 1000 * 3600) / (1000 * 60);
+        float s        = (tick - h * 1000 * 3600 - m * 1000 * 60) / 1000.0f;
+
+        printf("%02u:%02u:%06.3f\n", h, m, s);
+        if (RUN_SENDER)
+        {
+            printf("SND: int: %d, cnt: %d, tts: %u ms, pps: %.1f, fail: % d\n ",
+                   txd.time_since_last_send,
+                   txd.tx_success_counter + txd.tx_fail_counter,
+                   txd.time_to_send, res_snd.packets_per_second,
+                   txd.tx_fail_counter);
+        }
+        if (RUN_RECEIVER)
+        {
+            printf(
+                "RCV: cnt: %d, last_rx: %lld ms,  RSSI: %d, dr: %.0f, pps: "
+                "%.1f,"
+                " pl: %.0f%%, lcnt: %u, fail: %u\n",
+                rxd.rcv_count, rxd.last_packet_timestamp, rxd.RSSI,
+                res_rcv.data_rate, res_rcv.packets_per_second,
+                res_rcv.packet_loss * 100, rxd.packets_lost, rxd.rcv_errors);
+        }
+        printf("\n");
+
+        Thread::sleepUntil(loop_start + 1000);
+    }
+
+    trans->stop();
+    delete trans;
+    delete xbee_driver;
+    logger.stop();
+    printf("Log closed.\n");
+
+    for (;;)
+    {
+        GpioLedLog::high();
+        Thread::sleep(50);
+        GpioLedLog::low();
+        Thread::sleep(5000);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/test-xbee-gui.cpp b/src/tests/drivers/xbee/test-xbee-gui.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d0ca706aee67690264bd80c63e3635e9c8780b76
--- /dev/null
+++ b/src/tests/drivers/xbee/test-xbee-gui.cpp
@@ -0,0 +1,367 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <miosix.h>
+#include <mxgui/display.h>
+
+#include <array>
+#include <cstdio>
+#include <functional>
+#include <stdexcept>
+
+#include "ActiveObject.h"
+#include "Mark.h"
+#include "XbeeTransceiver.h"
+#include "drivers/Xbee/APIFramesLog.h"
+#include "drivers/Xbee/ATCommands.h"
+#include "drivers/Xbee/Xbee.h"
+#include "drivers/interrupt/external_interrupts.h"
+#include "gui/XbeeGui.h"
+#include "logger/Logger.h"
+#include "utils/ButtonHandler.h"
+
+using namespace miosix;
+using namespace mxgui;
+using namespace std::placeholders;
+
+using std::array;
+using std::bind;
+using std::to_string;
+
+/// Pin definitions
+using GpioMiso = Gpio<GPIOB_BASE, 4>;
+using GpioMosi = Gpio<GPIOA_BASE, 7>;
+using GpioSck  = Gpio<GPIOA_BASE, 5>;
+
+using GpioCS   = Gpio<GPIOC_BASE, 1>;
+using GpioATTN = Gpio<GPIOE_BASE, 5>;
+using GpioRST  = Gpio<GPIOE_BASE, 6>;
+
+using GpioUserBtn = Gpio<GPIOA_BASE, 0>;
+using GpioLedLog  = Gpio<GPIOC_BASE, 13>;
+
+// Forward dec
+void onStartButtonClick(View* btn, Interaction action);
+void onEnergyButtonClick(View* btn, Interaction action);
+void onMarkButtonClick(View* btn, Interaction action);
+void onStopButtonClick(View* btn, Interaction action);
+void startTransceiver(XbeeConfig config);
+void setupXbee(XbeeConfig config);
+void configure();
+
+// Global variables
+Logger& logger   = Logger::instance();
+Xbee::Xbee* xbee = nullptr;
+ConstSendInterval snd_int{0};
+XbeeTransceiver* trans = nullptr;
+XbeeGUI* gui;
+ButtonHandler<GpioUserBtn>* btn_handler;
+
+unsigned int mark_counter = 1;
+
+/**
+ * @brief Activeobject to perform an energy detect scan as frequently as
+ * possible
+ */
+class EnergyScanner : public ActiveObject
+{
+protected:
+    void run() override
+    {
+        while (!shouldStop())
+        {
+            Xbee::ATCommandResponseFrame response;
+
+            uint8_t duration = 0xFF;
+
+            if (xbee->sendATCommand("ED", &response, &duration, 1, 1000) &&
+                response.getCommandDataLength() == 30)
+            {
+                array<int, 30> scan;
+
+                for (uint16_t i = 0; i < response.getCommandDataLength(); i++)
+                {
+                    scan[i] = -(int)(*(response.getCommandDataPointer() + i));
+                }
+
+                gui->screen_energy.updateScan(scan);
+
+                EnergyScanData data{getTick(), scan};
+                logger.log(data);
+            }
+        }
+    }
+} energy_scanner;
+
+int main()
+{
+    // Hardware
+    configure();
+
+    // SD
+    try
+    {
+        logger.start();
+        printf("\nLog file opened! (%s)\n\n",
+               logger.getFileName(logger.getLogNumber()).c_str());
+    }
+    catch (const std::runtime_error& err)
+    {
+        GpioLedLog::high();
+        printf("\n!!!!!!Error opening log file!!!!!!!\n\n");
+    }
+
+    // XBee
+    SPIBus spi_bus(SPI1);
+    SPIBusConfig cfg{};
+    cfg.clock_div = SPIClockDivider::DIV16;
+
+    GpioPin cs   = GpioCS::getPin();
+    GpioPin attn = GpioATTN::getPin();
+    GpioPin rst  = GpioRST::getPin();
+
+    xbee = new Xbee::Xbee(spi_bus, cfg, cs, attn, rst);
+
+    // GUI
+    gui = new XbeeGUI();
+
+    btn_handler = new ButtonHandler<GpioUserBtn>(
+        0, bind(&ScreenManager::onButtonPress, &gui->screen_manager, _2));
+
+    btn_handler->start();
+
+    gui->screen_config.btn_start.addOnInteractionListener(onStartButtonClick);
+    gui->screen_config.btn_energy.addOnInteractionListener(onEnergyButtonClick);
+
+    gui->screen_status.btn_stop.addOnInteractionListener(onStopButtonClick);
+    gui->screen_status.btn_mark.addOnInteractionListener(onMarkButtonClick);
+
+    gui->screen_energy.btn_stop.addOnInteractionListener(onStopButtonClick);
+    gui->screen_energy.btn_mark.addOnInteractionListener(onMarkButtonClick);
+    gui->screen_energy.btn_reset.addOnInteractionListener(
+        [&](View* d, Interaction action) {
+            UNUSED(d);
+            if (action == Interaction::CLICK)
+                gui->screen_energy.resetStats();
+        });
+
+    gui->screen_end.tv_f.addOnInteractionListener(
+        [&](View* d, Interaction action) {
+            UNUSED(d);
+            if (action == Interaction::CLICK)
+                gui->screen_manager.showScreen(XbeeGUI::SCREEN_RESPECT);
+        });
+
+    gui->screen_end.tv_reset.addOnInteractionListener(
+        [&](View* d, Interaction action) {
+            UNUSED(d);
+            if (action == Interaction::CLICK)
+                miosix::reboot();
+        });
+
+    // Main loop: updates the information in the GUI
+    for (;;)
+    {
+        long long start = getTick();
+        // Update display values
+        switch (gui->screen_manager.getScreen())
+        {
+            case XbeeGUI::SCREEN_CONFIG:
+                gui->screen_config.updateLogStatus(logger);
+                break;
+            case XbeeGUI::SCREEN_STATUS:
+                if (trans && xbee)
+                {
+                    gui->screen_status.updateXbeeStatus(
+                        trans->getReceiver().getDataRate(),
+                        trans->getSender().getDataRate(),
+                        trans->getSender().getTxData(),
+                        trans->getReceiver().getRxData(), xbee->getStatus());
+
+                    logger.log(xbee->getStatus());
+                }
+
+                gui->screen_status.updateLogStatus(logger);
+                break;
+            case XbeeGUI::SCREEN_ENERGYSCAN:
+                gui->screen_energy.updateLogStatus(logger);
+                break;
+            default:
+                break;
+        }
+
+        logger.log(logger.getLogStats());
+        Thread::sleepUntil(start + 500);
+    }
+}
+
+void onStartButtonClick(View* btn, Interaction action)
+{
+    UNUSED(btn);
+    if (action == Interaction::CLICK)
+    {
+
+        XbeeConfig cfg = gui->screen_config.config;
+        cfg.timestamp  = getTick();
+        logger.log(cfg);
+
+        gui->screen_config.btn_start.setText("Starting...");
+
+        gui->screen_status.updateConfig(cfg);
+
+        setupXbee(cfg);
+        startTransceiver(cfg);
+
+        // Show status screen
+        gui->screen_manager.showScreen(XbeeGUI::SCREEN_STATUS);
+    }
+}
+
+void onStopButtonClick(View* btn, Interaction action)
+{
+    if (action == Interaction::LONG_CLICK)
+    {
+        TextView* tv_btn = dynamic_cast<TextView*>(btn);
+
+        if (tv_btn)
+        {
+            tv_btn->setText("Stopping...");
+        }
+
+        if (trans)
+        {
+            trans->stop();
+        }
+
+        if (energy_scanner.isRunning())
+        {
+            energy_scanner.stop();
+        }
+
+        logger.stop();
+
+        gui->screen_manager.showScreen(XbeeGUI::SCREEN_END);
+    }
+}
+
+void onMarkButtonClick(View* btn, Interaction action)
+{
+    UNUSED(btn);
+    if (action == Interaction::CLICK)
+    {
+        Mark m{getTick(), mark_counter++};
+        logger.log(m);
+
+        TextView* tv_btn = dynamic_cast<TextView*>(btn);
+        if (tv_btn)
+        {
+            tv_btn->setText("Mark Log (" + to_string(mark_counter) + ")");
+        }
+    }
+}
+
+void onEnergyButtonClick(View* btn, Interaction action)
+{
+    UNUSED(btn);
+    if (action == Interaction::CLICK)
+    {
+        energy_scanner.start();
+        gui->screen_manager.showScreen(XbeeGUI::SCREEN_ENERGYSCAN);
+    }
+}
+
+void startTransceiver(XbeeConfig config)
+{
+    snd_int.interval = config.send_interval;
+    trans = new XbeeTransceiver(*xbee, logger, snd_int, config.packet_size,
+                                config.send_interval);
+
+    if (!config.tx_enabled)
+    {
+        trans->disableSender();
+    }
+
+    trans->start();
+}
+
+void setupXbee(XbeeConfig config)
+{
+    if (config.data_rate_80k)
+    {
+        if (!Xbee::setDataRate(*xbee, true))
+        {
+            gui->screen_status.tv_cfg_data_rate.setBackgroundColor(mxgui::red);
+            TRACE("[main] Error setting xbee data rate!\n");
+        }
+    }
+
+    if (!config.freq_hop)
+    {
+        if (!Xbee::disableFrequencyHopping(*xbee))
+        {
+            gui->screen_status.tv_cfg_freq_hop.setBackgroundColor(mxgui::red);
+            TRACE("[main] Error disabling frequency hop!\n");
+        }
+    }
+}
+
+void configure()
+{
+    {
+        FastInterruptDisableLock dLock;
+
+        // Enable SPI5 and TIM5 peripheral clocks
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+
+        // Set SPI pins to correct alternate mode
+        GpioSck::mode(Mode::ALTERNATE);
+        GpioMiso::mode(Mode::ALTERNATE);
+        GpioMosi::mode(Mode::ALTERNATE);
+
+        GpioSck::alternateFunction(5);
+        GpioMiso::alternateFunction(5);
+        GpioMosi::alternateFunction(5);
+
+        GpioATTN::mode(Mode::INPUT_PULL_UP);
+
+        GpioLedLog::mode(Mode::OUTPUT);
+        GpioUserBtn::mode(Mode::INPUT_PULL_DOWN);
+
+        // Set chip select pin to OUTPUT
+        GpioCS::mode(Mode::OUTPUT);
+    }
+
+    // Chip select starts high (not asserted)
+    GpioCS::high();
+    GpioLedLog::low();
+
+    // Enable rising-edge interrupt detection on PA2
+    enableExternalInterrupt(GPIOE_BASE, 5, InterruptTrigger::FALLING_EDGE);
+}
+
+void __attribute__((used)) EXTI5_IRQHandlerImpl()
+{
+    if (xbee)
+    {
+        xbee->handleATTNInterrupt();
+    }
+}
\ No newline at end of file
diff --git a/src/shared/drivers/adc/InternalADCData.h b/src/tests/drivers/xbee/test-xbee-rcv.cpp
similarity index 75%
rename from src/shared/drivers/adc/InternalADCData.h
rename to src/tests/drivers/xbee/test-xbee-rcv.cpp
index 5c8bc5f2dd5aeec6052e4ad4b7ad28a213049389..eae22c0a43e919f6d051a1cde1ce4111e292f89e 100644
--- a/src/shared/drivers/adc/InternalADCData.h
+++ b/src/tests/drivers/xbee/test-xbee-rcv.cpp
@@ -1,36 +1,25 @@
-/* 
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
-#pragma once
+#define RUN_SENDER false
 
-#include <cstdint>
-
-struct InternalADCSample
-{
-    uint32_t timestamp;
-
-    uint8_t adc;
-    uint8_t channel_id; 
-
-    uint16_t value;
-};
\ No newline at end of file
+#include "test-xbee-bidir.cpp"
\ No newline at end of file
diff --git a/src/tests/drivers/xbee/test-xbee-snd.cpp b/src/tests/drivers/xbee/test-xbee-snd.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9660ad67a2e75b7f38c9836e104f566d79979367
--- /dev/null
+++ b/src/tests/drivers/xbee/test-xbee-snd.cpp
@@ -0,0 +1,25 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#define RUN_RECEIVER false
+
+#include "test-xbee-bidir.cpp"
diff --git a/src/tests/kalman/test-kalman-benchmark.cpp b/src/tests/kalman/test-kalman-benchmark.cpp
index bd9ba8ad8b949e9187c3700e48ceff2e0becb5dc..76942c8a35204d9eb76921406fc93408a0c2b257 100644
--- a/src/tests/kalman/test-kalman-benchmark.cpp
+++ b/src/tests/kalman/test-kalman-benchmark.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Mozzarelli
+ * Author: Luca Mozzarelli
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,14 +23,15 @@
 // This prgram runs through a simulated flight and reports the apogee detection,
 // while measuring the time elapsed
 
-
 // RESULT: Update operation 0.0319 on average
 #include <Common.h>
 #include <drivers/HardwareTimer.h>
 #include <kalman/Kalman.h>
+
 #include <iostream>
-#include "util/util.h"
+
 #include "test-kalman-data.h"
+#include "util/util.h"
 
 using namespace miosix;
 
@@ -47,22 +48,25 @@ int main(int argc, char const* argv[])
     // Setting pin mode for signaling ADA status
     {
         FastInterruptDisableLock dLock;
-        greenLed::mode(Mode::OUTPUT);
-        redLed::mode(Mode::OUTPUT);
+        //greenLed::mode(Mode::OUTPUT);
+        //redLed::mode(Mode::OUTPUT);
+
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
     }
 
     // Timer for benchmarking purposes
-    HardwareTimer<uint32_t, 2>& timer = HardwareTimer<uint32_t, 2>::instance();
+    HardwareTimer<uint32_t> timer{TIM5, TimerUtils::getPrescalerInputFrequency(
+                                            TimerUtils::InputClock::APB1)};
 
     // Instanciate matrices
-    MatrixBase<float,3,3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1};
-    MatrixBase<float,1,1> V2{10};
-    MatrixBase<float,3,3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01};
-    MatrixBase<float,1,3> C{1, 0, 0};
-    MatrixBase<float,3,3> A{1, 0, 0, 0, 1, 0, 0, 0, 1};
+    MatrixBase<float, 3, 3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1};
+    MatrixBase<float, 1, 1> V2{10};
+    MatrixBase<float, 3, 3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01};
+    MatrixBase<float, 1, 3> C{1, 0, 0};
+    MatrixBase<float, 3, 3> A{1, 0, 0, 0, 1, 0, 0, 0, 1};
 
     // Instanciate filter object
-    Kalman<3,1> filter = Kalman<3,1>(A, C, V1, V2, P);
+    Kalman<3, 1> filter = Kalman<3, 1>(A, C, V1, V2, P);
 
     float last_time = 0.0;  // Variable to save the time of the last sample
     float time;             // Current time as read from csv file
@@ -74,13 +78,16 @@ int main(int argc, char const* argv[])
     uint32_t tick1;
     uint32_t tick2;
 
+    printf("%d %d \n", TIME.size(), INPUT.size());
+
     for (unsigned i = 0; i < TIME.size(); i++)
     {
         if (i == 0)
         {
-            filter.X(0,0) = INPUT[0];
+            filter.X(0, 0) = INPUT[0];
             continue;
         }
+
         time = TIME[i];
         T    = time - last_time;
 
@@ -88,25 +95,30 @@ int main(int argc, char const* argv[])
         filter.A(0, 2) = 0.5 * T * T;
         filter.A(1, 2) = T;
 
-        MatrixBase<float,1,1> y{};
-        y(0,0) = INPUT[i];
+        MatrixBase<float, 1, 1> y{};
+        y(0, 0) = INPUT[i];
 
         tick1 = timer.tick();
+
         filter.update(y);
+        
         tick2 = timer.tick();
-        printf("%f \n", timer.toMilliSeconds(tick2 - tick1));
+
+        printf("%d : %f \n", i, timer.toMilliSeconds(tick2 - tick1));
+
         // printf("%f, %f, %f;\n", filter.X(0,0), filter.X(1,0), filter.X(2,0));
         // std::cout << MemoryProfiling::getCurrentFreeStack() << "\n";
         last_time = time;
-        if (filter.X(1,0) < 0)
+        if (filter.X(1, 0) < 0)
         {
-            greenLed::high();
-            redLed::low();
+            printf("APOGEE DETECTED at iteration %d ! \n", i);
+            //greenLed::high();
+            //redLed::low();
         }
         else
         {
-            greenLed::low();
-            redLed::high();
+            //greenLed::low();
+            //redLed::high();
         }
     }
     timer.stop();
diff --git a/src/tests/kalman/test-kalman-data.h b/src/tests/kalman/test-kalman-data.h
index 18720ca343360ef70868840e4397cfc039ac0c35..c11958d5e314707cd090fe8d73069abbd7675045 100644
--- a/src/tests/kalman/test-kalman-data.h
+++ b/src/tests/kalman/test-kalman-data.h
@@ -1,3 +1,25 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Mozzarelli
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
 #pragma once
 #include <vector>
 
@@ -86,7 +108,7 @@ static const std::vector<float> TIME = {
     44,    44.05, 44.1,  44.15, 44.2,  44.25, 44.3,  44.35, 44.4,  44.45, 44.5,
     44.55, 44.6,  44.65, 44.7,  44.75, 44.8,  44.85, 44.9,  44.95, 45,    45.05,
     45.1,  45.15, 45.2,  45.25, 45.3,  45.35, 45.4,  45.45, 45.5,  45.55, 45.6,
-    45.65, 45.7,  45.75, 45.8,  45.85, 45.9,  45.95, 46,    46.05, 46.1,  46.15,
+ 45.65, 45.7,  45.75, 45.8,  45.85, 45.9,  45.95, 46,    46.05, 46.1,  46.15,
     46.2,  46.25, 46.3,  46.35, 46.4,  46.45, 46.5,  46.55, 46.6,  46.65, 46.7,
     46.75, 46.8,  46.85, 46.9,  46.95, 47,    47.05, 47.1,  47.15, 47.2,  47.25,
     47.3,  47.35, 47.4,  47.45, 47.5,  47.55, 47.6,  47.65, 47.7,  47.75, 47.8,
@@ -178,7 +200,7 @@ static const std::vector<float> TIME = {
     94.6,  94.65, 94.7,  94.75, 94.8,  94.85, 94.9,  94.95, 95,    95.05, 95.1,
     95.15, 95.2,  95.25, 95.3,  95.35, 95.4,  95.45, 95.5,  95.55, 95.6,  95.65,
     95.7,  95.75, 95.8,  95.85, 95.9,  95.95, 96,    96.05, 96.1,  96.15, 96.2,
-    96.25, 96.3};
+    96.25, 96.3  };
 
 // ------------------------ INPUT ------------------------
 static const std::vector<float> INPUT = {
@@ -294,7 +316,7 @@ static const std::vector<float> INPUT = {
     10314.9,  10298.9,  10323,   10331,    10323,   10331,   10323,   10331,
     10339,    10314.9,  10347.1, 10323,    10323,   10314.9, 10355.1, 10323,
     10314.9,  10331,    10331,   10339,    10347.1, 10347.1, 10355.1, 10323,
-    10347.1,  10314.9,  10347.1, 10363.2,  10347.1, 10339,   10355.1, 10363.2,
+ 10347.1,  10314.9,  10347.1, 10363.2,  10347.1, 10339,   10355.1, 10363.2,
     10355.1,  10371.2,  10339,   10347.1,  10331,   10355.1, 10331,   10331,
     10363.2,  10355.1,  10379.3, 10379.3,  10387.4, 10355.1, 10339,   10371.2,
     10371.2,  10347.1,  10379.3, 10371.2,  10371.2, 10363.2, 10371.2, 10347.1,
@@ -422,7 +444,7 @@ static const std::vector<float> INPUT = {
     494.489,  478.164,  461.865, 453.725,  429.344, 423.934, 394.229, 375.369,
     367.298,  348.488,  332.392, 310.97,   289.592, 284.255, 257.608, 246.969,
     231.03,   212.466,  193.936, 186.004,  164.882, 146.436, 133.281, 114.89,
-    104.396,  80.8236,  59.9147, 49.476,   31.2335, 10.424,  -5.15584};
+    104.396,  80.8236,  59.9147, 49.476,   31.2335, 10.424,  -5.15584  };
 
 // ------------------------ STATE ------------------------
 static const std::vector<float> STATE_1 = {
diff --git a/src/tests/kalman/test-kalman-eigen-benchmark.cpp b/src/tests/kalman/test-kalman-eigen-benchmark.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d7bfa315c285628980404ec9c4b21e0e8f0efb95
--- /dev/null
+++ b/src/tests/kalman/test-kalman-eigen-benchmark.cpp
@@ -0,0 +1,142 @@
+/* Copyright (c) 2020 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+// This prgram runs through a simulated flight and reports the apogee detection,
+// while measuring the time elapsed
+
+#define EIGEN_RUNTIME_NO_MALLOC
+
+#include <Common.h>
+#include <drivers/HardwareTimer.h>
+#include <kalman/KalmanEigen.h>
+#include <src/tests/kalman/test-kalman-data.h>
+
+#include <iostream>
+
+#include "math/SkyQuaternion.h"
+#include "util/util.h"
+
+using namespace Eigen;
+using namespace miosix;
+
+int main()
+{
+    // Setting pin mode for signaling ADA status
+    {
+        FastInterruptDisableLock dLock;
+
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+    }
+
+    printf("RUNNING...\n");
+
+    // Timer for benchmarking purposes
+    HardwareTimer<uint32_t> timer{TIM5, TimerUtils::getPrescalerInputFrequency(
+                                            TimerUtils::InputClock::APB1)};
+
+    const int n = 3;
+    const int p = 1;
+
+    Matrix<float, n, n> F =
+        (Matrix<float, n, n>(n, n) << 1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
+            .finished();
+    // Output matrix
+    Matrix<float, p, n> H{1, 0, 0};
+
+    // Initial error covariance matrix
+    Matrix<float, n, n> P = (Matrix<float, n, n>(n, n) << 0.1, 0.0, 0.0, 0.0,
+                             0.1, 0.0, 0.0, 0.0, 0.1)
+                                .finished();
+    // Model variance matrix
+    Matrix<float, n, n> Q = (Matrix<float, n, n>(n, n) << 0.01, 0.0, 0.0, 0.0,
+                             0.01, 0.0, 0.0, 0.0, 0.01)
+                                .finished();
+    // Measurement variance
+    Matrix<float, p, p> R{10};
+
+    Matrix<float, n, 1> x0(INPUT[0], 0.0, 0.0);
+
+    Matrix<float, p, 1> y(p);  // vector with p elements (only one in this case)
+
+    KalmanEigen<float, n, p>::KalmanConfig config;
+    config.F = F;
+    config.H = H;
+    config.Q = Q;
+    config.R = R;
+    config.P = P;
+    config.x = x0;
+
+    KalmanEigen<float, n, p> filter(config);
+
+    float last_time = 0.0;  // Variable to save the time of the last sample
+    float time;             // Current time as read from csv file
+    float T;                // Time elapsed between last sample and current one
+
+    timer.start();
+    uint32_t tick1;
+    uint32_t tick2;
+
+    printf("%d %d \n", TIME.size(), INPUT.size());
+
+    for (unsigned i = 1; i < TIME.size(); i++)
+    {
+        time = TIME[i];
+        T    = time - last_time;
+
+        F(0, 1) = T;
+        F(0, 2) = 0.5 * T * T;
+        F(1, 2) = T;
+
+        y(0) = INPUT[i];
+
+        tick1 = timer.tick();
+
+        filter.predict(F);
+
+        if (!filter.correct(y))
+        {
+            printf("Correction failed at iteration : %d \n", i);
+        }
+
+        tick2 = timer.tick();
+
+        printf("%d : %f \n", i, timer.toMilliSeconds(tick2 - tick1));
+
+        // printf("%f, %f, %f;\n", filter.getState()(0), filter.getState()(1),
+        //       filter.getState()(2));
+
+        // printf("%u \n", MemoryProfiling::getCurrentFreeStack());
+
+        last_time = time;
+
+        if (filter.getState()(1) < 0)
+        {
+            printf("APOGEE DETECTED at iteration %d ! \n", i);
+        }
+    }
+
+    timer.stop();
+
+    // printf("Total time %d \n", timer.interval());
+
+    return 0;
+}
diff --git a/src/tests/logger/test-logger.cpp b/src/tests/logger/test-logger.cpp
index 36c3f55c9c1e1f6537567694caffbbe5d728547d..44d4e6684a6b45a1727d00e6d1702b475d2df97d 100644
--- a/src/tests/logger/test-logger.cpp
+++ b/src/tests/logger/test-logger.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Terraneo Federico
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/logger/test-logger.h b/src/tests/logger/test-logger.h
index 5e6e33fff11d243fef8b048835626d47d1280620..dea913420cd21184015a718d585e14a03376048d 100644
--- a/src/tests/logger/test-logger.h
+++ b/src/tests/logger/test-logger.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Terraneo Federico
+ * Author: Federico Terraneo
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/misc/xbee-bitrate.cpp b/src/tests/misc/xbee-bitrate.cpp
index 229fa358f1f54b48fc16f47d6aeee8b5aaf8b239..3bdeb4946ac8479b41c4e468cebc74b52fe0831a 100644
--- a/src/tests/misc/xbee-bitrate.cpp
+++ b/src/tests/misc/xbee-bitrate.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -31,7 +30,7 @@
 #include "drivers/Xbee/Xbee.h"
 #include "math/Stats.h"
 
-#include <drivers/BusTemplate.h>
+#include <drivers/spi/SPIDriver.h>
 
 using std::cin;
 using std::cout;
@@ -43,20 +42,24 @@ static constexpr int PKT_NUM = 100;
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-// typedef BusSPI<1, spi2::mosi, spi2::miso, spi2::sck> busSPI2;  // Creo la
-// SPI2
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
+
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, sensors::lsm6ds3h::cs, xbee::attn, xbee::reset>
-    Xbee_t;
 
-Xbee_t xbee_transceiver;
+Xbee::Xbee* xbee_transceiver;
+
 void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
 
 void enableXbeeInterrupt()
@@ -102,7 +105,7 @@ bool sendPacket(uint8_t size)
     }
     ++snd_cntr;
 
-    if (!xbee_transceiver.send(snd_buf, size))
+    if (!xbee_transceiver->send(snd_buf, size))
     {
         return false;
     }
@@ -119,8 +122,9 @@ void resetXBee()
 int main()
 {
     enableXbeeInterrupt();
-    busSPI2::init();
-    xbee_transceiver.start();
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
+
+    xbee_transceiver->start();
     resetXBee();
 
     printf("XBee bitrate measurement\n");
diff --git a/src/tests/misc/xbee-send-rcv.cpp b/src/tests/misc/xbee-send-rcv.cpp
index a5fced8963c8e89aa73735b3e7a93b8a918813af..86a365b24d01117b5c6335b4c6d3ed89c909476b 100644
--- a/src/tests/misc/xbee-send-rcv.cpp
+++ b/src/tests/misc/xbee-send-rcv.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,15 +13,17 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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.
  */
 
+#include <drivers/spi/SPIDriver.h>
 #include <interfaces-impl/hwmapping.h>
 #include <miosix.h>
+
 #include <cstdio>
 #include <iostream>
 #include <string>
@@ -31,33 +32,36 @@
 #include "drivers/Xbee/Xbee.h"
 #include "math/Stats.h"
 
-#include <drivers/BusTemplate.h>
-
 using std::cin;
 using std::cout;
 using std::string;
 
-using HwTimer                      = HardwareTimer<uint32_t, 2>;
 static const unsigned int PKT_SIZE = 128;
 
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2;  // Creo la
-// SPI2
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset>
-    Xbee_t;
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
 
-Xbee_t xbee_transceiver;
-void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
+Xbee::Xbee* xbee_transceiver;
+
+void __attribute__((used)) EXTI10_IRQHandlerImpl()
+{
+    Xbee::handleATTNInterrupt();
+}
 
 void enableXbeeInterrupt()
 {
@@ -103,7 +107,7 @@ void send()
             c = 48;
         }
 
-        if (!xbee_transceiver.send(buf, PKT_SIZE))
+        if (!xbee_transceiver->send(buf, PKT_SIZE))
         {
             printf("[%d] Send error %d\n", (int)getTick(), ++fail);
         }
@@ -124,7 +128,7 @@ void receive(void*)
     uint8_t buf[512];
     for (;;)
     {
-        ssize_t len = xbee_transceiver.receive(buf, 512);
+        ssize_t len = xbee_transceiver->receive(buf, 512);
         if (len <= 0)
         {
             printf("Receive failed.\n");
@@ -168,12 +172,9 @@ int main()
 
     // reset();
 
-    HwTimer& t = HwTimer::instance();
-    t.setPrescaler(1024);
-
-    busSPI2::init();
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
 
-    xbee_transceiver.start();
+    xbee_transceiver->start();
 
     // Send & receive
     Thread::create(receive, 2048);
diff --git a/src/tests/misc/xbee-time-to-send.cpp b/src/tests/misc/xbee-time-to-send.cpp
index 4e2d8dc7a45cc7eedb5f3395c67f6cfa2a169a21..87f44851af19af11178ba70899e0cb8da2f20c53 100644
--- a/src/tests/misc/xbee-time-to-send.cpp
+++ b/src/tests/misc/xbee-time-to-send.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -44,19 +43,24 @@ static constexpr int PKTS_PER_SECOND  = 4;
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2; 
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
+
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
+
+Xbee::Xbee* xbee_transceiver;
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset>
-    Xbee_t;
 
-Xbee_t xbee_transceiver;
 void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
 
 void enableXbeeInterrupt()
@@ -101,7 +105,7 @@ bool sendPacket(uint8_t size)
     }
     ++snd_cntr;
 
-    if (!xbee_transceiver.send(snd_buf, size))
+    if (!xbee_transceiver->send(snd_buf, size))
     {
         return false;
     }
@@ -112,8 +116,10 @@ bool sendPacket(uint8_t size)
 int main()
 {
     enableXbeeInterrupt();
-    busSPI2::init();
-    xbee_transceiver.start();
+
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
+
+    xbee_transceiver->start();
 
     printf("XBee time-to-send-measurement\n");
     printf(
diff --git a/src/tests/test-hsm.cpp b/src/tests/test-hsm.cpp
index 8495f86f8f0bf4404f82d76a3c1aa37545e1d2fb..37ba77c5f42040e562b674e0a48927cfff0e0d28 100644
--- a/src/tests/test-hsm.cpp
+++ b/src/tests/test-hsm.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Authors: Matteo Michele Piazzolla
+ * Author: Matteo Piazzolla
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -32,7 +32,7 @@ using namespace miosix;
 
 #define TOPIC_TEST  1
 
-#define CHECK_INIT() bool test_value = false
+#define CHECK_INIT() bool test_value = false; (void)test_value
 
 #define CHECK_STATE(HSM, SIGNAL, STATE) do{ \
 cout << "------------------------------" << endl; \
diff --git a/src/tests/test-interrupt.cpp b/src/tests/test-interrupt.cpp
index 16d45b62a8cd2c25039e9a4e509c2a0cf549341f..ede46687b69e2998033d49de14017416e4910dc9 100644
--- a/src/tests/test-interrupt.cpp
+++ b/src/tests/test-interrupt.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2018 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
@@ -23,6 +22,7 @@
 
 #include <miosix.h>
 #include <stdio.h>
+#include "drivers/interrupt/external_interrupts.h"
 
 using namespace miosix;
 
@@ -33,16 +33,6 @@ bool itr = false;
 void __attribute__((used)) EXTI0_IRQHandlerImpl()
 {
     itr = true;
-    EXTI->PR |= EXTI_PR_PR0;
-}
-
-// Attribute naked: Containing assembly code
-void __attribute__((naked)) EXTI0_IRQHandler()
-{
-    saveContext();
-    // bl _Z<length of function name><function name>v
-    asm volatile("bl _Z20EXTI0_IRQHandlerImplv");
-    restoreContext();
 }
 
 typedef Gpio<GPIOA_BASE, 0> user_button;
@@ -51,43 +41,17 @@ int main()
 {
     user_button::mode(Mode::INPUT);
     
-    {
-        FastInterruptDisableLock l;
-        RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
-    }
-    // Refer to the datasheet for a detailed description on the procedure and interrupt registers
-
-    // Clear the mask on the wanted line
-    EXTI->IMR |= EXTI_IMR_MR0;
-
-    // Trigger the interrupt on a falling edge
-    EXTI->FTSR |= EXTI_FTSR_TR0;
-
-    // Trigger the interrupt on a rising edge
-    //EXTI->RTSR |= EXTI_RTSR_TR0;
-
-    EXTI->PR |= EXTI_PR_PR0; // Reset pending register
-
-    // Enable interrupt on PA0 in SYSCFG
-    SYSCFG->EXTICR[0] = 0;
-
-    // Enable the interrput in the interrupt controller
-    NVIC_EnableIRQ(EXTI0_IRQn);
-    NVIC_SetPriority(EXTI0_IRQn, 15);
+    enableExternalInterrupt(GPIOA_BASE, 0, InterruptTrigger::RISING_EDGE, 15);
 
+    unsigned int counter = 0;
     while (true)
     {
         if (itr)
         {
-            printf("Interrupt received!\n");
+            ++counter;
+            printf("%d Interrupt received!\n", counter);
             itr = false;
-            ledOn();
-            Thread::sleep(200);
-            ledOff();
-            Thread::sleep(200);
-            ledOn();
-            Thread::sleep(200);
-            ledOff();
         }
+        Thread::sleep(50);
     }
 }
\ No newline at end of file
diff --git a/src/tests/test-pinobserver.cpp b/src/tests/test-pinobserver.cpp
index 42ce1d2636543cdda9a351b0cf6d12974f8caa02..1fc665a279ad88c9494a2c49435481f5b71ea76b 100644
--- a/src/tests/test-pinobserver.cpp
+++ b/src/tests/test-pinobserver.cpp
@@ -1,6 +1,5 @@
-/*
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/test-rls.cpp b/src/tests/test-rls.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..052f55d57aad450e2fd74ff84deb824753fa52f2
--- /dev/null
+++ b/src/tests/test-rls.cpp
@@ -0,0 +1,88 @@
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Mozzarelli
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+/*
+* This test simulates power control in an unknown resistor (i.e. thermal cutters ;) )
+* y(t) = phi(t)^T * theta(t)
+* P = V^2/R
+* 
+* y(t)      = P(t) = V(t)*I(t)  (power on the resistor)
+* phi(t)    = V(t)^2            (square of the voltage on the resistor)
+* theta(t)  = 1/R               (inverse of the resistance)
+*/
+
+#include <libs/simple-template-matrix/matrix.h>
+#include <rls/RLS.h>
+#include <math.h>
+#include <iostream>
+
+
+int main()
+{
+    
+    float R = 0.3;      // True resistance
+    float P_ref = 20;   // Power setpoint
+    float V;            // Voltage applied to the resistor
+    float I;            // Current in the resistor
+
+    float R0 = 0.5;                         // Inital guess of R
+    MatrixBase<float,1,1> theta0{1/R0};
+    MatrixBase<float,1,1> V0{1};            // Confidence of the initial guess (variance)
+    float mu = 0.7;                         // Forgetting factor
+    float R_est;                            // Estimated R
+
+    // Instantiate the RLS
+    RLS<1> rls{V0,theta0,mu};
+
+    MatrixBase<float,1,1> phi{0};
+    MatrixBase<float,1,1> y{0};
+
+    for (unsigned i = 0; i < 1000; i++)
+    {
+        // Get the estimate of R
+        R_est    = 1/(rls.getEstimate()(0,0));
+
+        // Compute the power on the resistor
+        V        = sqrtf(P_ref*R_est);
+
+        // Compute the mesured current
+        I        = V/R;
+
+        // Regressors vector
+        phi(0,0) = V*V;
+
+        // Measurement vector
+        y(0,0)   = V*I;
+
+        // Update the filter
+        rls.update(y,phi);
+
+        // std::cout << R << ", " << R_est << ", " << R-R_est << ", " << V*I << "\n";
+
+        // Start a slope on R
+        if (i > 500)
+        {
+            R = R+0.05;
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/src/tests/test-sensormanager.cpp b/src/tests/test-sensormanager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a77c51e3e25e7442515ba0f814baaa0aa2ad1170
--- /dev/null
+++ b/src/tests/test-sensormanager.cpp
@@ -0,0 +1,280 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <assert.h>
+
+#include <functional>
+#include <iostream>
+#include <type_traits>
+
+#include "TimestampTimer.h"
+#include "miosix.h"
+#include "sensors/Sensor.h"
+#include "sensors/SensorManager.h"
+#include "utils/testutils/TestSensor.h"
+
+using namespace miosix;
+using namespace TimestampTimer;
+
+// Data produced by the MySensor sensor.
+struct MySensorData : public PressureData, public TemperatureData
+{
+
+    MySensorData() : PressureData{0, 0.0}, TemperatureData{0, 0.0} {}
+
+    MySensorData(float p, float t)
+        : PressureData{getTimestamp(), p}, TemperatureData{getTimestamp(), t}
+    {
+    }
+};
+
+// Specific Sensor implementation.
+class MySensor : public Sensor<MySensorData>
+{
+public:
+    virtual MySensorData sampleImpl() override
+    {
+        return MySensorData(rand() % 100, rand() % 100);
+    }
+
+    bool init() override { return true; };
+    bool selfTest() override { return true; };
+};
+
+template <typename SensorData>
+class MyPressureFilter : public Sensor<SensorData>
+{
+    static_assert(
+        checkIfProduces<Sensor<SensorData>, PressureData>::value,
+        "Template argument must be a sensor that produces pressure data.");
+
+public:
+    MyPressureFilter(Sensor<SensorData>* original_sensor, float offset)
+        : original_sensor(original_sensor), offset(offset)
+    {
+    }
+
+    bool init() override { return true; };
+    bool selfTest() override { return true; };
+
+    SensorData sampleImpl() { return SensorData(); }
+
+    const SensorData& getLastSample() override
+    {
+        this->last_sample = original_sensor->getLastSample();
+        this->last_sample.press += offset;
+        return this->last_sample;
+    }
+
+private:
+    Sensor<SensorData>* original_sensor;
+    float offset;
+};
+
+struct MySensorDataFIFO : public AccelerometerData, public GyroscopeData
+{
+
+    MySensorDataFIFO()
+        : AccelerometerData{getTimestamp(), 0.0, 0.0, 0.0}, GyroscopeData{
+                                                                getTimestamp(),
+                                                                0.0, 0.0, 0.0}
+    {
+    }
+
+    MySensorDataFIFO(AccelerometerData acc, GyroscopeData gyro)
+        : AccelerometerData{getTimestamp(), acc.accel_x, acc.accel_y,
+                            acc.accel_z},
+          GyroscopeData{getTimestamp(), gyro.gyro_x, gyro.gyro_y, gyro.gyro_z}
+    {
+    }
+};
+
+class MySensorFIFO : public SensorFIFO<MySensorDataFIFO, 20>
+{
+    uint32_t fifo_size = 20;
+
+public:
+    MySensorFIFO() {}
+
+    // return last sample
+    virtual MySensorDataFIFO sampleImpl() override
+    {
+        for (uint32_t i = 0; i < fifo_size; i++)
+        {
+            AccelerometerData acc{getTimestamp(), 0.5, 0.5, 0.5};
+            GyroscopeData gyro{getTimestamp(), 0.5, 0.5, 0.5};
+
+            last_fifo[i] = MySensorDataFIFO{acc, gyro};
+
+            TRACE("Accel : %llu %f %f %f \n", acc.accel_timestamp, acc.accel_x,
+                  acc.accel_y, acc.accel_z);
+            TRACE("Gyro : %llu %f %f %f \n", gyro.gyro_timestamp, gyro.gyro_x,
+                  gyro.gyro_y, gyro.gyro_z);
+
+            Thread::sleep(5);
+        }
+
+        last_fifo_level = fifo_size;
+
+        return last_fifo[last_fifo_level - 1];
+    }
+
+    bool init() override { return true; };
+    bool selfTest() override { return true; };
+};
+
+template <typename FIFOData, uint32_t FifoSize>
+class FIFOProxy : public SensorFIFO<FIFOData, FifoSize>
+{
+public:
+    FIFOProxy(SensorFIFO<FIFOData, FifoSize>* sensor) : sensor(sensor) {}
+
+    bool init() override { return true; };
+    bool selfTest() override { return true; };
+
+    FIFOData sampleImpl()
+    {
+        index = 0;
+        return sensor->getFifoElement(index);
+    }
+
+    const FIFOData& getLastSample() override
+    {
+        if (index < FifoSize - 1)
+        {
+            index++;
+        }
+
+        TRACE("Index : %d \n", index);
+
+        return sensor->getFifoElement(index);
+    }
+
+private:
+    SensorFIFO<FIFOData, FifoSize>* sensor;
+    uint32_t index = 0;
+};
+
+class FailingSensor : public Sensor<TestData>
+{
+    bool init() { return true; }
+
+    bool selfTest()  // always fail self-test
+    {
+        TRACE("Failed to init sensor FailingSensor!\n");
+        return false;
+    }
+
+    TestData sampleImpl() { return TestData{}; }
+};
+
+int main()
+{
+    srand(time(NULL));
+
+    enableTimestampTimer();
+
+    MySensor s1;
+    MySensor s2;
+
+    MyPressureFilter<MySensorData> filter(&s1, 2.578f);
+
+    FailingSensor failig_s;  // must not be initialized and not sampled
+
+    SensorManager sm(
+        {{/*Sensor=*/&s1,
+          {/*ID=*/"s1",
+           /*Freq=*/1000,
+           /*Callback=*/[]() { std::cout << "Callback s1!" << endl; },
+           /*DMA=*/false,
+           /*Enabled=*/true}},
+         {/*Sensor=*/&s2,
+          {/*ID=*/"s2",
+           /*Freq=*/1000,
+           /*Callback=*/[]() { std::cout << "Callback s2!" << endl; },
+           /*DMA=*/false,
+           /*Enabled=*/true}},
+         {/*Sensor=*/&filter,
+          {/*ID=*/"filter",
+           /*Freq=*/2000,
+           /*Callback=*/
+           []() { std::cout << "Callback filter!" << endl; },
+           /*DMA=*/false,
+           /*Enabled=*/true}},
+         {/*Sensor=*/&failig_s,
+          {/*ID=*/"failing",
+           /*Freq=*/3000,
+           /*Callback=*/
+           []() { std::cout << "Callback failing sensor!" << endl; },
+           /*DMA=*/false,
+           /*Enabled=*/true}}});
+
+    sm.start();
+
+    Thread::sleep(1000);
+
+    // TEST NORMAL SENSORS
+
+    sm.disableSensor(&s1);
+
+    Thread::sleep(1000);
+
+    sm.enableSensor(&s1);
+
+    for (int i = 0; i < 3; i++)
+    {
+        TRACE("S1 : %llu %f %llu %f \n", s1.getLastSample().press_timestamp,
+              s1.getLastSample().press, s1.getLastSample().temp_timestamp,
+              s1.getLastSample().temp);
+        TRACE("Filter : %llu %f \n", filter.getLastSample().press_timestamp,
+              filter.getLastSample().press);
+
+        Thread::sleep(1000);
+    }
+
+    // TEST SENSORS WITH FIFO
+
+    const uint32_t fifo_size = 20;
+
+    MySensorFIFO s;
+
+    FIFOProxy<MySensorDataFIFO, fifo_size> fifo_proxy(&s);
+
+    for (int i = 0; i < 3; i++)
+    {
+        s.sample();
+
+        MySensorDataFIFO data = fifo_proxy.getLastSample();
+
+        TRACE("AccelProxy : %llu %f %f %f \n", data.accel_timestamp,
+              data.accel_x, data.accel_y, data.accel_z);
+        TRACE("GyroProxy : %llu %f %f %f \n", data.gyro_timestamp, data.gyro_x,
+              data.gyro_y, data.gyro_z);
+
+        Thread::sleep(1000);
+    }
+
+    for (;;)
+        ;
+
+    return 0;
+}
\ No newline at end of file
diff --git a/src/tests/test-serial.cpp b/src/tests/test-serial.cpp
index 744fc7a57bc4dc4b91e43f4a878eb535d4af0f20..35ca8e152c44b19c577169a65dcc2ce40a50ea9b 100644
--- a/src/tests/test-serial.cpp
+++ b/src/tests/test-serial.cpp
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2017 Skyward Experimental Rocketry
- * Authors: Luca Erbetta <luca.erbetta@skywarder.eu>
+ * Author: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@
  *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/test-taskscheduler.cpp b/src/tests/test-taskscheduler.cpp
index cde3979a0114c9e5f86932c8a72912da0f69ca4a..6d8e877e0716b70c146344241b7dbe486bfe7d4f 100644
--- a/src/tests/test-taskscheduler.cpp
+++ b/src/tests/test-taskscheduler.cpp
@@ -1,20 +1,19 @@
-/* 
- * Copyright (c) 2019 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- * 
+/* Copyright (c) 2019 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
  * 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
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS 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
diff --git a/src/tests/test-timestamp-timer.cpp b/src/tests/test-timestamp-timer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea258f00cb02c18ccb07855ae95ab6d7dac8157e
--- /dev/null
+++ b/src/tests/test-timestamp-timer.cpp
@@ -0,0 +1,39 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Conterio
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+#include <miosix.h>
+#include <stdio.h>
+#include "TimestampTimer.h"
+
+using namespace miosix;
+
+int main()
+{
+    TimestampTimer::enableTimestampTimer();
+
+    while (true)
+    {
+        printf("%llu microseconds \n", TimestampTimer::getTimestamp());
+
+        Thread::sleep(1000);
+    }
+}
\ No newline at end of file
diff --git a/src/tests/test-trace-logger.cpp b/src/tests/test-trace-logger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0c391fa6e18c2ed1d15e32f6a9e1a2c55f1a58c1
--- /dev/null
+++ b/src/tests/test-trace-logger.cpp
@@ -0,0 +1,59 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Luca Erbetta
+ *
+ * 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
+ * AUTHORS OR COPYRIGHT HOLDERS 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.
+ */
+
+
+#include <diagnostic/PrintLogger.h>
+#include <miosix.h>
+
+int main()
+{
+    Logging::startAsyncLogger();
+
+    PrintLogger logger = Logging::getLogger("root");
+    PrintLogger log2   = logger.getChild("b.c");
+    PrintLogger log3   = logger.getChild("a");
+    PrintLogger log4   = Logging::getLogger("async");
+
+    // Logging::getStdOutLogSink().setFormatString("{ts} [{name}] {msg}\n");
+    
+    PrintLogger logfile = Logging::getLogger("logfile");
+    unique_ptr<LogSink> logfile_sink = std::make_unique<FileLogSinkBuffered>();
+    logfile_sink->setLevel(LOGL_INFO);
+    Logging::addLogSink(logfile_sink);
+
+    int async_ctr = 0;
+    for (;;)
+    {
+        LOG_ERR(logger, "This is a message {}", 52);
+        LOG_INFO(log2, "Ops, there was an {}!", "error");
+        for (int i = 0; i < 4; i++)
+        {
+            LOG_INFO(log4, "This is an async log! {:d}", async_ctr++);
+        }
+        LOG_DEBUG(log3, "This is a verbose debug message {:.3f}", 1.234f);
+        LOG_CRIT(log3, "Float {:.1f} {:05.2f} {:f}", 1.234f, 1234.1234,
+                 -9876.98765432f);
+        LOG_ERR(logger, "Simple log with no arguments");
+
+        miosix::Thread::sleep(1000);
+    }
+}