[protozero] 01/07: New upstream version 1.6.0

Bas Couwenberg sebastic at debian.org
Tue Oct 24 18:14:36 UTC 2017


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository protozero.

commit 3d1e65294664451aab8f9efdc84a3b4c926ef99d
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Tue Oct 24 19:33:35 2017 +0200

    New upstream version 1.6.0
---
 .clang-tidy                                        |  50 ++
 .gitignore                                         |  18 -
 .npmignore                                         |  33 -
 .travis.yml                                        |  99 +--
 CHANGELOG.md                                       |  41 +-
 CMakeLists.txt                                     | 144 +++++
 CONTRIBUTING.md                                    |   9 +-
 FUZZING.md                                         |  22 +
 Makefile                                           | 148 -----
 README.md                                          |  72 ++-
 UPGRADING.md                                       |  14 +
 appveyor.yml                                       |  60 +-
 build-appveyor.bat                                 |  82 +--
 build-msys2.bat                                    |  18 +
 cmake/FindProtozero.cmake                          |  63 ++
 doc/.gitignore                                     |   2 -
 doc/CMakeLists.txt                                 |  37 ++
 doc/{Doxyfile => Doxyfile.in}                      |  13 +-
 doc/tutorial.md                                    |  12 +
 gyp/common.gypi                                    | 109 ----
 gyp/protozero.gyp                                  |  39 --
 include/protozero/{types.hpp => data_view.hpp}     | 119 ++--
 include/protozero/exception.hpp                    |  21 +
 include/protozero/iterators.hpp                    |  44 +-
 include/protozero/pbf_reader.hpp                   |  30 +-
 include/protozero/pbf_writer.hpp                   |   1 +
 include/protozero/types.hpp                        | 140 -----
 include/protozero/varint.hpp                       |   3 +-
 include/protozero/version.hpp                      |   6 +-
 include_dirs.js                                    |   2 -
 package.json                                       |  10 -
 test/.gitignore                                    |   6 -
 test/CMakeLists.txt                                | 120 ++++
 test/README.md                                     |  22 +-
 test/include/packed_access.hpp                     |  14 +-
 test/include/scalar_access.hpp                     |   3 -
 test/include/test.hpp                              |   4 -
 test/include/testcase.hpp                          |   2 +-
 test/{tests.cpp => reader_tests.cpp}               |  10 +-
 test/t/.gitignore                                  |   6 -
 .../{test_cases.cpp => reader_test_cases.cpp}      |   7 -
 test/t/basic/reader_test_cases.cpp                 | 101 +++
 test/t/basic/test_cases.cpp                        |  39 --
 test/t/bool/data-also-true.pbf                     |   2 +-
 test/t/bool/data-still-true.pbf                    |   2 +-
 test/t/bool/reader_test_cases.cpp                  | 141 +++++
 test/t/bool/test_cases.cpp                         | 155 -----
 test/t/bool/testcase.cpp                           |   2 +-
 test/t/bool/testcase.proto                         |   5 +-
 test/t/bool/writer_test_cases.cpp                  |   2 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  93 ++-
 test/t/bytes/writer_test_cases.cpp                 |   4 +-
 test/t/complex/reader_test_cases.cpp               | 686 ++++++++++++++++++++
 test/t/complex/test_cases.cpp                      | 700 ---------------------
 .../{test_cases.cpp => reader_test_cases.cpp}      |  85 ++-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   7 -
 test/t/double/writer_test_cases.cpp                |   2 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  24 +-
 test/t/enum/reader_test_cases.cpp                  |  83 +++
 test/t/enum/test_cases.cpp                         |  89 ---
 test/t/enum/testcase.proto                         |   2 +-
 test/t/enum/writer_test_cases.cpp                  |   4 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  10 +
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 test/t/fixed32/writer_test_cases.cpp               |   2 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   8 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 test/t/int32/writer_test_cases.cpp                 |   2 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 test/t/message/reader_test_cases.cpp               | 129 ++++
 test/t/message/test_cases.cpp                      | 141 -----
 test/t/message/writer_test_cases.cpp               |   2 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  26 +-
 test/t/nested/writer_test_cases.cpp                |   2 +-
 test/t/repeated/reader_test_cases.cpp              |  74 +++
 test/t/repeated/test_cases.cpp                     |  80 ---
 test/t/repeated/writer_test_cases.cpp              |   4 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  83 ++-
 .../{test_cases.cpp => reader_test_cases.cpp}      |  26 +-
 test/t/repeated_packed_enum/reader_test_cases.cpp  |  78 +++
 test/t/repeated_packed_enum/test_cases.cpp         |  84 ---
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../repeated_packed_fixed32/writer_test_cases.cpp  |  11 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |  16 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../repeated_packed_sfixed32/reader_test_cases.cpp |  30 +
 test/t/repeated_packed_sfixed32/test_cases.cpp     |   9 -
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   8 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../skip/{test_cases.cpp => reader_test_cases.cpp} |   4 -
 test/t/string/reader_test_cases.cpp                | 106 ++++
 test/t/string/test_cases.cpp                       | 116 ----
 test/t/string/writer_test_cases.cpp                |   6 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../tags/{test_cases.cpp => reader_test_cases.cpp} |  32 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |  48 +-
 .../{test_cases.cpp => reader_test_cases.cpp}      |   1 -
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 .../{test_cases.cpp => reader_test_cases.cpp}      |   0
 tools/CMakeLists.txt                               |  51 ++
 tools/pbf-decoder.cpp                              | 264 ++++++++
 114 files changed, 2905 insertions(+), 2431 deletions(-)

diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..35ed1a7
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,50 @@
+---
+Checks:          '*,-cert-dcl21-cpp,-cert-err60-cpp,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-type-reinterpret-cast,-google-runtime-references'
+#
+#  Disabled checks:
+#
+#  cert-dcl21-cpp
+#    It is unclear whether this is still a good recommendation in modern C++.
+#
+#  cert-err60-cpp
+#    Reports std::runtime_error as broken which we can't do anything about.
+#
+#  cppcoreguidelines-pro-bounds-pointer-arithmetic
+#    This is a low-level library, it needs to do pointer arithmetic.
+#
+#  cppcoreguidelines-pro-bounds-array-to-pointer-decay
+#    Limited use and many false positives including all for all asserts
+#
+#  cppcoreguidelines-pro-type-reinterpret-cast
+#    This is a low-level library, it needs to do reinterpret-casts.
+#
+#  google-runtime-references
+#    This is just a matter of preference, and we can't change the interfaces
+#    now anyways.
+#
+WarningsAsErrors: '*'
+HeaderFilterRegex: '\/include\/'
+AnalyzeTemporaryDtors: false
+CheckOptions:
+  - key:             google-readability-braces-around-statements.ShortStatementLines
+    value:           '1'
+  - key:             google-readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             google-readability-namespace-comments.ShortNamespaceLines
+    value:           '10'
+  - key:             google-readability-namespace-comments.SpacesBeforeComments
+    value:           '2'
+  - key:             modernize-loop-convert.MaxCopySize
+    value:           '16'
+  - key:             modernize-loop-convert.MinConfidence
+    value:           reasonable
+  - key:             modernize-loop-convert.NamingStyle
+    value:           CamelCase
+  - key:             modernize-pass-by-value.IncludeStyle
+    value:           llvm
+  - key:             modernize-replace-auto-ptr.IncludeStyle
+    value:           llvm
+  - key:             modernize-use-nullptr.NullMacros
+    value:           'NULL'
+...
+
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index ba94541..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,18 +0,0 @@
-coverage
-*.gcov
-out
-protozero.Makefile
-tests.target.mk
-*.o
-*.gcno
-*.gcno
-#Visual Studio files and folders
-Release
-Debug
-deps
-*.sdf
-*.sln
-*.suo
-*.vcxproj*
-*.7z
-compile_commands.json
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index ef6dd71..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,33 +0,0 @@
-coverage
-*.gcov
-out
-protozero.Makefile
-tests.target.mk
-*.o
-*.gcno
-*.gcno
-#Visual Studio files and folders
-Release
-Debug
-deps
-*.sdf
-*.sln
-*.suo
-*.vcxproj*
-*.7z
-test
-build-local.bat
-build-appveyor.bat
-bench
-notes.md
-coverity.sh
-protozero.gyp
-common.gypi
-tutorial.md
-CHANGELOG.md
-doc
-.travis.yml
-appveyor.yml
-Makefile
-.gitattributes
-.npmignore
diff --git a/.travis.yml b/.travis.yml
index a55b84c..c6be450 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,14 +26,14 @@ addons_shortcuts:
     apt:
       sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-3.9' ]
       packages: [ 'libprotobuf-dev','protobuf-compiler', 'clang-3.9' ]
-  addons_clang39: &clang40
+  addons_clang40: &clang40
     apt:
       sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0' ]
       packages: [ 'libprotobuf-dev','protobuf-compiler', 'clang-4.0' ]
-  addons_clang39: &clang50
+  addons_clang50: &clang50
     apt:
       sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
-      packages: [ 'libprotobuf-dev','protobuf-compiler', 'clang-5.0' ]
+      packages: [ 'libprotobuf-dev','protobuf-compiler', 'clang-5.0', 'clang-tidy-5.0' ]
   addons_gcc47: &gcc47
     apt:
       sources: [ 'ubuntu-toolchain-r-test' ]
@@ -53,7 +53,7 @@ addons_shortcuts:
   addons_gcc6: &gcc6
     apt:
       sources: [ 'ubuntu-toolchain-r-test' ]
-      packages: [ 'libprotobuf-dev','protobuf-compiler', 'g++-6', 'gcc-6', 'doxygen', 'graphviz' ]
+      packages: [ 'libprotobuf-dev','protobuf-compiler', 'g++-6', 'gcc-6' ]
 
 #-----------------------------------------------------------------------------
 
@@ -61,91 +61,116 @@ matrix:
   include:
     - os: linux
       compiler: "clang-3.5"
-      env: CXX=clang++-3.5
+      env: BUILD='Debug' CC=clang-3.5 CXX=clang++-3.5
       addons: *clang35
     - os: linux
       compiler: "clang-3.8"
-      env: CXX=clang++-3.8
+      env: BUILD='Debug' CC=clang-3.8 CXX=clang++-3.8
       addons: *clang38
     - os: linux
       compiler: "clang-3.9"
-      env: CXX=clang++-3.9
+      env: BUILD='Debug' CC=clang-3.9 CXX=clang++-3.9
       addons: *clang39
     - os: linux
       compiler: "clang-4.0"
-      env: CXX=clang++-4.0
+      env: BUILD='Debug' CC=clang-4.0 CXX=clang++-4.0
       addons: *clang40
     - os: linux
-      compiler: "clang-4.0"
-      env: CXX=clang++-5.0
+      compiler: "clang-5.0"
+      env: BUILD='Debug' CC=clang-5.0 CXX=clang++-5.0
+           CLANG_TIDY=clang-tidy-5.0
+      addons: *clang50
+    - os: linux
+      compiler: "clang-5.0"
+      env: BUILD='Release' CC=clang-5.0 CXX=clang++-5.0
       addons: *clang50
     - os: linux
       compiler: "clang-5.0"
-      env: CXX=clang++-5.0 CXX_STD=c++14
+      env: BUILD='Debug' CC=clang-5.0 CXX=clang++-5.0
+           CXXFLAGS="-fsanitize=address,undefined,integer -fno-sanitize-recover=all -fno-omit-frame-pointer"
+           LDFLAGS="-fsanitize=address,undefined,integer"
       addons: *clang50
     - os: linux
       compiler: "gcc-4.7"
-      env: CXX=g++-4.7
+      env: BUILD='Debug' CC=gcc-4.7 CXX=g++-4.7
       addons: *gcc47
     - os: linux
       compiler: "gcc-4.8"
-      env: CXX=g++-4.8
+      env: BUILD='Debug' CC=gcc-4.8 CXX=g++-4.8
       addons: *gcc48
     - os: linux
       compiler: "gcc-4.9"
-      env: CXX=g++-4.9 COVERAGE=gcov-4.9
-      addons: *gcc49
-    - os: linux
-      compiler: "gcc-4.9"
-      env: CXX=g++-4.9 CXX_STD=c++14
+      env: BUILD='Debug' CC=gcc-4.9 CXX=g++-4.9
+           COVERAGE=gcov-4.9
+           CXXFLAGS="--coverage" LDFLAGS="--coverage"
       addons: *gcc49
     - os: linux
       compiler: "gcc-5"
-      env: CXX=g++-5 CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0"
+      env: BUILD='Debug' CC=gcc-5 CXX=g++-5
+           CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0"
       addons: *gcc5
     - os: linux
       compiler: "gcc-5"
-      env: CXX=g++-5 CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=1"
+      env: BUILD='Debug' CC=gcc-5 CXX=g++-5
+           CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=1"
       addons: *gcc5
     - os: linux
       compiler: "gcc-6"
-      env: CXX=g++-6 BUILD_DOC=1
+      env: BUILD='Debug' CC=gcc-6 CXX=g++-6
       addons: *gcc6
     - os: linux
       compiler: "gcc-6"
-      env: CXX=g++-6 PROTOZERO_DATA_VIEW=std::experimental::string_view
+      env: BUILD='Debug' CC=gcc-6 CXX=g++-6
+           PROTOZERO_DATA_VIEW=std::experimental::string_view
       addons: *gcc6
+    - os: linux
+      compiler: "gcc-6"
+      env: BUILD='Release' CC=gcc-6 CXX=g++-6
+      addons: *gcc6
+    - os: osx
+      osx_image: xcode6.4
+      compiler: clang
+      env: BUILD='Debug'
+    - os: osx
+      osx_image: xcode7.3
+      compiler: clang
+      env: BUILD='Debug'
     - os: osx
-      osx_image: xcode6
+      osx_image: xcode8.3
       compiler: clang
+      env: BUILD='Debug'
     - os: osx
-      osx_image: xcode7
+      osx_image: xcode9.1
       compiler: clang
+      env: BUILD='Debug'
     - os: osx
-      osx_image: xcode8
+      osx_image: xcode9.1
       compiler: clang
+      env: BUILD='Release'
 
 #-----------------------------------------------------------------------------
 
-before_install:
- - echo ${CXX}
- - if [[ $(uname -s) == 'Darwin' ]]; then
-     brew install protobuf;
-   fi
-
 install:
- - make test
- - if [ -n "${BUILD_DOC}" ]; then make doc; fi
+  - if [[ $(uname -s) == 'Darwin' ]]; then
+      brew update;
+      brew install protobuf;
+    fi
 
 script:
+  - mkdir build
+  - cd build
+  - cmake .. -LA -DCMAKE_BUILD_TYPE=${BUILD} -DPROTOZERO_DATA_VIEW=$PROTOZERO_DATA_VIEW -DCLANG_TIDY=$(which ${CLANG_TIDY})
+  - make VERBOSE=1
+  - ctest --output-on-failure
+  - if [ -n "${CLANG_TIDY}" ]; then make clang-tidy; fi
   - |
     if [ -n "${COVERAGE}" ]; then
-      make clean
-      CXXFLAGS="--coverage ${CXXFLAGS}" LDFLAGS="--coverage ${LDFLAGS}" make test
       which ${COVERAGE}
       curl -S -f https://codecov.io/bash -o codecov
       chmod +x codecov
-      ${COVERAGE} -p $(find test/ -name '*.o')
-      ./codecov -Z -f '*protozero*' -X search
+      ${COVERAGE} -p $(find test/ tools/ -name '*.o')
+      ./codecov -Z -f '*protozero*' -f '*tools*' -f '!*catch*' -X search
     fi
 
+
+#-----------------------------------------------------------------------------
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fbcd08..e1aaf68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,49 @@
 
-# Change Log
+# Changelog
 
 All notable changes to this project will be documented in this file.
+The format is based on [Keep a Changelog](http://keepachangelog.com/)
 This project adheres to [Semantic Versioning](http://semver.org/).
 
-## [unreleased] -
+## [1.6.0] - 2017-10-24
 
 ### Added
 
+- Comparison functions (<, <=, >, >=) for `data_view`. Allows use in `std::map`
+  for instance.
+- Tool `pbf-decoder` for decoding raw messages. This has limited use for
+  normal users, but it can be used for fuzzing.
+
 ### Changed
 
+- Protozero now uses CMake to build the tests etc. This does not affect
+  simple users of the library, but if you are using CMake yourself you might
+  want to use the `cmake/FindProtozero.cmake` module provided. The README
+  contains more information about build options.
+- Moved `data_view` class from `types.hpp` into its own header file
+  `data_view.hpp`.
+- Implementation of the `const_fixed_iterator` to use only a single pointer
+  instead of two.
+- Made `operator==` and `operator!=` on `data_view` constexpr.
+- The `pbf_reader` constructor taking a `std::pair` is deprecated. Use one
+  of the other constructors instead.
+
 ### Fixed
 
+- Varints where the last byte was larger than what would fit in 64bit were
+  triggering undefined behaviour. This can only happen when the message
+  being decoded was corrupt in some way.
+- Do not assert when reading too long varints for bools any more. A valid
+  encoder should never generate varints with more than one byte for bools,
+  but if they are longer that's not really a problem, so just handle it.
+- Throw exception if the length of a packed repeated field of a fixed-length
+  type is invalid. The length must always be a multiple of the size of the
+  underlying type. This can only happen if the data is corrupted in some way,
+  a valid encoder would never generate data like this.
+- Throw an exception when reading invalid tags. This can only happen if the
+  data is corrupted in some way, a valid encoder would never generate invalid
+  tags.
+
 
 ## [1.5.3] - 2017-09-22
 
@@ -223,8 +255,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 - Make pbf reader and writer code endianess-aware.
 
 
-[unreleased]: https://github.com/osmcode/libosmium/compare/v1.5.3...HEAD
-[1.5.3]: https://github.com/osmcode/libosmium/compare/v1.5.3...v1.5.3
+[unreleased]: https://github.com/osmcode/libosmium/compare/v1.6.0...HEAD
+[1.6.0]: https://github.com/osmcode/libosmium/compare/v1.5.3...v1.6.0
+[1.5.3]: https://github.com/osmcode/libosmium/compare/v1.5.2...v1.5.3
 [1.5.2]: https://github.com/osmcode/libosmium/compare/v1.5.1...v1.5.2
 [1.5.1]: https://github.com/osmcode/libosmium/compare/v1.5.0...v1.5.1
 [1.5.0]: https://github.com/osmcode/libosmium/compare/v1.4.5...v1.5.0
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e2f35bd
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,144 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake config
+#
+#  protozero
+#
+#-----------------------------------------------------------------------------
+
+cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
+
+#-----------------------------------------------------------------------------
+
+project(protozero)
+
+set(PROTOZERO_VERSION_MAJOR 1)
+set(PROTOZERO_VERSION_MINOR 6)
+set(PROTOZERO_VERSION_PATCH 0)
+
+set(PROTOZERO_VERSION
+    "${PROTOZERO_VERSION_MAJOR}.${PROTOZERO_VERSION_MINOR}.${PROTOZERO_VERSION_PATCH}")
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+#-----------------------------------------------------------------------------
+
+option(WERROR "Add -Werror flag to build (turns warnings into errors)" ON)
+
+if(MSVC)
+    add_definitions(-std=c++11 /W3)
+    add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)
+else()
+    add_definitions(-std=c++11 -Wall -Wextra -pedantic -Wsign-compare -Wunused-parameter -Wno-float-equal -Wno-covered-switch-default)
+    if(WERROR)
+        add_definitions(-Werror)
+    endif()
+endif()
+
+include_directories("${CMAKE_SOURCE_DIR}/include")
+
+set(PROTOZERO_DATA_VIEW "" CACHE STRING "Type used for protozero::data_view")
+if(NOT PROTOZERO_DATA_VIEW STREQUAL "")
+    add_definitions(-DPROTOZERO_DATA_VIEW=${PROTOZERO_DATA_VIEW})
+endif()
+
+
+#-----------------------------------------------------------------------------
+#
+#  Find dependencies
+#
+#-----------------------------------------------------------------------------
+
+find_package(Protobuf)
+
+
+#-----------------------------------------------------------------------------
+#
+#  Optional "clang-tidy" target
+#
+#-----------------------------------------------------------------------------
+message(STATUS "Looking for clang-tidy")
+find_program(CLANG_TIDY NAMES clang-tidy clang-tidy-5.0)
+
+if(CLANG_TIDY)
+    message(STATUS "Looking for clang-tidy - found ${CLANG_TIDY}")
+    add_custom_target(clang-tidy
+        ${CLANG_TIDY}
+        -p ${CMAKE_BINARY_DIR}
+        ${CMAKE_SOURCE_DIR}/test/*.cpp
+        ${CMAKE_SOURCE_DIR}/test/t/*/reader_test_cases.cpp
+        ${CMAKE_SOURCE_DIR}/test/t/*/writer_test_cases.cpp
+        ${CMAKE_SOURCE_DIR}/tools/*.cpp
+    )
+    add_dependencies(clang-tidy writer_tests)
+else()
+    message(STATUS "Looking for clang-tidy - not found")
+    message(STATUS "  Build target 'clang-tidy' will not be available.")
+endif()
+
+
+#-----------------------------------------------------------------------------
+#
+#  Optional "cppcheck" target
+#
+#-----------------------------------------------------------------------------
+message(STATUS "Looking for cppcheck")
+find_program(CPPCHECK NAMES cppcheck)
+
+if(CPPCHECK)
+    message(STATUS "Looking for cppcheck - found")
+    add_custom_target(cppcheck
+        ${CPPCHECK}
+        -Uassert --std=c++11 --enable=all
+        ${CMAKE_SOURCE_DIR}/include/protozero/*.hpp
+        ${CMAKE_SOURCE_DIR}/test/*.cpp
+        ${CMAKE_SOURCE_DIR}/test/include/*.hpp
+        ${CMAKE_SOURCE_DIR}/test/t/*/*.cpp
+        ${CMAKE_SOURCE_DIR}/tools/*.cpp
+    )
+else()
+    message(STATUS "Looking for cppcheck - not found")
+    message(STATUS "  Build target 'cppcheck' will not be available.")
+endif()
+
+
+#-----------------------------------------------------------------------------
+#
+#  Include what you use
+#
+#-----------------------------------------------------------------------------
+message(STATUS "Looking for iwyu")
+find_program(IWYU_TOOL NAMES iwyu_tool)
+
+if(IWYU_TOOL)
+    message(STATUS "Looking for iwyu - found")
+    add_custom_target(iwyu
+        ${IWYU_TOOL} -p ${CMAKE_BINARY_DIR}
+    )
+else()
+    message(STATUS "Looking for iwyu - not found")
+    message(STATUS "  Build target 'iwyu' will not be available.")
+endif()
+
+
+#-----------------------------------------------------------------------------
+#
+#  Installation
+#
+#-----------------------------------------------------------------------------
+
+install(DIRECTORY include/protozero DESTINATION include)
+
+
+#-----------------------------------------------------------------------------
+
+enable_testing()
+
+add_subdirectory(doc)
+
+add_subdirectory(tools)
+
+add_subdirectory(test)
+
+
+#-----------------------------------------------------------------------------
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fae897b..c116f60 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,19 +8,14 @@ To release a new protozero version:
  - Make sure "make doc" builds
  - Update version number in
    - include/protozero/version.hpp (two places)
-   - package.json
+   - CMakeLists.txt (one place)
  - Update CHANGELOG.md
    (don't forget links at the bottom of the file)
  - Update UPGRADING.md if necessary
- - `git commit -m "Release X.Y.Z" include/protozero/version.hpp package.json CHANGELOG.md UPGRADING.md`
+ - `git commit -m "Release X.Y.Z" include/protozero/version.hpp CMakeLists.txt CHANGELOG.md UPGRADING.md`
  - `git tag vX.Y.Z`
  - `git push`
  - `git push --tags`
  - Go to https://github.com/mapbox/protozero/releases
    and edit the new release. Put "Version x.y.z" in title and
    cut-and-paste entry from CHANGELOG.md.
- - Publish to npm:
-   - First run `make testpack` to see what files will be published
-   - If you see any unwanted files, then add them to `.npmignore`
-   - Then run: `npm publish`
-
diff --git a/FUZZING.md b/FUZZING.md
new file mode 100644
index 0000000..44ae0ef
--- /dev/null
+++ b/FUZZING.md
@@ -0,0 +1,22 @@
+
+To do fuzz testing using [AFL](http://lcamtuf.coredump.cx/afl/) compile with
+the AFL compiler wrappers:
+
+    mkdir build
+    cd build
+    CC=afl-clang CXX=afl-clang++ cmake ..
+    mkdir testcase_dir
+
+You need some data to start the fuzzing. In this case I am using all the test
+messages from the unit tests:
+
+    find ../test/t/ -name data-\*.pbf -a -not -empty -exec cp {} testcase_dir/ \;
+
+Then do the actual fuzzing:
+
+    afl-fuzz -i testcase_dir -o findings_dir -- tools/pbf-decoder -
+
+See the AFL documentation for more information.
+
+This only checkes the reading side of Protozero!
+
diff --git a/Makefile b/Makefile
deleted file mode 100644
index b896d13..0000000
--- a/Makefile
+++ /dev/null
@@ -1,148 +0,0 @@
-
-# Installation directory
-DESTDIR ?= /usr
-
-WARNING_FLAGS := -Wall -Wextra -pedantic -Wsign-compare -Wsign-conversion -Wunused-parameter -Wno-float-equal -Wno-covered-switch-default -Wshadow
-
-ifneq ($(findstring clang,$(CXX)),)
-    WARNING_FLAGS += -Wno-reserved-id-macro -Weverything -Wno-weak-vtables -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-exit-time-destructors -Wno-switch-enum -Wno-padded -Wno-documentation-unknown-command
-endif
-
-ifeq ($(PROTOZERO_DATA_VIEW),std::experimental::string_view)
-    PROTOZERO_FLAGS := -include experimental/string_view -DPROTOZERO_USE_VIEW=std::experimental::string_view
-    CXX_STD ?= c++14
-endif
-
-CXX_STD ?= c++11
-
-COMMON_FLAGS := -fvisibility-inlines-hidden -std=$(CXX_STD) $(WARNING_FLAGS) $(PROTOZERO_FLAGS)
-
-RELEASE_FLAGS := -O3 -DNDEBUG -march=native
-DEBUG_FLAGS := -O0 -g -fno-inline-functions
-PTHREAD_FLAGS =
-
-CLANG_TIDY := clang-tidy-5.0
-
-OS:=$(shell uname -s)
-
-ifeq ($(OS),Linux)
-    PTHREAD_FLAGS = -pthread
-endif
-
-ifeq ($(OS),Darwin)
-    CXXFLAGS += -stdlib=libc++
-    LDFLAGS += -stdlib=libc++
-endif
-
-TEST_CASES := $(wildcard test/t/*/test_cases.cpp)
-TEST_CASES_O := $(subst .cpp,.o,$(TEST_CASES))
-
-WRITER_TEST_CASES := $(wildcard test/t/*/writer_test_cases.cpp)
-WRITER_TEST_CASES_O := $(subst .cpp,.o,$(WRITER_TEST_CASES))
-
-PROTO_FILES := $(subst writer_test_cases.cpp,testcase.proto,$(WRITER_TEST_CASES))
-PROTO_FILES_CC := $(subst .proto,.pb.cc,$(PROTO_FILES))
-PROTO_FILES_H := $(subst .proto,.pb.h,$(PROTO_FILES))
-PROTO_FILES_O := $(subst .proto,.pb.o,$(PROTO_FILES))
-
-HPP_FILES := include/protozero/byteswap.hpp \
-             include/protozero/config.hpp \
-             include/protozero/exception.hpp \
-             include/protozero/iterators.hpp \
-             include/protozero/pbf_builder.hpp \
-             include/protozero/pbf_message.hpp \
-             include/protozero/pbf_reader.hpp \
-             include/protozero/pbf_writer.hpp \
-             include/protozero/types.hpp \
-             include/protozero/varint.hpp \
-             include/protozero/version.hpp
-
-DOC_FILES = doc/advanced.md doc/cheatsheet.md doc/tutorial.md
-
-CFLAGS_PROTOBUF := $(subst -I,-isystem ,$(shell pkg-config protobuf --cflags))
-LDFLAGS_PROTOBUF := $(shell pkg-config protobuf --libs-only-L)
-
-all: ./test/tests test/writer_tests
-
-TEST_INCLUDES := -Iinclude -Itest/include -isystem test/catch
-
-./test/t/%/test_cases.o: test/t/%/test_cases.cpp test/include/test.hpp test/include/scalar_access.hpp test/include/packed_access.hpp $(HPP_FILES)
-	$(CXX) -c $(TEST_INCLUDES) $(CXXFLAGS) $(COMMON_FLAGS) $(DEBUG_FLAGS) $< -o $@
-
-./test/tests.o: test/tests.cpp $(HPP_FILES)
-	$(CXX) -c $(TEST_INCLUDES) $(CXXFLAGS) $(COMMON_FLAGS) $(DEBUG_FLAGS) $< -o $@
-
-./test/tests: test/tests.o $(TEST_CASES_O)
-	$(CXX) $(LDFLAGS) $^ -o $@
-
-./test/t/%/testcase.pb.cc: ./test/t/%/testcase.proto
-	protoc --cpp_out=. $^
-
-./test/t/%/testcase.pb.o: ./test/t/%/testcase.pb.cc
-	$(CXX) -c -I. $(TEST_INCLUDES) $(CXXFLAGS) $(CFLAGS_PROTOBUF) -std=c++11 $(DEBUG_FLAGS) $< -o $@
-
-./test/t/%/writer_test_cases.o: ./test/t/%/writer_test_cases.cpp $(HPP_FILES)
-	$(CXX) -c -I. $(TEST_INCLUDES) $(CXXFLAGS) $(CFLAGS_PROTOBUF) $(COMMON_FLAGS) $(DEBUG_FLAGS) $< -o $@
-
-./test/writer_tests.o: test/writer_tests.cpp $(HPP_FILES) $(PROTO_FILES_CC)
-	$(CXX) -c -I. $(TEST_INCLUDES) $(CXXFLAGS) $(COMMON_FLAGS) $(DEBUG_FLAGS) $< -o $@
-
-./test/writer_tests: test/writer_tests.o $(PROTO_FILES_O) $(WRITER_TEST_CASES_O)
-	$(CXX) $(LDFLAGS) $(LDFLAGS_PROTOBUF) $^ -lprotobuf-lite $(PTHREAD_FLAGS) -o $@
-
-test: all
-	./test/tests
-	./test/writer_tests
-
-iwyu: $(HPP_FILES) test/tests.cpp test/writer_tests.cpp
-	iwyu -Xiwyu -- -std=c++11 -Iinclude include/protozero/exception.hpp || true
-	iwyu -Xiwyu -- -std=c++11 -Iinclude include/protozero/types.hpp || true
-	iwyu -Xiwyu -- -std=c++11 -Iinclude include/protozero/varint.hpp || true
-	iwyu -Xiwyu -- -std=c++11 -Iinclude include/protozero/pbf_reader.hpp || true
-	iwyu -Xiwyu -- -std=c++11 -Iinclude include/protozero/pbf_writer.hpp || true
-	iwyu -Xiwyu -- -std=c++11 $(TEST_INCLUDES) test/tests.cpp || true
-	iwyu -Xiwyu -- -std=c++11 $(TEST_INCLUDES) test/writer_tests.cpp || true
-
-check: $(HPP_FILES) test/tests.cpp test/include/test.hpp test/include/testcase.hpp test/t/*/testcase.cpp $(TEST_CASES)
-	cppcheck -Uassert --std=c++11 --enable=all --suppress=incorrectStringBooleanError $^
-
-doc: doc/Doxyfile README.md UPGRADING.md $(DOC_FILES) $(HPP_FILES)
-	doxygen doc/Doxyfile
-
-clean:
-	rm -f ./test/tests
-	rm -f ./test/tests.o
-	rm -f ./test/tests.gc*
-	rm -f ./test/writer_tests
-	rm -f ./test/writer_tests.o
-	rm -f ./test/writer_tests.gc*
-	rm -f ./test/t/*/testcase.pb.cc
-	rm -f ./test/t/*/testcase.pb.h
-	rm -f ./test/t/*/testcase.pb.o
-	rm -f ./test/t/*/testcase.pb.gc*
-	rm -f ./test/t/*/testcase.o
-	rm -f ./test/t/*/testcase
-	rm -f ./test/t/*/test_cases.o
-	rm -f ./test/t/*/test_cases.gc*
-	rm -f ./test/t/*/writer_test_cases.o
-	rm -f ./test/t/*/writer_test_cases.gc*
-	rm -f ./*.gcov
-	rm -fr doc/doxygen_sqlite3.db doc/html coverage
-
-testpack:
-	rm -f ./*tgz
-	npm pack
-	tar -ztvf *tgz
-	rm -f ./*tgz
-
-install:
-	install -m 0755 -o root -g root -d $(DESTDIR)/include/protozero
-	install -m 0644 -o root -g root include/protozero/* $(DESTDIR)/include/protozero
-
-clang-tidy: clean
-	bear make -j4 # create compile_commands.json compilation database
-	# some checks disabled because they are too strict for our use case
-	$(CLANG_TIDY) -header-filter='.*' -checks='*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-reinterpret-cast,-google-runtime-references' $(TEST_CASES)
-
-.PHONY: all test iwyu check doc
-
diff --git a/README.md b/README.md
index 4aa57f2..42902b3 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,15 @@ this approach offers no value: just use the C++ API that can be generated with
 the Google Protobufs `protoc` program.
 
 [![Travis Build Status](https://travis-ci.org/mapbox/protozero.svg?branch=master)](https://travis-ci.org/mapbox/protozero)
-[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/o354pq10y96mnr6d?svg=true)](https://ci.appveyor.com/project/Mapbox/protozero)
+[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/mapbox/protozero?svg=true)](https://ci.appveyor.com/project/Mapbox/protozero)
 [![Coverage Status](https://codecov.io/gh/mapbox/protozero/branch/master/graph/badge.svg)](https://codecov.io/gh/mapbox/protozero)
 
 ## Depends
 
 * C++11 compiler
-* Tests depend on the Google Protobuf library, but use of Protozero doesn't
-  need it
+* CMake
+* Some tests depend on the Google Protobuf library, but use of Protozero
+  doesn't need it
 
 
 ## How it works
@@ -54,15 +55,9 @@ You have to have a working knowledge of how
 * Read the [upgrading instructions](UPGRADING.md) if you are upgrading from
   an older version of Protozero.
 
-Call `make doc` to build the Doxygen-based reference documentation. (You'll
-need [Doxygen](http://www.stack.nl/~dimitri/doxygen/) installed.) Then open
-`doc/html/index.html` in your browser to read it.
-
-
-## Installation
-
-Call `make install` to install include files in `/usr/include/protozero`. Call
-`make install DESTDIR=/usr/local` or similar to change install directory.
+The build process will also build the Doxygen-based reference documentation
+if you have [Doxygen](http://www.stack.nl/~dimitri/doxygen/) installed. Then
+open `doc/html/index.html` in your browser to read it.
 
 
 ## Endianness
@@ -73,63 +68,74 @@ case, please [open an issue](https://github.com/mapbox/protozero/issues) and
 tell us about your system.
 
 
-## Tests
+## Building tests
 
-Extensive tests are included. Call
+Extensive tests are included. Build them using CMake:
 
-    make test
-
-to build all tests and run them.
-
-See `test/README.md` for more details about the test.
+    mkdir build
+    cd build
+    cmake ..
+    make
 
-You can also use `gyp` to build the reader tests:
+Call `ctest` to run the tests.
 
-    gyp gyp/protozero.gyp --depth=. --build=Release
-    ./out/Release/tests
+The reader tests are always build, the writer tests are only build if the
+Google Protobuf library is found when running CMake.
 
-This will clobber the `Makefile` from the repository! Instead of `Release` you
-can use `Debug` for a debug build.
+See `test/README.md` for more details about the test.
 
 
 ## Coverage report
 
-To get a coverage report compile and link with `--coverage`:
+To get a coverage report set `CXXFLAGS` and `LDFLAGS` before calling CMake:
+
+    CXXFLAGS="--coverage" LDFLAGS="--coverage" cmake ..
 
-    CXXFLAGS="--coverage" LDFLAGS="--coverage" make test
+Then call `make` as usual and run the tests using `ctest`.
 
 If you are using `g++` use `gcov` to generate a report (results are in `*.gcov`
 files):
 
-    gcov -lp test/*tests.o test/t/*/*test_cases.o
+    gcov -lp $(find test/ -name '*.o')
 
 If you are using `clang++` use `llvm-cov` instead:
 
-    llvm-cov gcov -lp test/*tests.o test/t/*/*test_cases.o
+    llvm-cov gcov -lp $(find test/ -name '*.o')
 
 If you are using `g++` you can use `gcovr` to generate nice HTML output:
 
     mkdir -p coverage
-    gcovr -r . --html --html-details -o coverage/index.html
+    gcovr . -r SRCDIR --html --html-details -o coverage/index.html
 
 Open `coverage/index.html` in your browser to see the report.
 
 
 ## Clang-tidy
 
-Run
+After the CMake step, run
 
     make clang-tidy
 
 to check the code with [clang-tidy](https://clang.llvm.org/extra/clang-tidy/).
-You might have to change the `CLANG_TIDY` variable in the Makefile first.
+You might have to set `CLANG_TIDY` in CMake config.
 
 
 ## Cppcheck
 
-For extra checks with [Cppcheck](http://cppcheck.sourceforge.net/) you can call
+For extra checks with [Cppcheck](http://cppcheck.sourceforge.net/) you can,
+after the CMake step, call
+
+    make cppcheck
+
+
+## Installation
+
+After the CMake step, call `make install` to install the include files in
+`/usr/local/include/protozero`.
 
-    make check
+If you are using CMake to build your own software, you can copy the file
+`cmake/FindProtozero.cmake` and use it in your build. See the file for
+details.
 
 
 ## Who is using Protozero?
diff --git a/UPGRADING.md b/UPGRADING.md
index 7b34d8e..9a5db8e 100644
--- a/UPGRADING.md
+++ b/UPGRADING.md
@@ -13,6 +13,20 @@ macro `PROTOZERO_STRICT_API` in which case Protozero will compile without the
 code used for backwards compatibilty. You will then get compile errors for
 older API usages.
 
+## Upgrading from *v1.5* to *v1.6.0*
+
+* The `data_view` class moved from `types.hpp` into its own header file
+  `data_view.hpp`. Most people should not include those headers directly,
+  but if you do, you might have to change your includes.
+* There are two new exceptions `invalid_tag_exception` and
+  `invalid_length_exception` which cover cases that were only checked by
+  `assert` before this version. If you catch specific exceptions in your code
+  you might have to amend it. But just catching `protozero::exception` is
+  usually fine for most code (if you catch exceptions at all).
+* The `pbf_reader` constructor taking a `std::pair` is now deprecated. If you
+  are compiling with `PROTOZERO_STRICT_API` it is not available any more. Use
+  one of the other constructors instead.
+
 ## Upgrading from *v1.4.5* to *v1.5.0*
 
 * New functions for checking tag and type at the same time to make your
diff --git a/appveyor.yml b/appveyor.yml
index 80988cd..2770011 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,17 +1,53 @@
-os: Visual Studio 2015
+#-----------------------------------------------------------------------------
+#
+#  Configuration for continuous integration service at appveyor.com
+#
+#-----------------------------------------------------------------------------
 
-platform:
-  - x64
-  - x86
+platform: x64
 
-configuration:
-  - Debug
-  - Release
+image: Visual Studio 2017
+
+clone_depth: 1
+
+#-----------------------------------------------------------------------------
+
+environment:
+  matrix:
+  - config: MSYS2
+    autocrlf: true
+  - config: Debug
+    autocrlf: true
+  - config: RelWithDebInfo
+    autocrlf: true
+  - config: Debug
+    autocrlf: false
+  - config: RelWithDebInfo
+    autocrlf: false
+
+#-----------------------------------------------------------------------------
+
+init:
+  - git config --global core.autocrlf %autocrlf%
+  - git config --get core.autocrlf
 
 install:
-  - CALL build-appveyor.bat
+  - if [%config%]==[MSYS2] (
+          C:\msys64\usr\bin\pacman --noconfirm --sync --refresh --refresh --sysupgrade --sysupgrade
+       && C:\msys64\usr\bin\pacman -Rc --noconfirm mingw-w64-x86_64-gcc-libs
+    )
+
+build_script:
+  - if [%config%]==[MSYS2] (
+        build-msys2.bat
+    ) else (
+        build-appveyor.bat
+    )
+
+# remove garbage VS messages
+# http://help.appveyor.com/discussions/problems/4569-the-target-_convertpdbfiles-listed-in-a-beforetargets-attribute-at-c-does-not-exist-in-the-project-and-will-be-ignored
+before_build:
+  - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets"
+
 
-build: off
-test: off
-deploy: off
-#test
+#-----------------------------------------------------------------------------
diff --git a/build-appveyor.bat b/build-appveyor.bat
index d21177c..76e848c 100644
--- a/build-appveyor.bat
+++ b/build-appveyor.bat
@@ -2,35 +2,14 @@
 SETLOCAL
 SET EL=0
 
-ECHO =========== %~f0 ===========
+ECHO ~~~~~~ %~f0 ~~~~~~
 
-ECHO platform^: %platform%
-ECHO configuration^: %configuration%
+::show all available env vars
+SET
+ECHO cmake on AppVeyor
+cmake -version
 
-SET MSBUILD_VERBOSITY=normal
-IF NOT "%1"=="" SET MSBUILD_VERBOSITY=%1
-ECHO MSBUILD_VERBOSITY^: %MSBUILD_VERBOSITY%
-
-SET PATH=C:\Program Files\7-Zip;%PATH%
-SET PATH=c:\python27;%PATH%
-SET PATH="C:\Program Files (x86)\MSBuild\14.0\bin";%PATH%
-SET PATH=%~dp0deps\protobuf;%PATH%
-
-REM add unix style "find" to front of PATH
-REM used to glob files in gyp
-IF DEFINED APPVEYOR SET PATH=C:\Program Files\Git\usr\bin;%PATH%
-IF NOT DEFINED APPVEYOR SET PATH=%GIT_INSTALL_ROOT%\bin;%PATH%
-WHERE find
-
-IF EXIST %configuration% ECHO deleting %configuration% && RD /Q /S %configuration%
-IF %ERRORLEVEL% NEQ 0 GOTO ERROR
-
-IF EXIST protozero.sln DEL protozero.sln
-IF EXIST protozero.sdf DEL protozero.sdf
-DEL *.vcxproj
-DEL *.vcxproj.filters
-
-IF exist deps\gyp (ECHO gyp already cloned) ELSE (git clone --quiet --depth 1 https://chromium.googlesource.com/external/gyp.git deps/gyp)
+ECHO activating VS cmd prompt && CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
 SET protobuf_sdk=protozero-dep-protobuf-2.6.1.7z
@@ -38,42 +17,49 @@ IF EXIST %protobuf_sdk% (ECHO protobuf already downloaded) ELSE (ECHO downloadin
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 IF EXIST deps\protobuf (ECHO protobuf already extracted) ELSE (CALL 7z x -y %protobuf_sdk% | %windir%\system32\FIND "ing archive")
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+SET PATH=%~dp0deps\protobuf;%PATH%
 
-FOR /F %%x in ('find test/ -name "testcase.proto"') DO "deps\protobuf\%platform%\%configuration%\protoc" --cpp_out=. %%x
+IF EXIST build ECHO deleting build dir... && RD /Q /S build
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
-REM note windows requires --generator-output to be absolute
-python deps/gyp/gyp_main.py gyp/protozero.gyp --depth=. -f msvs -G msvs_version=2015
+MKDIR build
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
-SET MSBUILD_PLATFORM=%platform%
-IF /I "%MSBUILD_PLATFORM%" == "x86" set MSBUILD_PLATFORM=Win32
+CD build
+ECHO config^: %config%
+
+::This will produce lots of LNK4099 warnings which can be ignored.
+::Unfortunately they can't be disabled, see
+::http://stackoverflow.com/questions/661606/visual-c-how-to-disable-specific-linker-warnings
+SET CMAKE_CMD=cmake .. ^
+-LA -G "Visual Studio 14 Win64"
 
-msbuild gyp/protozero.sln ^
-/nologo ^
-/maxcpucount:%NUMBER_OF_PROCESSORS% ^
-/p:BuildInParellel=true ^
-/p:Configuration=%configuration%;Platform=%MSBUILD_PLATFORM% ^
-/verbosity:%MSBUILD_VERBOSITY% ^
-/consoleloggerparameters:Summary
+ECHO calling^: %CMAKE_CMD%
+%CMAKE_CMD%
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
-ECHO running gyp\%configuration%\%platform%\tests.exe ...
-gyp\%configuration%\%platform%\tests.exe
-:: IF %ERRORLEVEL% NEQ 0 GOTO ERROR
+SET avlogger=
+IF /I "%APPVEYOR%"=="True" SET avlogger=/logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
+
+msbuild protozero.sln ^
+/p:Configuration=%config% ^
+/toolsversion:14.0 ^
+/p:Platform=x64 ^
+/p:PlatformToolset=v140 %avlogger%
+IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
-ECHO running gyp\%configuration%\%platform%\writer_tests.exe ...
-gyp\%configuration%\%platform%\writer_tests.exe
+ctest --output-on-failure ^
+-C %config% ^
 IF %ERRORLEVEL% NEQ 0 GOTO ERROR
 
 GOTO DONE
 
 :ERROR
-ECHO ~~~~~~~~~ ERROR  %~f0 ~~~~~~~~~~~
+ECHO ~~~~~~ ERROR %~f0 ~~~~~~
 SET EL=%ERRORLEVEL%
-ECHO ERRORLEVEL^: %EL%
 
 :DONE
+IF %EL% NEQ 0 ECHO. && ECHO !!! ERRORLEVEL^: %EL% !!! && ECHO.
+ECHO ~~~~~~ DONE %~f0 ~~~~~~
 
-ECHO DONE %~f0 %platform% %configuration%
-EXIT /B %EL%
+EXIT /b %EL%
diff --git a/build-msys2.bat b/build-msys2.bat
new file mode 100644
index 0000000..85a4e03
--- /dev/null
+++ b/build-msys2.bat
@@ -0,0 +1,18 @@
+echo "Adding MSYS2 to path..."
+SET "PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%"
+echo %PATH%
+
+echo "Installing MSYS2 packages..."
+bash -lc "pacman -S --needed --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-doxygen mingw-w64-x86_64-protobuf"
+
+echo "Generating makefiles"
+mkdir build
+cd build
+cmake .. -G "MSYS Makefiles"
+
+echo "Building"
+make
+
+echo "Testing"
+ctest
+
diff --git a/cmake/FindProtozero.cmake b/cmake/FindProtozero.cmake
new file mode 100644
index 0000000..175dd23
--- /dev/null
+++ b/cmake/FindProtozero.cmake
@@ -0,0 +1,63 @@
+#----------------------------------------------------------------------
+#
+#  FindProtozero.cmake
+#
+#  Find the protozero headers.
+#
+#----------------------------------------------------------------------
+#
+#  Usage:
+#
+#    Copy this file somewhere into your project directory, where cmake can
+#    find it. Usually this will be a directory called "cmake" which you can
+#    add to the CMake module search path with the following line in your
+#    CMakeLists.txt:
+#
+#      list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
+#
+#    Then add the following in your CMakeLists.txt:
+#
+#      find_package(Protozero [version] [REQUIRED])
+#      include_directories(SYSTEM ${PROTOZERO_INCLUDE_DIR})
+#
+#    The version number is optional. If it is not set, any version of
+#    protozero will do.
+#
+#      if(NOT PROTOZERO_FOUND)
+#          message(WARNING "Protozero not found!\n")
+#      endif()
+#
+#----------------------------------------------------------------------
+#
+#  Variables:
+#
+#    PROTOZERO_FOUND        - True if Protozero was found.
+#    PROTOZERO_INCLUDE_DIR  - Where to find include files.
+#
+#----------------------------------------------------------------------
+
+# find include path
+find_path(PROTOZERO_INCLUDE_DIR protozero/version.hpp
+    PATH_SUFFIXES include
+    PATHS ../protozero
+)
+
+# Check version number
+if(Protozero_FIND_VERSION)
+    file(STRINGS "${PROTOZERO_INCLUDE_DIR}/protozero/version.hpp" _version_define REGEX "#define PROTOZERO_VERSION_STRING")
+    if("${_version_define}" MATCHES "#define PROTOZERO_VERSION_STRING \"([0-9.]+)\"")
+        set(_version "${CMAKE_MATCH_1}")
+    else()
+        set(_version "unknown")
+    endif()
+endif()
+
+#set(PROTOZERO_INCLUDE_DIRS "${PROTOZERO_INCLUDE_DIR}")
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Protozero
+                                  REQUIRED_VARS PROTOZERO_INCLUDE_DIR
+                                  VERSION_VAR _version)
+
+
+#----------------------------------------------------------------------
diff --git a/doc/.gitignore b/doc/.gitignore
deleted file mode 100644
index 03c8987..0000000
--- a/doc/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-doxygen_sqlite3.db
-html
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644
index 0000000..77c5420
--- /dev/null
+++ b/doc/CMakeLists.txt
@@ -0,0 +1,37 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake Config
+#
+#  protozero documentation
+#
+#-----------------------------------------------------------------------------
+
+message(STATUS "Configuring documentation")
+
+message(STATUS "Looking for doxygen")
+find_package(Doxygen)
+
+if(DOXYGEN_FOUND)
+    message(STATUS "Looking for doxygen - found")
+    configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
+
+    file(GLOB HEADER_FILES "${CMAKE_SOURCE_DIR}/include/protozero/*.hpp")
+    add_custom_command(OUTPUT html/index.html
+        COMMAND ${DOXYGEN_EXECUTABLE}
+        ARGS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
+        DEPENDS Doxyfile.in advanced.md cheatsheet.md tutorial.md
+                ${HEADER_FILES}
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+        COMMENT "Generating API documentation with Doxygen" VERBATIM)
+    add_custom_target(doc ALL
+                      DEPENDS html/index.html)
+else()
+    message(STATUS "Looking for doxygen - not found")
+    message(STATUS "  Disabled making of documentation.")
+endif()
+
+#-----------------------------------------------------------------------------
+message(STATUS "Configuring documentation - done")
+
+
+#-----------------------------------------------------------------------------
diff --git a/doc/Doxyfile b/doc/Doxyfile.in
similarity index 99%
rename from doc/Doxyfile
rename to doc/Doxyfile.in
index d0a076c..1481905 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile.in
@@ -38,7 +38,7 @@ PROJECT_NAME           = "protozero"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         =
+PROJECT_NUMBER         = @PROTOZERO_VERSION@
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -58,7 +58,7 @@ PROJECT_LOGO           =
 # entered, it will be relative to the location where doxygen was started. If
 # left blank the current directory will be used.
 
-OUTPUT_DIRECTORY       = doc
+OUTPUT_DIRECTORY       = "@PROJECT_BINARY_DIR@/doc"
 
 # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
 # directories (in 2 levels) under the output directory of each output format and
@@ -152,7 +152,7 @@ FULL_PATH_NAMES        = YES
 # will be relative from the directory where doxygen is started.
 # This tag requires that the tag FULL_PATH_NAMES is set to YES.
 
-STRIP_FROM_PATH        =
+STRIP_FROM_PATH        = @PROJECT_SOURCE_DIR@
 
 # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
 # path mentioned in the documentation of a class, which tells the reader which
@@ -753,7 +753,10 @@ WARN_LOGFILE           =
 # spaces.
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = README.md UPGRADING.md doc include/protozero
+INPUT                  = @PROJECT_SOURCE_DIR@/README.md \
+                         @PROJECT_SOURCE_DIR@/UPGRADING.md \
+                         @PROJECT_SOURCE_DIR@/doc \
+                         @PROJECT_SOURCE_DIR@/include/protozero
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -889,7 +892,7 @@ FILTER_SOURCE_PATTERNS =
 # (index.html). This can be useful if you have a project on for instance GitHub
 # and want to reuse the introduction page also for the doxygen output.
 
-USE_MDFILE_AS_MAINPAGE = README.md
+USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
 
 #---------------------------------------------------------------------------
 # Configuration options related to source browsing
diff --git a/doc/tutorial.md b/doc/tutorial.md
index 9525343..cccb69a 100644
--- a/doc/tutorial.md
+++ b/doc/tutorial.md
@@ -277,6 +277,18 @@ Protocol Buffers implementation.
 This exception indicates an illegal encoding of a varint. It means your input
 data is corrupted in some way.
 
+#### `invalid_tag_exception`
+
+This exception is thrown when a tag has an invalid value. Tags must be
+unsigned integers between 1 and 2^29-1. Tags between 19000 and 19999 are not
+allowed. See
+https://developers.google.com/protocol-buffers/docs/proto#assigning-tags
+
+#### `invalid_length_exception`
+
+This exception is thrown when a length field of a packed repeated field is
+invalid. For fixed size types the length must be a multiple of the size of
+the type.
 
 ### The `pbf_reader` class
 
diff --git a/gyp/common.gypi b/gyp/common.gypi
deleted file mode 100644
index 2e329aa..0000000
--- a/gyp/common.gypi
+++ /dev/null
@@ -1,109 +0,0 @@
-{
-  "conditions": [
-    ["OS=='win'", {
-          "target_defaults": {
-            "default_configuration": "Release_x64",
-            "msvs_configuration_attributes": {
-                "OutputDirectory": "$(SolutionDir)$(Configuration)\\$(PlatformTarget)\\",
-                },
-            "msvs_settings": {
-              "VCCLCompilerTool": {
-                "ExceptionHandling": 1, # /EHsc
-                "RuntimeTypeInfo": "true", # /GR
-                "ObjectFile": "$(SolutionDir)$(Configuration)\\$(PlatformTarget)\\%(RelativeDir)",
-              }
-            },
-            "configurations": {
-              "Debug_Win32": {
-                "msvs_configuration_platform": "Win32",
-                "defines": [ "DEBUG","_DEBUG"],
-                "msvs_settings": {
-                  "VCCLCompilerTool": {
-                    "RuntimeLibrary": "3", #0:static release /MT, 1:static debug /MTd, 2:shared /MD, 3:shared debug /MDd
-                    "Optimization": 0, # /Od, no optimization
-                    "MinimalRebuild": "false",
-                    "OmitFramePointers": "false",
-                    "BasicRuntimeChecks": 3 # /RTC1
-                  }
-                }
-              },
-              "Debug_x64": {
-                'inherit_from': ['Debug_Win32'],
-                "msvs_configuration_platform": "x64",
-              },
-              "Release_Win32": {
-                "msvs_configuration_platform": "Win32",
-                "defines": [ "NDEBUG"],
-                "msvs_settings": {
-                  "VCCLCompilerTool": {
-                    "RuntimeLibrary": "2", #0:static release /MT, 1:static debug /MTd, 2:shared /MD, 3:shared debug /MDd
-                    "Optimization": 3, # /Ox, full optimization
-                    "FavorSizeOrSpeed": 1, # /Ot, favour speed over size
-                    "InlineFunctionExpansion": 2, # /Ob2, inline anything eligible
-                    "WholeProgramOptimization": "true", # /GL, whole program optimization, needed for LTCG
-                    "OmitFramePointers": "true",
-                    "EnableFunctionLevelLinking": "true",
-                    "EnableIntrinsicFunctions": "true",
-                    #"AdditionalOptions": [
-                    #  "/MP", # compile across multiple CPUs
-                    #],
-                    "DebugInformationFormat": "0"
-                  },
-                  "VCLibrarianTool": {
-                    "AdditionalOptions": [
-                      "/LTCG" # link time code generation
-                    ],
-                  },
-                  "VCLinkerTool": {
-                    "LinkTimeCodeGeneration": 1, # link-time code generation
-                    "OptimizeReferences": 2, # /OPT:REF
-                    "EnableCOMDATFolding": 2, # /OPT:ICF
-                    "LinkIncremental": 1, # disable incremental linking
-                    "GenerateDebugInformation": "false"
-                  }
-                }
-              },
-              "Release_x64": {
-                'inherit_from': ['Release_Win32'],
-                "msvs_configuration_platform": "x64",
-              }
-            }
-          }
-    }, {
-        "target_defaults": {
-            "default_configuration": "Release",
-            "xcode_settings": {
-              "CLANG_CXX_LIBRARY": "libc++",
-              "CLANG_CXX_LANGUAGE_STANDARD":"c++11",
-              "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0",
-            },
-            "cflags_cc": ["-std=c++11"],
-            "configurations": {
-                "Debug": {
-                    "defines": [
-                        "DEBUG"
-                    ],
-                    "xcode_settings": {
-                        "GCC_OPTIMIZATION_LEVEL": "0",
-                        "GCC_GENERATE_DEBUGGING_SYMBOLS": "YES",
-                        "OTHER_CPLUSPLUSFLAGS": [ "-Wall", "-Wextra", "-pedantic", "-g", "-O0" ]
-                    }
-                },
-                "Release": {
-                    "defines": [
-                        "NDEBUG"
-                    ],
-                    "xcode_settings": {
-                        "GCC_OPTIMIZATION_LEVEL": "3",
-                        "GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
-                        "DEAD_CODE_STRIPPING": "YES",
-                        "GCC_INLINES_ARE_PRIVATE_EXTERN": "YES",
-                        "OTHER_CPLUSPLUSFLAGS": [ "-Wall", "-Wextra", "-pedantic", "-O3" ]
-                    }
-                }
-            }
-        }
-    }]
-  ]
-}
-
diff --git a/gyp/protozero.gyp b/gyp/protozero.gyp
deleted file mode 100644
index 0802f21..0000000
--- a/gyp/protozero.gyp
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "includes": [
-      "common.gypi"
-  ],
-  "targets": [
-    {
-      "target_name": "tests",
-      "type": "executable",
-      "sources": [
-        "<!@(find ../test/ -name '*.cpp' ! -name 'writer_tests.cpp' ! -name 'writer_test_cases.cpp' ! -name 'testcase.cpp')"
-      ],
-      "include_dirs": [
-        "../include/",
-        "../test/include/",
-        "../test/catch/"
-      ]
-    },
-    {
-      "target_name": "writer_tests",
-      "type": "executable",
-      "sources": [
-        "../test/writer_tests.cpp",
-        "<!@(find ../test/ -name 'writer_test_cases.cpp')",
-        "<!@(find ../include/protozero/ -name '*.hpp' ! -name 'config.hpp' ! -name 'pbf_builder.hpp' ! -name 'pbf_message.hpp' ! -name 'version.hpp')",
-        "<!@(find ../test/ -name '*.pb.cc')",
-      ],
-      "include_dirs": [
-        "../",
-        "../include/",
-        "../test/include/",
-        "../test/catch/",
-        "../deps/protobuf/include"
-      ],
-      "libraries" : [
-        "../deps/protobuf/$(PlatformTarget)/$(Configuration)/libprotobuf-lite.lib"
-      ]
-    }
-  ]
-}
diff --git a/include/protozero/types.hpp b/include/protozero/data_view.hpp
similarity index 62%
copy from include/protozero/types.hpp
copy to include/protozero/data_view.hpp
index 576c2e2..144025d 100644
--- a/include/protozero/types.hpp
+++ b/include/protozero/data_view.hpp
@@ -1,5 +1,5 @@
-#ifndef PROTOZERO_TYPES_HPP
-#define PROTOZERO_TYPES_HPP
+#ifndef PROTOZERO_DATA_VIEW_HPP
+#define PROTOZERO_DATA_VIEW_HPP
 
 /*****************************************************************************
 
@@ -11,14 +11,13 @@ documentation.
 *****************************************************************************/
 
 /**
- * @file types.hpp
+ * @file data_view.hpp
  *
- * @brief Contains the declaration of low-level types used in the pbf format.
+ * @brief Contains the implementation of the data_view class.
  */
 
 #include <algorithm>
 #include <cstddef>
-#include <cstdint>
 #include <cstring>
 #include <string>
 #include <utility>
@@ -27,41 +26,6 @@ documentation.
 
 namespace protozero {
 
-/**
- * The type used for field tags (field numbers).
- */
-using pbf_tag_type = uint32_t;
-
-/**
- * The type used to encode type information.
- * See the table on
- *    https://developers.google.com/protocol-buffers/docs/encoding
- */
-enum class pbf_wire_type : uint32_t {
-    varint           = 0, // int32/64, uint32/64, sint32/64, bool, enum
-    fixed64          = 1, // fixed64, sfixed64, double
-    length_delimited = 2, // string, bytes, embedded messages,
-                            // packed repeated fields
-    fixed32          = 5, // fixed32, sfixed32, float
-    unknown          = 99 // used for default setting in this library
-};
-
-/**
- * Get the tag and wire type of the current field in one integer suitable
- * for comparison with a switch statement.
- *
- * See pbf_reader.tag_and_type() for an example how to use this.
- */
-template <typename T>
-constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept {
-    return (static_cast<uint32_t>(static_cast<pbf_tag_type>(tag)) << 3) | static_cast<uint32_t>(wire_type);
-}
-
-/**
- * The type used for length values, such as the length of a field.
- */
-using pbf_length_type = uint32_t;
-
 #ifdef PROTOZERO_USE_VIEW
 using data_view = PROTOZERO_USE_VIEW;
 #else
@@ -141,6 +105,7 @@ public:
         return m_size == 0;
     }
 
+#ifndef PROTOZERO_STRICT_API
     /**
      * Convert data view to string.
      *
@@ -154,6 +119,7 @@ public:
         protozero_assert(m_data);
         return {m_data, m_size};
     }
+#endif
 
     /**
      * Convert data view to string.
@@ -165,6 +131,29 @@ public:
         return {m_data, m_size};
     }
 
+    /**
+     * Compares the contents of this object with the given other object.
+     *
+     * @returns 0 if they are the same, <0 if this object is smaller than
+     *          the other or >0 if it is larger. If both objects have the
+     *          same size returns <0 if this object is lexicographically
+     *          before the other, >0 otherwise.
+     *
+     * @pre Must not be default constructed data_view.
+     */
+    int compare(data_view other) const {
+        protozero_assert(m_data && other.m_data);
+        const int cmp = std::memcmp(data(), other.data(),
+                                    std::min(size(), other.size()));
+        if (cmp == 0) {
+            if (size() == other.size()) {
+                return 0;
+            }
+            return size() < other.size() ? -1 : 1;
+        }
+        return cmp;
+    }
+
 }; // class data_view
 
 /**
@@ -184,8 +173,9 @@ inline void swap(data_view& lhs, data_view& rhs) noexcept {
  * @param lhs First object.
  * @param rhs Second object.
  */
-inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
-    return lhs.size() == rhs.size() && std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data());
+inline constexpr bool operator==(const data_view lhs, const data_view rhs) noexcept {
+    return lhs.size() == rhs.size() &&
+           std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data());
 }
 
 /**
@@ -195,13 +185,52 @@ inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
  * @param lhs First object.
  * @param rhs Second object.
  */
-inline bool operator!=(const data_view& lhs, const data_view& rhs) noexcept {
+inline constexpr bool operator!=(const data_view lhs, const data_view rhs) noexcept {
     return !(lhs == rhs);
 }
 
-#endif
+/**
+ * Returns true if lhs.compare(rhs) < 0.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator<(const data_view lhs, const data_view rhs) noexcept {
+    return lhs.compare(rhs) < 0;
+}
 
+/**
+ * Returns true if lhs.compare(rhs) <= 0.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator<=(const data_view lhs, const data_view rhs) noexcept {
+    return lhs.compare(rhs) <= 0;
+}
+
+/**
+ * Returns true if lhs.compare(rhs) > 0.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator>(const data_view lhs, const data_view rhs) noexcept {
+    return lhs.compare(rhs) > 0;
+}
+
+/**
+ * Returns true if lhs.compare(rhs) >= 0.
+ *
+ * @param lhs First object.
+ * @param rhs Second object.
+ */
+inline bool operator>=(const data_view lhs, const data_view rhs) noexcept {
+    return lhs.compare(rhs) >= 0;
+}
+
+#endif
 
 } // end namespace protozero
 
-#endif // PROTOZERO_TYPES_HPP
+#endif // PROTOZERO_DATA_VIEW_HPP
diff --git a/include/protozero/exception.hpp b/include/protozero/exception.hpp
index ca4340e..8a20c94 100644
--- a/include/protozero/exception.hpp
+++ b/include/protozero/exception.hpp
@@ -63,6 +63,27 @@ struct end_of_buffer_exception : exception {
     const char* what() const noexcept override { return "end of buffer exception"; }
 };
 
+/**
+ * This exception is thrown when a tag has an invalid value. Tags must be
+ * unsigned integers between 1 and 2^29-1. Tags between 19000 and 19999 are
+ * not allowed. See
+ * https://developers.google.com/protocol-buffers/docs/proto#assigning-tags
+ */
+struct invalid_tag_exception : exception {
+    /// Returns the explanatory string.
+    const char* what() const noexcept override { return "invalid tag exception"; }
+};
+
+/**
+ * This exception is thrown when a length field of a packed repeated field is
+ * invalid. For fixed size types the length must be a multiple of the size of
+ * the type.
+ */
+struct invalid_length_exception : exception {
+    /// Returns the explanatory string.
+    const char* what() const noexcept override { return "invalid length exception"; }
+};
+
 } // end namespace protozero
 
 #endif // PROTOZERO_EXCEPTION_HPP
diff --git a/include/protozero/iterators.hpp b/include/protozero/iterators.hpp
index f8247a4..30bd7d6 100644
--- a/include/protozero/iterators.hpp
+++ b/include/protozero/iterators.hpp
@@ -105,7 +105,7 @@ public:
      * Complexity: Constant or linear depending on the underlaying iterator.
      */
     std::size_t size() const noexcept {
-        return begin().size();
+        return T::range_size(begin(), end());
     }
 
     /**
@@ -162,9 +162,6 @@ class const_fixed_iterator {
     /// Pointer to current iterator position
     const char* m_data = nullptr;
 
-    /// Pointer to end iterator position
-    const char* m_end = nullptr;
-
 public:
 
     using iterator_category = std::forward_iterator_tag;
@@ -173,11 +170,14 @@ public:
     using pointer           = value_type*;
     using reference         = value_type&;
 
+    static std::size_t range_size(const const_fixed_iterator& begin, const const_fixed_iterator& end) noexcept {
+        return static_cast<std::size_t>(end.m_data - begin.m_data) / sizeof(T);
+    }
+
     const_fixed_iterator() noexcept = default;
 
-    const_fixed_iterator(const char* data, const char* end) noexcept :
-        m_data(data),
-        m_end(end) {
+    explicit const_fixed_iterator(const char* data) noexcept :
+        m_data(data) {
     }
 
     const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
@@ -203,17 +203,13 @@ public:
     }
 
     const_fixed_iterator operator++(int) {
-        const const_fixed_iterator tmp(*this);
+        const const_fixed_iterator tmp{*this};
         ++(*this);
         return tmp;
     }
 
-    std::size_t size() const noexcept {
-        return static_cast<std::size_t>(m_end - m_data) / sizeof(T);
-    }
-
     bool operator==(const const_fixed_iterator& rhs) const noexcept {
-        return m_data == rhs.m_data && m_end == rhs.m_end;
+        return m_data == rhs.m_data;
     }
 
     bool operator!=(const const_fixed_iterator& rhs) const noexcept {
@@ -245,6 +241,15 @@ public:
     using pointer           = value_type*;
     using reference         = value_type&;
 
+    static std::size_t range_size(const const_varint_iterator& begin, const const_varint_iterator& end) noexcept {
+        // We know that each varint contains exactly one byte with the most
+        // significant bit not set. We can use this to quickly figure out
+        // how many varints there are without actually decoding the varints.
+        return static_cast<std::size_t>(std::count_if(begin.m_data, end.m_data, [](char c) {
+            return (static_cast<unsigned char>(c) & 0x80) == 0;
+        }));
+    }
+
     const_varint_iterator() noexcept = default;
 
     const_varint_iterator(const char* data, const char* end) noexcept :
@@ -271,20 +276,11 @@ public:
     }
 
     const_varint_iterator operator++(int) {
-        const const_varint_iterator tmp(*this);
+        const const_varint_iterator tmp{*this};
         ++(*this);
         return tmp;
     }
 
-    std::size_t size() const noexcept {
-        // We know that each varint contains exactly one byte with the most
-        // significant bit not set. We can use this to quickly figure out
-        // how many varints there are without actually decoding the varints.
-        return static_cast<std::size_t>(std::count_if(m_data, m_end, [](char c) {
-            return (static_cast<unsigned char>(c) & 0x80) == 0;
-        }));
-    }
-
     bool operator==(const const_varint_iterator& rhs) const noexcept {
         return m_data == rhs.m_data && m_end == rhs.m_end;
     }
@@ -337,7 +333,7 @@ public:
     }
 
     const_svarint_iterator operator++(int) {
-        const const_svarint_iterator tmp(*this);
+        const const_svarint_iterator tmp{*this};
         ++(*this);
         return tmp;
     }
diff --git a/include/protozero/pbf_reader.hpp b/include/protozero/pbf_reader.hpp
index 5efdebb..aea7015 100644
--- a/include/protozero/pbf_reader.hpp
+++ b/include/protozero/pbf_reader.hpp
@@ -23,6 +23,7 @@ documentation.
 #include <utility>
 
 #include <protozero/config.hpp>
+#include <protozero/data_view.hpp>
 #include <protozero/exception.hpp>
 #include <protozero/iterators.hpp>
 #include <protozero/types.hpp>
@@ -75,8 +76,9 @@ class pbf_reader {
     template <typename T>
     T get_fixed() {
         T result;
+        const char* data = m_data;
         skip_bytes(sizeof(T));
-        std::memcpy(&result, m_data - sizeof(T), sizeof(T));
+        std::memcpy(&result, data, sizeof(T));
 #if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
         detail::byteswap_inplace(&result);
 #endif
@@ -87,9 +89,11 @@ class pbf_reader {
     iterator_range<const_fixed_iterator<T>> packed_fixed() {
         protozero_assert(tag() != 0 && "call next() before accessing field value");
         const auto len = get_len_and_skip();
-        protozero_assert(len % sizeof(T) == 0);
-        return {const_fixed_iterator<T>(m_data - len, m_data),
-                const_fixed_iterator<T>(m_data, m_data)};
+        if (len % sizeof(T) != 0) {
+            throw invalid_length_exception{};
+        }
+        return {const_fixed_iterator<T>(m_data - len),
+                const_fixed_iterator<T>(m_data)};
     }
 
     template <typename T>
@@ -166,6 +170,7 @@ public:
           m_end(data + size) {
     }
 
+#ifndef PROTOZERO_STRICT_API
     /**
      * Construct a pbf_reader message from a data pointer and a length. The
      * pointer will be stored inside the pbf_reader object, no data is copied.
@@ -175,11 +180,13 @@ public:
      * The buffer must contain a complete protobuf message.
      *
      * @post There is no current field.
+     * @deprecated Use one of the other constructors.
      */
     explicit pbf_reader(const std::pair<const char*, std::size_t>& data) noexcept
         : m_data(data.first),
           m_end(data.first + data.second) {
     }
+#endif
 
     /**
      * Construct a pbf_reader message from a std::string. A pointer to the
@@ -275,9 +282,10 @@ public:
         m_tag = pbf_tag_type(value >> 3);
 
         // tags 0 and 19000 to 19999 are not allowed as per
-        // https://developers.google.com/protocol-buffers/docs/proto
-        protozero_assert(((m_tag >     0 && m_tag < 19000) ||
-                          (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range");
+        // https://developers.google.com/protocol-buffers/docs/proto#assigning-tags
+        if (m_tag == 0 || (m_tag >= 19000 && m_tag <= 19999)) {
+            throw invalid_tag_exception{};
+        }
 
         m_wire_type = pbf_wire_type(value & 0x07);
         switch (m_wire_type) {
@@ -459,7 +467,7 @@ public:
                 skip_bytes(4);
                 break;
             default:
-                protozero_assert(false && "can not be here because next() should have thrown already");
+                break;
         }
     }
 
@@ -478,9 +486,9 @@ public:
     bool get_bool() {
         protozero_assert(tag() != 0 && "call next() before accessing field value");
         protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint");
-        protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint");
-        skip_bytes(1);
-        return m_data[-1] != 0; // -1 okay because we incremented m_data the line before
+        const auto data = m_data;
+        skip_varint(&m_data, m_end);
+        return data[0] != 0;
     }
 
     /**
diff --git a/include/protozero/pbf_writer.hpp b/include/protozero/pbf_writer.hpp
index c50a46d..edf5522 100644
--- a/include/protozero/pbf_writer.hpp
+++ b/include/protozero/pbf_writer.hpp
@@ -26,6 +26,7 @@ documentation.
 #include <utility>
 
 #include <protozero/config.hpp>
+#include <protozero/data_view.hpp>
 #include <protozero/types.hpp>
 #include <protozero/varint.hpp>
 
diff --git a/include/protozero/types.hpp b/include/protozero/types.hpp
index 576c2e2..0d810e9 100644
--- a/include/protozero/types.hpp
+++ b/include/protozero/types.hpp
@@ -62,146 +62,6 @@ constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept
  */
 using pbf_length_type = uint32_t;
 
-#ifdef PROTOZERO_USE_VIEW
-using data_view = PROTOZERO_USE_VIEW;
-#else
-
-/**
- * Holds a pointer to some data and a length.
- *
- * This class is supposed to be compatible with the std::string_view
- * that will be available in C++17.
- */
-class data_view {
-
-    const char* m_data = nullptr;
-    std::size_t m_size = 0;
-
-public:
-
-    /**
-     * Default constructor. Construct an empty data_view.
-     */
-    constexpr data_view() noexcept = default;
-
-    /**
-     * Create data_view from pointer and size.
-     *
-     * @param ptr Pointer to the data.
-     * @param length Length of the data.
-     */
-    constexpr data_view(const char* ptr, std::size_t length) noexcept
-        : m_data(ptr),
-          m_size(length) {
-    }
-
-    /**
-     * Create data_view from string.
-     *
-     * @param str String with the data.
-     */
-    data_view(const std::string& str) noexcept // NOLINT clang-tidy: google-explicit-constructor
-        : m_data(str.data()),
-          m_size(str.size()) {
-    }
-
-    /**
-     * Create data_view from zero-terminated string.
-     *
-     * @param ptr Pointer to the data.
-     */
-    data_view(const char* ptr) noexcept // NOLINT clang-tidy: google-explicit-constructor
-        : m_data(ptr),
-          m_size(std::strlen(ptr)) {
-    }
-
-    /**
-     * Swap the contents of this object with the other.
-     *
-     * @param other Other object to swap data with.
-     */
-    void swap(data_view& other) noexcept {
-        using std::swap;
-        swap(m_data, other.m_data);
-        swap(m_size, other.m_size);
-    }
-
-    /// Return pointer to data.
-    constexpr const char* data() const noexcept {
-        return m_data;
-    }
-
-    /// Return length of data in bytes.
-    constexpr std::size_t size() const noexcept {
-        return m_size;
-    }
-
-    /// Returns true if size is 0.
-    constexpr bool empty() const noexcept {
-        return m_size == 0;
-    }
-
-    /**
-     * Convert data view to string.
-     *
-     * @pre Must not be default constructed data_view.
-     *
-     * @deprecated to_string() is not available in C++17 string_view so it
-     *             should not be used to make conversion to that class easier
-     *             in the future.
-     */
-    std::string to_string() const {
-        protozero_assert(m_data);
-        return {m_data, m_size};
-    }
-
-    /**
-     * Convert data view to string.
-     *
-     * @pre Must not be default constructed data_view.
-     */
-    explicit operator std::string() const {
-        protozero_assert(m_data);
-        return {m_data, m_size};
-    }
-
-}; // class data_view
-
-/**
- * Swap two data_view objects.
- *
- * @param lhs First object.
- * @param rhs Second object.
- */
-inline void swap(data_view& lhs, data_view& rhs) noexcept {
-    lhs.swap(rhs);
-}
-
-/**
- * Two data_view instances are equal if they have the same size and the
- * same content.
- *
- * @param lhs First object.
- * @param rhs Second object.
- */
-inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
-    return lhs.size() == rhs.size() && std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data());
-}
-
-/**
- * Two data_view instances are not equal if they have different sizes or the
- * content differs.
- *
- * @param lhs First object.
- * @param rhs Second object.
- */
-inline bool operator!=(const data_view& lhs, const data_view& rhs) noexcept {
-    return !(lhs == rhs);
-}
-
-#endif
-
-
 } // end namespace protozero
 
 #endif // PROTOZERO_TYPES_HPP
diff --git a/include/protozero/varint.hpp b/include/protozero/varint.hpp
index 6377671..21d170e 100644
--- a/include/protozero/varint.hpp
+++ b/include/protozero/varint.hpp
@@ -48,7 +48,7 @@ namespace detail {
                 b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) { break; }
                 b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) { break; }
                 b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) { break; }
-                b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) { break; }
+                b = *p++; val |= uint64_t((b & 0x01) << 63); if (b >= 0) { break; }
                 throw varint_too_long_exception{};
             } while (false);
         } else {
@@ -138,6 +138,7 @@ inline void skip_varint(const char** data, const char* end) {
  * @param data Output iterator the varint encoded value will be written to
  *             byte by byte.
  * @param value The integer that will be encoded.
+ * @returns the number of bytes written
  * @throws Any exception thrown by increment or dereference operator on data.
  */
 template <typename T>
diff --git a/include/protozero/version.hpp b/include/protozero/version.hpp
index 963632f..a0ab39c 100644
--- a/include/protozero/version.hpp
+++ b/include/protozero/version.hpp
@@ -20,15 +20,15 @@ documentation.
 #define PROTOZERO_VERSION_MAJOR 1
 
 /// The minor version number
-#define PROTOZERO_VERSION_MINOR 5
+#define PROTOZERO_VERSION_MINOR 6
 
 /// The patch number
-#define PROTOZERO_VERSION_PATCH 3
+#define PROTOZERO_VERSION_PATCH 0
 
 /// The complete version number
 #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
 
 /// Version number as string
-#define PROTOZERO_VERSION_STRING "1.5.3"
+#define PROTOZERO_VERSION_STRING "1.6.0"
 
 #endif // PROTOZERO_VERSION_HPP
diff --git a/include_dirs.js b/include_dirs.js
deleted file mode 100644
index b3c2686..0000000
--- a/include_dirs.js
+++ /dev/null
@@ -1,2 +0,0 @@
-var path = require('path');
-console.log(path.join(path.relative('.', __dirname),'include'));
diff --git a/package.json b/package.json
deleted file mode 100644
index cc38a04..0000000
--- a/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-    "name": "protozero",
-    "version": "1.5.3",
-    "description": "Minimalist protocol buffer decoder and encoder in C++",
-    "main": "include_dirs.js",
-    "repository"   :  {
-      "type" : "git",
-      "url"  : "git://github.com/mapbox/protozero.git"
-    }
-}
diff --git a/test/.gitignore b/test/.gitignore
deleted file mode 100644
index 02a9cc0..0000000
--- a/test/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-tests.o
-tests
-writer_tests.o
-writer_tests
-*.gcda
-*.gcno
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..9121636
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,120 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake config
+#
+#  protozero tests
+#
+#-----------------------------------------------------------------------------
+
+include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/catch")
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")
+
+set(TEST_DIRS alignment
+              basic
+              bool
+              bytes
+              complex
+              data_view
+              double
+              endian
+              enum
+              exceptions
+              fixed32
+              fixed64
+              float
+              int32
+              int64
+              message
+              nested
+              repeated
+              repeated_packed_bool
+              repeated_packed_double
+              repeated_packed_enum
+              repeated_packed_fixed32
+              repeated_packed_fixed64
+              repeated_packed_float
+              repeated_packed_int32
+              repeated_packed_int64
+              repeated_packed_sfixed32
+              repeated_packed_sfixed64
+              repeated_packed_sint32
+              repeated_packed_sint64
+              repeated_packed_uint32
+              repeated_packed_uint64
+              rollback
+              sfixed32
+              sfixed64
+              sint32
+              sint64
+              skip
+              string
+              tag_and_type
+              tags
+              uint32
+              uint64
+              varint
+              vector_tile
+              wrong_type_access
+              zigzag)
+
+string(REGEX REPLACE "([^;]+)" "t/\\1/reader_test_cases.cpp" _test_sources "${TEST_DIRS}")
+
+add_executable(reader_tests reader_tests.cpp ${_test_sources})
+
+add_test(NAME reader_tests COMMAND reader_tests)
+
+set_tests_properties(reader_tests PROPERTIES WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
+
+if(PROTOBUF_FOUND)
+    message(STATUS "Found protobuf libraries: Adding writer tests...")
+
+    include_directories(SYSTEM ${PROTOBUF_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
+
+    set(PROTOBUF_GENERATE_CPP_APPEND_PATH false)
+
+    foreach(_dir IN LISTS TEST_DIRS)
+        set(_full_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/t/${_dir}")
+        if(EXISTS "${_full_src_dir}/writer_test_cases.cpp")
+            message(STATUS "  Adding ${_dir}")
+            set(_full_bin_dir "${CMAKE_CURRENT_BINARY_DIR}/t/${_dir}")
+            set(_proto_file "${_full_src_dir}/testcase.proto")
+            set(_src_file "${_full_bin_dir}/testcase.pb.cc")
+            set(_hdr_file "${_full_bin_dir}/testcase.pb.h")
+
+            file(MAKE_DIRECTORY ${_full_bin_dir})
+
+            list(APPEND SOURCES     "${_full_src_dir}/writer_test_cases.cpp")
+            list(APPEND PROTO_FILES "${_proto_file}")
+            list(APPEND PROTO_SRCS  "${_src_file}")
+            list(APPEND PROTO_HDRS  "${_hdr_file}")
+
+            set_source_files_properties(${_proto_file} ${_hdr_file}
+                                        PROPERTIES GENERATED TRUE)
+
+            add_custom_command(
+                OUTPUT ${_src_file} ${_hdr_file}
+                COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
+                ARGS --cpp_out=${_full_bin_dir} -I ${_full_src_dir} ${_proto_file}
+                DEPENDS ${_proto_file}
+                VERBATIM)
+        endif()
+    endforeach()
+
+    add_executable(writer_tests writer_tests.cpp ${SOURCES} ${PROTO_SRCS} ${PROTO_HDRS})
+
+    target_link_libraries(writer_tests ${PROTOBUF_LITE_LIBRARY})
+
+    if(NOT MSVC)
+        set_target_properties(writer_tests PROPERTIES COMPILE_FLAGS "-pthread")
+        if(NOT APPLE)
+            set_target_properties(writer_tests PROPERTIES LINK_FLAGS "-pthread")
+        endif()
+    endif()
+
+    add_test(NAME writer_tests COMMAND writer_tests)
+else()
+    message(STATUS "Protobuf libraries not found: Disabling writer tests.")
+endif()
+
+
+#-----------------------------------------------------------------------------
diff --git a/test/README.md b/test/README.md
index fa97a7f..36d5751 100644
--- a/test/README.md
+++ b/test/README.md
@@ -3,33 +3,31 @@
 
 Tests are using the [Catch Unit Test Framework](https://github.com/philsquared/Catch).
 
-Call `make test` to compile and run all tests.
-
 
 ## Organization of the test cases
 
 Each test case is in its own directory under the `t` directory. Each directory
 contains (some of) the following files:
 
-* `test_cases.cpp`: The C++ source code that runs the tests.
-* `writer_test_cases.cpp`: The C++ source code that runs extra writer tests.
+* `reader_test_cases.cpp`: The C++ source code that runs the reader tests.
+* `writer_test_cases.cpp`: The C++ source code that runs the writer tests.
 * `data-*.pbf`: PBF data files used by the tests.
 * `testcase.proto`: Protobuf file describing the format of the data files.
 * `testcase.cpp`: C++ file for creating the data files.
 
-### Read/write tests
+### Reader tests
 
-The `Makefile` automatically finds all the `test_cases.cpp` files and
-compiles them. Together with the `tests.cpp` file they make up the
-`tests` executable which can be called to execute all the read tests.
+The CMake config finds all the `reader_test_cases.cpp` files and compiles them.
+Together with the `reader_tests.cpp` file they make up the `reader_tests`
+executable which can be called to execute all the reader tests.
 
 ### Extra writer tests
 
-The `Makefile` automatically finds all the `writer_test_cases.cpp` files and
-compiles them. Together with the `writer_tests.cpp` file they make up the
-`writer_tests` executable which can be called to execute all the write tests.
+The CMake config finds all the `writer_test_cases.cpp` files and compiles them.
+Together with the `writer_tests.cpp` file they make up the `writer_tests`
+executable which can be called to execute all the writer tests.
 
-The extra writer tests need the Google protobuf library to work.
+The writer tests need the Google protobuf library to work.
 
 
 ## Creating test data from scratch
diff --git a/test/include/packed_access.hpp b/test/include/packed_access.hpp
index a99f10a..eaeaceb 100644
--- a/test/include/packed_access.hpp
+++ b/test/include/packed_access.hpp
@@ -7,7 +7,6 @@
 using packed_field_type = PROTOZERO_TEST_CONCAT(protozero::packed_field_, PBF_TYPE);
 
 TEST_CASE("read repeated packed field: " PBF_TYPE_NAME) {
-
     // Run these tests twice, the second time we basically move the data
     // one byte down in the buffer. It doesn't matter how the data or buffer
     // is aligned before that, in at least one of these cases the ints will
@@ -15,7 +14,6 @@ TEST_CASE("read repeated packed field: " PBF_TYPE_NAME) {
     // will be extracted properly.
 
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
@@ -103,9 +101,8 @@ TEST_CASE("read repeated packed field: " PBF_TYPE_NAME) {
 }
 
 TEST_CASE("write repeated packed field: " PBF_TYPE_NAME) {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("empty") {
         cpp_type data[] = { 17 };
@@ -142,9 +139,8 @@ TEST_CASE("write repeated packed field: " PBF_TYPE_NAME) {
 }
 
 TEST_CASE("write repeated packed field using packed field: " PBF_TYPE_NAME) {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("empty - should do rollback") {
         {
@@ -185,11 +181,9 @@ TEST_CASE("write repeated packed field using packed field: " PBF_TYPE_NAME) {
 
         REQUIRE(buffer == load_data("repeated_packed_" PBF_TYPE_NAME "/data-many"));
     }
-
 }
 
 TEST_CASE("move repeated packed field: " PBF_TYPE_NAME) {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -247,9 +241,8 @@ TEST_CASE("move repeated packed field: " PBF_TYPE_NAME) {
 }
 
 TEST_CASE("write from different types of iterators: " PBF_TYPE_NAME) {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("from uint16_t") {
 #if PBF_TYPE_IS_SIGNED
@@ -297,6 +290,5 @@ TEST_CASE("write from different types of iterators: " PBF_TYPE_NAME) {
 
     REQUIRE_THROWS_AS(it_range.front(), const assert_error&);
     REQUIRE_THROWS_AS(it_range.drop_front(), const assert_error&);
-
 }
 
diff --git a/test/include/scalar_access.hpp b/test/include/scalar_access.hpp
index 79cb3f7..6f1e9df 100644
--- a/test/include/scalar_access.hpp
+++ b/test/include/scalar_access.hpp
@@ -91,11 +91,9 @@ TEST_CASE("read field: " PBF_TYPE_NAME) {
             REQUIRE_THROWS_AS(item.GET_TYPE(), const protozero::end_of_buffer_exception&);
         }
     }
-
 }
 
 TEST_CASE("write field: " PBF_TYPE_NAME) {
-
     std::string buffer;
     protozero::pbf_writer pw(buffer);
 
@@ -127,6 +125,5 @@ TEST_CASE("write field: " PBF_TYPE_NAME) {
         }
     }
 #endif
-
 }
 
diff --git a/test/include/test.hpp b/test/include/test.hpp
index 89d1e15..7542579 100644
--- a/test/include/test.hpp
+++ b/test/include/test.hpp
@@ -1,10 +1,6 @@
 #ifndef TEST_HPP
 #define TEST_HPP
 
-#ifdef _MSC_VER
-# define _SCL_SECURE_NO_WARNINGS
-#endif
-
 #include <catch.hpp>
 
 #include <stdexcept>
diff --git a/test/include/testcase.hpp b/test/include/testcase.hpp
index a4e33ec..c59afca 100644
--- a/test/include/testcase.hpp
+++ b/test/include/testcase.hpp
@@ -2,9 +2,9 @@
 #define TESTCASE_HPP
 
 #include <cassert>
-#include <string>
 #include <fstream>
 #include <limits>
+#include <string>
 
 template <class T>
 std::string write_to_file(const T& msg, const char* filename) {
diff --git a/test/tests.cpp b/test/reader_tests.cpp
similarity index 66%
rename from test/tests.cpp
rename to test/reader_tests.cpp
index 35ecd98..ed0f139 100644
--- a/test/tests.cpp
+++ b/test/reader_tests.cpp
@@ -7,11 +7,17 @@
 #include <test.hpp> // IWYU pragma: keep
 
 std::string load_data(const std::string& filename) {
-    std::string fullname{"test/t/"};
+    const char* tests_dir = std::getenv("TESTS_DIR");
+    if (tests_dir == nullptr) {
+        tests_dir = "test";
+    }
+
+    std::string fullname{tests_dir};
+    fullname += "/t/";
     fullname += filename;
     fullname += ".pbf";
 
-    std::ifstream stream{fullname, std::ios_base::in|std::ios_base::binary};
+    std::ifstream stream{fullname, std::ios_base::in | std::ios_base::binary};
     if (!stream.is_open()) {
         throw std::runtime_error{"could not open: '" + filename + "'"};
     }
diff --git a/test/t/.gitignore b/test/t/.gitignore
deleted file mode 100644
index 1799818..0000000
--- a/test/t/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-testcase
-testcase.pb.cc
-testcase.pb.h
-testcase.pb.o
-test_cases.o
-writer_test_cases.o
diff --git a/test/t/alignment/test_cases.cpp b/test/t/alignment/reader_test_cases.cpp
similarity index 99%
rename from test/t/alignment/test_cases.cpp
rename to test/t/alignment/reader_test_cases.cpp
index aa1c870..d7f995f 100644
--- a/test/t/alignment/test_cases.cpp
+++ b/test/t/alignment/reader_test_cases.cpp
@@ -8,7 +8,6 @@
 // will be extracted properly.
 
 TEST_CASE("check alignment issues for fixed32 field") {
-
     for (std::string::size_type n = 0; n < 2; ++n) {
 
         std::string abuffer;
@@ -73,15 +72,11 @@ TEST_CASE("check alignment issues for fixed32 field") {
             REQUIRE_THROWS(item.skip());
             REQUIRE_FALSE(item.next());
         }
-
     }
-
 }
 
 TEST_CASE("check alignment issues for fixed64 field") {
-
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
@@ -122,8 +117,6 @@ TEST_CASE("check alignment issues for fixed64 field") {
                 REQUIRE_THROWS_AS(item.get_fixed64(), const protozero::end_of_buffer_exception&);
             }
         }
-
     }
-
 }
 
diff --git a/test/t/basic/reader_test_cases.cpp b/test/t/basic/reader_test_cases.cpp
new file mode 100644
index 0000000..6d0dd55
--- /dev/null
+++ b/test/t/basic/reader_test_cases.cpp
@@ -0,0 +1,101 @@
+
+#include <test.hpp>
+
+TEST_CASE("default constructed pbf_reader is okay") {
+    protozero::pbf_reader item;
+
+    REQUIRE(item.length() == 0);
+    REQUIRE_FALSE(item); // test operator bool()
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("empty buffer in pbf_reader is okay") {
+    const std::string buffer;
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.length() == 0);
+    REQUIRE_FALSE(item); // test operator bool()
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("check every possible value for single byte in buffer") {
+    char buffer;
+    for (int i = 0; i <= 255; ++i) {
+        buffer = static_cast<char>(i);
+        protozero::pbf_reader item{&buffer, 1};
+
+        REQUIRE(item.length() == 1);
+        REQUIRE_FALSE(!item); // test operator bool()
+        REQUIRE_THROWS((item.next(), item.skip()));
+    }
+}
+
+TEST_CASE("next() should throw when illegal wire type is encountered") {
+    const char buffer = 1 << 3 | 7;
+
+    protozero::pbf_reader item{&buffer, 1};
+    REQUIRE_THROWS_AS(item.next(), const protozero::unknown_pbf_wire_type_exception&);
+}
+
+TEST_CASE("next() should throw when illegal tag is encountered") {
+    std::string data;
+
+    SECTION("tag 0") {
+        protozero::write_varint(std::back_inserter(data), 0 << 3 | 1);
+    }
+
+    SECTION("tag 19000") {
+        protozero::write_varint(std::back_inserter(data), 19000 << 3 | 1);
+    }
+
+    SECTION("tag 19001") {
+        protozero::write_varint(std::back_inserter(data), 19001 << 3 | 1);
+    }
+
+    SECTION("tag 19999") {
+        protozero::write_varint(std::back_inserter(data), 19999 << 3 | 1);
+    }
+
+    protozero::pbf_reader item{data};
+    REQUIRE_THROWS_AS(item.next(), const protozero::invalid_tag_exception&);
+}
+
+TEST_CASE("next() works when a legal tag is encountered") {
+    std::string data;
+
+    SECTION("tag 1") {
+        protozero::write_varint(std::back_inserter(data), 1u << 3 | 1);
+    }
+
+    SECTION("tag 18999") {
+        protozero::write_varint(std::back_inserter(data), 18999u << 3 | 1);
+    }
+
+    SECTION("tag 20000") {
+        protozero::write_varint(std::back_inserter(data), 20000u << 3 | 1);
+    }
+
+    SECTION("tag 1^29 - 1") {
+        protozero::write_varint(std::back_inserter(data), ((1u << 29) - 1) << 3 | 1);
+    }
+
+    protozero::pbf_reader item{data};
+    REQUIRE(item.next());
+}
+
+TEST_CASE("pbf_writer asserts on invalid tags") {
+    std::string data;
+    protozero::pbf_writer writer{data};
+
+    REQUIRE_THROWS_AS(writer.add_int32(0, 123), const assert_error&);
+    writer.add_int32(1, 123);
+    writer.add_int32(2, 123);
+    writer.add_int32(18999, 123);
+    REQUIRE_THROWS_AS(writer.add_int32(19000, 123), const assert_error&);
+    REQUIRE_THROWS_AS(writer.add_int32(19001, 123), const assert_error&);
+    REQUIRE_THROWS_AS(writer.add_int32(19999, 123), const assert_error&);
+    writer.add_int32(20000, 123);
+    writer.add_int32((1 << 29) - 1, 123);
+    REQUIRE_THROWS_AS(writer.add_int32(1 << 29, 123), const assert_error&);
+}
+
diff --git a/test/t/basic/test_cases.cpp b/test/t/basic/test_cases.cpp
deleted file mode 100644
index c992698..0000000
--- a/test/t/basic/test_cases.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("default constructed pbf_reader is okay") {
-    protozero::pbf_reader item;
-
-    REQUIRE(item.length() == 0);
-    REQUIRE_FALSE(item); // test operator bool()
-    REQUIRE_FALSE(item.next());
-}
-
-TEST_CASE("empty buffer in pbf_reader is okay") {
-    const std::string buffer;
-    protozero::pbf_reader item{buffer};
-
-    REQUIRE(item.length() == 0);
-    REQUIRE_FALSE(item); // test operator bool()
-    REQUIRE_FALSE(item.next());
-}
-
-TEST_CASE("check every possible value for single byte in buffer") {
-    char buffer;
-    for (int i = 0; i <= 255; ++i) {
-        buffer = static_cast<char>(i);
-        protozero::pbf_reader item{&buffer, 1};
-
-        REQUIRE(item.length() == 1);
-        REQUIRE_FALSE(!item); // test operator bool()
-        REQUIRE_THROWS((item.next(), item.skip()));
-    }
-}
-
-TEST_CASE("next() should throw when illegal wire type is encountered") {
-    char buffer = 1 << 3 | 7;
-
-    protozero::pbf_reader item{&buffer, 1};
-    REQUIRE_THROWS_AS(item.next(), const protozero::unknown_pbf_wire_type_exception&);
-}
-
diff --git a/test/t/bool/data-also-true.pbf b/test/t/bool/data-also-true.pbf
index e19a122..009a24f 100644
--- a/test/t/bool/data-also-true.pbf
+++ b/test/t/bool/data-also-true.pbf
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/test/t/bool/data-still-true.pbf b/test/t/bool/data-still-true.pbf
index e19a122..27e77d6 100644
--- a/test/t/bool/data-still-true.pbf
+++ b/test/t/bool/data-still-true.pbf
@@ -1 +1 @@
-
\ No newline at end of file
+�
\ No newline at end of file
diff --git a/test/t/bool/reader_test_cases.cpp b/test/t/bool/reader_test_cases.cpp
new file mode 100644
index 0000000..9bf518a
--- /dev/null
+++ b/test/t/bool/reader_test_cases.cpp
@@ -0,0 +1,141 @@
+
+#include <test.hpp>
+
+namespace TestBoolean {
+
+enum class Test : protozero::pbf_tag_type {
+    required_bool_b = 1
+};
+
+} // end namespace TestBoolean
+
+TEST_CASE("read bool field using pbf_reader: false") {
+    const std::string buffer = load_data("bool/data-false");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE_FALSE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_reader: true") {
+    const std::string buffer = load_data("bool/data-true");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_reader: also true") {
+    const std::string buffer = load_data("bool/data-also-true");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next(1));
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_reader: still true") {
+    const std::string buffer = load_data("bool/data-still-true");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next(1));
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_message: false") {
+    const std::string buffer = load_data("bool/data-false");
+
+    protozero::pbf_message<TestBoolean::Test> item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE_FALSE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_message: true") {
+    const std::string buffer = load_data("bool/data-true");
+
+    protozero::pbf_message<TestBoolean::Test> item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_message: also true") {
+    const std::string buffer = load_data("bool/data-also-true");
+
+    protozero::pbf_message<TestBoolean::Test> item{buffer};
+
+    REQUIRE(item.next(TestBoolean::Test::required_bool_b));
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read bool field using pbf_message: still true") {
+    const std::string buffer = load_data("bool/data-still-true");
+
+    protozero::pbf_message<TestBoolean::Test> item{buffer};
+
+    REQUIRE(item.next(TestBoolean::Test::required_bool_b));
+    REQUIRE(item.get_bool());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("write bool field using pbf_writer") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+
+    SECTION("false") {
+        pw.add_bool(1, false);
+        REQUIRE(buffer == load_data("bool/data-false"));
+    }
+
+    SECTION("true") {
+        pw.add_bool(1, true);
+        REQUIRE(buffer == load_data("bool/data-true"));
+    }
+}
+
+TEST_CASE("write bool field using pbf_builder") {
+    std::string buffer;
+    protozero::pbf_builder<TestBoolean::Test> pw{buffer};
+
+    SECTION("false") {
+        pw.add_bool(TestBoolean::Test::required_bool_b, false);
+        REQUIRE(buffer == load_data("bool/data-false"));
+    }
+
+    SECTION("true") {
+        pw.add_bool(TestBoolean::Test::required_bool_b, true);
+        REQUIRE(buffer == load_data("bool/data-true"));
+    }
+}
+
+TEST_CASE("write bool field using moved pbf_builder") {
+    std::string buffer;
+    protozero::pbf_builder<TestBoolean::Test> pw2{buffer};
+    REQUIRE(pw2.valid());
+
+    protozero::pbf_builder<TestBoolean::Test> pw{std::move(pw2)};
+    REQUIRE(pw.valid());
+    REQUIRE_FALSE(pw2.valid()); // NOLINT clang-tidy: hicpp-invalid-access-moved
+
+    SECTION("false") {
+        pw.add_bool(TestBoolean::Test::required_bool_b, false);
+        REQUIRE(buffer == load_data("bool/data-false"));
+    }
+
+    SECTION("true") {
+        pw.add_bool(TestBoolean::Test::required_bool_b, true);
+        REQUIRE(buffer == load_data("bool/data-true"));
+    }
+}
+
diff --git a/test/t/bool/test_cases.cpp b/test/t/bool/test_cases.cpp
deleted file mode 100644
index e87b0f6..0000000
--- a/test/t/bool/test_cases.cpp
+++ /dev/null
@@ -1,155 +0,0 @@
-
-#include <test.hpp>
-
-namespace TestBoolean {
-
-enum class Test : protozero::pbf_tag_type {
-    required_bool_b = 1
-};
-
-} // end namespace TestBoolean
-
-TEST_CASE("read bool field using pbf_reader") {
-
-    SECTION("false") {
-        const std::string buffer = load_data("bool/data-false");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE_FALSE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("true") {
-        const std::string buffer = load_data("bool/data-true");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("also true") {
-        const std::string buffer = load_data("bool/data-also-true");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next(1));
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("still true") {
-        const std::string buffer = load_data("bool/data-still-true");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next(1));
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-}
-
-TEST_CASE("read bool field using pbf_message") {
-
-    SECTION("false") {
-        const std::string buffer = load_data("bool/data-false");
-
-        protozero::pbf_message<TestBoolean::Test> item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE_FALSE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("true") {
-        const std::string buffer = load_data("bool/data-true");
-
-        protozero::pbf_message<TestBoolean::Test> item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("also true") {
-        const std::string buffer = load_data("bool/data-also-true");
-
-        protozero::pbf_message<TestBoolean::Test> item{buffer};
-
-        REQUIRE(item.next(TestBoolean::Test::required_bool_b));
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("still true") {
-        const std::string buffer = load_data("bool/data-still-true");
-
-        protozero::pbf_message<TestBoolean::Test> item{buffer};
-
-        REQUIRE(item.next(TestBoolean::Test::required_bool_b));
-        REQUIRE(item.get_bool());
-        REQUIRE_FALSE(item.next());
-    }
-
-}
-
-TEST_CASE("write bool field using pbf_writer") {
-
-    std::string buffer;
-    protozero::pbf_writer pw{buffer};
-
-    SECTION("false") {
-        pw.add_bool(1, false);
-        REQUIRE(buffer == load_data("bool/data-false"));
-    }
-
-    SECTION("true") {
-        pw.add_bool(1, true);
-        REQUIRE(buffer == load_data("bool/data-true"));
-    }
-
-}
-
-TEST_CASE("write bool field using pbf_builder") {
-
-    std::string buffer;
-    protozero::pbf_builder<TestBoolean::Test> pw{buffer};
-
-    SECTION("false") {
-        pw.add_bool(TestBoolean::Test::required_bool_b, false);
-        REQUIRE(buffer == load_data("bool/data-false"));
-    }
-
-    SECTION("true") {
-        pw.add_bool(TestBoolean::Test::required_bool_b, true);
-        REQUIRE(buffer == load_data("bool/data-true"));
-    }
-
-}
-
-TEST_CASE("write bool field using moved pbf_builder") {
-
-    std::string buffer;
-    protozero::pbf_builder<TestBoolean::Test> pw2{buffer};
-    REQUIRE(pw2.valid());
-
-    protozero::pbf_builder<TestBoolean::Test> pw{std::move(pw2)};
-    REQUIRE(pw.valid());
-    REQUIRE_FALSE(pw2.valid()); // NOLINT clang-tidy: hicpp-invalid-access-moved
-
-    SECTION("false") {
-        pw.add_bool(TestBoolean::Test::required_bool_b, false);
-        REQUIRE(buffer == load_data("bool/data-false"));
-    }
-
-    SECTION("true") {
-        pw.add_bool(TestBoolean::Test::required_bool_b, true);
-        REQUIRE(buffer == load_data("bool/data-true"));
-    }
-
-}
-
diff --git a/test/t/bool/testcase.cpp b/test/t/bool/testcase.cpp
index 3ad5083..b2a8f76 100644
--- a/test/t/bool/testcase.cpp
+++ b/test/t/bool/testcase.cpp
@@ -14,7 +14,7 @@ int main(int c, char *argv[]) {
     msg.set_b(2);
     write_to_file(msg, "data-also-true.pbf");
 
-    msg.set_b(127);
+    msg.set_b(2000);
     write_to_file(msg, "data-still-true.pbf");
 }
 
diff --git a/test/t/bool/testcase.proto b/test/t/bool/testcase.proto
index 3c9fefa..a341f38 100644
--- a/test/t/bool/testcase.proto
+++ b/test/t/bool/testcase.proto
@@ -5,7 +5,10 @@ package TestBoolean;
 
 message Test {
 
-    required bool b = 1;
+    // this should be bool, but we are using uint32
+    // to be able to encode values other than 0 (false)
+    // and 1 (true)
+    required uint32 b = 1;
 
 }
 
diff --git a/test/t/bool/writer_test_cases.cpp b/test/t/bool/writer_test_cases.cpp
index 9b26f62..457af6a 100644
--- a/test/t/bool/writer_test_cases.cpp
+++ b/test/t/bool/writer_test_cases.cpp
@@ -3,7 +3,7 @@
 
 #include <test.hpp> // IWYU pragma: keep
 
-#include "test/t/bool/testcase.pb.h"
+#include "t/bool/testcase.pb.h"
 
 TEST_CASE("write bool field and check with libprotobuf") {
 
diff --git a/test/t/bytes/test_cases.cpp b/test/t/bytes/reader_test_cases.cpp
similarity index 52%
rename from test/t/bytes/test_cases.cpp
rename to test/t/bytes/reader_test_cases.cpp
index d497f8c..a7a721b 100644
--- a/test/t/bytes/test_cases.cpp
+++ b/test/t/bytes/reader_test_cases.cpp
@@ -1,68 +1,63 @@
 
 #include <test.hpp>
 
-TEST_CASE("read bytes field") {
+TEST_CASE("read bytes field: empty") {
+    const std::string buffer = load_data("bytes/data-empty");
 
-    SECTION("empty") {
-        const std::string buffer = load_data("bytes/data-empty");
-
-        protozero::pbf_reader item{buffer};
+    protozero::pbf_reader item{buffer};
 
-        REQUIRE(item.next());
-        REQUIRE(item.get_bytes().empty());
-        REQUIRE_FALSE(item.next());
-    }
+    REQUIRE(item.next());
+    REQUIRE(item.get_bytes().empty());
+    REQUIRE_FALSE(item.next());
+}
 
-    SECTION("one") {
-        const std::string buffer = load_data("bytes/data-one");
+TEST_CASE("read bytes field: one") {
+    const std::string buffer = load_data("bytes/data-one");
 
-        protozero::pbf_reader item{buffer};
+    protozero::pbf_reader item{buffer};
 
-        REQUIRE(item.next());
-        REQUIRE(item.get_bytes() == "x");
-        REQUIRE_FALSE(item.next());
-    }
+    REQUIRE(item.next());
+    REQUIRE(item.get_bytes() == "x");
+    REQUIRE_FALSE(item.next());
+}
 
-    SECTION("string") {
-        const std::string buffer = load_data("bytes/data-string");
+TEST_CASE("read bytes field: string") {
+    const std::string buffer = load_data("bytes/data-string");
 
-        protozero::pbf_reader item{buffer};
+    protozero::pbf_reader item{buffer};
 
-        REQUIRE(item.next());
-        REQUIRE(item.get_bytes() == "foobar");
-        REQUIRE_FALSE(item.next());
-    }
+    REQUIRE(item.next());
+    REQUIRE(item.get_bytes() == "foobar");
+    REQUIRE_FALSE(item.next());
+}
 
-    SECTION("binary") {
-        const std::string buffer = load_data("bytes/data-binary");
+TEST_CASE("read bytes field: binary") {
+    const std::string buffer = load_data("bytes/data-binary");
 
-        protozero::pbf_reader item{buffer};
+    protozero::pbf_reader item{buffer};
 
-        REQUIRE(item.next());
-        const std::string data = item.get_bytes();
-        REQUIRE(data.size() == 3);
-        REQUIRE(data[0] == char(1));
-        REQUIRE(data[1] == char(2));
-        REQUIRE(data[2] == char(3));
-        REQUIRE_FALSE(item.next());
-    }
+    REQUIRE(item.next());
+    const std::string data = item.get_bytes();
+    REQUIRE(data.size() == 3);
+    REQUIRE(data[0] == char(1));
+    REQUIRE(data[1] == char(2));
+    REQUIRE(data[2] == char(3));
+    REQUIRE_FALSE(item.next());
+}
 
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("bytes/data-binary");
+TEST_CASE("read bytes field: end of buffer") {
+    const std::string buffer = load_data("bytes/data-binary");
 
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item{buffer.data(), i};
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_bytes(), const protozero::end_of_buffer_exception&);
-        }
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_bytes(), const protozero::end_of_buffer_exception&);
     }
-
 }
 
 TEST_CASE("write bytes field") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("empty") {
         pw.add_string(1, "");
@@ -89,13 +84,11 @@ TEST_CASE("write bytes field") {
 
         REQUIRE(buffer == load_data("bytes/data-binary"));
     }
-
 }
 
 TEST_CASE("write bytes field using vectored approach") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("using two strings") {
         std::string d1{"foo"};
@@ -134,10 +127,10 @@ TEST_CASE("write bytes field using vectored approach") {
 TEST_CASE("write bytes field using vectored approach with builder") {
     enum class foo : protozero::pbf_tag_type { bar = 1 };
     std::string buffer;
-    protozero::pbf_builder<foo> pw(buffer);
+    protozero::pbf_builder<foo> pw{buffer};
 
-    std::string d1{"foo"};
-    std::string d2{"bar"};
+    const std::string d1{"foo"};
+    const std::string d2{"bar"};
 
     pw.add_bytes_vectored(foo::bar, d1, d2);
 
diff --git a/test/t/bytes/writer_test_cases.cpp b/test/t/bytes/writer_test_cases.cpp
index 04bf1ee..29d3449 100644
--- a/test/t/bytes/writer_test_cases.cpp
+++ b/test/t/bytes/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/bytes/testcase.pb.h"
+#include "t/bytes/testcase.pb.h"
 
 TEST_CASE("write bytes field and check with libprotobuf") {
 
@@ -15,7 +15,7 @@ TEST_CASE("write bytes field and check with libprotobuf") {
 
         msg.ParseFromString(buffer);
 
-        REQUIRE(msg.s() == "");
+        REQUIRE(msg.s().empty());
     }
 
     SECTION("one") {
diff --git a/test/t/complex/reader_test_cases.cpp b/test/t/complex/reader_test_cases.cpp
new file mode 100644
index 0000000..5b722d8
--- /dev/null
+++ b/test/t/complex/reader_test_cases.cpp
@@ -0,0 +1,686 @@
+
+#include <test.hpp>
+
+namespace TestComplex {
+
+enum class Test : protozero::pbf_tag_type {
+    required_fixed32_f      = 1,
+    optional_int64_i        = 2,
+    optional_int64_j        = 3,
+    required_Sub_submessage = 5,
+    optional_string_s       = 8,
+    repeated_uint32_u       = 4,
+    packed_sint32_d         = 7
+};
+
+enum class Sub : protozero::pbf_tag_type {
+    required_string_s = 1
+};
+
+} // namespace TestComplex
+
+TEST_CASE("read complex data using pbf_reader: minimal") {
+    const std::string buffer = load_data("complex/data-minimal");
+
+    protozero::pbf_reader item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("read complex data using pbf_reader: some") {
+    const std::string buffer = load_data("complex/data-some");
+
+    protozero::pbf_reader item2{buffer};
+    protozero::pbf_reader item;
+    using std::swap;
+    swap(item, item2);
+
+    uint32_t sum_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 4: {
+                sum_of_u += item.get_uint32();
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(sum_of_u == 66);
+}
+
+TEST_CASE("read complex data using pbf_reader: all") {
+    const std::string buffer = load_data("complex/data-all");
+
+    protozero::pbf_reader item{buffer};
+
+    int number_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 3: {
+                REQUIRE(item.get_int64() == 555555555LL);
+                break;
+            }
+            case 4: {
+                item.skip();
+                ++number_of_u;
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            case 7: {
+                const auto pi = item.get_packed_sint32();
+                int32_t sum = 0;
+                for (auto val : pi) {
+                    sum += val;
+                }
+                REQUIRE(sum == 5);
+                break;
+            }
+            case 8: {
+                REQUIRE(item.get_string() == "optionalstring");
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(number_of_u == 5);
+}
+
+TEST_CASE("read complex data using pbf_reader: skip everything") {
+    const std::string buffer = load_data("complex/data-all");
+
+    protozero::pbf_reader item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1:
+            case 2:
+            case 3:
+            case 4:
+            case 5:
+            case 7:
+            case 8:
+                item.skip();
+                break;
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("read complex data using pbf_message: minimal") {
+    const std::string buffer = load_data("complex/data-minimal");
+
+    protozero::pbf_message<TestComplex::Test> item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case TestComplex::Test::required_fixed32_f: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case TestComplex::Test::required_Sub_submessage: {
+                protozero::pbf_message<TestComplex::Sub> subitem{item.get_message()};
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("read complex data using pbf_message: some") {
+    const std::string buffer = load_data("complex/data-some");
+
+    protozero::pbf_message<TestComplex::Test> item2{buffer};
+    protozero::pbf_message<TestComplex::Test> item;
+    using std::swap;
+    swap(item, item2);
+
+    uint32_t sum_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case TestComplex::Test::required_fixed32_f: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case TestComplex::Test::optional_int64_i: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case TestComplex::Test::repeated_uint32_u: {
+                sum_of_u += item.get_uint32();
+                break;
+            }
+            case TestComplex::Test::required_Sub_submessage: {
+                protozero::pbf_message<TestComplex::Sub> subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(sum_of_u == 66);
+}
+
+TEST_CASE("read complex data using pbf_message: all") {
+    const std::string buffer = load_data("complex/data-all");
+
+    protozero::pbf_message<TestComplex::Test> item{buffer};
+
+    int number_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case TestComplex::Test::required_fixed32_f: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case TestComplex::Test::optional_int64_i: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case TestComplex::Test::optional_int64_j: {
+                REQUIRE(item.get_int64() == 555555555LL);
+                break;
+            }
+            case TestComplex::Test::repeated_uint32_u: {
+                item.skip();
+                ++number_of_u;
+                break;
+            }
+            case TestComplex::Test::required_Sub_submessage: {
+                protozero::pbf_message<TestComplex::Sub> subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            case TestComplex::Test::packed_sint32_d: {
+                const auto pi = item.get_packed_sint32();
+                int32_t sum = 0;
+                for (auto val : pi) {
+                    sum += val;
+                }
+                REQUIRE(sum == 5);
+                break;
+            }
+            case TestComplex::Test::optional_string_s: {
+                REQUIRE(item.get_string() == "optionalstring");
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(number_of_u == 5);
+}
+
+TEST_CASE("read complex data using pbf_message: skip everything") {
+    const std::string buffer = load_data("complex/data-all");
+
+    protozero::pbf_message<TestComplex::Test> item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case TestComplex::Test::required_fixed32_f:
+            case TestComplex::Test::optional_int64_i:
+            case TestComplex::Test::optional_int64_j:
+            case TestComplex::Test::repeated_uint32_u:
+            case TestComplex::Test::required_Sub_submessage:
+            case TestComplex::Test::packed_sint32_d:
+            case TestComplex::Test::optional_string_s:
+                item.skip();
+                break;
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("write complex data using pbf_writer: minimal") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+    pw.add_fixed32(1, 12345678);
+
+    std::string submessage;
+    protozero::pbf_writer pws{submessage};
+    pws.add_string(1, "foobar");
+
+    pw.add_message(5, submessage);
+
+    protozero::pbf_reader item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("write complex data using pbf_writer: some") {
+    std::string buffer;
+    protozero::pbf_writer pw2{buffer};
+    pw2.add_fixed32(1, 12345678);
+
+    protozero::pbf_writer pw;
+    using std::swap;
+    swap(pw, pw2);
+
+    REQUIRE(pw.valid());
+    REQUIRE_FALSE(pw2.valid());
+
+    std::string submessage;
+    protozero::pbf_writer pws{submessage};
+    pws.add_string(1, "foobar");
+
+    pw.add_uint32(4, 22);
+    pw.add_uint32(4, 44);
+    pw.add_int64(2, -9876543);
+    pw.add_message(5, submessage);
+
+    protozero::pbf_reader item{buffer};
+
+    uint32_t sum_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 4: {
+                sum_of_u += item.get_uint32();
+                break;
+            }
+            case 5: {
+                const auto view = item.get_view();
+                protozero::pbf_reader subitem{view};
+                REQUIRE(subitem.next());
+                REQUIRE(std::string(subitem.get_view()) == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(sum_of_u == 66);
+}
+
+TEST_CASE("write complex data using pbf_writer: all") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+    pw.add_fixed32(1, 12345678);
+
+    std::string submessage;
+    protozero::pbf_writer pws{submessage};
+    pws.add_string(1, "foobar");
+    pw.add_message(5, submessage);
+
+    pw.add_uint32(4, 22);
+    pw.add_uint32(4, 44);
+    pw.add_int64(2, -9876543);
+    pw.add_uint32(4, 44);
+    pw.add_uint32(4, 66);
+    pw.add_uint32(4, 66);
+
+    const int32_t d[] = { -17, 22 };
+    pw.add_packed_sint32(7, std::begin(d), std::end(d));
+
+    pw.add_int64(3, 555555555);
+
+    protozero::pbf_reader item{buffer};
+
+    int number_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 3: {
+                REQUIRE(item.get_int64() == 555555555LL);
+                break;
+            }
+            case 4: {
+                item.skip();
+                ++number_of_u;
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            case 7: {
+                const auto pi = item.get_packed_sint32();
+                int32_t sum = 0;
+                for (auto val : pi) {
+                    sum += val;
+                }
+                REQUIRE(sum == 5);
+                break;
+            }
+            case 8: {
+                REQUIRE(item.get_string() == "optionalstring");
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(number_of_u == 5);
+}
+
+TEST_CASE("write complex data using pbf_builder: minimal") {
+    std::string buffer;
+    protozero::pbf_builder<TestComplex::Test> pw{buffer};
+    pw.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
+
+    std::string submessage;
+    protozero::pbf_builder<TestComplex::Sub> pws{submessage};
+    pws.add_string(TestComplex::Sub::required_string_s, "foobar");
+
+    pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
+
+    protozero::pbf_reader item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("write complex data using pbf_builder: some") {
+    std::string buffer;
+    protozero::pbf_builder<TestComplex::Test> pw2{buffer};
+    pw2.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
+
+    std::string dummy_buffer;
+    protozero::pbf_builder<TestComplex::Test> pw{dummy_buffer};
+    using std::swap;
+    swap(pw, pw2);
+
+    std::string submessage;
+    protozero::pbf_builder<TestComplex::Sub> pws{submessage};
+    pws.add_string(TestComplex::Sub::required_string_s, "foobar");
+
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 22);
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
+    pw.add_int64(TestComplex::Test::optional_int64_i, -9876543);
+    pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
+
+    protozero::pbf_reader item{buffer};
+
+    uint32_t sum_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 4: {
+                sum_of_u += item.get_uint32();
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(sum_of_u == 66);
+}
+
+TEST_CASE("write complex data using pbf_builder: all") {
+    std::string buffer;
+    protozero::pbf_builder<TestComplex::Test> pw{buffer};
+    pw.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
+
+    std::string submessage;
+    protozero::pbf_builder<TestComplex::Sub> pws{submessage};
+    pws.add_string(TestComplex::Sub::required_string_s, "foobar");
+    pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
+
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 22);
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
+    pw.add_int64(TestComplex::Test::optional_int64_i, -9876543);
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 66);
+    pw.add_uint32(TestComplex::Test::repeated_uint32_u, 66);
+
+    const int32_t d[] = { -17, 22 };
+    pw.add_packed_sint32(TestComplex::Test::packed_sint32_d, std::begin(d), std::end(d));
+
+    pw.add_int64(TestComplex::Test::optional_int64_j, 555555555);
+
+    protozero::pbf_reader item{buffer};
+
+    int number_of_u = 0;
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 12345678L);
+                break;
+            }
+            case 2: {
+                REQUIRE(true);
+                item.skip();
+                break;
+            }
+            case 3: {
+                REQUIRE(item.get_int64() == 555555555LL);
+                break;
+            }
+            case 4: {
+                item.skip();
+                ++number_of_u;
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            case 7: {
+                const auto pi = item.get_packed_sint32();
+                int32_t sum = 0;
+                for (auto val : pi) {
+                    sum += val;
+                }
+                REQUIRE(sum == 5);
+                break;
+            }
+            case 8: {
+                REQUIRE(item.get_string() == "optionalstring");
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+    REQUIRE(number_of_u == 5);
+}
+
+static void check_message(const std::string& buffer) {
+    protozero::pbf_reader item{buffer};
+
+    while (item.next()) {
+        switch (item.tag()) {
+            case 1: {
+                REQUIRE(item.get_fixed32() == 42L);
+                break;
+            }
+            case 5: {
+                protozero::pbf_reader subitem = item.get_message();
+                REQUIRE(subitem.next());
+                REQUIRE(subitem.get_string() == "foobar");
+                REQUIRE_FALSE(subitem.next());
+                break;
+            }
+            default: {
+                REQUIRE(false); // should not be here
+                break;
+            }
+        }
+    }
+}
+
+TEST_CASE("write complex with subwriter using pbf_writer") {
+    std::string buffer_test;
+    protozero::pbf_writer pbf_test{buffer_test};
+    pbf_test.add_fixed32(1, 42L);
+
+    SECTION("message in message") {
+        protozero::pbf_writer pbf_submessage{pbf_test, 5};
+        pbf_submessage.add_string(1, "foobar");
+    }
+
+    check_message(buffer_test);
+}
+
+TEST_CASE("write complex with subwriter using pbf_builder") {
+    std::string buffer_test;
+    protozero::pbf_builder<TestComplex::Test> pbf_test{buffer_test};
+    pbf_test.add_fixed32(TestComplex::Test::required_fixed32_f, 42L);
+
+    SECTION("message in message") {
+        protozero::pbf_builder<TestComplex::Sub> pbf_submessage{pbf_test, TestComplex::Test::required_Sub_submessage};
+        pbf_submessage.add_string(TestComplex::Sub::required_string_s, "foobar");
+    }
+
+    check_message(buffer_test);
+}
+
diff --git a/test/t/complex/test_cases.cpp b/test/t/complex/test_cases.cpp
deleted file mode 100644
index dfa1c54..0000000
--- a/test/t/complex/test_cases.cpp
+++ /dev/null
@@ -1,700 +0,0 @@
-
-#include <test.hpp>
-
-namespace TestComplex {
-
-enum class Test : protozero::pbf_tag_type {
-    required_fixed32_f      = 1,
-    optional_int64_i        = 2,
-    optional_int64_j        = 3,
-    required_Sub_submessage = 5,
-    optional_string_s       = 8,
-    repeated_uint32_u       = 4,
-    packed_sint32_d         = 7
-};
-
-enum class Sub : protozero::pbf_tag_type {
-    required_string_s = 1
-};
-
-} // end namespace TestComplex
-
-TEST_CASE("read complex data using pbf_reader") {
-
-    SECTION("minimal") {
-        const std::string buffer = load_data("complex/data-minimal");
-
-        protozero::pbf_reader item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-    SECTION("some") {
-        const std::string buffer = load_data("complex/data-some");
-
-        protozero::pbf_reader item2{buffer};
-        protozero::pbf_reader item;
-        using std::swap;
-        swap(item, item2);
-
-        uint32_t sum_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 4: {
-                    sum_of_u += item.get_uint32();
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(sum_of_u == 66);
-    }
-
-    SECTION("all") {
-        const std::string buffer = load_data("complex/data-all");
-
-        protozero::pbf_reader item{buffer};
-
-        int number_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 3: {
-                    REQUIRE(item.get_int64() == 555555555LL);
-                    break;
-                }
-                case 4: {
-                    item.skip();
-                    ++number_of_u;
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                case 7: {
-                    const auto pi = item.get_packed_sint32();
-                    int32_t sum = 0;
-                    for (auto val : pi) {
-                        sum += val;
-                    }
-                    REQUIRE(sum == 5);
-                    break;
-                }
-                case 8: {
-                    REQUIRE(item.get_string() == "optionalstring");
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(number_of_u == 5);
-    }
-
-    SECTION("skip everything") {
-        const std::string buffer = load_data("complex/data-all");
-
-        protozero::pbf_reader item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1:
-                case 2:
-                case 3:
-                case 4:
-                case 5:
-                case 7:
-                case 8:
-                    item.skip();
-                    break;
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-}
-
-TEST_CASE("read complex data using pbf_message") {
-
-    SECTION("minimal") {
-        const std::string buffer = load_data("complex/data-minimal");
-
-        protozero::pbf_message<TestComplex::Test> item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case TestComplex::Test::required_fixed32_f: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case TestComplex::Test::required_Sub_submessage: {
-                    protozero::pbf_message<TestComplex::Sub> subitem{item.get_message()};
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-    SECTION("some") {
-        const std::string buffer = load_data("complex/data-some");
-
-        protozero::pbf_message<TestComplex::Test> item2{buffer};
-        protozero::pbf_message<TestComplex::Test> item;
-        using std::swap;
-        swap(item, item2);
-
-        uint32_t sum_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case TestComplex::Test::required_fixed32_f: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case TestComplex::Test::optional_int64_i: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case TestComplex::Test::repeated_uint32_u: {
-                    sum_of_u += item.get_uint32();
-                    break;
-                }
-                case TestComplex::Test::required_Sub_submessage: {
-                    protozero::pbf_message<TestComplex::Sub> subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(sum_of_u == 66);
-    }
-
-    SECTION("all") {
-        const std::string buffer = load_data("complex/data-all");
-
-        protozero::pbf_message<TestComplex::Test> item{buffer};
-
-        int number_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case TestComplex::Test::required_fixed32_f: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case TestComplex::Test::optional_int64_i: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case TestComplex::Test::optional_int64_j: {
-                    REQUIRE(item.get_int64() == 555555555LL);
-                    break;
-                }
-                case TestComplex::Test::repeated_uint32_u: {
-                    item.skip();
-                    ++number_of_u;
-                    break;
-                }
-                case TestComplex::Test::required_Sub_submessage: {
-                    protozero::pbf_message<TestComplex::Sub> subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                case TestComplex::Test::packed_sint32_d: {
-                    const auto pi = item.get_packed_sint32();
-                    int32_t sum = 0;
-                    for (auto val : pi) {
-                        sum += val;
-                    }
-                    REQUIRE(sum == 5);
-                    break;
-                }
-                case TestComplex::Test::optional_string_s: {
-                    REQUIRE(item.get_string() == "optionalstring");
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(number_of_u == 5);
-    }
-
-    SECTION("skip everything") {
-        const std::string buffer = load_data("complex/data-all");
-
-        protozero::pbf_message<TestComplex::Test> item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case TestComplex::Test::required_fixed32_f:
-                case TestComplex::Test::optional_int64_i:
-                case TestComplex::Test::optional_int64_j:
-                case TestComplex::Test::repeated_uint32_u:
-                case TestComplex::Test::required_Sub_submessage:
-                case TestComplex::Test::packed_sint32_d:
-                case TestComplex::Test::optional_string_s:
-                    item.skip();
-                    break;
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-}
-
-TEST_CASE("write complex data using pbf_writer") {
-
-    SECTION("minimal") {
-        std::string buffer;
-        protozero::pbf_writer pw{buffer};
-        pw.add_fixed32(1, 12345678);
-
-        std::string submessage;
-        protozero::pbf_writer pws{submessage};
-        pws.add_string(1, "foobar");
-
-        pw.add_message(5, submessage);
-
-        protozero::pbf_reader item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-    SECTION("some") {
-        std::string buffer;
-        protozero::pbf_writer pw2{buffer};
-        pw2.add_fixed32(1, 12345678);
-
-        protozero::pbf_writer pw;
-        using std::swap;
-        swap(pw, pw2);
-
-        REQUIRE(pw.valid());
-        REQUIRE_FALSE(pw2.valid());
-
-        std::string submessage;
-        protozero::pbf_writer pws{submessage};
-        pws.add_string(1, "foobar");
-
-        pw.add_uint32(4, 22);
-        pw.add_uint32(4, 44);
-        pw.add_int64(2, -9876543);
-        pw.add_message(5, submessage);
-
-        protozero::pbf_reader item{buffer};
-
-        uint32_t sum_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 4: {
-                    sum_of_u += item.get_uint32();
-                    break;
-                }
-                case 5: {
-                    const auto view = item.get_view();
-                    protozero::pbf_reader subitem{view};
-                    REQUIRE(subitem.next());
-                    REQUIRE(std::string(subitem.get_view()) == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(sum_of_u == 66);
-    }
-
-    SECTION("all") {
-        std::string buffer;
-        protozero::pbf_writer pw{buffer};
-        pw.add_fixed32(1, 12345678);
-
-        std::string submessage;
-        protozero::pbf_writer pws{submessage};
-        pws.add_string(1, "foobar");
-        pw.add_message(5, submessage);
-
-        pw.add_uint32(4, 22);
-        pw.add_uint32(4, 44);
-        pw.add_int64(2, -9876543);
-        pw.add_uint32(4, 44);
-        pw.add_uint32(4, 66);
-        pw.add_uint32(4, 66);
-
-        const int32_t d[] = { -17, 22 };
-        pw.add_packed_sint32(7, std::begin(d), std::end(d));
-
-        pw.add_int64(3, 555555555);
-
-        protozero::pbf_reader item{buffer};
-
-        int number_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 3: {
-                    REQUIRE(item.get_int64() == 555555555LL);
-                    break;
-                }
-                case 4: {
-                    item.skip();
-                    ++number_of_u;
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                case 7: {
-                    const auto pi = item.get_packed_sint32();
-                    int32_t sum = 0;
-                    for (auto val : pi) {
-                        sum += val;
-                    }
-                    REQUIRE(sum == 5);
-                    break;
-                }
-                case 8: {
-                    REQUIRE(item.get_string() == "optionalstring");
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(number_of_u == 5);
-    }
-}
-
-TEST_CASE("write complex data using pbf_builder") {
-
-    SECTION("minimal") {
-        std::string buffer;
-        protozero::pbf_builder<TestComplex::Test> pw{buffer};
-        pw.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
-
-        std::string submessage;
-        protozero::pbf_builder<TestComplex::Sub> pws{submessage};
-        pws.add_string(TestComplex::Sub::required_string_s, "foobar");
-
-        pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
-
-        protozero::pbf_reader item{buffer};
-
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-    }
-
-    SECTION("some") {
-        std::string buffer;
-        protozero::pbf_builder<TestComplex::Test> pw2{buffer};
-        pw2.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
-
-        std::string dummy_buffer;
-        protozero::pbf_builder<TestComplex::Test> pw{dummy_buffer};
-        using std::swap;
-        swap(pw, pw2);
-
-        std::string submessage;
-        protozero::pbf_builder<TestComplex::Sub> pws{submessage};
-        pws.add_string(TestComplex::Sub::required_string_s, "foobar");
-
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 22);
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
-        pw.add_int64(TestComplex::Test::optional_int64_i, -9876543);
-        pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
-
-        protozero::pbf_reader item{buffer};
-
-        uint32_t sum_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 4: {
-                    sum_of_u += item.get_uint32();
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(sum_of_u == 66);
-    }
-
-    SECTION("all") {
-        std::string buffer;
-        protozero::pbf_builder<TestComplex::Test> pw{buffer};
-        pw.add_fixed32(TestComplex::Test::required_fixed32_f, 12345678);
-
-        std::string submessage;
-        protozero::pbf_builder<TestComplex::Sub> pws{submessage};
-        pws.add_string(TestComplex::Sub::required_string_s, "foobar");
-        pw.add_message(TestComplex::Test::required_Sub_submessage, submessage);
-
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 22);
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
-        pw.add_int64(TestComplex::Test::optional_int64_i, -9876543);
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 44);
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 66);
-        pw.add_uint32(TestComplex::Test::repeated_uint32_u, 66);
-
-        const int32_t d[] = { -17, 22 };
-        pw.add_packed_sint32(TestComplex::Test::packed_sint32_d, std::begin(d), std::end(d));
-
-        pw.add_int64(TestComplex::Test::optional_int64_j, 555555555);
-
-        protozero::pbf_reader item{buffer};
-
-        int number_of_u = 0;
-        while (item.next()) {
-            switch (item.tag()) {
-                case 1: {
-                    REQUIRE(item.get_fixed32() == 12345678L);
-                    break;
-                }
-                case 2: {
-                    REQUIRE(true);
-                    item.skip();
-                    break;
-                }
-                case 3: {
-                    REQUIRE(item.get_int64() == 555555555LL);
-                    break;
-                }
-                case 4: {
-                    item.skip();
-                    ++number_of_u;
-                    break;
-                }
-                case 5: {
-                    protozero::pbf_reader subitem = item.get_message();
-                    REQUIRE(subitem.next());
-                    REQUIRE(subitem.get_string() == "foobar");
-                    REQUIRE_FALSE(subitem.next());
-                    break;
-                }
-                case 7: {
-                    const auto pi = item.get_packed_sint32();
-                    int32_t sum = 0;
-                    for (auto val : pi) {
-                        sum += val;
-                    }
-                    REQUIRE(sum == 5);
-                    break;
-                }
-                case 8: {
-                    REQUIRE(item.get_string() == "optionalstring");
-                    break;
-                }
-                default: {
-                    REQUIRE(false); // should not be here
-                    break;
-                }
-            }
-        }
-        REQUIRE(number_of_u == 5);
-    }
-}
-
-static void check_message(const std::string& buffer) {
-    protozero::pbf_reader item{buffer};
-
-    while (item.next()) {
-        switch (item.tag()) {
-            case 1: {
-                REQUIRE(item.get_fixed32() == 42L);
-                break;
-            }
-            case 5: {
-                protozero::pbf_reader subitem = item.get_message();
-                REQUIRE(subitem.next());
-                REQUIRE(subitem.get_string() == "foobar");
-                REQUIRE_FALSE(subitem.next());
-                break;
-            }
-            default: {
-                REQUIRE(false); // should not be here
-                break;
-            }
-        }
-    }
-}
-
-TEST_CASE("write complex with subwriter using pbf_writer") {
-    std::string buffer_test;
-    protozero::pbf_writer pbf_test{buffer_test};
-    pbf_test.add_fixed32(1, 42L);
-
-    SECTION("message in message") {
-        protozero::pbf_writer pbf_submessage{pbf_test, 5};
-        pbf_submessage.add_string(1, "foobar");
-    }
-
-    check_message(buffer_test);
-}
-
-TEST_CASE("write complex with subwriter using pbf_builder") {
-    std::string buffer_test;
-    protozero::pbf_builder<TestComplex::Test> pbf_test{buffer_test};
-    pbf_test.add_fixed32(TestComplex::Test::required_fixed32_f, 42L);
-
-    SECTION("message in message") {
-        protozero::pbf_builder<TestComplex::Sub> pbf_submessage{pbf_test, TestComplex::Test::required_Sub_submessage};
-        pbf_submessage.add_string(TestComplex::Sub::required_string_s, "foobar");
-    }
-
-    check_message(buffer_test);
-}
-
diff --git a/test/t/data_view/test_cases.cpp b/test/t/data_view/reader_test_cases.cpp
similarity index 51%
rename from test/t/data_view/test_cases.cpp
rename to test/t/data_view/reader_test_cases.cpp
index a9c51c0..b4db378 100644
--- a/test/t/data_view/test_cases.cpp
+++ b/test/t/data_view/reader_test_cases.cpp
@@ -6,51 +6,51 @@
 #include <protozero/types.hpp>
 
 TEST_CASE("default constructed data_view") {
-    protozero::data_view view;
+    const protozero::data_view view{};
     REQUIRE(view.data() == nullptr);
     REQUIRE(view.size() == 0); // NOLINT clang-tidy: readability-container-size-empty
     REQUIRE(view.empty());
 }
 
 TEST_CASE("data_view from C string") {
-    protozero::data_view view{"foobar"};
+    const protozero::data_view view{"foobar"};
     REQUIRE(view.data());
     REQUIRE(view.size() == 6);
     REQUIRE_FALSE(view.empty());
 }
 
 TEST_CASE("data_view from std::string") {
-    std::string str{"foobar"};
-    protozero::data_view view{str};
+    const std::string str{"foobar"};
+    const protozero::data_view view{str};
     REQUIRE(view.data());
     REQUIRE(view.size() == 6);
 }
 
 TEST_CASE("data_view from ptr, size") {
-    std::string str{"foobar"};
-    protozero::data_view view{str.data(), str.size()};
+    const std::string str{"foobar"};
+    const protozero::data_view view{str.data(), str.size()};
     REQUIRE(view.data());
     REQUIRE(view.size() == 6);
 }
 
 TEST_CASE("data_view from C array") {
     const char* str = "foobar";
-    protozero::data_view view{str};
+    const protozero::data_view view{str};
     REQUIRE(view.data());
     REQUIRE(view.size() == 6);
 }
 
 TEST_CASE("data_view from std::array") {
-    std::array<char, 7> str{"foobar"};
-    protozero::data_view view{str.data(), 6};
+    const std::array<char, 7> str{"foobar"};
+    const protozero::data_view view{str.data(), 6};
     REQUIRE(view.data());
     REQUIRE(view.size() == 6);
 }
 
 TEST_CASE("convert data_view to std::string") {
-    protozero::data_view view{"foobar"};
+    const protozero::data_view view{"foobar"};
 
-    std::string s = std::string(view);
+    const std::string s = std::string(view);
     REQUIRE(s == "foobar");
     REQUIRE(std::string(view) == "foobar");
     REQUIRE(view.to_string() == "foobar");
@@ -60,7 +60,7 @@ TEST_CASE("convert data_view to std::string") {
 // This test only works with our own data_view implementation, because only
 // that one contains the protozero_assert() which generates the exception.
 TEST_CASE("converting default constructed data_view to string fails") {
-    protozero::data_view view;
+    const protozero::data_view view{};
     REQUIRE_THROWS_AS(view.to_string(), const assert_error&);
 }
 #endif
@@ -80,13 +80,13 @@ TEST_CASE("swapping data_view") {
 }
 
 TEST_CASE("comparing data_views") {
-    protozero::data_view v1{"foo"};
-    protozero::data_view v2{"bar"};
-    protozero::data_view v3{"foox"};
-    protozero::data_view v4{"foo"};
-    protozero::data_view v5{"fooooooo", 3};
-    protozero::data_view v6{"f\0o", 3};
-    protozero::data_view v7{"f\0obar", 3};
+    const protozero::data_view v1{"foo"};
+    const protozero::data_view v2{"bar"};
+    const protozero::data_view v3{"foox"};
+    const protozero::data_view v4{"foo"};
+    const protozero::data_view v5{"fooooooo", 3};
+    const protozero::data_view v6{"f\0o", 3};
+    const protozero::data_view v7{"f\0obar", 3};
 
     REQUIRE_FALSE(v1 == v2);
     REQUIRE_FALSE(v1 == v3);
@@ -113,3 +113,50 @@ TEST_CASE("comparing data_views") {
     REQUIRE_FALSE(v6 != v7);
 }
 
+TEST_CASE("ordering of data_views") {
+    const protozero::data_view v1{"foo"};
+    const protozero::data_view v2{"foo"};
+    const protozero::data_view v3{"bar"};
+    const protozero::data_view v4{"foox"};
+    const protozero::data_view v5{"zzz"};
+
+    REQUIRE(v1.compare(v1) == 0);
+    REQUIRE(v1.compare(v2) == 0);
+    REQUIRE(v1.compare(v3) > 0);
+    REQUIRE(v1.compare(v4) < 0);
+    REQUIRE(v1.compare(v5) < 0);
+
+    REQUIRE(v2.compare(v1) == 0);
+    REQUIRE(v2.compare(v2) == 0);
+    REQUIRE(v2.compare(v3) > 0);
+    REQUIRE(v2.compare(v4) < 0);
+    REQUIRE(v2.compare(v5) < 0);
+
+    REQUIRE(v3.compare(v1) < 0);
+    REQUIRE(v3.compare(v2) < 0);
+    REQUIRE(v3.compare(v3) == 0);
+    REQUIRE(v3.compare(v4) < 0);
+    REQUIRE(v3.compare(v5) < 0);
+
+    REQUIRE(v4.compare(v1) > 0);
+    REQUIRE(v4.compare(v2) > 0);
+    REQUIRE(v4.compare(v3) > 0);
+    REQUIRE(v4.compare(v4) == 0);
+    REQUIRE(v4.compare(v5) < 0);
+
+    REQUIRE(v5.compare(v1) > 0);
+    REQUIRE(v5.compare(v2) > 0);
+    REQUIRE(v5.compare(v3) > 0);
+    REQUIRE(v5.compare(v4) > 0);
+    REQUIRE(v5.compare(v5) == 0);
+
+    REQUIRE(v1 == v2);
+    REQUIRE(v1 <= v2);
+    REQUIRE(v1 >= v2);
+    REQUIRE(v1 < v4);
+    REQUIRE(v3 < v1);
+    REQUIRE(v3 <= v1);
+    REQUIRE(v5 > v1);
+    REQUIRE(v5 >= v1);
+}
+
diff --git a/test/t/double/test_cases.cpp b/test/t/double/reader_test_cases.cpp
similarity index 99%
rename from test/t/double/test_cases.cpp
rename to test/t/double/reader_test_cases.cpp
index e4165f5..4c22fcd 100644
--- a/test/t/double/test_cases.cpp
+++ b/test/t/double/reader_test_cases.cpp
@@ -2,15 +2,12 @@
 #include <test.hpp>
 
 TEST_CASE("read double field") {
-
     // Run these tests twice, the second time we basically move the data
     // one byte down in the buffer. It doesn't matter how the data or buffer
     // is aligned before that, in at least one of these cases the double will
     // not be aligned properly. So we test that even in that case the double
     // will be extracted properly.
-
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
@@ -51,13 +48,10 @@ TEST_CASE("read double field") {
                 REQUIRE_THROWS_AS(item.get_double(), const protozero::end_of_buffer_exception&);
             }
         }
-
     }
-
 }
 
 TEST_CASE("write double field") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -75,6 +69,5 @@ TEST_CASE("write double field") {
         pw.add_double(1, -9232.33);
         REQUIRE(buffer == load_data("double/data-neg"));
     }
-
 }
 
diff --git a/test/t/double/writer_test_cases.cpp b/test/t/double/writer_test_cases.cpp
index c4a85dd..5ec5e1c 100644
--- a/test/t/double/writer_test_cases.cpp
+++ b/test/t/double/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/double/testcase.pb.h"
+#include "t/double/testcase.pb.h"
 
 TEST_CASE("write double field and check with libprotobuf") {
 
diff --git a/test/t/endian/test_cases.cpp b/test/t/endian/reader_test_cases.cpp
similarity index 72%
rename from test/t/endian/test_cases.cpp
rename to test/t/endian/reader_test_cases.cpp
index 8968e09..95aafe0 100644
--- a/test/t/endian/test_cases.cpp
+++ b/test/t/endian/reader_test_cases.cpp
@@ -30,14 +30,30 @@ TEST_CASE("byte swapping") {
     REQUIRE(0 == check_swap_8(0));
     REQUIRE(1 == check_swap_8(1));
     REQUIRE(-1 == check_swap_8(-1));
-    REQUIRE(395503 == check_swap_8(395503));
-    REQUIRE(-804022 == check_swap_8(-804022));
-    REQUIRE(3280329805 == check_swap_8(3280329805));
-    REQUIRE(-2489204041 == check_swap_8(-2489204041));
+    REQUIRE(395503ll == check_swap_8(395503ll));
+    REQUIRE(-804022ll == check_swap_8(-804022ll));
+    REQUIRE(3280329805ll == check_swap_8(3280329805ll));
+    REQUIRE(-2489204041ll == check_swap_8(-2489204041ll));
     REQUIRE(std::numeric_limits<int64_t>::max() == check_swap_8(std::numeric_limits<int64_t>::max()));
     REQUIRE(std::numeric_limits<int64_t>::min() == check_swap_8(std::numeric_limits<int64_t>::min()));
 }
 
+TEST_CASE("byte swap uint32_t") {
+    uint32_t a = 17;
+    protozero::detail::byteswap_inplace(&a);
+    protozero::detail::byteswap_inplace(&a);
+
+    REQUIRE(17 == a);
+}
+
+TEST_CASE("byte swap uint64_t") {
+    uint64_t a = 347529808;
+    protozero::detail::byteswap_inplace(&a);
+    protozero::detail::byteswap_inplace(&a);
+
+    REQUIRE(347529808 == a);
+}
+
 TEST_CASE("byte swap double") {
     double a = 1.1;
     protozero::detail::byteswap_inplace(&a);
diff --git a/test/t/enum/reader_test_cases.cpp b/test/t/enum/reader_test_cases.cpp
new file mode 100644
index 0000000..026d1ed
--- /dev/null
+++ b/test/t/enum/reader_test_cases.cpp
@@ -0,0 +1,83 @@
+
+#include <test.hpp>
+
+TEST_CASE("read enum field: zero") {
+    const std::string buffer = load_data("enum/data-black");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_enum() == 0L);
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read enum field: positive") {
+    const std::string buffer = load_data("enum/data-blue");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_enum() == 3L);
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read enum field: negative") {
+    const std::string buffer = load_data("enum/data-neg");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_enum() == -1L);
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read enum field: max") {
+    const std::string buffer = load_data("enum/data-max");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_enum() == std::numeric_limits<int32_t>::max());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read enum field: min") {
+    const std::string buffer = load_data("enum/data-min");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_enum() == (std::numeric_limits<int32_t>::min() + 1));
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("write enum field") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+
+    SECTION("zero") {
+        pw.add_enum(1, 0L);
+        REQUIRE(buffer == load_data("enum/data-black"));
+    }
+
+    SECTION("positive") {
+        pw.add_enum(1, 3L);
+        REQUIRE(buffer == load_data("enum/data-blue"));
+    }
+
+    SECTION("negative") {
+        pw.add_enum(1, -1L);
+        REQUIRE(buffer == load_data("enum/data-neg"));
+    }
+
+    SECTION("max") {
+        pw.add_enum(1, std::numeric_limits<int32_t>::max());
+        REQUIRE(buffer == load_data("enum/data-max"));
+    }
+
+    SECTION("min") {
+        pw.add_enum(1, std::numeric_limits<int32_t>::min() + 1);
+        REQUIRE(buffer == load_data("enum/data-min"));
+    }
+}
+
diff --git a/test/t/enum/test_cases.cpp b/test/t/enum/test_cases.cpp
deleted file mode 100644
index 484d873..0000000
--- a/test/t/enum/test_cases.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("read enum field") {
-
-    SECTION("zero") {
-        const std::string buffer = load_data("enum/data-black");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_enum() == 0L);
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("positive") {
-        const std::string buffer = load_data("enum/data-blue");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_enum() == 3L);
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("negative") {
-        const std::string buffer = load_data("enum/data-neg");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_enum() == -1L);
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("max") {
-        const std::string buffer = load_data("enum/data-max");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_enum() == std::numeric_limits<int32_t>::max());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("min") {
-        const std::string buffer = load_data("enum/data-min");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_enum() == (std::numeric_limits<int32_t>::min() + 1));
-        REQUIRE_FALSE(item.next());
-    }
-
-}
-
-TEST_CASE("write enum field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw{buffer};
-
-    SECTION("zero") {
-        pw.add_enum(1, 0L);
-        REQUIRE(buffer == load_data("enum/data-black"));
-    }
-
-    SECTION("positive") {
-        pw.add_enum(1, 3L);
-        REQUIRE(buffer == load_data("enum/data-blue"));
-    }
-
-    SECTION("negative") {
-        pw.add_enum(1, -1L);
-        REQUIRE(buffer == load_data("enum/data-neg"));
-    }
-
-    SECTION("max") {
-        pw.add_enum(1, std::numeric_limits<int32_t>::max());
-        REQUIRE(buffer == load_data("enum/data-max"));
-    }
-
-    SECTION("min") {
-        pw.add_enum(1, std::numeric_limits<int32_t>::min() + 1);
-        REQUIRE(buffer == load_data("enum/data-min"));
-    }
-
-}
-
diff --git a/test/t/enum/testcase.proto b/test/t/enum/testcase.proto
index 12eef42..d99d7ee 100644
--- a/test/t/enum/testcase.proto
+++ b/test/t/enum/testcase.proto
@@ -8,7 +8,7 @@ enum Color {
     RED   = 1;
     GREEN = 2;
     BLUE  = 3;
-    MAX   = 2147483647;
+    MAX   = 2147483646;
     NEG   = -1;
 
     // Older versions (before 2.6.0) of the google protobuf compiler have a
diff --git a/test/t/enum/writer_test_cases.cpp b/test/t/enum/writer_test_cases.cpp
index 1f3a4c3..2d20c89 100644
--- a/test/t/enum/writer_test_cases.cpp
+++ b/test/t/enum/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/enum/testcase.pb.h"
+#include "t/enum/testcase.pb.h"
 
 TEST_CASE("write enum field and check with libprotobuf") {
 
@@ -35,7 +35,7 @@ TEST_CASE("write enum field and check with libprotobuf") {
     }
 
     SECTION("max") {
-        pw.add_enum(1, std::numeric_limits<int32_t>::max());
+        pw.add_enum(1, std::numeric_limits<int32_t>::max() - 1);
 
         msg.ParseFromString(buffer);
 
diff --git a/test/t/exceptions/test_cases.cpp b/test/t/exceptions/reader_test_cases.cpp
similarity index 67%
rename from test/t/exceptions/test_cases.cpp
rename to test/t/exceptions/reader_test_cases.cpp
index b1063f1..1c4170c 100644
--- a/test/t/exceptions/test_cases.cpp
+++ b/test/t/exceptions/reader_test_cases.cpp
@@ -21,3 +21,13 @@ TEST_CASE("exceptions messages for end of buffer") {
     REQUIRE(std::string{e.what()} == std::string{"end of buffer exception"});
 }
 
+TEST_CASE("exceptions messages for invalid tag") {
+    protozero::invalid_tag_exception e;
+    REQUIRE(std::string{e.what()} == std::string{"invalid tag exception"});
+}
+
+TEST_CASE("exceptions messages for invalid length") {
+    protozero::invalid_length_exception e;
+    REQUIRE(std::string{e.what()} == std::string{"invalid length exception"});
+}
+
diff --git a/test/t/fixed32/test_cases.cpp b/test/t/fixed32/reader_test_cases.cpp
similarity index 100%
rename from test/t/fixed32/test_cases.cpp
rename to test/t/fixed32/reader_test_cases.cpp
diff --git a/test/t/fixed32/writer_test_cases.cpp b/test/t/fixed32/writer_test_cases.cpp
index f09742c..6fbf023 100644
--- a/test/t/fixed32/writer_test_cases.cpp
+++ b/test/t/fixed32/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/fixed32/testcase.pb.h"
+#include "t/fixed32/testcase.pb.h"
 
 TEST_CASE("write fixed32 field and check with libprotobuf") {
 
diff --git a/test/t/fixed64/test_cases.cpp b/test/t/fixed64/reader_test_cases.cpp
similarity index 100%
rename from test/t/fixed64/test_cases.cpp
rename to test/t/fixed64/reader_test_cases.cpp
diff --git a/test/t/float/test_cases.cpp b/test/t/float/reader_test_cases.cpp
similarity index 98%
rename from test/t/float/test_cases.cpp
rename to test/t/float/reader_test_cases.cpp
index 9e92740..f1891b0 100644
--- a/test/t/float/test_cases.cpp
+++ b/test/t/float/reader_test_cases.cpp
@@ -2,7 +2,6 @@
 #include <test.hpp>
 
 TEST_CASE("read float field") {
-
     // Run these tests twice, the second time we basically move the data
     // one byte down in the buffer. It doesn't matter how the data or buffer
     // is aligned before that, in at least one of these cases the float will
@@ -10,7 +9,6 @@ TEST_CASE("read float field") {
     // will be extracted properly.
 
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
@@ -51,15 +49,12 @@ TEST_CASE("read float field") {
                 REQUIRE_THROWS_AS(item.get_float(), const protozero::end_of_buffer_exception&);
             }
         }
-
     }
-
 }
 
 TEST_CASE("write float field") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("zero") {
         pw.add_float(1, 0.0f);
@@ -75,6 +70,5 @@ TEST_CASE("write float field") {
         pw.add_float(1, -1.71f);
         REQUIRE(buffer == load_data("float/data-neg"));
     }
-
 }
 
diff --git a/test/t/int32/test_cases.cpp b/test/t/int32/reader_test_cases.cpp
similarity index 100%
rename from test/t/int32/test_cases.cpp
rename to test/t/int32/reader_test_cases.cpp
diff --git a/test/t/int32/writer_test_cases.cpp b/test/t/int32/writer_test_cases.cpp
index 9bdf634..e17813c 100644
--- a/test/t/int32/writer_test_cases.cpp
+++ b/test/t/int32/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/int32/testcase.pb.h"
+#include "t/int32/testcase.pb.h"
 
 TEST_CASE("write int32 field and check with libprotobuf") {
 
diff --git a/test/t/int64/test_cases.cpp b/test/t/int64/reader_test_cases.cpp
similarity index 100%
rename from test/t/int64/test_cases.cpp
rename to test/t/int64/reader_test_cases.cpp
diff --git a/test/t/message/reader_test_cases.cpp b/test/t/message/reader_test_cases.cpp
new file mode 100644
index 0000000..04e4e7d
--- /dev/null
+++ b/test/t/message/reader_test_cases.cpp
@@ -0,0 +1,129 @@
+
+#include <test.hpp>
+
+TEST_CASE("read message field: string") {
+    const std::string buffer = load_data("message/data-message");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    protozero::pbf_reader subitem{item.get_message()};
+    REQUIRE_FALSE(item.next());
+
+    REQUIRE(subitem.next());
+    REQUIRE(subitem.get_string() == "foobar");
+    REQUIRE_FALSE(subitem.next());
+}
+
+TEST_CASE("read message field: end of buffer") {
+    const std::string buffer = load_data("message/data-message");
+
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_string(), const protozero::end_of_buffer_exception&);
+    }
+}
+
+TEST_CASE("read message field: optional contents of message - empty") {
+    const std::string buffer = load_data("message/data-opt-empty");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read message field: string opt") {
+    const std::string buffer = load_data("message/data-opt-element");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_string() == "optional");
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("write message field") {
+    std::string buffer;
+    protozero::pbf_writer pbf_test{buffer};
+
+    SECTION("string") {
+        std::string buffer_submessage;
+        protozero::pbf_writer pbf_submessage{buffer_submessage};
+        pbf_submessage.add_string(1, "foobar");
+
+        pbf_test.add_message(1, buffer_submessage);
+    }
+
+    SECTION("string with subwriter") {
+        protozero::pbf_writer pbf_submessage{pbf_test, 1};
+        pbf_submessage.add_string(1, "foobar");
+    }
+
+    SECTION("string with subwriter with reserved size") {
+        std::string str{"foobar"};
+        const auto size = 1 /* tag */ + 1 /* length field */ + str.size();
+        protozero::pbf_writer pbf_submessage{pbf_test, 1, size};
+        pbf_submessage.add_string(1, "foobar");
+    }
+
+    REQUIRE(buffer == load_data("message/data-message"));
+}
+
+TEST_CASE("write message field into non-empty buffer") {
+    std::string buffer{"some data already in here"};
+    protozero::pbf_writer pbf_test{buffer};
+
+    SECTION("string") {
+        std::string buffer_submessage;
+        protozero::pbf_writer pbf_submessage{buffer_submessage};
+        pbf_submessage.add_string(1, "foobar");
+
+        pbf_test.add_message(1, buffer_submessage);
+    }
+
+    SECTION("string with subwriter") {
+        protozero::pbf_writer pbf_submessage{pbf_test, 1};
+        pbf_submessage.add_string(1, "foobar");
+    }
+
+    REQUIRE(buffer == std::string{"some data already in here"} + load_data("message/data-message"));
+}
+
+TEST_CASE("write message field reserving memory beforehand") {
+    std::string buffer;
+    protozero::pbf_writer pbf_test{buffer};
+    pbf_test.reserve(100);
+    REQUIRE(buffer.capacity() >= 100);
+
+    SECTION("string") {
+        std::string buffer_submessage;
+        protozero::pbf_writer pbf_submessage{buffer_submessage};
+        pbf_submessage.add_string(1, "foobar");
+
+        pbf_test.add_message(1, buffer_submessage);
+    }
+
+    SECTION("string with subwriter") {
+        protozero::pbf_writer pbf_submessage{pbf_test, 1};
+        pbf_submessage.add_string(1, "foobar");
+    }
+
+    REQUIRE(buffer == load_data("message/data-message"));
+}
+
+TEST_CASE("write optional message field") {
+    std::string buffer;
+    protozero::pbf_writer pbf_opt{buffer};
+
+    SECTION("add nothing") {
+        REQUIRE(buffer == load_data("message/data-opt-empty"));
+    }
+
+    SECTION("add string") {
+        pbf_opt.add_string(1, "optional");
+
+        REQUIRE(buffer == load_data("message/data-opt-element"));
+    }
+}
+
diff --git a/test/t/message/test_cases.cpp b/test/t/message/test_cases.cpp
deleted file mode 100644
index af4b11f..0000000
--- a/test/t/message/test_cases.cpp
+++ /dev/null
@@ -1,141 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("read message field") {
-
-    SECTION("string") {
-        const std::string buffer = load_data("message/data-message");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        protozero::pbf_reader subitem{item.get_message()};
-        REQUIRE_FALSE(item.next());
-
-        REQUIRE(subitem.next());
-        REQUIRE(subitem.get_string() == "foobar");
-        REQUIRE_FALSE(subitem.next());
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("message/data-message");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item{buffer.data(), i};
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_string(), const protozero::end_of_buffer_exception&);
-        }
-    }
-
-    SECTION("optional contents of message - empty") {
-        const std::string buffer = load_data("message/data-opt-empty");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("string") {
-        const std::string buffer = load_data("message/data-opt-element");
-
-        protozero::pbf_reader item{buffer};
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_string() == "optional");
-        REQUIRE_FALSE(item.next());
-    }
-
-}
-
-TEST_CASE("write message field") {
-
-    std::string buffer_test;
-    protozero::pbf_writer pbf_test{buffer_test};
-
-    SECTION("string") {
-        std::string buffer_submessage;
-        protozero::pbf_writer pbf_submessage{buffer_submessage};
-        pbf_submessage.add_string(1, "foobar");
-
-        pbf_test.add_message(1, buffer_submessage);
-    }
-
-    SECTION("string with subwriter") {
-        protozero::pbf_writer pbf_submessage{pbf_test, 1};
-        pbf_submessage.add_string(1, "foobar");
-    }
-
-    SECTION("string with subwriter with reserved size") {
-        std::string str{"foobar"};
-        auto size = 1 /* tag */ + 1 /* length field */ + str.size();
-        protozero::pbf_writer pbf_submessage{pbf_test, 1, size};
-        pbf_submessage.add_string(1, "foobar");
-    }
-
-    REQUIRE(buffer_test == load_data("message/data-message"));
-
-}
-
-TEST_CASE("write message field into non-empty buffer") {
-
-    std::string buffer_test{"some data already in here"};
-    protozero::pbf_writer pbf_test{buffer_test};
-
-    SECTION("string") {
-        std::string buffer_submessage;
-        protozero::pbf_writer pbf_submessage{buffer_submessage};
-        pbf_submessage.add_string(1, "foobar");
-
-        pbf_test.add_message(1, buffer_submessage);
-    }
-
-    SECTION("string with subwriter") {
-        protozero::pbf_writer pbf_submessage{pbf_test, 1};
-        pbf_submessage.add_string(1, "foobar");
-    }
-
-    REQUIRE(buffer_test == std::string{"some data already in here"} + load_data("message/data-message"));
-
-}
-
-TEST_CASE("write message field reserving memory beforehand") {
-
-    std::string buffer_test;
-    protozero::pbf_writer pbf_test{buffer_test};
-    pbf_test.reserve(100);
-    REQUIRE(buffer_test.capacity() >= 100);
-
-    SECTION("string") {
-        std::string buffer_submessage;
-        protozero::pbf_writer pbf_submessage{buffer_submessage};
-        pbf_submessage.add_string(1, "foobar");
-
-        pbf_test.add_message(1, buffer_submessage);
-    }
-
-    SECTION("string with subwriter") {
-        protozero::pbf_writer pbf_submessage{pbf_test, 1};
-        pbf_submessage.add_string(1, "foobar");
-    }
-
-    REQUIRE(buffer_test == load_data("message/data-message"));
-
-}
-
-TEST_CASE("write optional message field") {
-
-    std::string buffer_opt;
-    protozero::pbf_writer pbf_opt{buffer_opt};
-
-    SECTION("add nothing") {
-        REQUIRE(buffer_opt == load_data("message/data-opt-empty"));
-    }
-
-    SECTION("add string") {
-        pbf_opt.add_string(1, "optional");
-
-        REQUIRE(buffer_opt == load_data("message/data-opt-element"));
-    }
-
-}
-
diff --git a/test/t/message/writer_test_cases.cpp b/test/t/message/writer_test_cases.cpp
index e13d661..9cd2dfb 100644
--- a/test/t/message/writer_test_cases.cpp
+++ b/test/t/message/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/message/testcase.pb.h"
+#include "t/message/testcase.pb.h"
 
 TEST_CASE("write message field and check with libprotobuf") {
 
diff --git a/test/t/nested/test_cases.cpp b/test/t/nested/reader_test_cases.cpp
similarity index 88%
rename from test/t/nested/test_cases.cpp
rename to test/t/nested/reader_test_cases.cpp
index 879e031..0c35d53 100644
--- a/test/t/nested/test_cases.cpp
+++ b/test/t/nested/reader_test_cases.cpp
@@ -77,26 +77,21 @@ inline void check_empty(protozero::pbf_reader message) {
     }
 }
 
-TEST_CASE("read nested message fields") {
+TEST_CASE("read nested message fields: string") {
+    const std::string buffer = load_data("nested/data-message");
 
-    SECTION("string") {
-        const std::string buffer = load_data("nested/data-message");
-
-        protozero::pbf_reader message{buffer};
-        check(message);
-    }
+    protozero::pbf_reader message{buffer};
+    check(message);
+}
 
-    SECTION("no submessage") {
-        const std::string buffer = load_data("nested/data-no-message");
-
-        protozero::pbf_reader message{buffer};
-        check_empty(message);
-    }
+TEST_CASE("read nested message fields: no submessage") {
+    const std::string buffer = load_data("nested/data-no-message");
 
+    protozero::pbf_reader message{buffer};
+    check_empty(message);
 }
 
 TEST_CASE("write nested message fields") {
-
     std::string buffer_test;
     protozero::pbf_writer pbf_test{buffer_test};
 
@@ -131,7 +126,6 @@ TEST_CASE("write nested message fields") {
 }
 
 TEST_CASE("write nested message fields - no message") {
-
     std::string buffer_test;
     protozero::pbf_writer pbf_test{buffer_test};
 
@@ -152,7 +146,7 @@ TEST_CASE("write nested message fields - no message") {
     }
 
     SECTION("string with subwriter") {
-        protozero::pbf_writer pbf_sub(pbf_test, 1);
+        protozero::pbf_writer pbf_sub{pbf_test, 1};
     }
 
     pbf_test.add_int32(2, 77);
diff --git a/test/t/nested/writer_test_cases.cpp b/test/t/nested/writer_test_cases.cpp
index b6272b4..925fb7c 100644
--- a/test/t/nested/writer_test_cases.cpp
+++ b/test/t/nested/writer_test_cases.cpp
@@ -1,7 +1,7 @@
 
 #include <test.hpp>
 
-#include "test/t/nested/testcase.pb.h"
+#include "t/nested/testcase.pb.h"
 
 TEST_CASE("write nested message fields and check with libprotobuf") {
 
diff --git a/test/t/repeated/reader_test_cases.cpp b/test/t/repeated/reader_test_cases.cpp
new file mode 100644
index 0000000..8fb17c4
--- /dev/null
+++ b/test/t/repeated/reader_test_cases.cpp
@@ -0,0 +1,74 @@
+
+#include <test.hpp>
+
+TEST_CASE("read repeated fields: empty") {
+    const std::string buffer = load_data("repeated/data-empty");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read repeated fields: one") {
+    const std::string buffer = load_data("repeated/data-one");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == 0L);
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read repeated fields: many") {
+    const std::string buffer = load_data("repeated/data-many");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == 0L);
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == 1L);
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == -1L);
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == std::numeric_limits<int32_t>::max());
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_int32() == std::numeric_limits<int32_t>::min());
+
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read repeated fields: end of buffer") {
+    const std::string buffer = load_data("repeated/data-one");
+
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_int32(), const protozero::end_of_buffer_exception&);
+    }
+}
+
+TEST_CASE("write repeated fields") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+
+    SECTION("one") {
+        pw.add_int32(1, 0L);
+        REQUIRE(buffer == load_data("repeated/data-one"));
+    }
+
+    SECTION("many") {
+        pw.add_int32(1, 0L);
+        pw.add_int32(1, 1L);
+        pw.add_int32(1, -1L);
+        pw.add_int32(1, std::numeric_limits<int32_t>::max());
+        pw.add_int32(1, std::numeric_limits<int32_t>::min());
+
+        REQUIRE(buffer == load_data("repeated/data-many"));
+    }
+}
+
diff --git a/test/t/repeated/test_cases.cpp b/test/t/repeated/test_cases.cpp
deleted file mode 100644
index 6a08b18..0000000
--- a/test/t/repeated/test_cases.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("read repeated fields") {
-
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == 0L);
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == 0L);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == 1L);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == -1L);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == std::numeric_limits<int32_t>::max());
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_int32() == std::numeric_limits<int32_t>::min());
-
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated/data-one");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_int32(), const protozero::end_of_buffer_exception&);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated fields") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("one") {
-        pw.add_int32(1, 0L);
-        REQUIRE(buffer == load_data("repeated/data-one"));
-    }
-
-    SECTION("many") {
-        pw.add_int32(1, 0L);
-        pw.add_int32(1, 1L);
-        pw.add_int32(1, -1L);
-        pw.add_int32(1, std::numeric_limits<int32_t>::max());
-        pw.add_int32(1, std::numeric_limits<int32_t>::min());
-
-        REQUIRE(buffer == load_data("repeated/data-many"));
-    }
-
-}
-
diff --git a/test/t/repeated/writer_test_cases.cpp b/test/t/repeated/writer_test_cases.cpp
index 90194b0..ebe1b06 100644
--- a/test/t/repeated/writer_test_cases.cpp
+++ b/test/t/repeated/writer_test_cases.cpp
@@ -1,12 +1,12 @@
 
 #include <test.hpp>
 
-#include "test/t/repeated/testcase.pb.h"
+#include "t/repeated/testcase.pb.h"
 
 TEST_CASE("write repeated fields and check with libprotobuf") {
 
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     TestRepeated::Test msg;
 
diff --git a/test/t/repeated_packed_bool/test_cases.cpp b/test/t/repeated_packed_bool/reader_test_cases.cpp
similarity index 62%
rename from test/t/repeated_packed_bool/test_cases.cpp
rename to test/t/repeated_packed_bool/reader_test_cases.cpp
index 002f819..aea44a4 100644
--- a/test/t/repeated_packed_bool/test_cases.cpp
+++ b/test/t/repeated_packed_bool/reader_test_cases.cpp
@@ -1,64 +1,59 @@
 
 #include <test.hpp>
 
-TEST_CASE("read repeated packed bool field") {
+TEST_CASE("read repeated packed bool field: empty") {
+    const std::string buffer = load_data("repeated_packed_bool/data-empty");
 
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_bool/data-empty");
+    protozero::pbf_reader item{buffer};
 
-        protozero::pbf_reader item{buffer};
+    REQUIRE_FALSE(item.next());
+}
 
-        REQUIRE_FALSE(item.next());
-    }
+TEST_CASE("read repeated packed bool field: one") {
+    const std::string buffer = load_data("repeated_packed_bool/data-one");
 
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_bool/data-one");
+    protozero::pbf_reader item{buffer};
 
-        protozero::pbf_reader item{buffer};
+    REQUIRE(item.next());
+    const auto it_range = item.get_packed_bool();
+    REQUIRE(it_range.size() == 1);
+    REQUIRE_FALSE(item.next());
 
-        REQUIRE(item.next());
-        const auto it_range = item.get_packed_bool();
-        REQUIRE(it_range.size() == 1);
-        REQUIRE_FALSE(item.next());
+    REQUIRE(it_range.begin() != it_range.end());
+    REQUIRE(*it_range.begin());
+    REQUIRE(std::next(it_range.begin()) == it_range.end());
+}
 
-        REQUIRE(it_range.begin() != it_range.end());
-        REQUIRE(*it_range.begin());
-        REQUIRE(std::next(it_range.begin()) == it_range.end());
-    }
+TEST_CASE("read repeated packed bool field: many") {
+    const std::string buffer = load_data("repeated_packed_bool/data-many");
 
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_bool/data-many");
+    protozero::pbf_reader item{buffer};
 
-        protozero::pbf_reader item{buffer};
+    REQUIRE(item.next());
+    const auto it_range = item.get_packed_bool();
+    REQUIRE(it_range.size() == 4);
+    REQUIRE_FALSE(item.next());
 
-        REQUIRE(item.next());
-        const auto it_range = item.get_packed_bool();
-        REQUIRE(it_range.size() == 4);
-        REQUIRE_FALSE(item.next());
-
-        auto it = it_range.begin();
-        REQUIRE(it != it_range.end());
-        REQUIRE(*it++);
-        REQUIRE(*it++);
-        REQUIRE_FALSE(*it++);
-        REQUIRE(*it++);
-        REQUIRE(it == it_range.end());
-    }
+    auto it = it_range.begin();
+    REQUIRE(it != it_range.end());
+    REQUIRE(*it++);
+    REQUIRE(*it++);
+    REQUIRE_FALSE(*it++);
+    REQUIRE(*it++);
+    REQUIRE(it == it_range.end());
+}
 
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_bool/data-many");
+TEST_CASE("read repeated packed bool field: end of buffer") {
+    const std::string buffer = load_data("repeated_packed_bool/data-many");
 
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item{buffer.data(), i};
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_bool(), const protozero::end_of_buffer_exception&);
-        }
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_packed_bool(), const protozero::end_of_buffer_exception&);
     }
-
 }
 
 TEST_CASE("write repeated packed bool field") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -86,7 +81,6 @@ TEST_CASE("write repeated packed bool field") {
 }
 
 TEST_CASE("write repeated packed bool field using packed_field_bool") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -118,11 +112,9 @@ TEST_CASE("write repeated packed bool field using packed_field_bool") {
 
         REQUIRE(buffer == load_data("repeated_packed_bool/data-many"));
     }
-
 }
 
 TEST_CASE("write repeated packed bool field using packed_field_bool with pbf_builder") {
-
     enum class msg : protozero::pbf_tag_type {
         f = 1
     };
@@ -158,6 +150,5 @@ TEST_CASE("write repeated packed bool field using packed_field_bool with pbf_bui
 
         REQUIRE(buffer == load_data("repeated_packed_bool/data-many"));
     }
-
 }
 
diff --git a/test/t/repeated_packed_double/test_cases.cpp b/test/t/repeated_packed_double/reader_test_cases.cpp
similarity index 78%
rename from test/t/repeated_packed_double/test_cases.cpp
rename to test/t/repeated_packed_double/reader_test_cases.cpp
index 238c77d..4d2a789 100644
--- a/test/t/repeated_packed_double/test_cases.cpp
+++ b/test/t/repeated_packed_double/reader_test_cases.cpp
@@ -2,7 +2,6 @@
 #include <test.hpp>
 
 TEST_CASE("read repeated packed double field") {
-
     // Run these tests twice, the second time we basically move the data
     // one byte down in the buffer. It doesn't matter how the data or buffer
     // is aligned before that, in at least one of these cases the doubles will
@@ -10,24 +9,23 @@ TEST_CASE("read repeated packed double field") {
     // will be extracted properly.
 
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
 
         SECTION("empty") {
             abuffer.append(load_data("repeated_packed_double/data-empty"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE_FALSE(item.next());
         }
 
         SECTION("one") {
             abuffer.append(load_data("repeated_packed_double/data-one"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE(item.next());
-            auto it_range = item.get_packed_double();
+            const auto it_range = item.get_packed_double();
             REQUIRE_FALSE(item.next());
 
             REQUIRE(*it_range.begin() == 17.34);
@@ -36,10 +34,10 @@ TEST_CASE("read repeated packed double field") {
 
         SECTION("many") {
             abuffer.append(load_data("repeated_packed_double/data-many"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE(item.next());
-            auto it_range = item.get_packed_double();
+            const auto it_range = item.get_packed_double();
             REQUIRE_FALSE(item.next());
 
             auto it = it_range.begin();
@@ -55,41 +53,37 @@ TEST_CASE("read repeated packed double field") {
             abuffer.append(load_data("repeated_packed_double/data-many"));
 
             for (std::string::size_type i = 1; i < abuffer.size() - n; ++i) {
-                protozero::pbf_reader item(abuffer.data() + n, i);
+                protozero::pbf_reader item{abuffer.data() + n, i};
                 REQUIRE(item.next());
                 REQUIRE_THROWS_AS(item.get_packed_double(), const protozero::end_of_buffer_exception&);
             }
         }
-
     }
-
 }
 
 TEST_CASE("write repeated packed double field") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("empty") {
-        double data[] = { 17.34 };
+        const double data[] = { 17.34 };
         pw.add_packed_double(1, std::begin(data), std::begin(data) /* !!!! */);
 
         REQUIRE(buffer == load_data("repeated_packed_double/data-empty"));
     }
 
     SECTION("one") {
-        double data[] = { 17.34 };
+        const double data[] = { 17.34 };
         pw.add_packed_double(1, std::begin(data), std::end(data));
 
         REQUIRE(buffer == load_data("repeated_packed_double/data-one"));
     }
 
     SECTION("many") {
-        double data[] = { 17.34, 0.0, 1.0, std::numeric_limits<double>::min(), std::numeric_limits<double>::max() };
+        const double data[] = { 17.34, 0.0, 1.0, std::numeric_limits<double>::min(), std::numeric_limits<double>::max() };
         pw.add_packed_double(1, std::begin(data), std::end(data));
 
         REQUIRE(buffer == load_data("repeated_packed_double/data-many"));
     }
-
 }
 
diff --git a/test/t/repeated_packed_enum/reader_test_cases.cpp b/test/t/repeated_packed_enum/reader_test_cases.cpp
new file mode 100644
index 0000000..248474c
--- /dev/null
+++ b/test/t/repeated_packed_enum/reader_test_cases.cpp
@@ -0,0 +1,78 @@
+
+#include <test.hpp>
+
+TEST_CASE("read repeated packed enum field: empty") {
+    const std::string buffer = load_data("repeated_packed_enum/data-empty");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read repeated packed enum field: one") {
+    const std::string buffer = load_data("repeated_packed_enum/data-one");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    const auto it_range = item.get_packed_enum();
+    REQUIRE_FALSE(item.next());
+
+    REQUIRE(it_range.begin() != it_range.end());
+    REQUIRE(*it_range.begin() == 0 /* BLACK */);
+    REQUIRE(std::next(it_range.begin()) == it_range.end());
+}
+
+TEST_CASE("read repeated packed enum field: many") {
+    const std::string buffer = load_data("repeated_packed_enum/data-many");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    const auto it_range = item.get_packed_enum();
+    REQUIRE_FALSE(item.next());
+
+    auto it = it_range.begin();
+    REQUIRE(it != it_range.end());
+    REQUIRE(*it++ == 0 /* BLACK */);
+    REQUIRE(*it++ == 3 /* BLUE */);
+    REQUIRE(*it++ == 2 /* GREEN */);
+    REQUIRE(it == it_range.end());
+}
+
+TEST_CASE("read repeated packed enum field: end of buffer") {
+    const std::string buffer = load_data("repeated_packed_enum/data-many");
+
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_packed_enum(), const protozero::end_of_buffer_exception&);
+    }
+}
+
+TEST_CASE("write repeated packed enum field") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+
+    SECTION("empty") {
+        const int32_t data[] = { 0 /* BLACK */ };
+        pw.add_packed_enum(1, std::begin(data), std::begin(data) /* !!!! */);
+
+        REQUIRE(buffer == load_data("repeated_packed_enum/data-empty"));
+    }
+
+    SECTION("one") {
+        const int32_t data[] = { 0 /* BLACK */ };
+        pw.add_packed_enum(1, std::begin(data), std::end(data));
+
+        REQUIRE(buffer == load_data("repeated_packed_enum/data-one"));
+    }
+
+    SECTION("many") {
+        const int32_t data[] = { 0 /* BLACK */, 3 /* BLUE */, 2 /* GREEN */ };
+        pw.add_packed_enum(1, std::begin(data), std::end(data));
+
+        REQUIRE(buffer == load_data("repeated_packed_enum/data-many"));
+    }
+}
+
diff --git a/test/t/repeated_packed_enum/test_cases.cpp b/test/t/repeated_packed_enum/test_cases.cpp
deleted file mode 100644
index d3b7794..0000000
--- a/test/t/repeated_packed_enum/test_cases.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("read repeated packed enum field") {
-
-    SECTION("empty") {
-        const std::string buffer = load_data("repeated_packed_enum/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("repeated_packed_enum/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_range = item.get_packed_enum();
-        REQUIRE_FALSE(item.next());
-
-        REQUIRE(it_range.begin() != it_range.end());
-        REQUIRE(*it_range.begin() == 0 /* BLACK */);
-        REQUIRE(std::next(it_range.begin()) == it_range.end());
-    }
-
-    SECTION("many") {
-        const std::string buffer = load_data("repeated_packed_enum/data-many");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto it_range = item.get_packed_enum();
-        REQUIRE_FALSE(item.next());
-
-        auto it = it_range.begin();
-        REQUIRE(it != it_range.end());
-        REQUIRE(*it++ == 0 /* BLACK */);
-        REQUIRE(*it++ == 3 /* BLUE */);
-        REQUIRE(*it++ == 2 /* GREEN */);
-        REQUIRE(it == it_range.end());
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("repeated_packed_enum/data-many");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_packed_enum(), const protozero::end_of_buffer_exception&);
-        }
-    }
-
-}
-
-TEST_CASE("write repeated packed enum field") {
-
-    std::string buffer;
-    protozero::pbf_writer pw(buffer);
-
-    SECTION("empty") {
-        int32_t data[] = { 0 /* BLACK */ };
-        pw.add_packed_enum(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        REQUIRE(buffer == load_data("repeated_packed_enum/data-empty"));
-    }
-
-    SECTION("one") {
-        int32_t data[] = { 0 /* BLACK */ };
-        pw.add_packed_enum(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_enum/data-one"));
-    }
-
-    SECTION("many") {
-        int32_t data[] = { 0 /* BLACK */, 3 /* BLUE */, 2 /* GREEN */ };
-        pw.add_packed_enum(1, std::begin(data), std::end(data));
-
-        REQUIRE(buffer == load_data("repeated_packed_enum/data-many"));
-    }
-
-}
-
diff --git a/test/t/repeated_packed_fixed32/test_cases.cpp b/test/t/repeated_packed_fixed32/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_fixed32/test_cases.cpp
rename to test/t/repeated_packed_fixed32/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_fixed32/writer_test_cases.cpp b/test/t/repeated_packed_fixed32/writer_test_cases.cpp
index 2d49c0f..416b174 100644
--- a/test/t/repeated_packed_fixed32/writer_test_cases.cpp
+++ b/test/t/repeated_packed_fixed32/writer_test_cases.cpp
@@ -1,22 +1,18 @@
 
 #include <test.hpp>
 
-#include "test/t/repeated_packed_fixed32/testcase.pb.h"
+#include "t/repeated_packed_fixed32/testcase.pb.h"
 
 TEST_CASE("write repeated packed fixed32 field and check with libprotobuf") {
 
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     TestRepeatedPackedFixed32::Test msg;
 
     SECTION("empty") {
         uint32_t data[] = { 17UL };
         pw.add_packed_fixed32(1, std::begin(data), std::begin(data) /* !!!! */);
-
-        msg.ParseFromString(buffer);
-
-        REQUIRE(msg.i().size() == 0);
     }
 
     SECTION("one") {
@@ -41,13 +37,12 @@ TEST_CASE("write repeated packed fixed32 field and check with libprotobuf") {
         REQUIRE(msg.i(2) ==  1UL);
         REQUIRE(msg.i(3) == std::numeric_limits<uint32_t>::max());
     }
-
 }
 
 TEST_CASE("write from different types of iterators and check with libprotobuf") {
 
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     TestRepeatedPackedFixed32::Test msg;
 
diff --git a/test/t/repeated_packed_fixed64/test_cases.cpp b/test/t/repeated_packed_fixed64/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_fixed64/test_cases.cpp
rename to test/t/repeated_packed_fixed64/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_float/test_cases.cpp b/test/t/repeated_packed_float/reader_test_cases.cpp
similarity index 88%
rename from test/t/repeated_packed_float/test_cases.cpp
rename to test/t/repeated_packed_float/reader_test_cases.cpp
index c0d9d46..e600933 100644
--- a/test/t/repeated_packed_float/test_cases.cpp
+++ b/test/t/repeated_packed_float/reader_test_cases.cpp
@@ -2,7 +2,6 @@
 #include <test.hpp>
 
 TEST_CASE("read repeated packed float field") {
-
     // Run these tests twice, the second time we basically move the data
     // one byte down in the buffer. It doesn't matter how the data or buffer
     // is aligned before that, in at least one of these cases the floats will
@@ -10,21 +9,20 @@ TEST_CASE("read repeated packed float field") {
     // will be extracted properly.
 
     for (std::string::size_type n = 0; n < 2; ++n) {
-
         std::string abuffer;
         abuffer.reserve(1000);
         abuffer.append(n, '\0');
 
         SECTION("empty") {
             abuffer.append(load_data("repeated_packed_float/data-empty"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE_FALSE(item.next());
         }
 
         SECTION("one") {
             abuffer.append(load_data("repeated_packed_float/data-one"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE(item.next());
             auto it_range = item.get_packed_float();
@@ -36,7 +34,7 @@ TEST_CASE("read repeated packed float field") {
 
         SECTION("many") {
             abuffer.append(load_data("repeated_packed_float/data-many"));
-            protozero::pbf_reader item(abuffer.data() + n, abuffer.size() - n);
+            protozero::pbf_reader item{abuffer.data() + n, abuffer.size() - n};
 
             REQUIRE(item.next());
             auto it_range = item.get_packed_float();
@@ -55,20 +53,17 @@ TEST_CASE("read repeated packed float field") {
             abuffer.append(load_data("repeated_packed_float/data-many"));
 
             for (std::string::size_type i = 1; i < abuffer.size() - n; ++i) {
-                protozero::pbf_reader item(abuffer.data() + n, i);
+                protozero::pbf_reader item{abuffer.data() + n, i};
                 REQUIRE(item.next());
                 REQUIRE_THROWS_AS(item.get_packed_float(), const protozero::end_of_buffer_exception&);
             }
         }
-
     }
-
 }
 
 TEST_CASE("write repeated packed float field") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("empty") {
         float data[] = { 17.34f };
@@ -90,6 +85,5 @@ TEST_CASE("write repeated packed float field") {
 
         REQUIRE(buffer == load_data("repeated_packed_float/data-many"));
     }
-
 }
 
diff --git a/test/t/repeated_packed_int32/test_cases.cpp b/test/t/repeated_packed_int32/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_int32/test_cases.cpp
rename to test/t/repeated_packed_int32/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_int64/test_cases.cpp b/test/t/repeated_packed_int64/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_int64/test_cases.cpp
rename to test/t/repeated_packed_int64/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_sfixed32/reader_test_cases.cpp b/test/t/repeated_packed_sfixed32/reader_test_cases.cpp
new file mode 100644
index 0000000..eb308f7
--- /dev/null
+++ b/test/t/repeated_packed_sfixed32/reader_test_cases.cpp
@@ -0,0 +1,30 @@
+
+#include <test.hpp>
+
+#define PBF_TYPE sfixed32
+#define PBF_TYPE_IS_SIGNED 1
+using cpp_type = int32_t;
+
+#include <packed_access.hpp>
+
+TEST_CASE("length value must be dividable by sizeof(T)") {
+    std::string data{load_data("repeated_packed_sfixed32/data-many")};
+
+    SECTION("1") {
+        data[1] = 1;
+    }
+
+    SECTION("2") {
+        data[1] = 2;
+    }
+
+    SECTION("3") {
+        data[1] = 3;
+    }
+
+    protozero::pbf_reader item{data};
+
+    REQUIRE(item.next());
+    REQUIRE_THROWS_AS(item.get_packed_sfixed32(), const protozero::invalid_length_exception&);
+}
+
diff --git a/test/t/repeated_packed_sfixed32/test_cases.cpp b/test/t/repeated_packed_sfixed32/test_cases.cpp
deleted file mode 100644
index c8eb8dd..0000000
--- a/test/t/repeated_packed_sfixed32/test_cases.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-
-#include <test.hpp>
-
-#define PBF_TYPE sfixed32
-#define PBF_TYPE_IS_SIGNED 1
-using cpp_type = int32_t;
-
-#include <packed_access.hpp>
-
diff --git a/test/t/repeated_packed_sfixed64/test_cases.cpp b/test/t/repeated_packed_sfixed64/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_sfixed64/test_cases.cpp
rename to test/t/repeated_packed_sfixed64/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_sint32/test_cases.cpp b/test/t/repeated_packed_sint32/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_sint32/test_cases.cpp
rename to test/t/repeated_packed_sint32/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_sint64/test_cases.cpp b/test/t/repeated_packed_sint64/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_sint64/test_cases.cpp
rename to test/t/repeated_packed_sint64/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_uint32/test_cases.cpp b/test/t/repeated_packed_uint32/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_uint32/test_cases.cpp
rename to test/t/repeated_packed_uint32/reader_test_cases.cpp
diff --git a/test/t/repeated_packed_uint64/test_cases.cpp b/test/t/repeated_packed_uint64/reader_test_cases.cpp
similarity index 100%
rename from test/t/repeated_packed_uint64/test_cases.cpp
rename to test/t/repeated_packed_uint64/reader_test_cases.cpp
diff --git a/test/t/rollback/test_cases.cpp b/test/t/rollback/reader_test_cases.cpp
similarity index 97%
rename from test/t/rollback/test_cases.cpp
rename to test/t/rollback/reader_test_cases.cpp
index 51bf5f3..fa4c77f 100644
--- a/test/t/rollback/test_cases.cpp
+++ b/test/t/rollback/reader_test_cases.cpp
@@ -2,7 +2,6 @@
 #include <test.hpp>
 
 TEST_CASE("rollback when using packed_field functions") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -51,7 +50,7 @@ TEST_CASE("rollback when using packed_field functions") {
 
         msg.next();
         REQUIRE(msg.tag() == 1);
-        auto it_range = msg.get_packed_sint64();
+        const auto it_range = msg.get_packed_sint64();
         auto it = it_range.begin();
         REQUIRE(*it++ == 17L);
         REQUIRE(it == it_range.end());
@@ -86,7 +85,7 @@ TEST_CASE("rollback when using packed_field functions") {
 
         msg.next();
         REQUIRE(msg.tag() == 1);
-        auto it_range = msg.get_packed_sint64();
+        const auto it_range = msg.get_packed_sint64();
         auto it = it_range.begin();
         REQUIRE(*it++ == 17L);
         REQUIRE(*it++ ==  0L);
@@ -138,7 +137,6 @@ TEST_CASE("rollback when using packed_field functions") {
 }
 
 TEST_CASE("rollback when using submessages") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -191,7 +189,7 @@ TEST_CASE("rollback on parent message is not allowed even if there is a submessa
 
 TEST_CASE("rollback on message is not allowed if there is a nested submessage") {
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     pw.add_fixed64(2, 111);
     pw.add_string(3, "foo");
diff --git a/test/t/sfixed32/test_cases.cpp b/test/t/sfixed32/reader_test_cases.cpp
similarity index 100%
rename from test/t/sfixed32/test_cases.cpp
rename to test/t/sfixed32/reader_test_cases.cpp
diff --git a/test/t/sfixed64/test_cases.cpp b/test/t/sfixed64/reader_test_cases.cpp
similarity index 100%
rename from test/t/sfixed64/test_cases.cpp
rename to test/t/sfixed64/reader_test_cases.cpp
diff --git a/test/t/sint32/test_cases.cpp b/test/t/sint32/reader_test_cases.cpp
similarity index 100%
rename from test/t/sint32/test_cases.cpp
rename to test/t/sint32/reader_test_cases.cpp
diff --git a/test/t/sint64/test_cases.cpp b/test/t/sint64/reader_test_cases.cpp
similarity index 100%
rename from test/t/sint64/test_cases.cpp
rename to test/t/sint64/reader_test_cases.cpp
diff --git a/test/t/skip/test_cases.cpp b/test/t/skip/reader_test_cases.cpp
similarity index 99%
rename from test/t/skip/test_cases.cpp
rename to test/t/skip/reader_test_cases.cpp
index ed1a3b5..b6de3e8 100644
--- a/test/t/skip/test_cases.cpp
+++ b/test/t/skip/reader_test_cases.cpp
@@ -2,7 +2,6 @@
 #include <test.hpp>
 
 TEST_CASE("skip() skips the right amount of bytes") {
-
     // These are all the data files which contain exactly one field.
     //
     // Create this list with:
@@ -107,9 +106,7 @@ TEST_CASE("skip() skips the right amount of bytes") {
     }
 }
 
-
 TEST_CASE("exceptional cases") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
     pw.add_fixed32(1, 123);
@@ -129,6 +126,5 @@ TEST_CASE("exceptional cases") {
         REQUIRE(item.next());
         REQUIRE_THROWS_AS(item.skip(), const protozero::end_of_buffer_exception&);
     }
-
 }
 
diff --git a/test/t/string/reader_test_cases.cpp b/test/t/string/reader_test_cases.cpp
new file mode 100644
index 0000000..f80c4c0
--- /dev/null
+++ b/test/t/string/reader_test_cases.cpp
@@ -0,0 +1,106 @@
+
+#include <test.hpp>
+
+TEST_CASE("read string field using get_string: empty") {
+    const std::string buffer = load_data("string/data-empty");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_string().empty());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_string: one") {
+    const std::string buffer = load_data("string/data-one");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_string() == "x");
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_string: string") {
+    const std::string buffer = load_data("string/data-string");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(item.get_string() == "foobar");
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_string: end of buffer") {
+    const std::string buffer = load_data("string/data-string");
+
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_string(), const protozero::end_of_buffer_exception&);
+    }
+}
+
+TEST_CASE("read string field using get_view: empty") {
+    const std::string buffer = load_data("string/data-empty");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    const auto v = item.get_view();
+    REQUIRE(v.empty());
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_view: one") {
+    const std::string buffer = load_data("string/data-one");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    const auto v = item.get_view();
+    REQUIRE(*v.data() == 'x');
+    REQUIRE(v.size() == 1);
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_view: string") {
+    const std::string buffer = load_data("string/data-string");
+
+    protozero::pbf_reader item{buffer};
+
+    REQUIRE(item.next());
+    REQUIRE(std::string(item.get_view()) == "foobar");
+    REQUIRE_FALSE(item.next());
+}
+
+TEST_CASE("read string field using get_view: end of buffer") {
+    const std::string buffer = load_data("string/data-string");
+
+    for (std::string::size_type i = 1; i < buffer.size(); ++i) {
+        protozero::pbf_reader item{buffer.data(), i};
+        REQUIRE(item.next());
+        REQUIRE_THROWS_AS(item.get_view(), const protozero::end_of_buffer_exception&);
+    }
+}
+
+TEST_CASE("write string field") {
+    std::string buffer_test;
+    protozero::pbf_writer pbf_test{buffer_test};
+
+    SECTION("empty") {
+        pbf_test.add_string(1, "");
+        REQUIRE(buffer_test == load_data("string/data-empty"));
+    }
+
+    SECTION("one") {
+        pbf_test.add_string(1, "x");
+        REQUIRE(buffer_test == load_data("string/data-one"));
+    }
+
+    SECTION("string") {
+        pbf_test.add_string(1, "foobar");
+        REQUIRE(buffer_test == load_data("string/data-string"));
+    }
+}
+
diff --git a/test/t/string/test_cases.cpp b/test/t/string/test_cases.cpp
deleted file mode 100644
index 393c3bb..0000000
--- a/test/t/string/test_cases.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-
-#include <test.hpp>
-
-TEST_CASE("read string field using get_string") {
-
-    SECTION("empty") {
-        const std::string buffer = load_data("string/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_string().empty());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("string/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_string() == "x");
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("string") {
-        const std::string buffer = load_data("string/data-string");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(item.get_string() == "foobar");
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("string/data-string");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_string(), const protozero::end_of_buffer_exception&);
-        }
-    }
-
-}
-
-TEST_CASE("read string field using get_view") {
-
-    SECTION("empty") {
-        const std::string buffer = load_data("string/data-empty");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto v = item.get_view();
-        REQUIRE(v.empty());
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("one") {
-        const std::string buffer = load_data("string/data-one");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        auto v = item.get_view();
-        REQUIRE(*v.data() == 'x');
-        REQUIRE(v.size() == 1);
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("string") {
-        const std::string buffer = load_data("string/data-string");
-
-        protozero::pbf_reader item(buffer);
-
-        REQUIRE(item.next());
-        REQUIRE(std::string(item.get_view()) == "foobar");
-        REQUIRE_FALSE(item.next());
-    }
-
-    SECTION("end_of_buffer") {
-        const std::string buffer = load_data("string/data-string");
-
-        for (std::string::size_type i = 1; i < buffer.size(); ++i) {
-            protozero::pbf_reader item(buffer.data(), i);
-            REQUIRE(item.next());
-            REQUIRE_THROWS_AS(item.get_view(), const protozero::end_of_buffer_exception&);
-        }
-    }
-
-}
-
-TEST_CASE("write string field") {
-
-    std::string buffer_test;
-    protozero::pbf_writer pbf_test(buffer_test);
-
-    SECTION("empty") {
-        pbf_test.add_string(1, "");
-        REQUIRE(buffer_test == load_data("string/data-empty"));
-    }
-
-    SECTION("one") {
-        pbf_test.add_string(1, "x");
-        REQUIRE(buffer_test == load_data("string/data-one"));
-    }
-
-    SECTION("string") {
-        pbf_test.add_string(1, "foobar");
-        REQUIRE(buffer_test == load_data("string/data-string"));
-    }
-
-}
-
diff --git a/test/t/string/writer_test_cases.cpp b/test/t/string/writer_test_cases.cpp
index 145fd78..6645dab 100644
--- a/test/t/string/writer_test_cases.cpp
+++ b/test/t/string/writer_test_cases.cpp
@@ -1,12 +1,12 @@
 
 #include <test.hpp>
 
-#include "test/t/string/testcase.pb.h"
+#include "t/string/testcase.pb.h"
 
 TEST_CASE("write string field and check with libprotobuf") {
 
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     TestString::Test msg;
 
@@ -15,7 +15,7 @@ TEST_CASE("write string field and check with libprotobuf") {
 
         msg.ParseFromString(buffer);
 
-        REQUIRE(msg.s() == "");
+        REQUIRE(msg.s().empty());
     }
 
     SECTION("one") {
diff --git a/test/t/tag_and_type/test_cases.cpp b/test/t/tag_and_type/reader_test_cases.cpp
similarity index 100%
rename from test/t/tag_and_type/test_cases.cpp
rename to test/t/tag_and_type/reader_test_cases.cpp
diff --git a/test/t/tags/test_cases.cpp b/test/t/tags/reader_test_cases.cpp
similarity index 63%
rename from test/t/tags/test_cases.cpp
rename to test/t/tags/reader_test_cases.cpp
index 22169ec..f2626ed 100644
--- a/test/t/tags/test_cases.cpp
+++ b/test/t/tags/reader_test_cases.cpp
@@ -2,7 +2,7 @@
 #include <test.hpp>
 
 inline void check_tag(const std::string& buffer, protozero::pbf_tag_type tag) {
-    protozero::pbf_reader item(buffer);
+    protozero::pbf_reader item{buffer};
 
     REQUIRE(item.next());
     REQUIRE(item.tag() == tag);
@@ -10,30 +10,25 @@ inline void check_tag(const std::string& buffer, protozero::pbf_tag_type tag) {
     REQUIRE_FALSE(item.next());
 }
 
-TEST_CASE("read tags") {
-
-    SECTION("tag 1") {
-        check_tag(load_data("tags/data-tag-1"), 1L);
-    }
-
-    SECTION("tag 200") {
-        check_tag(load_data("tags/data-tag-200"), 200L);
-    }
+TEST_CASE("read tag: 1") {
+    check_tag(load_data("tags/data-tag-1"), 1L);
+}
 
-    SECTION("tag 200000") {
-        check_tag(load_data("tags/data-tag-200000"), 200000L);
-    }
+TEST_CASE("read tag: 200") {
+    check_tag(load_data("tags/data-tag-200"), 200L);
+}
 
-    SECTION("tag max") {
-        check_tag(load_data("tags/data-tag-max"), (1L << 29) - 1);
-    }
+TEST_CASE("read tag: 200000") {
+    check_tag(load_data("tags/data-tag-200000"), 200000L);
+}
 
+TEST_CASE("read tag: max") {
+    check_tag(load_data("tags/data-tag-max"), (1L << 29) - 1);
 }
 
 TEST_CASE("write tags") {
-
     std::string buffer;
-    protozero::pbf_writer pw(buffer);
+    protozero::pbf_writer pw{buffer};
 
     SECTION("tag 1") {
         pw.add_int32(1L, 333L);
@@ -54,6 +49,5 @@ TEST_CASE("write tags") {
         pw.add_int32((1L << 29) - 1, 333L);
         REQUIRE(buffer == load_data("tags/data-tag-max"));
     }
-
 }
 
diff --git a/test/t/uint32/test_cases.cpp b/test/t/uint32/reader_test_cases.cpp
similarity index 100%
rename from test/t/uint32/test_cases.cpp
rename to test/t/uint32/reader_test_cases.cpp
diff --git a/test/t/uint64/test_cases.cpp b/test/t/uint64/reader_test_cases.cpp
similarity index 100%
rename from test/t/uint64/test_cases.cpp
rename to test/t/uint64/reader_test_cases.cpp
diff --git a/test/t/varint/test_cases.cpp b/test/t/varint/reader_test_cases.cpp
similarity index 72%
rename from test/t/varint/test_cases.cpp
rename to test/t/varint/reader_test_cases.cpp
index a88c1af..68111be 100644
--- a/test/t/varint/test_cases.cpp
+++ b/test/t/varint/reader_test_cases.cpp
@@ -6,7 +6,6 @@ TEST_CASE("max varint length") {
 }
 
 TEST_CASE("varint") {
-
     std::string buffer;
     protozero::pbf_writer pw{buffer};
 
@@ -102,11 +101,24 @@ TEST_CASE("varint") {
             REQUIRE_THROWS_AS(item.skip(), const protozero::varint_too_long_exception&);
         }
     }
+}
 
+TEST_CASE("10-byte varint") {
+    std::string buffer;
+    protozero::pbf_writer pw{buffer};
+    pw.add_uint64(1, 1);
+    buffer.back() = static_cast<char>(0xff);
+    for (int i = 0; i < 9; ++i) {
+        buffer.push_back(static_cast<char>(0xff));
+    }
+    buffer.push_back(0x02);
+
+    protozero::pbf_reader item{buffer};
+    REQUIRE(item.next());
+    REQUIRE_THROWS_AS(item.get_uint64(), const protozero::varint_too_long_exception&);
 }
 
 TEST_CASE("lots of varints back and forth") {
-
     std::string buffer;
 
     for (uint32_t n = 0; n < 70000; ++n) {
@@ -139,5 +151,37 @@ TEST_CASE("lots of varints back and forth") {
         buffer.clear();
     }
 
+    for (int i = 0; i < 63; ++i) {
+        int64_t n = 1ll << i;
+        protozero::pbf_writer pw{buffer};
+        pw.add_int64(1, n);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+        REQUIRE(n == item.get_int64());
+        REQUIRE_FALSE(item.next());
+        buffer.clear();
+    }
+
+    for (int i = 0; i < 63; ++i) {
+        int64_t n = - (1ll << i);
+        protozero::pbf_writer pw{buffer};
+        pw.add_int64(1, n);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+        REQUIRE(n == item.get_int64());
+        REQUIRE_FALSE(item.next());
+        buffer.clear();
+    }
+
+    for (int i = 0; i < 64; ++i) {
+        uint64_t n = 1ull << i;
+        protozero::pbf_writer pw{buffer};
+        pw.add_int64(1, n);
+        protozero::pbf_reader item{buffer};
+        REQUIRE(item.next());
+        REQUIRE(n == item.get_int64());
+        REQUIRE_FALSE(item.next());
+        buffer.clear();
+    }
 }
 
diff --git a/test/t/vector_tile/test_cases.cpp b/test/t/vector_tile/reader_test_cases.cpp
similarity index 99%
rename from test/t/vector_tile/test_cases.cpp
rename to test/t/vector_tile/reader_test_cases.cpp
index f28f634..4d0c729 100644
--- a/test/t/vector_tile/test_cases.cpp
+++ b/test/t/vector_tile/reader_test_cases.cpp
@@ -149,6 +149,5 @@ TEST_CASE("reading vector tiles") {
         REQUIRE(n_id == 502);
         REQUIRE(n_geomtype == 502);
     }
-
 }
 
diff --git a/test/t/wrong_type_access/test_cases.cpp b/test/t/wrong_type_access/reader_test_cases.cpp
similarity index 100%
rename from test/t/wrong_type_access/test_cases.cpp
rename to test/t/wrong_type_access/reader_test_cases.cpp
diff --git a/test/t/zigzag/test_cases.cpp b/test/t/zigzag/reader_test_cases.cpp
similarity index 100%
rename from test/t/zigzag/test_cases.cpp
rename to test/t/zigzag/reader_test_cases.cpp
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..721a19f
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,51 @@
+#-----------------------------------------------------------------------------
+#
+#  CMake config
+#
+#  protozero tools
+#
+#-----------------------------------------------------------------------------
+
+# Needs getopt, which is not available on Windows
+if(NOT MSVC)
+    add_executable(pbf-decoder pbf-decoder.cpp)
+
+    add_test(NAME pbf-decoder-no-args
+             COMMAND pbf-decoder)
+    set_tests_properties(pbf-decoder-no-args PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^Usage:")
+
+    add_test(NAME pbf-decoder-help
+             COMMAND pbf-decoder --help)
+    set_tests_properties(pbf-decoder-help PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^Usage:")
+
+    add_test(NAME pbf-decoder-empty
+             COMMAND pbf-decoder "${CMAKE_SOURCE_DIR}/test/t/message/data-opt-empty.pbf")
+    set_tests_properties(pbf-decoder-empty PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^$")
+
+    add_test(NAME pbf-decoder-data
+             COMMAND pbf-decoder "${CMAKE_SOURCE_DIR}/test/t/message/data-message.pbf")
+    set_tests_properties(pbf-decoder-data PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^1:")
+
+    add_test(NAME pbf-decoder-vt
+             COMMAND pbf-decoder -l 999999 -o 0 "${CMAKE_SOURCE_DIR}/test/t/vector_tile/data.vector.pbf")
+    set_tests_properties(pbf-decoder-vt PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^3:")
+
+    add_test(NAME pbf-decoder-fail
+             COMMAND pbf-decoder -l 1 "${CMAKE_SOURCE_DIR}/test/t/vector_tile/data.vector.pbf")
+    set_tests_properties(pbf-decoder-fail PROPERTIES
+                         WILL_FAIL true)
+
+    add_test(NAME pbf-decoder-fail-msg
+             COMMAND pbf-decoder -l 1 "${CMAKE_SOURCE_DIR}/test/t/vector_tile/data.vector.pbf")
+    set_tests_properties(pbf-decoder-fail-msg PROPERTIES
+                         PASS_REGULAR_EXPRESSION "^end of buffer exception")
+
+endif()
+
+
+#-----------------------------------------------------------------------------
diff --git a/tools/pbf-decoder.cpp b/tools/pbf-decoder.cpp
new file mode 100644
index 0000000..3f6700e
--- /dev/null
+++ b/tools/pbf-decoder.cpp
@@ -0,0 +1,264 @@
+/*****************************************************************************
+
+Protobuf decoder tool
+
+Tool to decode unknown protocol buffer encoded messages. The protocol buffer
+format doesn't contain enough information about the contents of a file to make
+it decodable without the format description usually found in a `.proto` file,
+so this tool does some informed guessing.
+
+Usage:
+
+    pbf-decoder [OPTIONS] [FILENAME]
+
+Use "-" as a file name to read from STDIN.
+
+The output always goes to STDOUT.
+
+Call with --help/-h to see more options.
+
+*****************************************************************************/
+
+#include <protozero/pbf_reader.hpp>
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <exception>
+#include <fstream>
+#include <getopt.h>
+#include <iostream>
+#include <limits>
+#include <sstream>
+#include <string>
+
+std::string decode(const char* data, std::size_t len, const std::string& indent);
+
+// Try decoding as a nested message
+bool decode_message(std::stringstream& out, const std::string& indent, const protozero::data_view view) {
+    try {
+        const auto nested = decode(view.data(), view.size(), indent + "  ");
+        out << '\n' << nested;
+        return true;
+    } catch (const protozero::exception&) {
+    }
+    return false;
+}
+
+// Try decoding as a string (only printable characters allowed).
+bool decode_printable_string(std::stringstream& out, const protozero::data_view view) {
+    static constexpr const std::size_t max_string_length = 60;
+
+    const std::string str{view.data(), view.size()};
+    if (str.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_:-") != std::string::npos) {
+        return false;
+    }
+
+    if (str.size() > max_string_length) {
+        out << '"' << str.substr(0, max_string_length) << "\"...\n";
+    } else {
+        out << '"' << str << '"' << '\n';
+    }
+    return true;
+}
+
+// Try decoding as a string.
+bool decode_string(std::stringstream& out, const protozero::data_view view) {
+    static constexpr const std::size_t max_string_length = 60;
+
+    std::string str{view.data(), std::min(view.size(), max_string_length)};
+    out << '"';
+
+    for (const auto c : str) {
+        if (std::isprint(c) != 0) {
+            out << c;
+        } else {
+            out << '.';
+        }
+    }
+
+    out << '"' << '\n';
+
+    return true;
+}
+
+// Print a list of numbers from a range
+template <typename TRange>
+void print_number_range(std::stringstream& out, const TRange& range) {
+    bool first = true;
+    for (auto val : range) {
+        if (first) {
+            first = false;
+        } else {
+            out << ',';
+        }
+        out << val;
+    }
+    out << '\n';
+}
+
+// Try decoding as packed repeated double
+bool decode_packed_double(std::stringstream& out, std::size_t size, protozero::pbf_reader& message) {
+    if (size % 8 != 0) {
+        return false;
+    }
+    try {
+        print_number_range(out, message.get_packed_double());
+        return true;
+    } catch (const protozero::exception&) {
+    }
+
+    return false;
+}
+
+// Try decoding as packed repeated float
+bool decode_packed_float(std::stringstream& out, std::size_t size, protozero::pbf_reader& message) {
+    if (size % 4 != 0) {
+        return false;
+    }
+    try {
+        print_number_range(out, message.get_packed_float());
+        return true;
+    } catch (const protozero::exception&) {
+    }
+
+    return false;
+}
+
+// Try decoding as packed repeated varint
+bool decode_packed_varint(std::stringstream& out, protozero::pbf_reader& message) {
+    try {
+        print_number_range(out, message.get_packed_int64());
+        return true;
+    } catch (const protozero::exception&) {
+    }
+
+    return false;
+}
+
+std::string decode(const char* data, std::size_t len, const std::string& indent) {
+    std::stringstream stream;
+    protozero::pbf_reader message{data, len};
+    while (message.next()) {
+        stream << indent << message.tag() << ": ";
+        switch (message.wire_type()) {
+            case protozero::pbf_wire_type::varint: {
+                // This is int32, int64, uint32, uint64, sint32, sint64, bool, or enum.
+                // Try decoding as int64.
+                stream << message.get_int64() << '\n';
+                break;
+            }
+            case protozero::pbf_wire_type::fixed64:
+                // This is fixed64, sfixed64, or double.
+                // Try decoding as a double, because int64_t or uint64_t
+                // would probably be encoded as varint.
+                stream << message.get_double() << '\n';
+                break;
+            case protozero::pbf_wire_type::length_delimited: {
+                // This is string, bytes, embedded messages, or packed repeated fields.
+                protozero::pbf_reader message_copy{message};
+                const auto view = message.get_view();
+
+                decode_message(stream, indent, view) ||
+                    decode_printable_string(stream, view) ||
+                    decode_packed_double(stream, view.size(), message_copy) ||
+                    decode_packed_float(stream, view.size(), message_copy) ||
+                    decode_packed_varint(stream, message_copy) ||
+                    decode_string(stream, view);
+                break;
+            }
+            case protozero::pbf_wire_type::fixed32:
+                // This is fixed32, sfixed32, or float.
+                // Try decoding as a float, because int32_t or uint32_t
+                // would probably be encoded as varint.
+                stream << message.get_float() << '\n';
+                break;
+            default:
+                throw protozero::unknown_pbf_wire_type_exception{};
+        }
+    }
+
+    return stream.str();
+}
+
+void print_help() {
+    std::cout << "Usage: pbf-decoder [OPTIONS] [INPUT_FILE]\n\n"
+              << "Dump raw contents of protobuf encoded file.\n"
+              << "To read from STDIN use '-' as INPUT_FILE.\n"
+              << "\nOptions:\n"
+              << "  -h, --help           This help message\n"
+              << "  -l, --length=LENGTH  Read only LENGTH bytes\n"
+              << "  -o, --offset=OFFSET  Start reading from OFFSET bytes\n";
+}
+
+std::string read_from_file(const char* filename) {
+    std::ifstream file{filename, std::ios::binary};
+    return std::string{std::istreambuf_iterator<char>(file.rdbuf()),
+                       std::istreambuf_iterator<char>()};
+}
+
+std::string read_from_stdin() {
+    return std::string{std::istreambuf_iterator<char>(std::cin.rdbuf()),
+                       std::istreambuf_iterator<char>()};
+}
+
+int main(int argc, char* argv[]) {
+    static struct option long_options[] = {
+        {"help",         no_argument, nullptr, 'h'},
+        {"length", required_argument, nullptr, 'l'},
+        {"offset", required_argument, nullptr, 'o'},
+        {nullptr, 0, nullptr, 0}
+    };
+
+    std::size_t offset = 0;
+    std::size_t length = std::numeric_limits<std::size_t>::max();
+
+    while (true) {
+        const int c = getopt_long(argc, argv, "hl:o:", long_options, nullptr);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+            case 'h':
+                print_help();
+                return 0;
+            case 'l':
+                length = std::atoll(optarg); // NOLINT clang-tidy: cert-err34-c
+                                             // good enough for a limited-use tool
+                break;
+            case 'o':
+                offset = std::atoll(optarg); // NOLINT clang-tidy: cert-err34-c
+                                             // good enough for a limited-use tool
+                break;
+            default:
+                return 1;
+        }
+    }
+
+    const int remaining_args = argc - optind;
+    if (remaining_args != 1) {
+        std::cerr << "Usage: " << argv[0] << " [OPTIONS] [INPUT_FILE]\n\n"
+                  << "Call with --help/-h to see options.\n";
+        return 1;
+    }
+
+    const std::string filename{argv[optind]};
+
+    try {
+        const std::string buffer{filename == "-" ? read_from_stdin() :
+                                                   read_from_file(argv[optind])};
+
+        if (length > buffer.size() - offset) {
+            length = buffer.size() - offset;
+        }
+
+        std::cout << decode(buffer.data() + offset, length, "");
+    } catch (const std::exception& ex) {
+        std::cerr << ex.what() << '\n';
+        return 1;
+    }
+
+    return 0;
+}
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/protozero.git



More information about the Pkg-grass-devel mailing list